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::TryCatch(try_catch) => self.generate_try_catch(try_catch),
3346            Expression::Command(cmd) => {
3347                self.write(&cmd.this);
3348                Ok(())
3349            }
3350            Expression::Kill(kill) => {
3351                self.write_keyword("KILL");
3352                if let Some(kind) = &kill.kind {
3353                    self.write_space();
3354                    self.write_keyword(kind);
3355                }
3356                self.write_space();
3357                self.generate_expression(&kill.this)?;
3358                Ok(())
3359            }
3360            Expression::Execute(exec) => {
3361                self.write_keyword("EXECUTE");
3362                self.write_space();
3363                self.generate_expression(&exec.this)?;
3364                for (i, param) in exec.parameters.iter().enumerate() {
3365                    if i == 0 {
3366                        self.write_space();
3367                    } else {
3368                        self.write(", ");
3369                    }
3370                    self.write(&param.name);
3371                    // Only write = value for named parameters (not positional)
3372                    if !param.positional {
3373                        self.write(" = ");
3374                        self.generate_expression(&param.value)?;
3375                    }
3376                    if param.output {
3377                        self.write_space();
3378                        self.write_keyword("OUTPUT");
3379                    }
3380                }
3381                if let Some(ref suffix) = exec.suffix {
3382                    self.write_space();
3383                    self.write(suffix);
3384                }
3385                Ok(())
3386            }
3387            Expression::Annotated(annotated) => {
3388                self.generate_expression(&annotated.this)?;
3389                for comment in &annotated.trailing_comments {
3390                    self.write(" ");
3391                    self.write_formatted_comment(comment);
3392                }
3393                Ok(())
3394            }
3395
3396            // DDL statements
3397            Expression::CreateTable(ct) => self.generate_create_table(ct),
3398            Expression::DropTable(dt) => self.generate_drop_table(dt),
3399            Expression::Undrop(u) => self.generate_undrop(u),
3400            Expression::AlterTable(at) => self.generate_alter_table(at),
3401            Expression::CreateIndex(ci) => self.generate_create_index(ci),
3402            Expression::DropIndex(di) => self.generate_drop_index(di),
3403            Expression::CreateView(cv) => self.generate_create_view(cv),
3404            Expression::DropView(dv) => self.generate_drop_view(dv),
3405            Expression::AlterView(av) => self.generate_alter_view(av),
3406            Expression::AlterIndex(ai) => self.generate_alter_index(ai),
3407            Expression::Truncate(tr) => self.generate_truncate(tr),
3408            Expression::Use(u) => self.generate_use(u),
3409            // Phase 4: Additional DDL statements
3410            Expression::CreateSchema(cs) => self.generate_create_schema(cs),
3411            Expression::DropSchema(ds) => self.generate_drop_schema(ds),
3412            Expression::DropNamespace(dn) => self.generate_drop_namespace(dn),
3413            Expression::CreateDatabase(cd) => self.generate_create_database(cd),
3414            Expression::DropDatabase(dd) => self.generate_drop_database(dd),
3415            Expression::CreateFunction(cf) => self.generate_create_function(cf),
3416            Expression::DropFunction(df) => self.generate_drop_function(df),
3417            Expression::CreateProcedure(cp) => self.generate_create_procedure(cp),
3418            Expression::DropProcedure(dp) => self.generate_drop_procedure(dp),
3419            Expression::CreateSequence(cs) => self.generate_create_sequence(cs),
3420            Expression::CreateSynonym(cs) => {
3421                self.write_keyword("CREATE SYNONYM");
3422                self.write_space();
3423                self.generate_table(&cs.name)?;
3424                self.write_space();
3425                self.write_keyword("FOR");
3426                self.write_space();
3427                self.generate_table(&cs.target)?;
3428                Ok(())
3429            }
3430            Expression::DropSequence(ds) => self.generate_drop_sequence(ds),
3431            Expression::AlterSequence(als) => self.generate_alter_sequence(als),
3432            Expression::CreateTrigger(ct) => self.generate_create_trigger(ct),
3433            Expression::DropTrigger(dt) => self.generate_drop_trigger(dt),
3434            Expression::CreateType(ct) => self.generate_create_type(ct),
3435            Expression::DropType(dt) => self.generate_drop_type(dt),
3436            Expression::Describe(d) => self.generate_describe(d),
3437            Expression::Show(s) => self.generate_show(s),
3438
3439            // CACHE/UNCACHE/LOAD TABLE (Spark/Hive)
3440            Expression::Cache(c) => self.generate_cache(c),
3441            Expression::Uncache(u) => self.generate_uncache(u),
3442            Expression::LoadData(l) => self.generate_load_data(l),
3443            Expression::Pragma(p) => self.generate_pragma(p),
3444            Expression::Grant(g) => self.generate_grant(g),
3445            Expression::Revoke(r) => self.generate_revoke(r),
3446            Expression::Comment(c) => self.generate_comment(c),
3447            Expression::SetStatement(s) => self.generate_set_statement(s),
3448
3449            // PIVOT/UNPIVOT
3450            Expression::Pivot(pivot) => self.generate_pivot(pivot),
3451            Expression::Unpivot(unpivot) => self.generate_unpivot(unpivot),
3452
3453            // VALUES table constructor
3454            Expression::Values(values) => self.generate_values(values),
3455
3456            // === BATCH-GENERATED MATCH ARMS (481 variants) ===
3457            Expression::AIAgg(e) => self.generate_ai_agg(e),
3458            Expression::AIClassify(e) => self.generate_ai_classify(e),
3459            Expression::AddPartition(e) => self.generate_add_partition(e),
3460            Expression::AlgorithmProperty(e) => self.generate_algorithm_property(e),
3461            Expression::Aliases(e) => self.generate_aliases(e),
3462            Expression::AllowedValuesProperty(e) => self.generate_allowed_values_property(e),
3463            Expression::AlterColumn(e) => self.generate_alter_column(e),
3464            Expression::AlterSession(e) => self.generate_alter_session(e),
3465            Expression::AlterSet(e) => self.generate_alter_set(e),
3466            Expression::AlterSortKey(e) => self.generate_alter_sort_key(e),
3467            Expression::Analyze(e) => self.generate_analyze(e),
3468            Expression::AnalyzeDelete(e) => self.generate_analyze_delete(e),
3469            Expression::AnalyzeHistogram(e) => self.generate_analyze_histogram(e),
3470            Expression::AnalyzeListChainedRows(e) => self.generate_analyze_list_chained_rows(e),
3471            Expression::AnalyzeSample(e) => self.generate_analyze_sample(e),
3472            Expression::AnalyzeStatistics(e) => self.generate_analyze_statistics(e),
3473            Expression::AnalyzeValidate(e) => self.generate_analyze_validate(e),
3474            Expression::AnalyzeWith(e) => self.generate_analyze_with(e),
3475            Expression::Anonymous(e) => self.generate_anonymous(e),
3476            Expression::AnonymousAggFunc(e) => self.generate_anonymous_agg_func(e),
3477            Expression::Apply(e) => self.generate_apply(e),
3478            Expression::ApproxPercentileEstimate(e) => self.generate_approx_percentile_estimate(e),
3479            Expression::ApproxQuantile(e) => self.generate_approx_quantile(e),
3480            Expression::ApproxQuantiles(e) => self.generate_approx_quantiles(e),
3481            Expression::ApproxTopK(e) => self.generate_approx_top_k(e),
3482            Expression::ApproxTopKAccumulate(e) => self.generate_approx_top_k_accumulate(e),
3483            Expression::ApproxTopKCombine(e) => self.generate_approx_top_k_combine(e),
3484            Expression::ApproxTopKEstimate(e) => self.generate_approx_top_k_estimate(e),
3485            Expression::ApproxTopSum(e) => self.generate_approx_top_sum(e),
3486            Expression::ArgMax(e) => self.generate_arg_max(e),
3487            Expression::ArgMin(e) => self.generate_arg_min(e),
3488            Expression::ArrayAll(e) => self.generate_array_all(e),
3489            Expression::ArrayAny(e) => self.generate_array_any(e),
3490            Expression::ArrayConstructCompact(e) => self.generate_array_construct_compact(e),
3491            Expression::ArraySum(e) => self.generate_array_sum(e),
3492            Expression::AtIndex(e) => self.generate_at_index(e),
3493            Expression::Attach(e) => self.generate_attach(e),
3494            Expression::AttachOption(e) => self.generate_attach_option(e),
3495            Expression::AutoIncrementProperty(e) => self.generate_auto_increment_property(e),
3496            Expression::AutoRefreshProperty(e) => self.generate_auto_refresh_property(e),
3497            Expression::BackupProperty(e) => self.generate_backup_property(e),
3498            Expression::Base64DecodeBinary(e) => self.generate_base64_decode_binary(e),
3499            Expression::Base64DecodeString(e) => self.generate_base64_decode_string(e),
3500            Expression::Base64Encode(e) => self.generate_base64_encode(e),
3501            Expression::BlockCompressionProperty(e) => self.generate_block_compression_property(e),
3502            Expression::Booland(e) => self.generate_booland(e),
3503            Expression::Boolor(e) => self.generate_boolor(e),
3504            Expression::BuildProperty(e) => self.generate_build_property(e),
3505            Expression::ByteString(e) => self.generate_byte_string(e),
3506            Expression::CaseSpecificColumnConstraint(e) => {
3507                self.generate_case_specific_column_constraint(e)
3508            }
3509            Expression::CastToStrType(e) => self.generate_cast_to_str_type(e),
3510            Expression::Changes(e) => self.generate_changes(e),
3511            Expression::CharacterSetColumnConstraint(e) => {
3512                self.generate_character_set_column_constraint(e)
3513            }
3514            Expression::CharacterSetProperty(e) => self.generate_character_set_property(e),
3515            Expression::CheckColumnConstraint(e) => self.generate_check_column_constraint(e),
3516            Expression::AssumeColumnConstraint(e) => self.generate_assume_column_constraint(e),
3517            Expression::CheckJson(e) => self.generate_check_json(e),
3518            Expression::CheckXml(e) => self.generate_check_xml(e),
3519            Expression::ChecksumProperty(e) => self.generate_checksum_property(e),
3520            Expression::Clone(e) => self.generate_clone(e),
3521            Expression::ClusterBy(e) => self.generate_cluster_by(e),
3522            Expression::ClusterByColumnsProperty(e) => self.generate_cluster_by_columns_property(e),
3523            Expression::ClusteredByProperty(e) => self.generate_clustered_by_property(e),
3524            Expression::CollateProperty(e) => self.generate_collate_property(e),
3525            Expression::ColumnConstraint(e) => self.generate_column_constraint(e),
3526            Expression::ColumnDef(e) => self.generate_column_def_expr(e),
3527            Expression::ColumnPosition(e) => self.generate_column_position(e),
3528            Expression::ColumnPrefix(e) => self.generate_column_prefix(e),
3529            Expression::Columns(e) => self.generate_columns(e),
3530            Expression::CombinedAggFunc(e) => self.generate_combined_agg_func(e),
3531            Expression::CombinedParameterizedAgg(e) => self.generate_combined_parameterized_agg(e),
3532            Expression::Commit(e) => self.generate_commit(e),
3533            Expression::Comprehension(e) => self.generate_comprehension(e),
3534            Expression::Compress(e) => self.generate_compress(e),
3535            Expression::CompressColumnConstraint(e) => self.generate_compress_column_constraint(e),
3536            Expression::ComputedColumnConstraint(e) => self.generate_computed_column_constraint(e),
3537            Expression::ConditionalInsert(e) => self.generate_conditional_insert(e),
3538            Expression::Constraint(e) => self.generate_constraint(e),
3539            Expression::ConvertTimezone(e) => self.generate_convert_timezone(e),
3540            Expression::ConvertToCharset(e) => self.generate_convert_to_charset(e),
3541            Expression::Copy(e) => self.generate_copy(e),
3542            Expression::CopyParameter(e) => self.generate_copy_parameter(e),
3543            Expression::Corr(e) => self.generate_corr(e),
3544            Expression::CosineDistance(e) => self.generate_cosine_distance(e),
3545            Expression::CovarPop(e) => self.generate_covar_pop(e),
3546            Expression::CovarSamp(e) => self.generate_covar_samp(e),
3547            Expression::Credentials(e) => self.generate_credentials(e),
3548            Expression::CredentialsProperty(e) => self.generate_credentials_property(e),
3549            Expression::Cte(e) => self.generate_cte(e),
3550            Expression::Cube(e) => self.generate_cube(e),
3551            Expression::CurrentDatetime(e) => self.generate_current_datetime(e),
3552            Expression::CurrentSchema(e) => self.generate_current_schema(e),
3553            Expression::CurrentSchemas(e) => self.generate_current_schemas(e),
3554            Expression::CurrentUser(e) => self.generate_current_user(e),
3555            Expression::DPipe(e) => self.generate_d_pipe(e),
3556            Expression::DataBlocksizeProperty(e) => self.generate_data_blocksize_property(e),
3557            Expression::DataDeletionProperty(e) => self.generate_data_deletion_property(e),
3558            Expression::Date(e) => self.generate_date_func(e),
3559            Expression::DateBin(e) => self.generate_date_bin(e),
3560            Expression::DateFormatColumnConstraint(e) => {
3561                self.generate_date_format_column_constraint(e)
3562            }
3563            Expression::DateFromParts(e) => self.generate_date_from_parts(e),
3564            Expression::Datetime(e) => self.generate_datetime(e),
3565            Expression::DatetimeAdd(e) => self.generate_datetime_add(e),
3566            Expression::DatetimeDiff(e) => self.generate_datetime_diff(e),
3567            Expression::DatetimeSub(e) => self.generate_datetime_sub(e),
3568            Expression::DatetimeTrunc(e) => self.generate_datetime_trunc(e),
3569            Expression::Dayname(e) => self.generate_dayname(e),
3570            Expression::Declare(e) => self.generate_declare(e),
3571            Expression::DeclareItem(e) => self.generate_declare_item(e),
3572            Expression::DecodeCase(e) => self.generate_decode_case(e),
3573            Expression::DecompressBinary(e) => self.generate_decompress_binary(e),
3574            Expression::DecompressString(e) => self.generate_decompress_string(e),
3575            Expression::Decrypt(e) => self.generate_decrypt(e),
3576            Expression::DecryptRaw(e) => self.generate_decrypt_raw(e),
3577            Expression::DefaultColumnConstraint(e) => {
3578                self.write_keyword("DEFAULT");
3579                self.write_space();
3580                self.generate_expression(&e.this)?;
3581                if let Some(ref col) = e.for_column {
3582                    self.write_space();
3583                    self.write_keyword("FOR");
3584                    self.write_space();
3585                    self.generate_identifier(col)?;
3586                }
3587                Ok(())
3588            }
3589            Expression::DefinerProperty(e) => self.generate_definer_property(e),
3590            Expression::Detach(e) => self.generate_detach(e),
3591            Expression::DictProperty(e) => self.generate_dict_property(e),
3592            Expression::DictRange(e) => self.generate_dict_range(e),
3593            Expression::Directory(e) => self.generate_directory(e),
3594            Expression::DistKeyProperty(e) => self.generate_dist_key_property(e),
3595            Expression::DistStyleProperty(e) => self.generate_dist_style_property(e),
3596            Expression::DistributeBy(e) => self.generate_distribute_by(e),
3597            Expression::DistributedByProperty(e) => self.generate_distributed_by_property(e),
3598            Expression::DotProduct(e) => self.generate_dot_product(e),
3599            Expression::DropPartition(e) => self.generate_drop_partition(e),
3600            Expression::DuplicateKeyProperty(e) => self.generate_duplicate_key_property(e),
3601            Expression::Elt(e) => self.generate_elt(e),
3602            Expression::Encode(e) => self.generate_encode(e),
3603            Expression::EncodeProperty(e) => self.generate_encode_property(e),
3604            Expression::Encrypt(e) => self.generate_encrypt(e),
3605            Expression::EncryptRaw(e) => self.generate_encrypt_raw(e),
3606            Expression::EngineProperty(e) => self.generate_engine_property(e),
3607            Expression::EnviromentProperty(e) => self.generate_enviroment_property(e),
3608            Expression::EphemeralColumnConstraint(e) => {
3609                self.generate_ephemeral_column_constraint(e)
3610            }
3611            Expression::EqualNull(e) => self.generate_equal_null(e),
3612            Expression::EuclideanDistance(e) => self.generate_euclidean_distance(e),
3613            Expression::ExecuteAsProperty(e) => self.generate_execute_as_property(e),
3614            Expression::Export(e) => self.generate_export(e),
3615            Expression::ExternalProperty(e) => self.generate_external_property(e),
3616            Expression::FallbackProperty(e) => self.generate_fallback_property(e),
3617            Expression::FarmFingerprint(e) => self.generate_farm_fingerprint(e),
3618            Expression::FeaturesAtTime(e) => self.generate_features_at_time(e),
3619            Expression::Fetch(e) => self.generate_fetch(e),
3620            Expression::FileFormatProperty(e) => self.generate_file_format_property(e),
3621            Expression::Filter(e) => self.generate_filter(e),
3622            Expression::Float64(e) => self.generate_float64(e),
3623            Expression::ForIn(e) => self.generate_for_in(e),
3624            Expression::ForeignKey(e) => self.generate_foreign_key(e),
3625            Expression::Format(e) => self.generate_format(e),
3626            Expression::FormatPhrase(e) => self.generate_format_phrase(e),
3627            Expression::FreespaceProperty(e) => self.generate_freespace_property(e),
3628            Expression::From(e) => self.generate_from(e),
3629            Expression::FromBase(e) => self.generate_from_base(e),
3630            Expression::FromTimeZone(e) => self.generate_from_time_zone(e),
3631            Expression::GapFill(e) => self.generate_gap_fill(e),
3632            Expression::GenerateDateArray(e) => self.generate_generate_date_array(e),
3633            Expression::GenerateEmbedding(e) => self.generate_generate_embedding(e),
3634            Expression::GenerateSeries(e) => self.generate_generate_series(e),
3635            Expression::GenerateTimestampArray(e) => self.generate_generate_timestamp_array(e),
3636            Expression::GeneratedAsIdentityColumnConstraint(e) => {
3637                self.generate_generated_as_identity_column_constraint(e)
3638            }
3639            Expression::GeneratedAsRowColumnConstraint(e) => {
3640                self.generate_generated_as_row_column_constraint(e)
3641            }
3642            Expression::Get(e) => self.generate_get(e),
3643            Expression::GetExtract(e) => self.generate_get_extract(e),
3644            Expression::Getbit(e) => self.generate_getbit(e),
3645            Expression::GrantPrincipal(e) => self.generate_grant_principal(e),
3646            Expression::GrantPrivilege(e) => self.generate_grant_privilege(e),
3647            Expression::Group(e) => self.generate_group(e),
3648            Expression::GroupBy(e) => self.generate_group_by(e),
3649            Expression::Grouping(e) => self.generate_grouping(e),
3650            Expression::GroupingId(e) => self.generate_grouping_id(e),
3651            Expression::GroupingSets(e) => self.generate_grouping_sets(e),
3652            Expression::HashAgg(e) => self.generate_hash_agg(e),
3653            Expression::Having(e) => self.generate_having(e),
3654            Expression::HavingMax(e) => self.generate_having_max(e),
3655            Expression::Heredoc(e) => self.generate_heredoc(e),
3656            Expression::HexEncode(e) => self.generate_hex_encode(e),
3657            Expression::Hll(e) => self.generate_hll(e),
3658            Expression::InOutColumnConstraint(e) => self.generate_in_out_column_constraint(e),
3659            Expression::IncludeProperty(e) => self.generate_include_property(e),
3660            Expression::Index(e) => self.generate_index(e),
3661            Expression::IndexColumnConstraint(e) => self.generate_index_column_constraint(e),
3662            Expression::IndexConstraintOption(e) => self.generate_index_constraint_option(e),
3663            Expression::IndexParameters(e) => self.generate_index_parameters(e),
3664            Expression::IndexTableHint(e) => self.generate_index_table_hint(e),
3665            Expression::InheritsProperty(e) => self.generate_inherits_property(e),
3666            Expression::InputModelProperty(e) => self.generate_input_model_property(e),
3667            Expression::InputOutputFormat(e) => self.generate_input_output_format(e),
3668            Expression::Install(e) => self.generate_install(e),
3669            Expression::IntervalOp(e) => self.generate_interval_op(e),
3670            Expression::IntervalSpan(e) => self.generate_interval_span(e),
3671            Expression::IntoClause(e) => self.generate_into_clause(e),
3672            Expression::Introducer(e) => self.generate_introducer(e),
3673            Expression::IsolatedLoadingProperty(e) => self.generate_isolated_loading_property(e),
3674            Expression::JSON(e) => self.generate_json(e),
3675            Expression::JSONArray(e) => self.generate_json_array(e),
3676            Expression::JSONArrayAgg(e) => self.generate_json_array_agg_struct(e),
3677            Expression::JSONArrayAppend(e) => self.generate_json_array_append(e),
3678            Expression::JSONArrayContains(e) => self.generate_json_array_contains(e),
3679            Expression::JSONArrayInsert(e) => self.generate_json_array_insert(e),
3680            Expression::JSONBExists(e) => self.generate_jsonb_exists(e),
3681            Expression::JSONBExtractScalar(e) => self.generate_jsonb_extract_scalar(e),
3682            Expression::JSONBObjectAgg(e) => self.generate_jsonb_object_agg(e),
3683            Expression::JSONObjectAgg(e) => self.generate_json_object_agg_struct(e),
3684            Expression::JSONColumnDef(e) => self.generate_json_column_def(e),
3685            Expression::JSONExists(e) => self.generate_json_exists(e),
3686            Expression::JSONCast(e) => self.generate_json_cast(e),
3687            Expression::JSONExtract(e) => self.generate_json_extract_path(e),
3688            Expression::JSONExtractArray(e) => self.generate_json_extract_array(e),
3689            Expression::JSONExtractQuote(e) => self.generate_json_extract_quote(e),
3690            Expression::JSONExtractScalar(e) => self.generate_json_extract_scalar(e),
3691            Expression::JSONFormat(e) => self.generate_json_format(e),
3692            Expression::JSONKeyValue(e) => self.generate_json_key_value(e),
3693            Expression::JSONKeys(e) => self.generate_json_keys(e),
3694            Expression::JSONKeysAtDepth(e) => self.generate_json_keys_at_depth(e),
3695            Expression::JSONPath(e) => self.generate_json_path_expr(e),
3696            Expression::JSONPathFilter(e) => self.generate_json_path_filter(e),
3697            Expression::JSONPathKey(e) => self.generate_json_path_key(e),
3698            Expression::JSONPathRecursive(e) => self.generate_json_path_recursive(e),
3699            Expression::JSONPathRoot(_) => self.generate_json_path_root(),
3700            Expression::JSONPathScript(e) => self.generate_json_path_script(e),
3701            Expression::JSONPathSelector(e) => self.generate_json_path_selector(e),
3702            Expression::JSONPathSlice(e) => self.generate_json_path_slice(e),
3703            Expression::JSONPathSubscript(e) => self.generate_json_path_subscript(e),
3704            Expression::JSONPathUnion(e) => self.generate_json_path_union(e),
3705            Expression::JSONRemove(e) => self.generate_json_remove(e),
3706            Expression::JSONSchema(e) => self.generate_json_schema(e),
3707            Expression::JSONSet(e) => self.generate_json_set(e),
3708            Expression::JSONStripNulls(e) => self.generate_json_strip_nulls(e),
3709            Expression::JSONTable(e) => self.generate_json_table(e),
3710            Expression::JSONType(e) => self.generate_json_type(e),
3711            Expression::JSONValue(e) => self.generate_json_value(e),
3712            Expression::JSONValueArray(e) => self.generate_json_value_array(e),
3713            Expression::JarowinklerSimilarity(e) => self.generate_jarowinkler_similarity(e),
3714            Expression::JoinHint(e) => self.generate_join_hint(e),
3715            Expression::JournalProperty(e) => self.generate_journal_property(e),
3716            Expression::LanguageProperty(e) => self.generate_language_property(e),
3717            Expression::Lateral(e) => self.generate_lateral(e),
3718            Expression::LikeProperty(e) => self.generate_like_property(e),
3719            Expression::Limit(e) => self.generate_limit(e),
3720            Expression::LimitOptions(e) => self.generate_limit_options(e),
3721            Expression::List(e) => self.generate_list(e),
3722            Expression::ToMap(e) => self.generate_tomap(e),
3723            Expression::Localtime(e) => self.generate_localtime(e),
3724            Expression::Localtimestamp(e) => self.generate_localtimestamp(e),
3725            Expression::LocationProperty(e) => self.generate_location_property(e),
3726            Expression::Lock(e) => self.generate_lock(e),
3727            Expression::LockProperty(e) => self.generate_lock_property(e),
3728            Expression::LockingProperty(e) => self.generate_locking_property(e),
3729            Expression::LockingStatement(e) => self.generate_locking_statement(e),
3730            Expression::LogProperty(e) => self.generate_log_property(e),
3731            Expression::MD5Digest(e) => self.generate_md5_digest(e),
3732            Expression::MLForecast(e) => self.generate_ml_forecast(e),
3733            Expression::MLTranslate(e) => self.generate_ml_translate(e),
3734            Expression::MakeInterval(e) => self.generate_make_interval(e),
3735            Expression::ManhattanDistance(e) => self.generate_manhattan_distance(e),
3736            Expression::Map(e) => self.generate_map(e),
3737            Expression::MapCat(e) => self.generate_map_cat(e),
3738            Expression::MapDelete(e) => self.generate_map_delete(e),
3739            Expression::MapInsert(e) => self.generate_map_insert(e),
3740            Expression::MapPick(e) => self.generate_map_pick(e),
3741            Expression::MaskingPolicyColumnConstraint(e) => {
3742                self.generate_masking_policy_column_constraint(e)
3743            }
3744            Expression::MatchAgainst(e) => self.generate_match_against(e),
3745            Expression::MatchRecognizeMeasure(e) => self.generate_match_recognize_measure(e),
3746            Expression::MaterializedProperty(e) => self.generate_materialized_property(e),
3747            Expression::Merge(e) => self.generate_merge(e),
3748            Expression::MergeBlockRatioProperty(e) => self.generate_merge_block_ratio_property(e),
3749            Expression::MergeTreeTTL(e) => self.generate_merge_tree_ttl(e),
3750            Expression::MergeTreeTTLAction(e) => self.generate_merge_tree_ttl_action(e),
3751            Expression::Minhash(e) => self.generate_minhash(e),
3752            Expression::ModelAttribute(e) => self.generate_model_attribute(e),
3753            Expression::Monthname(e) => self.generate_monthname(e),
3754            Expression::MultitableInserts(e) => self.generate_multitable_inserts(e),
3755            Expression::NextValueFor(e) => self.generate_next_value_for(e),
3756            Expression::Normal(e) => self.generate_normal(e),
3757            Expression::Normalize(e) => self.generate_normalize(e),
3758            Expression::NotNullColumnConstraint(e) => self.generate_not_null_column_constraint(e),
3759            Expression::Nullif(e) => self.generate_nullif(e),
3760            Expression::NumberToStr(e) => self.generate_number_to_str(e),
3761            Expression::ObjectAgg(e) => self.generate_object_agg(e),
3762            Expression::ObjectIdentifier(e) => self.generate_object_identifier(e),
3763            Expression::ObjectInsert(e) => self.generate_object_insert(e),
3764            Expression::Offset(e) => self.generate_offset(e),
3765            Expression::Qualify(e) => self.generate_qualify(e),
3766            Expression::OnCluster(e) => self.generate_on_cluster(e),
3767            Expression::OnCommitProperty(e) => self.generate_on_commit_property(e),
3768            Expression::OnCondition(e) => self.generate_on_condition(e),
3769            Expression::OnConflict(e) => self.generate_on_conflict(e),
3770            Expression::OnProperty(e) => self.generate_on_property(e),
3771            Expression::Opclass(e) => self.generate_opclass(e),
3772            Expression::OpenJSON(e) => self.generate_open_json(e),
3773            Expression::OpenJSONColumnDef(e) => self.generate_open_json_column_def(e),
3774            Expression::Operator(e) => self.generate_operator(e),
3775            Expression::OrderBy(e) => self.generate_order_by(e),
3776            Expression::OutputModelProperty(e) => self.generate_output_model_property(e),
3777            Expression::OverflowTruncateBehavior(e) => self.generate_overflow_truncate_behavior(e),
3778            Expression::ParameterizedAgg(e) => self.generate_parameterized_agg(e),
3779            Expression::ParseDatetime(e) => self.generate_parse_datetime(e),
3780            Expression::ParseIp(e) => self.generate_parse_ip(e),
3781            Expression::ParseJSON(e) => self.generate_parse_json(e),
3782            Expression::ParseTime(e) => self.generate_parse_time(e),
3783            Expression::ParseUrl(e) => self.generate_parse_url(e),
3784            Expression::Partition(e) => self.generate_partition_expr(e),
3785            Expression::PartitionBoundSpec(e) => self.generate_partition_bound_spec(e),
3786            Expression::PartitionByListProperty(e) => self.generate_partition_by_list_property(e),
3787            Expression::PartitionByRangeProperty(e) => self.generate_partition_by_range_property(e),
3788            Expression::PartitionByRangePropertyDynamic(e) => {
3789                self.generate_partition_by_range_property_dynamic(e)
3790            }
3791            Expression::PartitionByTruncate(e) => self.generate_partition_by_truncate(e),
3792            Expression::PartitionList(e) => self.generate_partition_list(e),
3793            Expression::PartitionRange(e) => self.generate_partition_range(e),
3794            Expression::PartitionByProperty(e) => self.generate_partition_by_property(e),
3795            Expression::PartitionedByBucket(e) => self.generate_partitioned_by_bucket(e),
3796            Expression::PartitionedByProperty(e) => self.generate_partitioned_by_property(e),
3797            Expression::PartitionedOfProperty(e) => self.generate_partitioned_of_property(e),
3798            Expression::PeriodForSystemTimeConstraint(e) => {
3799                self.generate_period_for_system_time_constraint(e)
3800            }
3801            Expression::PivotAlias(e) => self.generate_pivot_alias(e),
3802            Expression::PivotAny(e) => self.generate_pivot_any(e),
3803            Expression::Predict(e) => self.generate_predict(e),
3804            Expression::PreviousDay(e) => self.generate_previous_day(e),
3805            Expression::PrimaryKey(e) => self.generate_primary_key(e),
3806            Expression::PrimaryKeyColumnConstraint(e) => {
3807                self.generate_primary_key_column_constraint(e)
3808            }
3809            Expression::PathColumnConstraint(e) => self.generate_path_column_constraint(e),
3810            Expression::ProjectionDef(e) => self.generate_projection_def(e),
3811            Expression::OptionsProperty(e) => self.generate_options_property(e),
3812            Expression::Properties(e) => self.generate_properties(e),
3813            Expression::Property(e) => self.generate_property(e),
3814            Expression::PseudoType(e) => self.generate_pseudo_type(e),
3815            Expression::Put(e) => self.generate_put(e),
3816            Expression::Quantile(e) => self.generate_quantile(e),
3817            Expression::QueryBand(e) => self.generate_query_band(e),
3818            Expression::QueryOption(e) => self.generate_query_option(e),
3819            Expression::QueryTransform(e) => self.generate_query_transform(e),
3820            Expression::Randn(e) => self.generate_randn(e),
3821            Expression::Randstr(e) => self.generate_randstr(e),
3822            Expression::RangeBucket(e) => self.generate_range_bucket(e),
3823            Expression::RangeN(e) => self.generate_range_n(e),
3824            Expression::ReadCSV(e) => self.generate_read_csv(e),
3825            Expression::ReadParquet(e) => self.generate_read_parquet(e),
3826            Expression::RecursiveWithSearch(e) => self.generate_recursive_with_search(e),
3827            Expression::Reduce(e) => self.generate_reduce(e),
3828            Expression::Reference(e) => self.generate_reference(e),
3829            Expression::Refresh(e) => self.generate_refresh(e),
3830            Expression::RefreshTriggerProperty(e) => self.generate_refresh_trigger_property(e),
3831            Expression::RegexpCount(e) => self.generate_regexp_count(e),
3832            Expression::RegexpExtractAll(e) => self.generate_regexp_extract_all(e),
3833            Expression::RegexpFullMatch(e) => self.generate_regexp_full_match(e),
3834            Expression::RegexpILike(e) => self.generate_regexp_i_like(e),
3835            Expression::RegexpInstr(e) => self.generate_regexp_instr(e),
3836            Expression::RegexpSplit(e) => self.generate_regexp_split(e),
3837            Expression::RegrAvgx(e) => self.generate_regr_avgx(e),
3838            Expression::RegrAvgy(e) => self.generate_regr_avgy(e),
3839            Expression::RegrCount(e) => self.generate_regr_count(e),
3840            Expression::RegrIntercept(e) => self.generate_regr_intercept(e),
3841            Expression::RegrR2(e) => self.generate_regr_r2(e),
3842            Expression::RegrSlope(e) => self.generate_regr_slope(e),
3843            Expression::RegrSxx(e) => self.generate_regr_sxx(e),
3844            Expression::RegrSxy(e) => self.generate_regr_sxy(e),
3845            Expression::RegrSyy(e) => self.generate_regr_syy(e),
3846            Expression::RegrValx(e) => self.generate_regr_valx(e),
3847            Expression::RegrValy(e) => self.generate_regr_valy(e),
3848            Expression::RemoteWithConnectionModelProperty(e) => {
3849                self.generate_remote_with_connection_model_property(e)
3850            }
3851            Expression::RenameColumn(e) => self.generate_rename_column(e),
3852            Expression::ReplacePartition(e) => self.generate_replace_partition(e),
3853            Expression::Returning(e) => self.generate_returning(e),
3854            Expression::ReturnsProperty(e) => self.generate_returns_property(e),
3855            Expression::Rollback(e) => self.generate_rollback(e),
3856            Expression::Rollup(e) => self.generate_rollup(e),
3857            Expression::RowFormatDelimitedProperty(e) => {
3858                self.generate_row_format_delimited_property(e)
3859            }
3860            Expression::RowFormatProperty(e) => self.generate_row_format_property(e),
3861            Expression::RowFormatSerdeProperty(e) => self.generate_row_format_serde_property(e),
3862            Expression::SHA2(e) => self.generate_sha2(e),
3863            Expression::SHA2Digest(e) => self.generate_sha2_digest(e),
3864            Expression::SafeAdd(e) => self.generate_safe_add(e),
3865            Expression::SafeDivide(e) => self.generate_safe_divide(e),
3866            Expression::SafeMultiply(e) => self.generate_safe_multiply(e),
3867            Expression::SafeSubtract(e) => self.generate_safe_subtract(e),
3868            Expression::SampleProperty(e) => self.generate_sample_property(e),
3869            Expression::Schema(e) => self.generate_schema(e),
3870            Expression::SchemaCommentProperty(e) => self.generate_schema_comment_property(e),
3871            Expression::ScopeResolution(e) => self.generate_scope_resolution(e),
3872            Expression::Search(e) => self.generate_search(e),
3873            Expression::SearchIp(e) => self.generate_search_ip(e),
3874            Expression::SecurityProperty(e) => self.generate_security_property(e),
3875            Expression::SemanticView(e) => self.generate_semantic_view(e),
3876            Expression::SequenceProperties(e) => self.generate_sequence_properties(e),
3877            Expression::SerdeProperties(e) => self.generate_serde_properties(e),
3878            Expression::SessionParameter(e) => self.generate_session_parameter(e),
3879            Expression::Set(e) => self.generate_set(e),
3880            Expression::SetConfigProperty(e) => self.generate_set_config_property(e),
3881            Expression::SetItem(e) => self.generate_set_item(e),
3882            Expression::SetOperation(e) => self.generate_set_operation(e),
3883            Expression::SetProperty(e) => self.generate_set_property(e),
3884            Expression::SettingsProperty(e) => self.generate_settings_property(e),
3885            Expression::SharingProperty(e) => self.generate_sharing_property(e),
3886            Expression::Slice(e) => self.generate_slice(e),
3887            Expression::SortArray(e) => self.generate_sort_array(e),
3888            Expression::SortBy(e) => self.generate_sort_by(e),
3889            Expression::SortKeyProperty(e) => self.generate_sort_key_property(e),
3890            Expression::SplitPart(e) => self.generate_split_part(e),
3891            Expression::SqlReadWriteProperty(e) => self.generate_sql_read_write_property(e),
3892            Expression::SqlSecurityProperty(e) => self.generate_sql_security_property(e),
3893            Expression::StDistance(e) => self.generate_st_distance(e),
3894            Expression::StPoint(e) => self.generate_st_point(e),
3895            Expression::StabilityProperty(e) => self.generate_stability_property(e),
3896            Expression::StandardHash(e) => self.generate_standard_hash(e),
3897            Expression::StorageHandlerProperty(e) => self.generate_storage_handler_property(e),
3898            Expression::StrPosition(e) => self.generate_str_position(e),
3899            Expression::StrToDate(e) => self.generate_str_to_date(e),
3900            Expression::DateStrToDate(f) => self.generate_simple_func("DATE_STR_TO_DATE", &f.this),
3901            Expression::DateToDateStr(f) => self.generate_simple_func("DATE_TO_DATE_STR", &f.this),
3902            Expression::StrToMap(e) => self.generate_str_to_map(e),
3903            Expression::StrToTime(e) => self.generate_str_to_time(e),
3904            Expression::StrToUnix(e) => self.generate_str_to_unix(e),
3905            Expression::StringToArray(e) => self.generate_string_to_array(e),
3906            Expression::Struct(e) => self.generate_struct(e),
3907            Expression::Stuff(e) => self.generate_stuff(e),
3908            Expression::SubstringIndex(e) => self.generate_substring_index(e),
3909            Expression::Summarize(e) => self.generate_summarize(e),
3910            Expression::Systimestamp(e) => self.generate_systimestamp(e),
3911            Expression::TableAlias(e) => self.generate_table_alias(e),
3912            Expression::TableFromRows(e) => self.generate_table_from_rows(e),
3913            Expression::RowsFrom(e) => self.generate_rows_from(e),
3914            Expression::TableSample(e) => self.generate_table_sample(e),
3915            Expression::Tag(e) => self.generate_tag(e),
3916            Expression::Tags(e) => self.generate_tags(e),
3917            Expression::TemporaryProperty(e) => self.generate_temporary_property(e),
3918            Expression::Time(e) => self.generate_time_func(e),
3919            Expression::TimeAdd(e) => self.generate_time_add(e),
3920            Expression::TimeDiff(e) => self.generate_time_diff(e),
3921            Expression::TimeFromParts(e) => self.generate_time_from_parts(e),
3922            Expression::TimeSlice(e) => self.generate_time_slice(e),
3923            Expression::TimeStrToDate(e) => self.generate_time_str_to_date(e),
3924            Expression::TimeStrToTime(e) => self.generate_time_str_to_time(e),
3925            Expression::TimeSub(e) => self.generate_time_sub(e),
3926            Expression::TimeToStr(e) => self.generate_time_to_str(e),
3927            Expression::TimeToUnix(e) => self.generate_time_to_unix(e),
3928            Expression::TimeTrunc(e) => self.generate_time_trunc(e),
3929            Expression::TimeUnit(e) => self.generate_time_unit(e),
3930            Expression::Timestamp(e) => self.generate_timestamp_func(e),
3931            Expression::TimestampAdd(e) => self.generate_timestamp_add(e),
3932            Expression::TimestampDiff(e) => self.generate_timestamp_diff(e),
3933            Expression::TimestampFromParts(e) => self.generate_timestamp_from_parts(e),
3934            Expression::TimestampSub(e) => self.generate_timestamp_sub(e),
3935            Expression::TimestampTzFromParts(e) => self.generate_timestamp_tz_from_parts(e),
3936            Expression::ToBinary(e) => self.generate_to_binary(e),
3937            Expression::ToBoolean(e) => self.generate_to_boolean(e),
3938            Expression::ToChar(e) => self.generate_to_char(e),
3939            Expression::ToDecfloat(e) => self.generate_to_decfloat(e),
3940            Expression::ToDouble(e) => self.generate_to_double(e),
3941            Expression::ToFile(e) => self.generate_to_file(e),
3942            Expression::ToNumber(e) => self.generate_to_number(e),
3943            Expression::ToTableProperty(e) => self.generate_to_table_property(e),
3944            Expression::Transaction(e) => self.generate_transaction(e),
3945            Expression::Transform(e) => self.generate_transform(e),
3946            Expression::TransformModelProperty(e) => self.generate_transform_model_property(e),
3947            Expression::TransientProperty(e) => self.generate_transient_property(e),
3948            Expression::Translate(e) => self.generate_translate(e),
3949            Expression::TranslateCharacters(e) => self.generate_translate_characters(e),
3950            Expression::TruncateTable(e) => self.generate_truncate_table(e),
3951            Expression::TryBase64DecodeBinary(e) => self.generate_try_base64_decode_binary(e),
3952            Expression::TryBase64DecodeString(e) => self.generate_try_base64_decode_string(e),
3953            Expression::TryToDecfloat(e) => self.generate_try_to_decfloat(e),
3954            Expression::TsOrDsAdd(e) => self.generate_ts_or_ds_add(e),
3955            Expression::TsOrDsDiff(e) => self.generate_ts_or_ds_diff(e),
3956            Expression::TsOrDsToDate(e) => self.generate_ts_or_ds_to_date(e),
3957            Expression::TsOrDsToTime(e) => self.generate_ts_or_ds_to_time(e),
3958            Expression::Unhex(e) => self.generate_unhex(e),
3959            Expression::UnicodeString(e) => self.generate_unicode_string(e),
3960            Expression::Uniform(e) => self.generate_uniform(e),
3961            Expression::UniqueColumnConstraint(e) => self.generate_unique_column_constraint(e),
3962            Expression::UniqueKeyProperty(e) => self.generate_unique_key_property(e),
3963            Expression::RollupProperty(e) => self.generate_rollup_property(e),
3964            Expression::UnixToStr(e) => self.generate_unix_to_str(e),
3965            Expression::UnixToTime(e) => self.generate_unix_to_time(e),
3966            Expression::UnpivotColumns(e) => self.generate_unpivot_columns(e),
3967            Expression::UserDefinedFunction(e) => self.generate_user_defined_function(e),
3968            Expression::UsingTemplateProperty(e) => self.generate_using_template_property(e),
3969            Expression::UtcTime(e) => self.generate_utc_time(e),
3970            Expression::UtcTimestamp(e) => self.generate_utc_timestamp(e),
3971            Expression::Uuid(e) => self.generate_uuid(e),
3972            Expression::Var(v) => {
3973                if matches!(self.config.dialect, Some(DialectType::MySQL))
3974                    && v.this.len() > 2
3975                    && (v.this.starts_with("0x") || v.this.starts_with("0X"))
3976                    && !v.this[2..].chars().all(|c| c.is_ascii_hexdigit())
3977                {
3978                    return self.generate_identifier(&Identifier {
3979                        name: v.this.clone(),
3980                        quoted: true,
3981                        trailing_comments: Vec::new(),
3982                        span: None,
3983                    });
3984                }
3985                self.write(&v.this);
3986                Ok(())
3987            }
3988            Expression::Variadic(e) => {
3989                self.write_keyword("VARIADIC");
3990                self.write_space();
3991                self.generate_expression(&e.this)?;
3992                Ok(())
3993            }
3994            Expression::VarMap(e) => self.generate_var_map(e),
3995            Expression::VectorSearch(e) => self.generate_vector_search(e),
3996            Expression::Version(e) => self.generate_version(e),
3997            Expression::ViewAttributeProperty(e) => self.generate_view_attribute_property(e),
3998            Expression::VolatileProperty(e) => self.generate_volatile_property(e),
3999            Expression::WatermarkColumnConstraint(e) => {
4000                self.generate_watermark_column_constraint(e)
4001            }
4002            Expression::Week(e) => self.generate_week(e),
4003            Expression::When(e) => self.generate_when(e),
4004            Expression::Whens(e) => self.generate_whens(e),
4005            Expression::Where(e) => self.generate_where(e),
4006            Expression::WidthBucket(e) => self.generate_width_bucket(e),
4007            Expression::Window(e) => self.generate_window(e),
4008            Expression::WindowSpec(e) => self.generate_window_spec(e),
4009            Expression::WithDataProperty(e) => self.generate_with_data_property(e),
4010            Expression::WithFill(e) => self.generate_with_fill(e),
4011            Expression::WithJournalTableProperty(e) => self.generate_with_journal_table_property(e),
4012            Expression::WithOperator(e) => self.generate_with_operator(e),
4013            Expression::WithProcedureOptions(e) => self.generate_with_procedure_options(e),
4014            Expression::WithSchemaBindingProperty(e) => {
4015                self.generate_with_schema_binding_property(e)
4016            }
4017            Expression::WithSystemVersioningProperty(e) => {
4018                self.generate_with_system_versioning_property(e)
4019            }
4020            Expression::WithTableHint(e) => self.generate_with_table_hint(e),
4021            Expression::XMLElement(e) => self.generate_xml_element(e),
4022            Expression::XMLGet(e) => self.generate_xml_get(e),
4023            Expression::XMLKeyValueOption(e) => self.generate_xml_key_value_option(e),
4024            Expression::XMLTable(e) => self.generate_xml_table(e),
4025            Expression::Xor(e) => self.generate_xor(e),
4026            Expression::Zipf(e) => self.generate_zipf(e),
4027            _ => self.write_unsupported_comment("unsupported expression"),
4028        }
4029    }
4030
4031    fn generate_select(&mut self, select: &Select) -> Result<()> {
4032        use crate::dialects::DialectType;
4033
4034        // Redshift-style EXCLUDE: for dialects other than Redshift, wrap in a derived table
4035        // e.g., SELECT *, col4 EXCLUDE (col2, col3) FROM t
4036        //   → SELECT * EXCLUDE (col2, col3) FROM (SELECT *, col4 FROM t)
4037        if let Some(exclude) = &select.exclude {
4038            if !exclude.is_empty() && !matches!(self.config.dialect, Some(DialectType::Redshift)) {
4039                // Build the inner select (same as original but without exclude)
4040                let mut inner_select = select.clone();
4041                inner_select.exclude = None;
4042                let inner_expr = Expression::Select(Box::new(inner_select));
4043
4044                // Build the subquery
4045                let subquery = crate::expressions::Subquery {
4046                    this: inner_expr,
4047                    alias: None,
4048                    column_aliases: Vec::new(),
4049                    alias_explicit_as: false,
4050                    alias_keyword: None,
4051                    order_by: None,
4052                    limit: None,
4053                    offset: None,
4054                    distribute_by: None,
4055                    sort_by: None,
4056                    cluster_by: None,
4057                    lateral: false,
4058                    modifiers_inside: false,
4059                    trailing_comments: Vec::new(),
4060                    inferred_type: None,
4061                };
4062
4063                // Build the outer select: SELECT * EXCLUDE (cols) FROM (inner)
4064                let star = Expression::Star(crate::expressions::Star {
4065                    table: None,
4066                    except: Some(
4067                        exclude
4068                            .iter()
4069                            .map(|e| match e {
4070                                Expression::Column(col) => col.name.clone(),
4071                                Expression::Identifier(id) => id.clone(),
4072                                _ => crate::expressions::Identifier::new("unknown".to_string()),
4073                            })
4074                            .collect(),
4075                    ),
4076                    replace: None,
4077                    rename: None,
4078                    trailing_comments: Vec::new(),
4079                    span: None,
4080                });
4081
4082                let outer_select = Select {
4083                    expressions: vec![star],
4084                    from: Some(crate::expressions::From {
4085                        expressions: vec![Expression::Subquery(Box::new(subquery))],
4086                    }),
4087                    ..Select::new()
4088                };
4089
4090                return self.generate_select(&outer_select);
4091            }
4092        }
4093
4094        // Output leading comments before SELECT
4095        for comment in &select.leading_comments {
4096            self.write_formatted_comment(comment);
4097            self.write(" ");
4098        }
4099
4100        // WITH clause
4101        if let Some(with) = &select.with {
4102            self.generate_with(with)?;
4103            if self.config.pretty {
4104                self.write_newline();
4105                self.write_indent();
4106            } else {
4107                self.write_space();
4108            }
4109        }
4110
4111        // Output post-SELECT comments (comments that appeared after SELECT keyword)
4112        // These are output BEFORE SELECT, as Python SQLGlot normalizes them this way
4113        for comment in &select.post_select_comments {
4114            self.write_formatted_comment(comment);
4115            self.write(" ");
4116        }
4117
4118        self.write_keyword("SELECT");
4119
4120        // Generate query hint if present /*+ ... */
4121        if let Some(hint) = &select.hint {
4122            self.generate_hint(hint)?;
4123        }
4124
4125        // For SQL Server, convert LIMIT to TOP (structural transformation)
4126        // But only when there's no OFFSET (otherwise use OFFSET/FETCH syntax)
4127        // TOP clause (SQL Server style - before DISTINCT)
4128        let use_top_from_limit = matches!(
4129            self.config.dialect,
4130            Some(DialectType::TSQL) | Some(DialectType::Fabric)
4131        ) && select.top.is_none()
4132            && select.limit.is_some()
4133            && select.offset.is_none(); // Don't use TOP when there's OFFSET
4134
4135        // For TOP-supporting dialects: DISTINCT before TOP
4136        // For non-TOP dialects: TOP is converted to LIMIT later; DISTINCT goes here
4137        let is_top_dialect = matches!(
4138            self.config.dialect,
4139            Some(DialectType::TSQL) | Some(DialectType::Teradata) | Some(DialectType::Fabric)
4140        );
4141        let keep_top_verbatim = !is_top_dialect
4142            && select.limit.is_none()
4143            && select
4144                .top
4145                .as_ref()
4146                .map_or(false, |top| top.percent || top.with_ties);
4147
4148        if select.distinct && (is_top_dialect || select.top.is_some()) {
4149            self.write_space();
4150            self.write_keyword("DISTINCT");
4151        }
4152
4153        if is_top_dialect || keep_top_verbatim {
4154            if let Some(top) = &select.top {
4155                self.write_space();
4156                self.write_keyword("TOP");
4157                if top.parenthesized {
4158                    if matches!(&top.this, Expression::Subquery(_) | Expression::Paren(_)) {
4159                        self.write_space();
4160                        self.generate_expression(&top.this)?;
4161                    } else {
4162                        self.write(" (");
4163                        self.generate_expression(&top.this)?;
4164                        self.write(")");
4165                    }
4166                } else {
4167                    self.write_space();
4168                    self.generate_expression(&top.this)?;
4169                }
4170                if top.percent {
4171                    self.write_space();
4172                    self.write_keyword("PERCENT");
4173                }
4174                if top.with_ties {
4175                    self.write_space();
4176                    self.write_keyword("WITH TIES");
4177                }
4178            } else if use_top_from_limit {
4179                // Convert LIMIT to TOP for SQL Server (only when no OFFSET)
4180                if let Some(limit) = &select.limit {
4181                    self.write_space();
4182                    self.write_keyword("TOP");
4183                    // Use parentheses for complex expressions, but not for simple literals
4184                    let is_simple_literal = matches!(&limit.this, Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(_)));
4185                    if is_simple_literal {
4186                        self.write_space();
4187                        self.generate_expression(&limit.this)?;
4188                    } else {
4189                        self.write(" (");
4190                        self.generate_expression(&limit.this)?;
4191                        self.write(")");
4192                    }
4193                }
4194            }
4195        }
4196
4197        if select.distinct && !is_top_dialect && select.top.is_none() {
4198            self.write_space();
4199            self.write_keyword("DISTINCT");
4200        }
4201
4202        // DISTINCT ON clause (PostgreSQL)
4203        if let Some(distinct_on) = &select.distinct_on {
4204            self.write_space();
4205            self.write_keyword("ON");
4206            self.write(" (");
4207            for (i, expr) in distinct_on.iter().enumerate() {
4208                if i > 0 {
4209                    self.write(", ");
4210                }
4211                self.generate_expression(expr)?;
4212            }
4213            self.write(")");
4214        }
4215
4216        // MySQL operation modifiers (HIGH_PRIORITY, STRAIGHT_JOIN, SQL_CALC_FOUND_ROWS, etc.)
4217        for modifier in &select.operation_modifiers {
4218            self.write_space();
4219            self.write_keyword(modifier);
4220        }
4221
4222        // BigQuery SELECT AS STRUCT / SELECT AS VALUE
4223        if let Some(kind) = &select.kind {
4224            self.write_space();
4225            self.write_keyword("AS");
4226            self.write_space();
4227            self.write_keyword(kind);
4228        }
4229
4230        // Expressions (only if there are any)
4231        if !select.expressions.is_empty() {
4232            if self.config.pretty {
4233                self.write_newline();
4234                self.indent_level += 1;
4235            } else {
4236                self.write_space();
4237            }
4238        }
4239
4240        for (i, expr) in select.expressions.iter().enumerate() {
4241            if i > 0 {
4242                self.write(",");
4243                if self.config.pretty {
4244                    self.write_newline();
4245                } else {
4246                    self.write_space();
4247                }
4248            }
4249            if self.config.pretty {
4250                self.write_indent();
4251            }
4252            self.generate_expression(expr)?;
4253        }
4254
4255        if self.config.pretty && !select.expressions.is_empty() {
4256            self.indent_level -= 1;
4257        }
4258
4259        // Redshift-style EXCLUDE clause at the end of the projection list
4260        // For Redshift dialect: append EXCLUDE (col1, col2) after the expressions
4261        // For other dialects (DuckDB, Snowflake): this is handled by wrapping in a derived table
4262        // (done after the full select is generated below)
4263        if let Some(exclude) = &select.exclude {
4264            if !exclude.is_empty() && matches!(self.config.dialect, Some(DialectType::Redshift)) {
4265                self.write_space();
4266                self.write_keyword("EXCLUDE");
4267                self.write(" (");
4268                for (i, col) in exclude.iter().enumerate() {
4269                    if i > 0 {
4270                        self.write(", ");
4271                    }
4272                    self.generate_expression(col)?;
4273                }
4274                self.write(")");
4275            }
4276        }
4277
4278        // INTO clause (SELECT ... INTO table_name)
4279        // Also handles Oracle PL/SQL: BULK COLLECT INTO v1, v2, ...
4280        if let Some(into) = &select.into {
4281            if self.config.pretty {
4282                self.write_newline();
4283                self.write_indent();
4284            } else {
4285                self.write_space();
4286            }
4287            if into.bulk_collect {
4288                self.write_keyword("BULK COLLECT INTO");
4289            } else {
4290                self.write_keyword("INTO");
4291            }
4292            if into.temporary {
4293                self.write_space();
4294                self.write_keyword("TEMPORARY");
4295            }
4296            if into.unlogged {
4297                self.write_space();
4298                self.write_keyword("UNLOGGED");
4299            }
4300            self.write_space();
4301            // If we have multiple expressions, output them comma-separated
4302            if !into.expressions.is_empty() {
4303                for (i, expr) in into.expressions.iter().enumerate() {
4304                    if i > 0 {
4305                        self.write(", ");
4306                    }
4307                    self.generate_expression(expr)?;
4308                }
4309            } else {
4310                self.generate_expression(&into.this)?;
4311            }
4312        }
4313
4314        // FROM clause
4315        if let Some(from) = &select.from {
4316            if self.config.pretty {
4317                self.write_newline();
4318                self.write_indent();
4319            } else {
4320                self.write_space();
4321            }
4322            self.write_keyword("FROM");
4323            self.write_space();
4324
4325            // BigQuery, Hive, Spark, Databricks, SQLite, and ClickHouse prefer explicit CROSS JOIN over comma syntax for multiple tables
4326            // But keep commas when TABLESAMPLE is present (Spark/Hive handle TABLESAMPLE differently with commas)
4327            // Also keep commas when the source dialect is Generic/None and target is one of these dialects
4328            // (Python sqlglot: the Hive/Spark parser marks comma joins as CROSS, but Generic parser keeps them implicit)
4329            let has_tablesample = from
4330                .expressions
4331                .iter()
4332                .any(|e| matches!(e, Expression::TableSample(_)));
4333            let is_cross_join_dialect = matches!(
4334                self.config.dialect,
4335                Some(DialectType::BigQuery)
4336                    | Some(DialectType::Hive)
4337                    | Some(DialectType::Spark)
4338                    | Some(DialectType::Databricks)
4339                    | Some(DialectType::SQLite)
4340                    | Some(DialectType::ClickHouse)
4341            );
4342            // Skip CROSS JOIN conversion when source is Generic/None and target is a CROSS JOIN dialect
4343            // This matches Python sqlglot where comma-to-CROSS-JOIN is done in the dialect's parser, not generator
4344            let source_is_same_as_target = self.config.source_dialect.is_some()
4345                && self.config.source_dialect == self.config.dialect;
4346            let source_is_cross_join_dialect = matches!(
4347                self.config.source_dialect,
4348                Some(DialectType::BigQuery)
4349                    | Some(DialectType::Hive)
4350                    | Some(DialectType::Spark)
4351                    | Some(DialectType::Databricks)
4352                    | Some(DialectType::SQLite)
4353                    | Some(DialectType::ClickHouse)
4354            );
4355            let use_cross_join = !has_tablesample
4356                && is_cross_join_dialect
4357                && (source_is_same_as_target
4358                    || source_is_cross_join_dialect
4359                    || self.config.source_dialect.is_none());
4360
4361            // Snowflake wraps standalone VALUES in FROM clause with parentheses
4362            let wrap_values_in_parens = matches!(self.config.dialect, Some(DialectType::Snowflake));
4363
4364            for (i, expr) in from.expressions.iter().enumerate() {
4365                if i > 0 {
4366                    if use_cross_join {
4367                        self.write(" CROSS JOIN ");
4368                    } else {
4369                        self.write(", ");
4370                    }
4371                }
4372                if wrap_values_in_parens && matches!(expr, Expression::Values(_)) {
4373                    self.write("(");
4374                    self.generate_expression(expr)?;
4375                    self.write(")");
4376                } else {
4377                    self.generate_expression(expr)?;
4378                }
4379                // Output leading comments that were on the table name before FROM
4380                // (e.g., FROM \n/* comment */\n tbl PIVOT(...) -> ... PIVOT(...) /* comment */)
4381                let leading = Self::extract_table_leading_comments(expr);
4382                for comment in &leading {
4383                    self.write_space();
4384                    self.write_formatted_comment(comment);
4385                }
4386            }
4387        }
4388
4389        // JOINs - handle nested join structure for pretty printing
4390        // Deferred-condition joins "own" the non-deferred joins that follow them
4391        // until the next deferred join or end of list
4392        if self.config.pretty {
4393            self.generate_joins_with_nesting(&select.joins)?;
4394        } else {
4395            for join in &select.joins {
4396                self.generate_join(join)?;
4397            }
4398            // Output deferred ON/USING conditions (right-to-left, which is reverse order)
4399            for join in select.joins.iter().rev() {
4400                if join.deferred_condition {
4401                    self.generate_join_condition(join)?;
4402                }
4403            }
4404        }
4405
4406        // LATERAL VIEW clauses (Hive/Spark)
4407        for (lv_idx, lateral_view) in select.lateral_views.iter().enumerate() {
4408            self.generate_lateral_view(lateral_view, lv_idx)?;
4409        }
4410
4411        // PREWHERE (ClickHouse)
4412        if let Some(prewhere) = &select.prewhere {
4413            self.write_clause_condition("PREWHERE", prewhere)?;
4414        }
4415
4416        // WHERE
4417        if let Some(where_clause) = &select.where_clause {
4418            self.write_clause_condition("WHERE", &where_clause.this)?;
4419        }
4420
4421        // CONNECT BY (Oracle hierarchical queries)
4422        if let Some(connect) = &select.connect {
4423            self.generate_connect(connect)?;
4424        }
4425
4426        // GROUP BY
4427        if let Some(group_by) = &select.group_by {
4428            if self.config.pretty {
4429                // Output leading comments on their own lines before GROUP BY
4430                for comment in &group_by.comments {
4431                    self.write_newline();
4432                    self.write_indent();
4433                    self.write_formatted_comment(comment);
4434                }
4435                self.write_newline();
4436                self.write_indent();
4437            } else {
4438                self.write_space();
4439                // In non-pretty mode, output comments inline
4440                for comment in &group_by.comments {
4441                    self.write_formatted_comment(comment);
4442                    self.write_space();
4443                }
4444            }
4445            let clickhouse_bare_modifiers =
4446                matches!(self.config.dialect, Some(DialectType::ClickHouse))
4447                    && group_by.all.is_none()
4448                    && (group_by.totals || !group_by.expressions.is_empty())
4449                    && group_by.expressions.iter().all(|expr| match expr {
4450                        Expression::Cube(c) => c.expressions.is_empty(),
4451                        Expression::Rollup(r) => r.expressions.is_empty(),
4452                        _ => false,
4453                    });
4454
4455            if clickhouse_bare_modifiers {
4456                let trailing_cube = group_by
4457                    .expressions
4458                    .iter()
4459                    .any(|expr| matches!(expr, Expression::Cube(c) if c.expressions.is_empty()));
4460                let trailing_rollup = group_by
4461                    .expressions
4462                    .iter()
4463                    .any(|expr| matches!(expr, Expression::Rollup(r) if r.expressions.is_empty()));
4464
4465                if trailing_cube {
4466                    self.write_keyword("WITH CUBE");
4467                } else if trailing_rollup {
4468                    self.write_keyword("WITH ROLLUP");
4469                }
4470
4471                if group_by.totals {
4472                    if trailing_cube || trailing_rollup {
4473                        self.write_space();
4474                    }
4475                    self.write_keyword("WITH TOTALS");
4476                }
4477            } else {
4478                self.write_keyword("GROUP BY");
4479                // Handle ALL/DISTINCT modifier: Some(true) = ALL, Some(false) = DISTINCT
4480                match group_by.all {
4481                    Some(true) => {
4482                        self.write_space();
4483                        self.write_keyword("ALL");
4484                    }
4485                    Some(false) => {
4486                        self.write_space();
4487                        self.write_keyword("DISTINCT");
4488                    }
4489                    None => {}
4490                }
4491                if !group_by.expressions.is_empty() {
4492                    // Check for trailing WITH CUBE or WITH ROLLUP (Hive/MySQL syntax)
4493                    // These are represented as Cube/Rollup expressions with empty expressions at the end
4494                    let mut trailing_cube = false;
4495                    let mut trailing_rollup = false;
4496                    let mut plain_expressions: Vec<&Expression> = Vec::new();
4497                    let mut grouping_sets_expressions: Vec<&Expression> = Vec::new();
4498                    let mut cube_expressions: Vec<&Expression> = Vec::new();
4499                    let mut rollup_expressions: Vec<&Expression> = Vec::new();
4500
4501                    for expr in &group_by.expressions {
4502                        match expr {
4503                            Expression::Cube(c) if c.expressions.is_empty() => {
4504                                trailing_cube = true;
4505                            }
4506                            Expression::Rollup(r) if r.expressions.is_empty() => {
4507                                trailing_rollup = true;
4508                            }
4509                            Expression::Function(f) if f.name == "CUBE" => {
4510                                cube_expressions.push(expr);
4511                            }
4512                            Expression::Function(f) if f.name == "ROLLUP" => {
4513                                rollup_expressions.push(expr);
4514                            }
4515                            Expression::Function(f) if f.name == "GROUPING SETS" => {
4516                                grouping_sets_expressions.push(expr);
4517                            }
4518                            _ => {
4519                                plain_expressions.push(expr);
4520                            }
4521                        }
4522                    }
4523
4524                    // Reorder: plain expressions first, then GROUPING SETS, CUBE, ROLLUP
4525                    let mut regular_expressions: Vec<&Expression> = Vec::new();
4526                    regular_expressions.extend(plain_expressions);
4527                    regular_expressions.extend(grouping_sets_expressions);
4528                    regular_expressions.extend(cube_expressions);
4529                    regular_expressions.extend(rollup_expressions);
4530
4531                    if self.config.pretty {
4532                        self.write_newline();
4533                        self.indent_level += 1;
4534                        self.write_indent();
4535                    } else {
4536                        self.write_space();
4537                    }
4538
4539                    for (i, expr) in regular_expressions.iter().enumerate() {
4540                        if i > 0 {
4541                            if self.config.pretty {
4542                                self.write(",");
4543                                self.write_newline();
4544                                self.write_indent();
4545                            } else {
4546                                self.write(", ");
4547                            }
4548                        }
4549                        self.generate_expression(expr)?;
4550                    }
4551
4552                    if self.config.pretty {
4553                        self.indent_level -= 1;
4554                    }
4555
4556                    // Output trailing WITH CUBE or WITH ROLLUP
4557                    if trailing_cube {
4558                        self.write_space();
4559                        self.write_keyword("WITH CUBE");
4560                    } else if trailing_rollup {
4561                        self.write_space();
4562                        self.write_keyword("WITH ROLLUP");
4563                    }
4564                }
4565
4566                // ClickHouse: WITH TOTALS
4567                if group_by.totals {
4568                    self.write_space();
4569                    self.write_keyword("WITH TOTALS");
4570                }
4571            }
4572        }
4573
4574        // HAVING
4575        if let Some(having) = &select.having {
4576            if self.config.pretty {
4577                // Output leading comments on their own lines before HAVING
4578                for comment in &having.comments {
4579                    self.write_newline();
4580                    self.write_indent();
4581                    self.write_formatted_comment(comment);
4582                }
4583            } else {
4584                for comment in &having.comments {
4585                    self.write_space();
4586                    self.write_formatted_comment(comment);
4587                }
4588            }
4589            self.write_clause_condition("HAVING", &having.this)?;
4590        }
4591
4592        // QUALIFY and WINDOW clause ordering depends on input SQL
4593        if select.qualify_after_window {
4594            // WINDOW before QUALIFY (DuckDB style)
4595            if let Some(windows) = &select.windows {
4596                self.write_window_clause(windows)?;
4597            }
4598            if let Some(qualify) = &select.qualify {
4599                self.write_clause_condition("QUALIFY", &qualify.this)?;
4600            }
4601        } else {
4602            // QUALIFY before WINDOW (Snowflake/BigQuery default)
4603            if let Some(qualify) = &select.qualify {
4604                self.write_clause_condition("QUALIFY", &qualify.this)?;
4605            }
4606            if let Some(windows) = &select.windows {
4607                self.write_window_clause(windows)?;
4608            }
4609        }
4610
4611        // DISTRIBUTE BY (Hive/Spark)
4612        if let Some(distribute_by) = &select.distribute_by {
4613            self.write_clause_expressions("DISTRIBUTE BY", &distribute_by.expressions)?;
4614        }
4615
4616        // CLUSTER BY (Hive/Spark)
4617        if let Some(cluster_by) = &select.cluster_by {
4618            self.write_order_clause("CLUSTER BY", &cluster_by.expressions)?;
4619        }
4620
4621        // SORT BY (Hive/Spark - comes before ORDER BY)
4622        if let Some(sort_by) = &select.sort_by {
4623            self.write_order_clause("SORT BY", &sort_by.expressions)?;
4624        }
4625
4626        // ORDER BY (or ORDER SIBLINGS BY for Oracle hierarchical queries)
4627        if let Some(order_by) = &select.order_by {
4628            if self.config.pretty {
4629                // Output leading comments on their own lines before ORDER BY
4630                for comment in &order_by.comments {
4631                    self.write_newline();
4632                    self.write_indent();
4633                    self.write_formatted_comment(comment);
4634                }
4635            } else {
4636                for comment in &order_by.comments {
4637                    self.write_space();
4638                    self.write_formatted_comment(comment);
4639                }
4640            }
4641            let keyword = if order_by.siblings {
4642                "ORDER SIBLINGS BY"
4643            } else {
4644                "ORDER BY"
4645            };
4646            self.write_order_clause(keyword, &order_by.expressions)?;
4647        }
4648
4649        // TSQL: FETCH requires ORDER BY. If there's a FETCH but no ORDER BY, add ORDER BY (SELECT NULL) OFFSET 0 ROWS
4650        if select.order_by.is_none()
4651            && select.fetch.is_some()
4652            && matches!(
4653                self.config.dialect,
4654                Some(DialectType::TSQL) | Some(DialectType::Fabric)
4655            )
4656        {
4657            if self.config.pretty {
4658                self.write_newline();
4659                self.write_indent();
4660            } else {
4661                self.write_space();
4662            }
4663            self.write_keyword("ORDER BY (SELECT NULL) OFFSET 0 ROWS");
4664        }
4665
4666        // LIMIT and OFFSET
4667        // PostgreSQL and others use: LIMIT count OFFSET offset
4668        // SQL Server uses: OFFSET ... FETCH (no LIMIT)
4669        // Presto/Trino uses: OFFSET n LIMIT m (offset before limit)
4670        let is_presto_like = matches!(
4671            self.config.dialect,
4672            Some(DialectType::Presto) | Some(DialectType::Trino)
4673        );
4674
4675        if is_presto_like && select.offset.is_some() {
4676            // Presto/Trino syntax: OFFSET n LIMIT m (offset comes first)
4677            if let Some(offset) = &select.offset {
4678                if self.config.pretty {
4679                    self.write_newline();
4680                    self.write_indent();
4681                } else {
4682                    self.write_space();
4683                }
4684                self.write_keyword("OFFSET");
4685                self.write_space();
4686                self.write_limit_expr(&offset.this)?;
4687                if offset.rows == Some(true) {
4688                    self.write_space();
4689                    self.write_keyword("ROWS");
4690                }
4691            }
4692            if let Some(limit) = &select.limit {
4693                if self.config.pretty {
4694                    self.write_newline();
4695                    self.write_indent();
4696                } else {
4697                    self.write_space();
4698                }
4699                self.write_keyword("LIMIT");
4700                self.write_space();
4701                self.write_limit_expr(&limit.this)?;
4702                if limit.percent {
4703                    self.write_space();
4704                    self.write_keyword("PERCENT");
4705                }
4706                // Emit any comments that were captured from before the LIMIT keyword
4707                for comment in &limit.comments {
4708                    self.write(" ");
4709                    self.write_formatted_comment(comment);
4710                }
4711            }
4712        } else {
4713            // Check if FETCH will be converted to LIMIT (used for ordering)
4714            let fetch_as_limit = select.fetch.as_ref().map_or(false, |fetch| {
4715                !fetch.percent
4716                    && !fetch.with_ties
4717                    && fetch.count.is_some()
4718                    && matches!(
4719                        self.config.dialect,
4720                        Some(DialectType::Spark)
4721                            | Some(DialectType::Hive)
4722                            | Some(DialectType::DuckDB)
4723                            | Some(DialectType::SQLite)
4724                            | Some(DialectType::MySQL)
4725                            | Some(DialectType::BigQuery)
4726                            | Some(DialectType::Databricks)
4727                            | Some(DialectType::StarRocks)
4728                            | Some(DialectType::Doris)
4729                            | Some(DialectType::Athena)
4730                            | Some(DialectType::ClickHouse)
4731                            | Some(DialectType::Redshift)
4732                    )
4733            });
4734
4735            // Standard LIMIT clause (skip for SQL Server - we use TOP or OFFSET/FETCH instead)
4736            if let Some(limit) = &select.limit {
4737                // SQL Server uses TOP (no OFFSET) or OFFSET/FETCH (with OFFSET) instead of LIMIT
4738                if !matches!(
4739                    self.config.dialect,
4740                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
4741                ) {
4742                    if self.config.pretty {
4743                        self.write_newline();
4744                        self.write_indent();
4745                    } else {
4746                        self.write_space();
4747                    }
4748                    self.write_keyword("LIMIT");
4749                    self.write_space();
4750                    self.write_limit_expr(&limit.this)?;
4751                    if limit.percent {
4752                        self.write_space();
4753                        self.write_keyword("PERCENT");
4754                    }
4755                    // Emit any comments that were captured from before the LIMIT keyword
4756                    for comment in &limit.comments {
4757                        self.write(" ");
4758                        self.write_formatted_comment(comment);
4759                    }
4760                }
4761            }
4762
4763            // Convert TOP to LIMIT for non-TOP dialects
4764            if select.top.is_some() && !is_top_dialect && select.limit.is_none() {
4765                if let Some(top) = &select.top {
4766                    if !top.percent && !top.with_ties {
4767                        if self.config.pretty {
4768                            self.write_newline();
4769                            self.write_indent();
4770                        } else {
4771                            self.write_space();
4772                        }
4773                        self.write_keyword("LIMIT");
4774                        self.write_space();
4775                        self.generate_expression(&top.this)?;
4776                    }
4777                }
4778            }
4779
4780            // If FETCH will be converted to LIMIT and there's also OFFSET,
4781            // emit LIMIT from FETCH BEFORE the OFFSET
4782            if fetch_as_limit && select.offset.is_some() {
4783                if let Some(fetch) = &select.fetch {
4784                    if self.config.pretty {
4785                        self.write_newline();
4786                        self.write_indent();
4787                    } else {
4788                        self.write_space();
4789                    }
4790                    self.write_keyword("LIMIT");
4791                    self.write_space();
4792                    self.generate_expression(fetch.count.as_ref().unwrap())?;
4793                }
4794            }
4795
4796            // OFFSET
4797            // In SQL Server, OFFSET requires ORDER BY and uses different syntax
4798            // OFFSET x ROWS FETCH NEXT y ROWS ONLY
4799            if let Some(offset) = &select.offset {
4800                if self.config.pretty {
4801                    self.write_newline();
4802                    self.write_indent();
4803                } else {
4804                    self.write_space();
4805                }
4806                if matches!(self.config.dialect, Some(DialectType::TSQL)) {
4807                    // SQL Server 2012+ OFFSET ... FETCH syntax
4808                    self.write_keyword("OFFSET");
4809                    self.write_space();
4810                    self.write_limit_expr(&offset.this)?;
4811                    self.write_space();
4812                    self.write_keyword("ROWS");
4813                    // If there was a LIMIT, use FETCH NEXT ... ROWS ONLY
4814                    if let Some(limit) = &select.limit {
4815                        self.write_space();
4816                        self.write_keyword("FETCH NEXT");
4817                        self.write_space();
4818                        self.write_limit_expr(&limit.this)?;
4819                        self.write_space();
4820                        self.write_keyword("ROWS ONLY");
4821                    }
4822                } else {
4823                    self.write_keyword("OFFSET");
4824                    self.write_space();
4825                    self.write_limit_expr(&offset.this)?;
4826                    // Output ROWS keyword if it was in the original SQL
4827                    if offset.rows == Some(true) {
4828                        self.write_space();
4829                        self.write_keyword("ROWS");
4830                    }
4831                }
4832            }
4833        }
4834
4835        // ClickHouse LIMIT BY clause (after LIMIT/OFFSET)
4836        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
4837            if let Some(limit_by) = &select.limit_by {
4838                if !limit_by.is_empty() {
4839                    self.write_space();
4840                    self.write_keyword("BY");
4841                    self.write_space();
4842                    for (i, expr) in limit_by.iter().enumerate() {
4843                        if i > 0 {
4844                            self.write(", ");
4845                        }
4846                        self.generate_expression(expr)?;
4847                    }
4848                }
4849            }
4850        }
4851
4852        // ClickHouse SETTINGS and FORMAT modifiers (after LIMIT/OFFSET)
4853        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
4854            if let Some(settings) = &select.settings {
4855                if self.config.pretty {
4856                    self.write_newline();
4857                    self.write_indent();
4858                } else {
4859                    self.write_space();
4860                }
4861                self.write_keyword("SETTINGS");
4862                self.write_space();
4863                for (i, expr) in settings.iter().enumerate() {
4864                    if i > 0 {
4865                        self.write(", ");
4866                    }
4867                    self.generate_expression(expr)?;
4868                }
4869            }
4870
4871            if let Some(format_expr) = &select.format {
4872                if self.config.pretty {
4873                    self.write_newline();
4874                    self.write_indent();
4875                } else {
4876                    self.write_space();
4877                }
4878                self.write_keyword("FORMAT");
4879                self.write_space();
4880                self.generate_expression(format_expr)?;
4881            }
4882        }
4883
4884        // FETCH FIRST/NEXT
4885        if let Some(fetch) = &select.fetch {
4886            // Check if we already emitted LIMIT from FETCH before OFFSET
4887            let fetch_already_as_limit = select.offset.is_some()
4888                && !fetch.percent
4889                && !fetch.with_ties
4890                && fetch.count.is_some()
4891                && matches!(
4892                    self.config.dialect,
4893                    Some(DialectType::Spark)
4894                        | Some(DialectType::Hive)
4895                        | Some(DialectType::DuckDB)
4896                        | Some(DialectType::SQLite)
4897                        | Some(DialectType::MySQL)
4898                        | Some(DialectType::BigQuery)
4899                        | Some(DialectType::Databricks)
4900                        | Some(DialectType::StarRocks)
4901                        | Some(DialectType::Doris)
4902                        | Some(DialectType::Athena)
4903                        | Some(DialectType::ClickHouse)
4904                        | Some(DialectType::Redshift)
4905                );
4906
4907            if fetch_already_as_limit {
4908                // Already emitted as LIMIT before OFFSET, skip
4909            } else {
4910                if self.config.pretty {
4911                    self.write_newline();
4912                    self.write_indent();
4913                } else {
4914                    self.write_space();
4915                }
4916
4917                // Convert FETCH to LIMIT for dialects that prefer LIMIT syntax
4918                let use_limit = !fetch.percent
4919                    && !fetch.with_ties
4920                    && fetch.count.is_some()
4921                    && matches!(
4922                        self.config.dialect,
4923                        Some(DialectType::Spark)
4924                            | Some(DialectType::Hive)
4925                            | Some(DialectType::DuckDB)
4926                            | Some(DialectType::SQLite)
4927                            | Some(DialectType::MySQL)
4928                            | Some(DialectType::BigQuery)
4929                            | Some(DialectType::Databricks)
4930                            | Some(DialectType::StarRocks)
4931                            | Some(DialectType::Doris)
4932                            | Some(DialectType::Athena)
4933                            | Some(DialectType::ClickHouse)
4934                            | Some(DialectType::Redshift)
4935                    );
4936
4937                if use_limit {
4938                    self.write_keyword("LIMIT");
4939                    self.write_space();
4940                    self.generate_expression(fetch.count.as_ref().unwrap())?;
4941                } else {
4942                    self.write_keyword("FETCH");
4943                    self.write_space();
4944                    self.write_keyword(&fetch.direction);
4945                    if let Some(ref count) = fetch.count {
4946                        self.write_space();
4947                        self.generate_expression(count)?;
4948                    }
4949                    if fetch.percent {
4950                        self.write_space();
4951                        self.write_keyword("PERCENT");
4952                    }
4953                    if fetch.rows {
4954                        self.write_space();
4955                        self.write_keyword("ROWS");
4956                    }
4957                    if fetch.with_ties {
4958                        self.write_space();
4959                        self.write_keyword("WITH TIES");
4960                    } else {
4961                        self.write_space();
4962                        self.write_keyword("ONLY");
4963                    }
4964                }
4965            } // close fetch_already_as_limit else
4966        }
4967
4968        // SAMPLE / TABLESAMPLE
4969        if let Some(sample) = &select.sample {
4970            use crate::dialects::DialectType;
4971            if self.config.pretty {
4972                self.write_newline();
4973            } else {
4974                self.write_space();
4975            }
4976
4977            if sample.is_using_sample {
4978                // DuckDB USING SAMPLE: METHOD (size UNIT) [REPEATABLE (seed)]
4979                self.write_keyword("USING SAMPLE");
4980                self.generate_sample_body(sample)?;
4981            } else {
4982                self.write_keyword("TABLESAMPLE");
4983
4984                // Snowflake defaults to BERNOULLI when no explicit method is given
4985                let snowflake_bernoulli =
4986                    matches!(self.config.dialect, Some(DialectType::Snowflake))
4987                        && !sample.explicit_method;
4988                if snowflake_bernoulli {
4989                    self.write_space();
4990                    self.write_keyword("BERNOULLI");
4991                }
4992
4993                // Handle BUCKET sampling: TABLESAMPLE (BUCKET 1 OUT OF 5 ON x)
4994                if matches!(sample.method, SampleMethod::Bucket) {
4995                    self.write_space();
4996                    self.write("(");
4997                    self.write_keyword("BUCKET");
4998                    self.write_space();
4999                    if let Some(ref num) = sample.bucket_numerator {
5000                        self.generate_expression(num)?;
5001                    }
5002                    self.write_space();
5003                    self.write_keyword("OUT OF");
5004                    self.write_space();
5005                    if let Some(ref denom) = sample.bucket_denominator {
5006                        self.generate_expression(denom)?;
5007                    }
5008                    if let Some(ref field) = sample.bucket_field {
5009                        self.write_space();
5010                        self.write_keyword("ON");
5011                        self.write_space();
5012                        self.generate_expression(field)?;
5013                    }
5014                    self.write(")");
5015                } else if sample.unit_after_size {
5016                    // Syntax: TABLESAMPLE [METHOD] (size ROWS) or TABLESAMPLE [METHOD] (size PERCENT)
5017                    if sample.explicit_method && sample.method_before_size {
5018                        self.write_space();
5019                        match sample.method {
5020                            SampleMethod::Bernoulli => self.write_keyword("BERNOULLI"),
5021                            SampleMethod::System => self.write_keyword("SYSTEM"),
5022                            SampleMethod::Block => self.write_keyword("BLOCK"),
5023                            SampleMethod::Row => self.write_keyword("ROW"),
5024                            SampleMethod::Reservoir => self.write_keyword("RESERVOIR"),
5025                            _ => {}
5026                        }
5027                    }
5028                    self.write(" (");
5029                    self.generate_expression(&sample.size)?;
5030                    self.write_space();
5031                    match sample.method {
5032                        SampleMethod::Percent => self.write_keyword("PERCENT"),
5033                        SampleMethod::Row => self.write_keyword("ROWS"),
5034                        SampleMethod::Reservoir => self.write_keyword("ROWS"),
5035                        _ => {
5036                            self.write_keyword("PERCENT");
5037                        }
5038                    }
5039                    self.write(")");
5040                } else {
5041                    // Syntax: TABLESAMPLE METHOD (size)
5042                    self.write_space();
5043                    match sample.method {
5044                        SampleMethod::Bernoulli => self.write_keyword("BERNOULLI"),
5045                        SampleMethod::System => self.write_keyword("SYSTEM"),
5046                        SampleMethod::Block => self.write_keyword("BLOCK"),
5047                        SampleMethod::Row => self.write_keyword("ROW"),
5048                        SampleMethod::Percent => self.write_keyword("BERNOULLI"),
5049                        SampleMethod::Bucket => {}
5050                        SampleMethod::Reservoir => self.write_keyword("RESERVOIR"),
5051                    }
5052                    self.write(" (");
5053                    self.generate_expression(&sample.size)?;
5054                    if matches!(sample.method, SampleMethod::Percent) {
5055                        self.write_space();
5056                        self.write_keyword("PERCENT");
5057                    }
5058                    self.write(")");
5059                }
5060            }
5061
5062            if let Some(seed) = &sample.seed {
5063                self.write_space();
5064                // Databricks/Spark use REPEATABLE, not SEED
5065                let use_seed = sample.use_seed_keyword
5066                    && !matches!(
5067                        self.config.dialect,
5068                        Some(crate::dialects::DialectType::Databricks)
5069                            | Some(crate::dialects::DialectType::Spark)
5070                    );
5071                if use_seed {
5072                    self.write_keyword("SEED");
5073                } else {
5074                    self.write_keyword("REPEATABLE");
5075                }
5076                self.write(" (");
5077                self.generate_expression(seed)?;
5078                self.write(")");
5079            }
5080        }
5081
5082        // FOR UPDATE/SHARE locks
5083        // Skip locking clauses for dialects that don't support them
5084        if self.config.locking_reads_supported {
5085            for lock in &select.locks {
5086                if self.config.pretty {
5087                    self.write_newline();
5088                    self.write_indent();
5089                } else {
5090                    self.write_space();
5091                }
5092                self.generate_lock(lock)?;
5093            }
5094        }
5095
5096        // FOR XML clause (T-SQL)
5097        if !select.for_xml.is_empty() {
5098            if self.config.pretty {
5099                self.write_newline();
5100                self.write_indent();
5101            } else {
5102                self.write_space();
5103            }
5104            self.write_keyword("FOR XML");
5105            for (i, opt) in select.for_xml.iter().enumerate() {
5106                if self.config.pretty {
5107                    if i > 0 {
5108                        self.write(",");
5109                    }
5110                    self.write_newline();
5111                    self.write_indent();
5112                    self.write("  "); // extra indent for options
5113                } else {
5114                    if i > 0 {
5115                        self.write(",");
5116                    }
5117                    self.write_space();
5118                }
5119                self.generate_for_xml_option(opt)?;
5120            }
5121        }
5122
5123        // FOR JSON clause (T-SQL)
5124        if !select.for_json.is_empty() {
5125            if self.config.pretty {
5126                self.write_newline();
5127                self.write_indent();
5128            } else {
5129                self.write_space();
5130            }
5131            self.write_keyword("FOR JSON");
5132            for (i, opt) in select.for_json.iter().enumerate() {
5133                if self.config.pretty {
5134                    if i > 0 {
5135                        self.write(",");
5136                    }
5137                    self.write_newline();
5138                    self.write_indent();
5139                    self.write("  "); // extra indent for options
5140                } else {
5141                    if i > 0 {
5142                        self.write(",");
5143                    }
5144                    self.write_space();
5145                }
5146                self.generate_for_xml_option(opt)?;
5147            }
5148        }
5149
5150        // TSQL: OPTION clause
5151        if let Some(ref option) = select.option {
5152            if matches!(
5153                self.config.dialect,
5154                Some(crate::dialects::DialectType::TSQL)
5155                    | Some(crate::dialects::DialectType::Fabric)
5156            ) {
5157                self.write_space();
5158                self.write(option);
5159            }
5160        }
5161
5162        Ok(())
5163    }
5164
5165    /// Generate a single FOR XML option
5166    fn generate_for_xml_option(&mut self, opt: &Expression) -> Result<()> {
5167        match opt {
5168            Expression::QueryOption(qo) => {
5169                // Extract the option name from Var
5170                if let Expression::Var(var) = &*qo.this {
5171                    self.write(&var.this);
5172                } else {
5173                    self.generate_expression(&qo.this)?;
5174                }
5175                // If there's an expression (like PATH('element')), output it in parens
5176                if let Some(expr) = &qo.expression {
5177                    self.write("(");
5178                    self.generate_expression(expr)?;
5179                    self.write(")");
5180                }
5181            }
5182            _ => {
5183                self.generate_expression(opt)?;
5184            }
5185        }
5186        Ok(())
5187    }
5188
5189    fn generate_with(&mut self, with: &With) -> Result<()> {
5190        use crate::dialects::DialectType;
5191
5192        // Output leading comments before WITH
5193        for comment in &with.leading_comments {
5194            self.write_formatted_comment(comment);
5195            self.write(" ");
5196        }
5197        self.write_keyword("WITH");
5198        if with.recursive && self.config.cte_recursive_keyword_required {
5199            self.write_space();
5200            self.write_keyword("RECURSIVE");
5201        }
5202        self.write_space();
5203
5204        // BigQuery doesn't support column aliases in CTE definitions
5205        let skip_cte_columns = matches!(self.config.dialect, Some(DialectType::BigQuery));
5206
5207        for (i, cte) in with.ctes.iter().enumerate() {
5208            if i > 0 {
5209                self.write(",");
5210                if self.config.pretty {
5211                    self.write_space();
5212                } else {
5213                    self.write(" ");
5214                }
5215            }
5216            if matches!(self.config.dialect, Some(DialectType::ClickHouse)) && !cte.alias_first {
5217                self.generate_expression(&cte.this)?;
5218                self.write_space();
5219                self.write_keyword("AS");
5220                self.write_space();
5221                self.generate_identifier(&cte.alias)?;
5222                continue;
5223            }
5224            self.generate_identifier(&cte.alias)?;
5225            // Output CTE comments after alias name, before AS
5226            for comment in &cte.comments {
5227                self.write_space();
5228                self.write_formatted_comment(comment);
5229            }
5230            if !cte.columns.is_empty() && !skip_cte_columns {
5231                self.write("(");
5232                for (j, col) in cte.columns.iter().enumerate() {
5233                    if j > 0 {
5234                        self.write(", ");
5235                    }
5236                    self.generate_identifier(col)?;
5237                }
5238                self.write(")");
5239            }
5240            // USING KEY (columns) for DuckDB recursive CTEs
5241            if !cte.key_expressions.is_empty() {
5242                self.write_space();
5243                self.write_keyword("USING KEY");
5244                self.write(" (");
5245                for (i, key) in cte.key_expressions.iter().enumerate() {
5246                    if i > 0 {
5247                        self.write(", ");
5248                    }
5249                    self.generate_identifier(key)?;
5250                }
5251                self.write(")");
5252            }
5253            self.write_space();
5254            self.write_keyword("AS");
5255            // MATERIALIZED / NOT MATERIALIZED
5256            if let Some(materialized) = cte.materialized {
5257                self.write_space();
5258                if materialized {
5259                    self.write_keyword("MATERIALIZED");
5260                } else {
5261                    self.write_keyword("NOT MATERIALIZED");
5262                }
5263            }
5264            self.write(" (");
5265            if self.config.pretty {
5266                self.write_newline();
5267                self.indent_level += 1;
5268                self.write_indent();
5269            }
5270            // For Spark/Databricks, VALUES in a CTE must be wrapped with SELECT * FROM
5271            // e.g., WITH t AS (VALUES ('foo_val') AS t(foo1)) -> WITH t AS (SELECT * FROM VALUES ('foo_val') AS t(foo1))
5272            let wrap_values_in_select = matches!(
5273                self.config.dialect,
5274                Some(DialectType::Spark) | Some(DialectType::Databricks)
5275            ) && matches!(&cte.this, Expression::Values(_));
5276
5277            if wrap_values_in_select {
5278                self.write_keyword("SELECT");
5279                self.write(" * ");
5280                self.write_keyword("FROM");
5281                self.write_space();
5282            }
5283            self.generate_expression(&cte.this)?;
5284            if self.config.pretty {
5285                self.write_newline();
5286                self.indent_level -= 1;
5287                self.write_indent();
5288            }
5289            self.write(")");
5290        }
5291
5292        // Generate SEARCH/CYCLE clause if present
5293        if let Some(search) = &with.search {
5294            self.write_space();
5295            self.generate_expression(search)?;
5296        }
5297
5298        Ok(())
5299    }
5300
5301    /// Generate joins with proper nesting structure for pretty printing.
5302    /// Deferred-condition joins "own" the non-deferred joins that follow them
5303    /// within the same nesting_group.
5304    fn generate_joins_with_nesting(&mut self, joins: &[Join]) -> Result<()> {
5305        let mut i = 0;
5306        while i < joins.len() {
5307            if joins[i].deferred_condition {
5308                let parent_group = joins[i].nesting_group;
5309
5310                // This join owns the following non-deferred joins in the same nesting_group
5311                // First output the join keyword and table (without condition)
5312                self.generate_join_without_condition(&joins[i])?;
5313
5314                // Find the range of child joins: same nesting_group and not deferred
5315                let child_start = i + 1;
5316                let mut child_end = child_start;
5317                while child_end < joins.len()
5318                    && !joins[child_end].deferred_condition
5319                    && joins[child_end].nesting_group == parent_group
5320                {
5321                    child_end += 1;
5322                }
5323
5324                // Output child joins with extra indentation
5325                if child_start < child_end {
5326                    self.indent_level += 1;
5327                    for j in child_start..child_end {
5328                        self.generate_join(&joins[j])?;
5329                    }
5330                    self.indent_level -= 1;
5331                }
5332
5333                // Output the deferred condition at the parent level
5334                self.generate_join_condition(&joins[i])?;
5335
5336                i = child_end;
5337            } else {
5338                // Regular join (no nesting)
5339                self.generate_join(&joins[i])?;
5340                i += 1;
5341            }
5342        }
5343        Ok(())
5344    }
5345
5346    /// Generate a join's keyword and table reference, but not its ON/USING condition.
5347    /// Used for deferred-condition joins where the condition is output after child joins.
5348    fn generate_join_without_condition(&mut self, join: &Join) -> Result<()> {
5349        // Save and temporarily clear the condition to prevent generate_join from outputting it
5350        // We achieve this by creating a modified copy
5351        let mut join_copy = join.clone();
5352        join_copy.on = None;
5353        join_copy.using = Vec::new();
5354        join_copy.deferred_condition = false;
5355        self.generate_join(&join_copy)
5356    }
5357
5358    fn generate_join(&mut self, join: &Join) -> Result<()> {
5359        // Implicit (comma) joins: output as ", table" instead of "CROSS JOIN table"
5360        if join.kind == JoinKind::Implicit {
5361            self.write(",");
5362            if self.config.pretty {
5363                self.write_newline();
5364                self.write_indent();
5365            } else {
5366                self.write_space();
5367            }
5368            self.generate_expression(&join.this)?;
5369            return Ok(());
5370        }
5371
5372        if self.config.pretty {
5373            self.write_newline();
5374            self.write_indent();
5375        } else {
5376            self.write_space();
5377        }
5378
5379        // Helper: format hint suffix (e.g., " LOOP" or "")
5380        // Only include join hints for dialects that support them
5381        let hint_str = if self.config.join_hints {
5382            join.join_hint
5383                .as_ref()
5384                .map(|h| format!(" {}", h))
5385                .unwrap_or_default()
5386        } else {
5387            String::new()
5388        };
5389
5390        let clickhouse_join_keyword =
5391            if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
5392                if let Some(hint) = &join.join_hint {
5393                    let mut global = false;
5394                    let mut strictness: Option<&'static str> = None;
5395                    for part in hint.split_whitespace() {
5396                        if part.eq_ignore_ascii_case("GLOBAL") {
5397                            global = true;
5398                        } else if part.eq_ignore_ascii_case("ALL") {
5399                            strictness = Some("ALL");
5400                        } else if part.eq_ignore_ascii_case("ANY") {
5401                            strictness = Some("ANY");
5402                        } else if part.eq_ignore_ascii_case("ASOF") {
5403                            strictness = Some("ASOF");
5404                        } else if part.eq_ignore_ascii_case("SEMI") {
5405                            strictness = Some("SEMI");
5406                        } else if part.eq_ignore_ascii_case("ANTI") {
5407                            strictness = Some("ANTI");
5408                        }
5409                    }
5410
5411                    if global || strictness.is_some() {
5412                        let join_type = match join.kind {
5413                            JoinKind::Left => {
5414                                if join.use_outer_keyword {
5415                                    "LEFT OUTER"
5416                                } else if join.use_inner_keyword {
5417                                    "LEFT INNER"
5418                                } else {
5419                                    "LEFT"
5420                                }
5421                            }
5422                            JoinKind::Right => {
5423                                if join.use_outer_keyword {
5424                                    "RIGHT OUTER"
5425                                } else if join.use_inner_keyword {
5426                                    "RIGHT INNER"
5427                                } else {
5428                                    "RIGHT"
5429                                }
5430                            }
5431                            JoinKind::Full => {
5432                                if join.use_outer_keyword {
5433                                    "FULL OUTER"
5434                                } else {
5435                                    "FULL"
5436                                }
5437                            }
5438                            JoinKind::Inner => {
5439                                if join.use_inner_keyword {
5440                                    "INNER"
5441                                } else {
5442                                    ""
5443                                }
5444                            }
5445                            _ => "",
5446                        };
5447
5448                        let mut parts = Vec::new();
5449                        if global {
5450                            parts.push("GLOBAL");
5451                        }
5452                        if !join_type.is_empty() {
5453                            parts.push(join_type);
5454                        }
5455                        if let Some(strict) = strictness {
5456                            parts.push(strict);
5457                        }
5458                        parts.push("JOIN");
5459                        Some(parts.join(" "))
5460                    } else {
5461                        None
5462                    }
5463                } else {
5464                    None
5465                }
5466            } else {
5467                None
5468            };
5469
5470        // Output any comments associated with this join
5471        // In pretty mode, comments go on their own line before the join keyword
5472        // In non-pretty mode, comments go inline before the join keyword
5473        if !join.comments.is_empty() {
5474            if self.config.pretty {
5475                // In pretty mode, go back before the newline+indent we just wrote
5476                // and output comments on their own lines
5477                // We need to output comments BEFORE the join keyword on separate lines
5478                // Trim the trailing newline+indent we already wrote
5479                let trimmed = self.output.trim_end().len();
5480                self.output.truncate(trimmed);
5481                for comment in &join.comments {
5482                    self.write_newline();
5483                    self.write_indent();
5484                    self.write_formatted_comment(comment);
5485                }
5486                self.write_newline();
5487                self.write_indent();
5488            } else {
5489                for comment in &join.comments {
5490                    self.write_formatted_comment(comment);
5491                    self.write_space();
5492                }
5493            }
5494        }
5495
5496        let directed_str = if join.directed { " DIRECTED" } else { "" };
5497
5498        if let Some(keyword) = clickhouse_join_keyword {
5499            self.write_keyword(&keyword);
5500        } else {
5501            match join.kind {
5502                JoinKind::Inner => {
5503                    if join.use_inner_keyword {
5504                        if hint_str.is_empty() && directed_str.is_empty() {
5505                            self.write_keyword("INNER JOIN");
5506                        } else {
5507                            self.write_keyword("INNER");
5508                            if !hint_str.is_empty() {
5509                                self.write_keyword(&hint_str);
5510                            }
5511                            if !directed_str.is_empty() {
5512                                self.write_keyword(directed_str);
5513                            }
5514                            self.write_keyword(" JOIN");
5515                        }
5516                    } else {
5517                        if !hint_str.is_empty() {
5518                            self.write_keyword(hint_str.trim());
5519                            self.write_keyword(" ");
5520                        }
5521                        if !directed_str.is_empty() {
5522                            self.write_keyword("DIRECTED ");
5523                        }
5524                        self.write_keyword("JOIN");
5525                    }
5526                }
5527                JoinKind::Left => {
5528                    if join.use_outer_keyword {
5529                        if hint_str.is_empty() && directed_str.is_empty() {
5530                            self.write_keyword("LEFT OUTER JOIN");
5531                        } else {
5532                            self.write_keyword("LEFT OUTER");
5533                            if !hint_str.is_empty() {
5534                                self.write_keyword(&hint_str);
5535                            }
5536                            if !directed_str.is_empty() {
5537                                self.write_keyword(directed_str);
5538                            }
5539                            self.write_keyword(" JOIN");
5540                        }
5541                    } else if join.use_inner_keyword {
5542                        if hint_str.is_empty() && directed_str.is_empty() {
5543                            self.write_keyword("LEFT INNER JOIN");
5544                        } else {
5545                            self.write_keyword("LEFT INNER");
5546                            if !hint_str.is_empty() {
5547                                self.write_keyword(&hint_str);
5548                            }
5549                            if !directed_str.is_empty() {
5550                                self.write_keyword(directed_str);
5551                            }
5552                            self.write_keyword(" JOIN");
5553                        }
5554                    } else {
5555                        if hint_str.is_empty() && directed_str.is_empty() {
5556                            self.write_keyword("LEFT JOIN");
5557                        } else {
5558                            self.write_keyword("LEFT");
5559                            if !hint_str.is_empty() {
5560                                self.write_keyword(&hint_str);
5561                            }
5562                            if !directed_str.is_empty() {
5563                                self.write_keyword(directed_str);
5564                            }
5565                            self.write_keyword(" JOIN");
5566                        }
5567                    }
5568                }
5569                JoinKind::Right => {
5570                    if join.use_outer_keyword {
5571                        if hint_str.is_empty() && directed_str.is_empty() {
5572                            self.write_keyword("RIGHT OUTER JOIN");
5573                        } else {
5574                            self.write_keyword("RIGHT OUTER");
5575                            if !hint_str.is_empty() {
5576                                self.write_keyword(&hint_str);
5577                            }
5578                            if !directed_str.is_empty() {
5579                                self.write_keyword(directed_str);
5580                            }
5581                            self.write_keyword(" JOIN");
5582                        }
5583                    } else if join.use_inner_keyword {
5584                        if hint_str.is_empty() && directed_str.is_empty() {
5585                            self.write_keyword("RIGHT INNER JOIN");
5586                        } else {
5587                            self.write_keyword("RIGHT INNER");
5588                            if !hint_str.is_empty() {
5589                                self.write_keyword(&hint_str);
5590                            }
5591                            if !directed_str.is_empty() {
5592                                self.write_keyword(directed_str);
5593                            }
5594                            self.write_keyword(" JOIN");
5595                        }
5596                    } else {
5597                        if hint_str.is_empty() && directed_str.is_empty() {
5598                            self.write_keyword("RIGHT JOIN");
5599                        } else {
5600                            self.write_keyword("RIGHT");
5601                            if !hint_str.is_empty() {
5602                                self.write_keyword(&hint_str);
5603                            }
5604                            if !directed_str.is_empty() {
5605                                self.write_keyword(directed_str);
5606                            }
5607                            self.write_keyword(" JOIN");
5608                        }
5609                    }
5610                }
5611                JoinKind::Full => {
5612                    if join.use_outer_keyword {
5613                        if hint_str.is_empty() && directed_str.is_empty() {
5614                            self.write_keyword("FULL OUTER JOIN");
5615                        } else {
5616                            self.write_keyword("FULL OUTER");
5617                            if !hint_str.is_empty() {
5618                                self.write_keyword(&hint_str);
5619                            }
5620                            if !directed_str.is_empty() {
5621                                self.write_keyword(directed_str);
5622                            }
5623                            self.write_keyword(" JOIN");
5624                        }
5625                    } else {
5626                        if hint_str.is_empty() && directed_str.is_empty() {
5627                            self.write_keyword("FULL JOIN");
5628                        } else {
5629                            self.write_keyword("FULL");
5630                            if !hint_str.is_empty() {
5631                                self.write_keyword(&hint_str);
5632                            }
5633                            if !directed_str.is_empty() {
5634                                self.write_keyword(directed_str);
5635                            }
5636                            self.write_keyword(" JOIN");
5637                        }
5638                    }
5639                }
5640                JoinKind::Outer => {
5641                    if directed_str.is_empty() {
5642                        self.write_keyword("OUTER JOIN");
5643                    } else {
5644                        self.write_keyword("OUTER");
5645                        self.write_keyword(directed_str);
5646                        self.write_keyword(" JOIN");
5647                    }
5648                }
5649                JoinKind::Cross => {
5650                    if directed_str.is_empty() {
5651                        self.write_keyword("CROSS JOIN");
5652                    } else {
5653                        self.write_keyword("CROSS");
5654                        self.write_keyword(directed_str);
5655                        self.write_keyword(" JOIN");
5656                    }
5657                }
5658                JoinKind::Natural => {
5659                    if join.use_inner_keyword {
5660                        if directed_str.is_empty() {
5661                            self.write_keyword("NATURAL INNER JOIN");
5662                        } else {
5663                            self.write_keyword("NATURAL INNER");
5664                            self.write_keyword(directed_str);
5665                            self.write_keyword(" JOIN");
5666                        }
5667                    } else {
5668                        if directed_str.is_empty() {
5669                            self.write_keyword("NATURAL JOIN");
5670                        } else {
5671                            self.write_keyword("NATURAL");
5672                            self.write_keyword(directed_str);
5673                            self.write_keyword(" JOIN");
5674                        }
5675                    }
5676                }
5677                JoinKind::NaturalLeft => {
5678                    if join.use_outer_keyword {
5679                        if directed_str.is_empty() {
5680                            self.write_keyword("NATURAL LEFT OUTER JOIN");
5681                        } else {
5682                            self.write_keyword("NATURAL LEFT OUTER");
5683                            self.write_keyword(directed_str);
5684                            self.write_keyword(" JOIN");
5685                        }
5686                    } else {
5687                        if directed_str.is_empty() {
5688                            self.write_keyword("NATURAL LEFT JOIN");
5689                        } else {
5690                            self.write_keyword("NATURAL LEFT");
5691                            self.write_keyword(directed_str);
5692                            self.write_keyword(" JOIN");
5693                        }
5694                    }
5695                }
5696                JoinKind::NaturalRight => {
5697                    if join.use_outer_keyword {
5698                        if directed_str.is_empty() {
5699                            self.write_keyword("NATURAL RIGHT OUTER JOIN");
5700                        } else {
5701                            self.write_keyword("NATURAL RIGHT OUTER");
5702                            self.write_keyword(directed_str);
5703                            self.write_keyword(" JOIN");
5704                        }
5705                    } else {
5706                        if directed_str.is_empty() {
5707                            self.write_keyword("NATURAL RIGHT JOIN");
5708                        } else {
5709                            self.write_keyword("NATURAL RIGHT");
5710                            self.write_keyword(directed_str);
5711                            self.write_keyword(" JOIN");
5712                        }
5713                    }
5714                }
5715                JoinKind::NaturalFull => {
5716                    if join.use_outer_keyword {
5717                        if directed_str.is_empty() {
5718                            self.write_keyword("NATURAL FULL OUTER JOIN");
5719                        } else {
5720                            self.write_keyword("NATURAL FULL OUTER");
5721                            self.write_keyword(directed_str);
5722                            self.write_keyword(" JOIN");
5723                        }
5724                    } else {
5725                        if directed_str.is_empty() {
5726                            self.write_keyword("NATURAL FULL JOIN");
5727                        } else {
5728                            self.write_keyword("NATURAL FULL");
5729                            self.write_keyword(directed_str);
5730                            self.write_keyword(" JOIN");
5731                        }
5732                    }
5733                }
5734                JoinKind::Semi => self.write_keyword("SEMI JOIN"),
5735                JoinKind::Anti => self.write_keyword("ANTI JOIN"),
5736                JoinKind::LeftSemi => self.write_keyword("LEFT SEMI JOIN"),
5737                JoinKind::LeftAnti => self.write_keyword("LEFT ANTI JOIN"),
5738                JoinKind::RightSemi => self.write_keyword("RIGHT SEMI JOIN"),
5739                JoinKind::RightAnti => self.write_keyword("RIGHT ANTI JOIN"),
5740                JoinKind::CrossApply => {
5741                    // CROSS APPLY -> INNER JOIN LATERAL for non-TSQL dialects
5742                    if matches!(self.config.dialect, Some(DialectType::TSQL) | None) {
5743                        self.write_keyword("CROSS APPLY");
5744                    } else {
5745                        self.write_keyword("INNER JOIN LATERAL");
5746                    }
5747                }
5748                JoinKind::OuterApply => {
5749                    // OUTER APPLY -> LEFT JOIN LATERAL for non-TSQL dialects
5750                    if matches!(self.config.dialect, Some(DialectType::TSQL) | None) {
5751                        self.write_keyword("OUTER APPLY");
5752                    } else {
5753                        self.write_keyword("LEFT JOIN LATERAL");
5754                    }
5755                }
5756                JoinKind::AsOf => self.write_keyword("ASOF JOIN"),
5757                JoinKind::AsOfLeft => {
5758                    if join.use_outer_keyword {
5759                        self.write_keyword("ASOF LEFT OUTER JOIN");
5760                    } else {
5761                        self.write_keyword("ASOF LEFT JOIN");
5762                    }
5763                }
5764                JoinKind::AsOfRight => {
5765                    if join.use_outer_keyword {
5766                        self.write_keyword("ASOF RIGHT OUTER JOIN");
5767                    } else {
5768                        self.write_keyword("ASOF RIGHT JOIN");
5769                    }
5770                }
5771                JoinKind::Lateral => self.write_keyword("LATERAL JOIN"),
5772                JoinKind::LeftLateral => {
5773                    if join.use_outer_keyword {
5774                        self.write_keyword("LEFT OUTER LATERAL JOIN");
5775                    } else {
5776                        self.write_keyword("LEFT LATERAL JOIN");
5777                    }
5778                }
5779                JoinKind::Straight => self.write_keyword("STRAIGHT_JOIN"),
5780                JoinKind::Implicit => {
5781                    // BigQuery, Hive, Spark, and Databricks prefer explicit CROSS JOIN over comma syntax
5782                    // But only when source is the same dialect (identity) or source is another CROSS JOIN dialect
5783                    // When source is Generic, keep commas (Python sqlglot: parser marks joins, not generator)
5784                    use crate::dialects::DialectType;
5785                    let is_cj_dialect = matches!(
5786                        self.config.dialect,
5787                        Some(DialectType::BigQuery)
5788                            | Some(DialectType::Hive)
5789                            | Some(DialectType::Spark)
5790                            | Some(DialectType::Databricks)
5791                    );
5792                    let source_is_same = self.config.source_dialect.is_some()
5793                        && self.config.source_dialect == self.config.dialect;
5794                    let source_is_cj = matches!(
5795                        self.config.source_dialect,
5796                        Some(DialectType::BigQuery)
5797                            | Some(DialectType::Hive)
5798                            | Some(DialectType::Spark)
5799                            | Some(DialectType::Databricks)
5800                    );
5801                    if is_cj_dialect
5802                        && (source_is_same || source_is_cj || self.config.source_dialect.is_none())
5803                    {
5804                        self.write_keyword("CROSS JOIN");
5805                    } else {
5806                        // Implicit join uses comma: FROM a, b
5807                        // We already wrote a space before the match, so replace with comma
5808                        // by removing trailing space and writing ", "
5809                        self.output.truncate(self.output.trim_end().len());
5810                        self.write(",");
5811                    }
5812                }
5813                JoinKind::Array => self.write_keyword("ARRAY JOIN"),
5814                JoinKind::LeftArray => self.write_keyword("LEFT ARRAY JOIN"),
5815                JoinKind::Paste => self.write_keyword("PASTE JOIN"),
5816                JoinKind::Positional => self.write_keyword("POSITIONAL JOIN"),
5817            }
5818        }
5819
5820        // ARRAY JOIN items need comma-separated output (Tuple holds multiple items)
5821        if matches!(join.kind, JoinKind::Array | JoinKind::LeftArray) {
5822            match &join.this {
5823                Expression::Tuple(t) if t.expressions.is_empty() => {}
5824                Expression::Tuple(t) => {
5825                    self.write_space();
5826                    for (i, item) in t.expressions.iter().enumerate() {
5827                        if i > 0 {
5828                            self.write(", ");
5829                        }
5830                        self.generate_expression(item)?;
5831                    }
5832                }
5833                other => {
5834                    self.write_space();
5835                    self.generate_expression(other)?;
5836                }
5837            }
5838        } else {
5839            self.write_space();
5840            self.generate_expression(&join.this)?;
5841        }
5842
5843        // Only output MATCH_CONDITION/ON/USING inline if the condition wasn't deferred
5844        if !join.deferred_condition {
5845            // Output MATCH_CONDITION first (Snowflake ASOF JOIN)
5846            if let Some(match_cond) = &join.match_condition {
5847                self.write_space();
5848                self.write_keyword("MATCH_CONDITION");
5849                self.write(" (");
5850                self.generate_expression(match_cond)?;
5851                self.write(")");
5852            }
5853
5854            if let Some(on) = &join.on {
5855                if self.config.pretty {
5856                    self.write_newline();
5857                    self.indent_level += 1;
5858                    self.write_indent();
5859                    self.write_keyword("ON");
5860                    self.write_space();
5861                    self.generate_join_on_condition(on)?;
5862                    self.indent_level -= 1;
5863                } else {
5864                    self.write_space();
5865                    self.write_keyword("ON");
5866                    self.write_space();
5867                    self.generate_expression(on)?;
5868                }
5869            }
5870
5871            if !join.using.is_empty() {
5872                if self.config.pretty {
5873                    self.write_newline();
5874                    self.indent_level += 1;
5875                    self.write_indent();
5876                    self.write_keyword("USING");
5877                    self.write(" (");
5878                    for (i, col) in join.using.iter().enumerate() {
5879                        if i > 0 {
5880                            self.write(", ");
5881                        }
5882                        self.generate_identifier(col)?;
5883                    }
5884                    self.write(")");
5885                    self.indent_level -= 1;
5886                } else {
5887                    self.write_space();
5888                    self.write_keyword("USING");
5889                    self.write(" (");
5890                    for (i, col) in join.using.iter().enumerate() {
5891                        if i > 0 {
5892                            self.write(", ");
5893                        }
5894                        self.generate_identifier(col)?;
5895                    }
5896                    self.write(")");
5897                }
5898            }
5899        }
5900
5901        // Generate PIVOT/UNPIVOT expressions that follow this join
5902        for pivot in &join.pivots {
5903            self.write_space();
5904            self.generate_expression(pivot)?;
5905        }
5906
5907        Ok(())
5908    }
5909
5910    /// Generate just the ON/USING/MATCH_CONDITION for a join (used for deferred conditions)
5911    fn generate_join_condition(&mut self, join: &Join) -> Result<()> {
5912        // Generate MATCH_CONDITION first (Snowflake ASOF JOIN)
5913        if let Some(match_cond) = &join.match_condition {
5914            self.write_space();
5915            self.write_keyword("MATCH_CONDITION");
5916            self.write(" (");
5917            self.generate_expression(match_cond)?;
5918            self.write(")");
5919        }
5920
5921        if let Some(on) = &join.on {
5922            if self.config.pretty {
5923                self.write_newline();
5924                self.indent_level += 1;
5925                self.write_indent();
5926                self.write_keyword("ON");
5927                self.write_space();
5928                // In pretty mode, split AND conditions onto separate lines
5929                self.generate_join_on_condition(on)?;
5930                self.indent_level -= 1;
5931            } else {
5932                self.write_space();
5933                self.write_keyword("ON");
5934                self.write_space();
5935                self.generate_expression(on)?;
5936            }
5937        }
5938
5939        if !join.using.is_empty() {
5940            if self.config.pretty {
5941                self.write_newline();
5942                self.indent_level += 1;
5943                self.write_indent();
5944                self.write_keyword("USING");
5945                self.write(" (");
5946                for (i, col) in join.using.iter().enumerate() {
5947                    if i > 0 {
5948                        self.write(", ");
5949                    }
5950                    self.generate_identifier(col)?;
5951                }
5952                self.write(")");
5953                self.indent_level -= 1;
5954            } else {
5955                self.write_space();
5956                self.write_keyword("USING");
5957                self.write(" (");
5958                for (i, col) in join.using.iter().enumerate() {
5959                    if i > 0 {
5960                        self.write(", ");
5961                    }
5962                    self.generate_identifier(col)?;
5963                }
5964                self.write(")");
5965            }
5966        }
5967
5968        // Generate PIVOT/UNPIVOT expressions that follow this join (for deferred conditions)
5969        for pivot in &join.pivots {
5970            self.write_space();
5971            self.generate_expression(pivot)?;
5972        }
5973
5974        Ok(())
5975    }
5976
5977    /// Generate JOIN ON condition with AND clauses on separate lines in pretty mode
5978    fn generate_join_on_condition(&mut self, expr: &Expression) -> Result<()> {
5979        if let Expression::And(and_op) = expr {
5980            if let Some(conditions) = self.flatten_connector_terms(and_op, ConnectorOperator::And) {
5981                self.generate_expression(conditions[0])?;
5982                for condition in conditions.iter().skip(1) {
5983                    self.write_newline();
5984                    self.write_indent();
5985                    self.write_keyword("AND");
5986                    self.write_space();
5987                    self.generate_expression(condition)?;
5988                }
5989                return Ok(());
5990            }
5991        }
5992
5993        self.generate_expression(expr)
5994    }
5995
5996    fn generate_joined_table(&mut self, jt: &JoinedTable) -> Result<()> {
5997        // Parenthesized join: (tbl1 CROSS JOIN tbl2)
5998        self.write("(");
5999        self.generate_expression(&jt.left)?;
6000
6001        // Generate all joins
6002        for join in &jt.joins {
6003            self.generate_join(join)?;
6004        }
6005
6006        // Generate LATERAL VIEW clauses (Hive/Spark)
6007        for (lv_idx, lv) in jt.lateral_views.iter().enumerate() {
6008            self.generate_lateral_view(lv, lv_idx)?;
6009        }
6010
6011        self.write(")");
6012
6013        // Alias
6014        if let Some(alias) = &jt.alias {
6015            self.write_space();
6016            self.write_keyword("AS");
6017            self.write_space();
6018            self.generate_identifier(alias)?;
6019        }
6020
6021        Ok(())
6022    }
6023
6024    fn generate_lateral_view(&mut self, lv: &LateralView, lv_index: usize) -> Result<()> {
6025        use crate::dialects::DialectType;
6026
6027        if self.config.pretty {
6028            self.write_newline();
6029            self.write_indent();
6030        } else {
6031            self.write_space();
6032        }
6033
6034        // For Hive/Spark/Databricks (or no dialect specified), output native LATERAL VIEW syntax
6035        // For PostgreSQL and other specific dialects, convert to CROSS JOIN (LATERAL or UNNEST)
6036        let use_lateral_join = matches!(
6037            self.config.dialect,
6038            Some(DialectType::PostgreSQL)
6039                | Some(DialectType::DuckDB)
6040                | Some(DialectType::Snowflake)
6041                | Some(DialectType::TSQL)
6042                | Some(DialectType::Presto)
6043                | Some(DialectType::Trino)
6044                | Some(DialectType::Athena)
6045        );
6046
6047        // Check if target dialect should use UNNEST instead of EXPLODE
6048        let use_unnest = matches!(
6049            self.config.dialect,
6050            Some(DialectType::DuckDB)
6051                | Some(DialectType::Presto)
6052                | Some(DialectType::Trino)
6053                | Some(DialectType::Athena)
6054        );
6055
6056        // Check if we need POSEXPLODE -> UNNEST WITH ORDINALITY
6057        let (is_posexplode, is_inline, func_args) = match &lv.this {
6058            Expression::Explode(uf) => {
6059                // Expression::Explode is the dedicated EXPLODE expression type
6060                (false, false, vec![uf.this.clone()])
6061            }
6062            Expression::Unnest(uf) => {
6063                let mut args = vec![uf.this.clone()];
6064                args.extend(uf.expressions.clone());
6065                (false, false, args)
6066            }
6067            Expression::Function(func) => {
6068                if func.name.eq_ignore_ascii_case("POSEXPLODE")
6069                    || func.name.eq_ignore_ascii_case("POSEXPLODE_OUTER")
6070                {
6071                    (true, false, func.args.clone())
6072                } else if func.name.eq_ignore_ascii_case("INLINE") {
6073                    (false, true, func.args.clone())
6074                } else if func.name.eq_ignore_ascii_case("EXPLODE")
6075                    || func.name.eq_ignore_ascii_case("EXPLODE_OUTER")
6076                {
6077                    (false, false, func.args.clone())
6078                } else {
6079                    (false, false, vec![])
6080                }
6081            }
6082            _ => (false, false, vec![]),
6083        };
6084
6085        if use_lateral_join {
6086            // Convert to CROSS JOIN for PostgreSQL-like dialects
6087            if lv.outer {
6088                self.write_keyword("LEFT JOIN LATERAL");
6089            } else {
6090                self.write_keyword("CROSS JOIN");
6091            }
6092            self.write_space();
6093
6094            if use_unnest && !func_args.is_empty() {
6095                // Convert EXPLODE(y) -> UNNEST(y), POSEXPLODE(y) -> UNNEST(y)
6096                // For DuckDB, also convert ARRAY(y) -> [y]
6097                let unnest_args = if matches!(self.config.dialect, Some(DialectType::DuckDB)) {
6098                    // DuckDB: ARRAY(y) -> [y]
6099                    func_args
6100                        .iter()
6101                        .map(|a| {
6102                            if let Expression::Function(ref f) = a {
6103                                if f.name.eq_ignore_ascii_case("ARRAY") && f.args.len() == 1 {
6104                                    return Expression::ArrayFunc(Box::new(
6105                                        crate::expressions::ArrayConstructor {
6106                                            expressions: f.args.clone(),
6107                                            bracket_notation: true,
6108                                            use_list_keyword: false,
6109                                        },
6110                                    ));
6111                                }
6112                            }
6113                            a.clone()
6114                        })
6115                        .collect::<Vec<_>>()
6116                } else if matches!(
6117                    self.config.dialect,
6118                    Some(DialectType::Presto)
6119                        | Some(DialectType::Trino)
6120                        | Some(DialectType::Athena)
6121                ) {
6122                    // Presto: ARRAY(y) -> ARRAY[y]
6123                    func_args
6124                        .iter()
6125                        .map(|a| {
6126                            if let Expression::Function(ref f) = a {
6127                                if f.name.eq_ignore_ascii_case("ARRAY") && f.args.len() >= 1 {
6128                                    return Expression::ArrayFunc(Box::new(
6129                                        crate::expressions::ArrayConstructor {
6130                                            expressions: f.args.clone(),
6131                                            bracket_notation: true,
6132                                            use_list_keyword: false,
6133                                        },
6134                                    ));
6135                                }
6136                            }
6137                            a.clone()
6138                        })
6139                        .collect::<Vec<_>>()
6140                } else {
6141                    func_args
6142                };
6143
6144                // POSEXPLODE -> LATERAL (SELECT pos - 1 AS pos, col FROM UNNEST(y) WITH ORDINALITY AS t(col, pos))
6145                if is_posexplode {
6146                    self.write_keyword("LATERAL");
6147                    self.write(" (");
6148                    self.write_keyword("SELECT");
6149                    self.write_space();
6150
6151                    // Build the outer SELECT list: pos - 1 AS pos, then data columns
6152                    // column_aliases[0] is the position column, rest are data columns
6153                    let pos_alias = if !lv.column_aliases.is_empty() {
6154                        lv.column_aliases[0].clone()
6155                    } else {
6156                        Identifier::new("pos")
6157                    };
6158                    let data_aliases: Vec<Identifier> = if lv.column_aliases.len() > 1 {
6159                        lv.column_aliases[1..].to_vec()
6160                    } else {
6161                        vec![Identifier::new("col")]
6162                    };
6163
6164                    // pos - 1 AS pos
6165                    self.generate_identifier(&pos_alias)?;
6166                    self.write(" - 1");
6167                    self.write_space();
6168                    self.write_keyword("AS");
6169                    self.write_space();
6170                    self.generate_identifier(&pos_alias)?;
6171
6172                    // , col [, key, value ...]
6173                    for data_col in &data_aliases {
6174                        self.write(", ");
6175                        self.generate_identifier(data_col)?;
6176                    }
6177
6178                    self.write_space();
6179                    self.write_keyword("FROM");
6180                    self.write_space();
6181                    self.write_keyword("UNNEST");
6182                    self.write("(");
6183                    for (i, arg) in unnest_args.iter().enumerate() {
6184                        if i > 0 {
6185                            self.write(", ");
6186                        }
6187                        self.generate_expression(arg)?;
6188                    }
6189                    self.write(")");
6190                    self.write_space();
6191                    self.write_keyword("WITH ORDINALITY");
6192                    self.write_space();
6193                    self.write_keyword("AS");
6194                    self.write_space();
6195
6196                    // Inner alias: t(data_cols..., pos) - data columns first, pos last
6197                    let table_alias_ident = lv
6198                        .table_alias
6199                        .clone()
6200                        .unwrap_or_else(|| Identifier::new("t"));
6201                    self.generate_identifier(&table_alias_ident)?;
6202                    self.write("(");
6203                    for (i, data_col) in data_aliases.iter().enumerate() {
6204                        if i > 0 {
6205                            self.write(", ");
6206                        }
6207                        self.generate_identifier(data_col)?;
6208                    }
6209                    self.write(", ");
6210                    self.generate_identifier(&pos_alias)?;
6211                    self.write("))");
6212                } else if is_inline && matches!(self.config.dialect, Some(DialectType::DuckDB)) {
6213                    // INLINE -> LATERAL (SELECT UNNEST(arg, max_depth => 2)) AS alias
6214                    self.write_keyword("LATERAL");
6215                    self.write(" (");
6216                    self.write_keyword("SELECT");
6217                    self.write_space();
6218                    self.write_keyword("UNNEST");
6219                    self.write("(");
6220                    for (i, arg) in unnest_args.iter().enumerate() {
6221                        if i > 0 {
6222                            self.write(", ");
6223                        }
6224                        self.generate_expression(arg)?;
6225                    }
6226                    self.write(", ");
6227                    self.write_keyword("max_depth");
6228                    self.write(" => 2))");
6229
6230                    // Add table and column aliases
6231                    if let Some(alias) = &lv.table_alias {
6232                        self.write_space();
6233                        self.write_keyword("AS");
6234                        self.write_space();
6235                        self.generate_identifier(alias)?;
6236                        if !lv.column_aliases.is_empty() {
6237                            self.write("(");
6238                            for (i, col) in lv.column_aliases.iter().enumerate() {
6239                                if i > 0 {
6240                                    self.write(", ");
6241                                }
6242                                self.generate_identifier(col)?;
6243                            }
6244                            self.write(")");
6245                        }
6246                    } else if !lv.column_aliases.is_empty() {
6247                        // Auto-generate alias like _u_N
6248                        self.write_space();
6249                        self.write_keyword("AS");
6250                        self.write_space();
6251                        self.write(&format!("_u_{}", lv_index));
6252                        self.write("(");
6253                        for (i, col) in lv.column_aliases.iter().enumerate() {
6254                            if i > 0 {
6255                                self.write(", ");
6256                            }
6257                            self.generate_identifier(col)?;
6258                        }
6259                        self.write(")");
6260                    }
6261                } else {
6262                    self.write_keyword("UNNEST");
6263                    self.write("(");
6264                    for (i, arg) in unnest_args.iter().enumerate() {
6265                        if i > 0 {
6266                            self.write(", ");
6267                        }
6268                        self.generate_expression(arg)?;
6269                    }
6270                    self.write(")");
6271
6272                    // Add table and column aliases for non-POSEXPLODE
6273                    if let Some(alias) = &lv.table_alias {
6274                        self.write_space();
6275                        self.write_keyword("AS");
6276                        self.write_space();
6277                        self.generate_identifier(alias)?;
6278                        if !lv.column_aliases.is_empty() {
6279                            self.write("(");
6280                            for (i, col) in lv.column_aliases.iter().enumerate() {
6281                                if i > 0 {
6282                                    self.write(", ");
6283                                }
6284                                self.generate_identifier(col)?;
6285                            }
6286                            self.write(")");
6287                        }
6288                    } else if !lv.column_aliases.is_empty() {
6289                        self.write_space();
6290                        self.write_keyword("AS");
6291                        self.write(" t(");
6292                        for (i, col) in lv.column_aliases.iter().enumerate() {
6293                            if i > 0 {
6294                                self.write(", ");
6295                            }
6296                            self.generate_identifier(col)?;
6297                        }
6298                        self.write(")");
6299                    }
6300                }
6301            } else {
6302                // Not EXPLODE/POSEXPLODE or not using UNNEST, use LATERAL
6303                if !lv.outer {
6304                    self.write_keyword("LATERAL");
6305                    self.write_space();
6306                }
6307                self.generate_expression(&lv.this)?;
6308
6309                // Add table and column aliases
6310                if let Some(alias) = &lv.table_alias {
6311                    self.write_space();
6312                    self.write_keyword("AS");
6313                    self.write_space();
6314                    self.generate_identifier(alias)?;
6315                    if !lv.column_aliases.is_empty() {
6316                        self.write("(");
6317                        for (i, col) in lv.column_aliases.iter().enumerate() {
6318                            if i > 0 {
6319                                self.write(", ");
6320                            }
6321                            self.generate_identifier(col)?;
6322                        }
6323                        self.write(")");
6324                    }
6325                } else if !lv.column_aliases.is_empty() {
6326                    self.write_space();
6327                    self.write_keyword("AS");
6328                    self.write(" t(");
6329                    for (i, col) in lv.column_aliases.iter().enumerate() {
6330                        if i > 0 {
6331                            self.write(", ");
6332                        }
6333                        self.generate_identifier(col)?;
6334                    }
6335                    self.write(")");
6336                }
6337            }
6338
6339            // For LEFT JOIN LATERAL, need ON TRUE
6340            if lv.outer {
6341                self.write_space();
6342                self.write_keyword("ON TRUE");
6343            }
6344        } else {
6345            // Output native LATERAL VIEW syntax (Hive/Spark/Databricks or default)
6346            self.write_keyword("LATERAL VIEW");
6347            if lv.outer {
6348                self.write_space();
6349                self.write_keyword("OUTER");
6350            }
6351            if self.config.pretty {
6352                self.write_newline();
6353                self.write_indent();
6354            } else {
6355                self.write_space();
6356            }
6357            self.generate_expression(&lv.this)?;
6358
6359            // Table alias
6360            if let Some(alias) = &lv.table_alias {
6361                self.write_space();
6362                self.generate_identifier(alias)?;
6363            }
6364
6365            // Column aliases
6366            if !lv.column_aliases.is_empty() {
6367                self.write_space();
6368                self.write_keyword("AS");
6369                self.write_space();
6370                for (i, col) in lv.column_aliases.iter().enumerate() {
6371                    if i > 0 {
6372                        self.write(", ");
6373                    }
6374                    self.generate_identifier(col)?;
6375                }
6376            }
6377        }
6378
6379        Ok(())
6380    }
6381
6382    fn generate_union(&mut self, outermost: &Union) -> Result<()> {
6383        // Collect the left-recursive chain of Union nodes iteratively.
6384        // This avoids stack overflow for deeply nested chains like
6385        // SELECT 1 UNION ALL SELECT 2 UNION ALL ... UNION ALL SELECT N
6386        // where the parser builds: Union(Union(Union(A, B), C), D)
6387        let mut chain: Vec<&Union> = vec![outermost];
6388        let mut leftmost: &Expression = &outermost.left;
6389        while let Expression::Union(inner) = leftmost {
6390            chain.push(inner);
6391            leftmost = &inner.left;
6392        }
6393        // chain[0] = outermost, chain[last] = innermost
6394        // leftmost = innermost.left (a non-Union expression, typically Select)
6395
6396        // WITH clause (only on outermost)
6397        if let Some(with) = &outermost.with {
6398            self.generate_with(with)?;
6399            self.write_space();
6400        }
6401
6402        // Generate the base (leftmost) expression
6403        self.generate_expression(leftmost)?;
6404
6405        // Generate each union step from innermost to outermost
6406        for union in chain.iter().rev() {
6407            self.generate_union_step(union)?;
6408        }
6409        Ok(())
6410    }
6411
6412    /// Generate a single UNION step: keyword, right expression, and trailing modifiers.
6413    fn generate_union_step(&mut self, union: &Union) -> Result<()> {
6414        if self.config.pretty {
6415            self.write_newline();
6416            self.write_indent();
6417        } else {
6418            self.write_space();
6419        }
6420
6421        // BigQuery set operation modifiers: [side] [kind] UNION
6422        if let Some(side) = &union.side {
6423            self.write_keyword(side);
6424            self.write_space();
6425        }
6426        if let Some(kind) = &union.kind {
6427            self.write_keyword(kind);
6428            self.write_space();
6429        }
6430
6431        self.write_keyword("UNION");
6432        if union.all {
6433            self.write_space();
6434            self.write_keyword("ALL");
6435        } else if union.distinct {
6436            self.write_space();
6437            self.write_keyword("DISTINCT");
6438        }
6439
6440        // BigQuery: CORRESPONDING/STRICT CORRESPONDING -> BY NAME, BY (cols) -> ON (cols)
6441        // DuckDB: BY NAME
6442        if union.corresponding || union.by_name {
6443            self.write_space();
6444            self.write_keyword("BY NAME");
6445        }
6446        if !union.on_columns.is_empty() {
6447            self.write_space();
6448            self.write_keyword("ON");
6449            self.write(" (");
6450            for (i, col) in union.on_columns.iter().enumerate() {
6451                if i > 0 {
6452                    self.write(", ");
6453                }
6454                self.generate_expression(col)?;
6455            }
6456            self.write(")");
6457        }
6458
6459        if self.config.pretty {
6460            self.write_newline();
6461            self.write_indent();
6462        } else {
6463            self.write_space();
6464        }
6465        self.generate_expression(&union.right)?;
6466        // ORDER BY, LIMIT, OFFSET for the set operation
6467        if let Some(order_by) = &union.order_by {
6468            if self.config.pretty {
6469                self.write_newline();
6470            } else {
6471                self.write_space();
6472            }
6473            self.write_keyword("ORDER BY");
6474            self.write_space();
6475            for (i, ordered) in order_by.expressions.iter().enumerate() {
6476                if i > 0 {
6477                    self.write(", ");
6478                }
6479                self.generate_ordered(ordered)?;
6480            }
6481        }
6482        if let Some(limit) = &union.limit {
6483            if self.config.pretty {
6484                self.write_newline();
6485            } else {
6486                self.write_space();
6487            }
6488            self.write_keyword("LIMIT");
6489            self.write_space();
6490            self.generate_expression(limit)?;
6491        }
6492        if let Some(offset) = &union.offset {
6493            if self.config.pretty {
6494                self.write_newline();
6495            } else {
6496                self.write_space();
6497            }
6498            self.write_keyword("OFFSET");
6499            self.write_space();
6500            self.generate_expression(offset)?;
6501        }
6502        // DISTRIBUTE BY (Hive/Spark)
6503        if let Some(distribute_by) = &union.distribute_by {
6504            self.write_space();
6505            self.write_keyword("DISTRIBUTE BY");
6506            self.write_space();
6507            for (i, expr) in distribute_by.expressions.iter().enumerate() {
6508                if i > 0 {
6509                    self.write(", ");
6510                }
6511                self.generate_expression(expr)?;
6512            }
6513        }
6514        // SORT BY (Hive/Spark)
6515        if let Some(sort_by) = &union.sort_by {
6516            self.write_space();
6517            self.write_keyword("SORT BY");
6518            self.write_space();
6519            for (i, ord) in sort_by.expressions.iter().enumerate() {
6520                if i > 0 {
6521                    self.write(", ");
6522                }
6523                self.generate_ordered(ord)?;
6524            }
6525        }
6526        // CLUSTER BY (Hive/Spark)
6527        if let Some(cluster_by) = &union.cluster_by {
6528            self.write_space();
6529            self.write_keyword("CLUSTER BY");
6530            self.write_space();
6531            for (i, ord) in cluster_by.expressions.iter().enumerate() {
6532                if i > 0 {
6533                    self.write(", ");
6534                }
6535                self.generate_ordered(ord)?;
6536            }
6537        }
6538        Ok(())
6539    }
6540
6541    fn generate_intersect(&mut self, outermost: &Intersect) -> Result<()> {
6542        // Collect the left-recursive chain iteratively to avoid stack overflow
6543        let mut chain: Vec<&Intersect> = vec![outermost];
6544        let mut leftmost: &Expression = &outermost.left;
6545        while let Expression::Intersect(inner) = leftmost {
6546            chain.push(inner);
6547            leftmost = &inner.left;
6548        }
6549
6550        if let Some(with) = &outermost.with {
6551            self.generate_with(with)?;
6552            self.write_space();
6553        }
6554
6555        self.generate_expression(leftmost)?;
6556
6557        for intersect in chain.iter().rev() {
6558            self.generate_intersect_step(intersect)?;
6559        }
6560        Ok(())
6561    }
6562
6563    /// Generate a single INTERSECT step: keyword, right expression, and trailing modifiers.
6564    fn generate_intersect_step(&mut self, intersect: &Intersect) -> Result<()> {
6565        if self.config.pretty {
6566            self.write_newline();
6567            self.write_indent();
6568        } else {
6569            self.write_space();
6570        }
6571
6572        // BigQuery set operation modifiers: [side] [kind] INTERSECT
6573        if let Some(side) = &intersect.side {
6574            self.write_keyword(side);
6575            self.write_space();
6576        }
6577        if let Some(kind) = &intersect.kind {
6578            self.write_keyword(kind);
6579            self.write_space();
6580        }
6581
6582        self.write_keyword("INTERSECT");
6583        if intersect.all {
6584            self.write_space();
6585            self.write_keyword("ALL");
6586        } else if intersect.distinct {
6587            self.write_space();
6588            self.write_keyword("DISTINCT");
6589        }
6590
6591        // BigQuery: CORRESPONDING/STRICT CORRESPONDING -> BY NAME, BY (cols) -> ON (cols)
6592        // DuckDB: BY NAME
6593        if intersect.corresponding || intersect.by_name {
6594            self.write_space();
6595            self.write_keyword("BY NAME");
6596        }
6597        if !intersect.on_columns.is_empty() {
6598            self.write_space();
6599            self.write_keyword("ON");
6600            self.write(" (");
6601            for (i, col) in intersect.on_columns.iter().enumerate() {
6602                if i > 0 {
6603                    self.write(", ");
6604                }
6605                self.generate_expression(col)?;
6606            }
6607            self.write(")");
6608        }
6609
6610        if self.config.pretty {
6611            self.write_newline();
6612            self.write_indent();
6613        } else {
6614            self.write_space();
6615        }
6616        self.generate_expression(&intersect.right)?;
6617        // ORDER BY, LIMIT, OFFSET for the set operation
6618        if let Some(order_by) = &intersect.order_by {
6619            if self.config.pretty {
6620                self.write_newline();
6621            } else {
6622                self.write_space();
6623            }
6624            self.write_keyword("ORDER BY");
6625            self.write_space();
6626            for (i, ordered) in order_by.expressions.iter().enumerate() {
6627                if i > 0 {
6628                    self.write(", ");
6629                }
6630                self.generate_ordered(ordered)?;
6631            }
6632        }
6633        if let Some(limit) = &intersect.limit {
6634            if self.config.pretty {
6635                self.write_newline();
6636            } else {
6637                self.write_space();
6638            }
6639            self.write_keyword("LIMIT");
6640            self.write_space();
6641            self.generate_expression(limit)?;
6642        }
6643        if let Some(offset) = &intersect.offset {
6644            if self.config.pretty {
6645                self.write_newline();
6646            } else {
6647                self.write_space();
6648            }
6649            self.write_keyword("OFFSET");
6650            self.write_space();
6651            self.generate_expression(offset)?;
6652        }
6653        // DISTRIBUTE BY (Hive/Spark)
6654        if let Some(distribute_by) = &intersect.distribute_by {
6655            self.write_space();
6656            self.write_keyword("DISTRIBUTE BY");
6657            self.write_space();
6658            for (i, expr) in distribute_by.expressions.iter().enumerate() {
6659                if i > 0 {
6660                    self.write(", ");
6661                }
6662                self.generate_expression(expr)?;
6663            }
6664        }
6665        // SORT BY (Hive/Spark)
6666        if let Some(sort_by) = &intersect.sort_by {
6667            self.write_space();
6668            self.write_keyword("SORT BY");
6669            self.write_space();
6670            for (i, ord) in sort_by.expressions.iter().enumerate() {
6671                if i > 0 {
6672                    self.write(", ");
6673                }
6674                self.generate_ordered(ord)?;
6675            }
6676        }
6677        // CLUSTER BY (Hive/Spark)
6678        if let Some(cluster_by) = &intersect.cluster_by {
6679            self.write_space();
6680            self.write_keyword("CLUSTER BY");
6681            self.write_space();
6682            for (i, ord) in cluster_by.expressions.iter().enumerate() {
6683                if i > 0 {
6684                    self.write(", ");
6685                }
6686                self.generate_ordered(ord)?;
6687            }
6688        }
6689        Ok(())
6690    }
6691
6692    fn generate_except(&mut self, outermost: &Except) -> Result<()> {
6693        // Collect the left-recursive chain iteratively to avoid stack overflow
6694        let mut chain: Vec<&Except> = vec![outermost];
6695        let mut leftmost: &Expression = &outermost.left;
6696        while let Expression::Except(inner) = leftmost {
6697            chain.push(inner);
6698            leftmost = &inner.left;
6699        }
6700
6701        if let Some(with) = &outermost.with {
6702            self.generate_with(with)?;
6703            self.write_space();
6704        }
6705
6706        self.generate_expression(leftmost)?;
6707
6708        for except in chain.iter().rev() {
6709            self.generate_except_step(except)?;
6710        }
6711        Ok(())
6712    }
6713
6714    /// Generate a single EXCEPT step: keyword, right expression, and trailing modifiers.
6715    fn generate_except_step(&mut self, except: &Except) -> Result<()> {
6716        use crate::dialects::DialectType;
6717
6718        if self.config.pretty {
6719            self.write_newline();
6720            self.write_indent();
6721        } else {
6722            self.write_space();
6723        }
6724
6725        // BigQuery set operation modifiers: [side] [kind] EXCEPT
6726        if let Some(side) = &except.side {
6727            self.write_keyword(side);
6728            self.write_space();
6729        }
6730        if let Some(kind) = &except.kind {
6731            self.write_keyword(kind);
6732            self.write_space();
6733        }
6734
6735        // Oracle uses MINUS instead of EXCEPT (but not for EXCEPT ALL)
6736        match self.config.dialect {
6737            Some(DialectType::Oracle) if !except.all => {
6738                self.write_keyword("MINUS");
6739            }
6740            Some(DialectType::ClickHouse) => {
6741                self.write_keyword("EXCEPT");
6742                let preserve_all = self.config.source_dialect.is_none()
6743                    || matches!(self.config.source_dialect, Some(DialectType::ClickHouse));
6744                if except.all && preserve_all {
6745                    self.write_space();
6746                    self.write_keyword("ALL");
6747                }
6748                if except.distinct {
6749                    self.write_space();
6750                    self.write_keyword("DISTINCT");
6751                }
6752            }
6753            Some(DialectType::BigQuery) => {
6754                // BigQuery: bare EXCEPT defaults to EXCEPT DISTINCT
6755                self.write_keyword("EXCEPT");
6756                if except.all {
6757                    self.write_space();
6758                    self.write_keyword("ALL");
6759                } else {
6760                    self.write_space();
6761                    self.write_keyword("DISTINCT");
6762                }
6763            }
6764            _ => {
6765                self.write_keyword("EXCEPT");
6766                if except.all {
6767                    self.write_space();
6768                    self.write_keyword("ALL");
6769                } else if except.distinct {
6770                    self.write_space();
6771                    self.write_keyword("DISTINCT");
6772                }
6773            }
6774        }
6775
6776        // BigQuery: CORRESPONDING/STRICT CORRESPONDING -> BY NAME, BY (cols) -> ON (cols)
6777        // DuckDB: BY NAME
6778        if except.corresponding || except.by_name {
6779            self.write_space();
6780            self.write_keyword("BY NAME");
6781        }
6782        if !except.on_columns.is_empty() {
6783            self.write_space();
6784            self.write_keyword("ON");
6785            self.write(" (");
6786            for (i, col) in except.on_columns.iter().enumerate() {
6787                if i > 0 {
6788                    self.write(", ");
6789                }
6790                self.generate_expression(col)?;
6791            }
6792            self.write(")");
6793        }
6794
6795        if self.config.pretty {
6796            self.write_newline();
6797            self.write_indent();
6798        } else {
6799            self.write_space();
6800        }
6801        self.generate_expression(&except.right)?;
6802        // ORDER BY, LIMIT, OFFSET for the set operation
6803        if let Some(order_by) = &except.order_by {
6804            if self.config.pretty {
6805                self.write_newline();
6806            } else {
6807                self.write_space();
6808            }
6809            self.write_keyword("ORDER BY");
6810            self.write_space();
6811            for (i, ordered) in order_by.expressions.iter().enumerate() {
6812                if i > 0 {
6813                    self.write(", ");
6814                }
6815                self.generate_ordered(ordered)?;
6816            }
6817        }
6818        if let Some(limit) = &except.limit {
6819            if self.config.pretty {
6820                self.write_newline();
6821            } else {
6822                self.write_space();
6823            }
6824            self.write_keyword("LIMIT");
6825            self.write_space();
6826            self.generate_expression(limit)?;
6827        }
6828        if let Some(offset) = &except.offset {
6829            if self.config.pretty {
6830                self.write_newline();
6831            } else {
6832                self.write_space();
6833            }
6834            self.write_keyword("OFFSET");
6835            self.write_space();
6836            self.generate_expression(offset)?;
6837        }
6838        // DISTRIBUTE BY (Hive/Spark)
6839        if let Some(distribute_by) = &except.distribute_by {
6840            self.write_space();
6841            self.write_keyword("DISTRIBUTE BY");
6842            self.write_space();
6843            for (i, expr) in distribute_by.expressions.iter().enumerate() {
6844                if i > 0 {
6845                    self.write(", ");
6846                }
6847                self.generate_expression(expr)?;
6848            }
6849        }
6850        // SORT BY (Hive/Spark)
6851        if let Some(sort_by) = &except.sort_by {
6852            self.write_space();
6853            self.write_keyword("SORT BY");
6854            self.write_space();
6855            for (i, ord) in sort_by.expressions.iter().enumerate() {
6856                if i > 0 {
6857                    self.write(", ");
6858                }
6859                self.generate_ordered(ord)?;
6860            }
6861        }
6862        // CLUSTER BY (Hive/Spark)
6863        if let Some(cluster_by) = &except.cluster_by {
6864            self.write_space();
6865            self.write_keyword("CLUSTER BY");
6866            self.write_space();
6867            for (i, ord) in cluster_by.expressions.iter().enumerate() {
6868                if i > 0 {
6869                    self.write(", ");
6870                }
6871                self.generate_ordered(ord)?;
6872            }
6873        }
6874        Ok(())
6875    }
6876
6877    fn generate_insert(&mut self, insert: &Insert) -> Result<()> {
6878        // For TSQL/Fabric/Spark/Hive/Databricks, CTEs must be prepended before INSERT
6879        let prepend_query_cte = if insert.with.is_none() {
6880            use crate::dialects::DialectType;
6881            let should_prepend = matches!(
6882                self.config.dialect,
6883                Some(DialectType::TSQL)
6884                    | Some(DialectType::Fabric)
6885                    | Some(DialectType::Spark)
6886                    | Some(DialectType::Databricks)
6887                    | Some(DialectType::Hive)
6888            );
6889            if should_prepend {
6890                if let Some(Expression::Select(select)) = &insert.query {
6891                    select.with.clone()
6892                } else {
6893                    None
6894                }
6895            } else {
6896                None
6897            }
6898        } else {
6899            None
6900        };
6901
6902        // Output WITH clause if on INSERT (e.g., WITH ... INSERT INTO ...)
6903        if let Some(with) = &insert.with {
6904            self.generate_with(with)?;
6905            self.write_space();
6906        } else if let Some(with) = &prepend_query_cte {
6907            self.generate_with(with)?;
6908            self.write_space();
6909        }
6910
6911        // Output leading comments before INSERT
6912        for comment in &insert.leading_comments {
6913            self.write_formatted_comment(comment);
6914            self.write(" ");
6915        }
6916
6917        // Handle directory insert (INSERT OVERWRITE DIRECTORY)
6918        if let Some(dir) = &insert.directory {
6919            self.write_keyword("INSERT OVERWRITE");
6920            if dir.local {
6921                self.write_space();
6922                self.write_keyword("LOCAL");
6923            }
6924            self.write_space();
6925            self.write_keyword("DIRECTORY");
6926            self.write_space();
6927            self.write("'");
6928            self.write(&dir.path);
6929            self.write("'");
6930
6931            // ROW FORMAT clause
6932            if let Some(row_format) = &dir.row_format {
6933                self.write_space();
6934                self.write_keyword("ROW FORMAT");
6935                if row_format.delimited {
6936                    self.write_space();
6937                    self.write_keyword("DELIMITED");
6938                }
6939                if let Some(val) = &row_format.fields_terminated_by {
6940                    self.write_space();
6941                    self.write_keyword("FIELDS TERMINATED BY");
6942                    self.write_space();
6943                    self.generate_string_literal(val)?;
6944                }
6945                if let Some(val) = &row_format.collection_items_terminated_by {
6946                    self.write_space();
6947                    self.write_keyword("COLLECTION ITEMS TERMINATED BY");
6948                    self.write_space();
6949                    self.write("'");
6950                    self.write(val);
6951                    self.write("'");
6952                }
6953                if let Some(val) = &row_format.map_keys_terminated_by {
6954                    self.write_space();
6955                    self.write_keyword("MAP KEYS TERMINATED BY");
6956                    self.write_space();
6957                    self.write("'");
6958                    self.write(val);
6959                    self.write("'");
6960                }
6961                if let Some(val) = &row_format.lines_terminated_by {
6962                    self.write_space();
6963                    self.write_keyword("LINES TERMINATED BY");
6964                    self.write_space();
6965                    self.write("'");
6966                    self.write(val);
6967                    self.write("'");
6968                }
6969                if let Some(val) = &row_format.null_defined_as {
6970                    self.write_space();
6971                    self.write_keyword("NULL DEFINED AS");
6972                    self.write_space();
6973                    self.write("'");
6974                    self.write(val);
6975                    self.write("'");
6976                }
6977            }
6978
6979            // STORED AS clause
6980            if let Some(format) = &dir.stored_as {
6981                self.write_space();
6982                self.write_keyword("STORED AS");
6983                self.write_space();
6984                self.write_keyword(format);
6985            }
6986
6987            // Query (SELECT statement)
6988            if let Some(query) = &insert.query {
6989                self.write_space();
6990                self.generate_expression(query)?;
6991            }
6992
6993            return Ok(());
6994        }
6995
6996        if insert.is_replace {
6997            // MySQL/SQLite REPLACE INTO statement
6998            self.write_keyword("REPLACE INTO");
6999        } else if insert.overwrite {
7000            // Use dialect-specific INSERT OVERWRITE format
7001            self.write_keyword("INSERT");
7002            // Output hint if present (Oracle: INSERT /*+ APPEND */ INTO)
7003            if let Some(ref hint) = insert.hint {
7004                self.generate_hint(hint)?;
7005            }
7006            self.write(&self.config.insert_overwrite.to_ascii_uppercase());
7007        } else if let Some(ref action) = insert.conflict_action {
7008            // SQLite conflict action: INSERT OR ABORT|FAIL|IGNORE|REPLACE|ROLLBACK INTO
7009            self.write_keyword("INSERT OR");
7010            self.write_space();
7011            self.write_keyword(action);
7012            self.write_space();
7013            self.write_keyword("INTO");
7014        } else if insert.ignore {
7015            // MySQL INSERT IGNORE syntax
7016            self.write_keyword("INSERT IGNORE INTO");
7017        } else {
7018            self.write_keyword("INSERT");
7019            // Output hint if present (Oracle: INSERT /*+ APPEND */ INTO)
7020            if let Some(ref hint) = insert.hint {
7021                self.generate_hint(hint)?;
7022            }
7023            self.write_space();
7024            self.write_keyword("INTO");
7025        }
7026        // ClickHouse: INSERT INTO FUNCTION func_name(args...)
7027        if let Some(ref func) = insert.function_target {
7028            self.write_space();
7029            self.write_keyword("FUNCTION");
7030            self.write_space();
7031            self.generate_expression(func)?;
7032        } else {
7033            self.write_space();
7034            self.generate_table(&insert.table)?;
7035        }
7036
7037        // Table alias (PostgreSQL: INSERT INTO table AS t(...), Oracle: INSERT INTO table t ...)
7038        if let Some(ref alias) = insert.alias {
7039            self.write_space();
7040            if insert.alias_explicit_as {
7041                self.write_keyword("AS");
7042                self.write_space();
7043            }
7044            self.generate_identifier(alias)?;
7045        }
7046
7047        // IF EXISTS clause (Hive)
7048        if insert.if_exists {
7049            self.write_space();
7050            self.write_keyword("IF EXISTS");
7051        }
7052
7053        // REPLACE WHERE clause (Databricks)
7054        if let Some(ref replace_where) = insert.replace_where {
7055            if self.config.pretty {
7056                self.write_newline();
7057                self.write_indent();
7058            } else {
7059                self.write_space();
7060            }
7061            self.write_keyword("REPLACE WHERE");
7062            self.write_space();
7063            self.generate_expression(replace_where)?;
7064        }
7065
7066        // Generate PARTITION clause if present
7067        if !insert.partition.is_empty() {
7068            self.write_space();
7069            self.write_keyword("PARTITION");
7070            self.write("(");
7071            for (i, (col, val)) in insert.partition.iter().enumerate() {
7072                if i > 0 {
7073                    self.write(", ");
7074                }
7075                self.generate_identifier(col)?;
7076                if let Some(v) = val {
7077                    self.write(" = ");
7078                    self.generate_expression(v)?;
7079                }
7080            }
7081            self.write(")");
7082        }
7083
7084        // ClickHouse: PARTITION BY expr
7085        if let Some(ref partition_by) = insert.partition_by {
7086            self.write_space();
7087            self.write_keyword("PARTITION BY");
7088            self.write_space();
7089            self.generate_expression(partition_by)?;
7090        }
7091
7092        // ClickHouse: SETTINGS key = val, ...
7093        if !insert.settings.is_empty() {
7094            self.write_space();
7095            self.write_keyword("SETTINGS");
7096            self.write_space();
7097            for (i, setting) in insert.settings.iter().enumerate() {
7098                if i > 0 {
7099                    self.write(", ");
7100                }
7101                self.generate_expression(setting)?;
7102            }
7103        }
7104
7105        if !insert.columns.is_empty() {
7106            if insert.alias.is_some() && insert.alias_explicit_as {
7107                // No space when explicit AS alias is present: INSERT INTO table AS t(a, b, c)
7108                self.write("(");
7109            } else {
7110                // Space for implicit alias or no alias: INSERT INTO dest d (i, value)
7111                self.write(" (");
7112            }
7113            for (i, col) in insert.columns.iter().enumerate() {
7114                if i > 0 {
7115                    self.write(", ");
7116                }
7117                self.generate_identifier(col)?;
7118            }
7119            self.write(")");
7120        }
7121
7122        // OUTPUT clause (TSQL)
7123        if let Some(ref output) = insert.output {
7124            self.generate_output_clause(output)?;
7125        }
7126
7127        // BY NAME modifier (DuckDB)
7128        if insert.by_name {
7129            self.write_space();
7130            self.write_keyword("BY NAME");
7131        }
7132
7133        if insert.default_values {
7134            self.write_space();
7135            self.write_keyword("DEFAULT VALUES");
7136        } else if let Some(query) = &insert.query {
7137            if self.config.pretty {
7138                self.write_newline();
7139            } else {
7140                self.write_space();
7141            }
7142            // If we prepended CTEs from nested SELECT (TSQL), strip the WITH from SELECT
7143            if prepend_query_cte.is_some() {
7144                if let Expression::Select(select) = query {
7145                    let mut select_no_with = select.clone();
7146                    select_no_with.with = None;
7147                    self.generate_select(&select_no_with)?;
7148                } else {
7149                    self.generate_expression(query)?;
7150                }
7151            } else {
7152                self.generate_expression(query)?;
7153            }
7154        } else if !insert.values.is_empty() {
7155            if self.config.pretty {
7156                // Pretty printing: VALUES on new line, each tuple indented
7157                self.write_newline();
7158                self.write_keyword("VALUES");
7159                self.write_newline();
7160                self.indent_level += 1;
7161                for (i, row) in insert.values.iter().enumerate() {
7162                    if i > 0 {
7163                        self.write(",");
7164                        self.write_newline();
7165                    }
7166                    self.write_indent();
7167                    self.write("(");
7168                    for (j, val) in row.iter().enumerate() {
7169                        if j > 0 {
7170                            self.write(", ");
7171                        }
7172                        self.generate_expression(val)?;
7173                    }
7174                    self.write(")");
7175                }
7176                self.indent_level -= 1;
7177            } else {
7178                // Non-pretty: single line
7179                self.write_space();
7180                self.write_keyword("VALUES");
7181                for (i, row) in insert.values.iter().enumerate() {
7182                    if i > 0 {
7183                        self.write(",");
7184                    }
7185                    self.write(" (");
7186                    for (j, val) in row.iter().enumerate() {
7187                        if j > 0 {
7188                            self.write(", ");
7189                        }
7190                        self.generate_expression(val)?;
7191                    }
7192                    self.write(")");
7193                }
7194            }
7195        }
7196
7197        // Source table (Hive/Spark): INSERT OVERWRITE TABLE target TABLE source
7198        if let Some(ref source) = insert.source {
7199            self.write_space();
7200            self.write_keyword("TABLE");
7201            self.write_space();
7202            self.generate_expression(source)?;
7203        }
7204
7205        // Source alias (MySQL: VALUES (...) AS new_data)
7206        if let Some(alias) = &insert.source_alias {
7207            self.write_space();
7208            self.write_keyword("AS");
7209            self.write_space();
7210            self.generate_identifier(alias)?;
7211        }
7212
7213        // ON CONFLICT clause (Materialize doesn't support ON CONFLICT)
7214        if let Some(on_conflict) = &insert.on_conflict {
7215            if !matches!(self.config.dialect, Some(DialectType::Materialize)) {
7216                self.write_space();
7217                self.generate_expression(on_conflict)?;
7218            }
7219        }
7220
7221        // RETURNING clause
7222        if !insert.returning.is_empty() {
7223            self.write_space();
7224            self.write_keyword("RETURNING");
7225            self.write_space();
7226            for (i, expr) in insert.returning.iter().enumerate() {
7227                if i > 0 {
7228                    self.write(", ");
7229                }
7230                self.generate_expression(expr)?;
7231            }
7232        }
7233
7234        Ok(())
7235    }
7236
7237    fn generate_update(&mut self, update: &Update) -> Result<()> {
7238        // Output leading comments before UPDATE
7239        for comment in &update.leading_comments {
7240            self.write_formatted_comment(comment);
7241            self.write(" ");
7242        }
7243
7244        // WITH clause (CTEs)
7245        if let Some(ref with) = update.with {
7246            self.generate_with(with)?;
7247            self.write_space();
7248        }
7249
7250        self.write_keyword("UPDATE");
7251        if let Some(hint) = &update.hint {
7252            self.generate_hint(hint)?;
7253        }
7254        self.write_space();
7255        self.generate_table(&update.table)?;
7256
7257        let mysql_like_update_from = matches!(
7258            self.config.dialect,
7259            Some(DialectType::MySQL) | Some(DialectType::SingleStore)
7260        ) && update.from_clause.is_some();
7261
7262        let mut set_pairs = update.set.clone();
7263
7264        // MySQL-style UPDATE doesn't support FROM after SET. Convert FROM tables to JOIN ... ON TRUE.
7265        let mut pre_set_joins = update.table_joins.clone();
7266        if mysql_like_update_from {
7267            let target_name = update
7268                .table
7269                .alias
7270                .as_ref()
7271                .map(|a| a.name.clone())
7272                .unwrap_or_else(|| update.table.name.name.clone());
7273
7274            for (col, _) in &mut set_pairs {
7275                if !col.name.contains('.') {
7276                    col.name = format!("{}.{}", target_name, col.name);
7277                }
7278            }
7279
7280            if let Some(from_clause) = &update.from_clause {
7281                for table_expr in &from_clause.expressions {
7282                    pre_set_joins.push(crate::expressions::Join {
7283                        this: table_expr.clone(),
7284                        on: Some(Expression::Boolean(crate::expressions::BooleanLiteral {
7285                            value: true,
7286                        })),
7287                        using: Vec::new(),
7288                        kind: crate::expressions::JoinKind::Inner,
7289                        use_inner_keyword: false,
7290                        use_outer_keyword: false,
7291                        deferred_condition: false,
7292                        join_hint: None,
7293                        match_condition: None,
7294                        pivots: Vec::new(),
7295                        comments: Vec::new(),
7296                        nesting_group: 0,
7297                        directed: false,
7298                    });
7299                }
7300            }
7301            for join in &update.from_joins {
7302                let mut join = join.clone();
7303                if join.on.is_none() && join.using.is_empty() {
7304                    join.on = Some(Expression::Boolean(crate::expressions::BooleanLiteral {
7305                        value: true,
7306                    }));
7307                }
7308                pre_set_joins.push(join);
7309            }
7310        }
7311
7312        // Extra tables for multi-table UPDATE (MySQL syntax)
7313        for extra_table in &update.extra_tables {
7314            self.write(", ");
7315            self.generate_table(extra_table)?;
7316        }
7317
7318        // JOINs attached to the table list (MySQL multi-table syntax)
7319        for join in &pre_set_joins {
7320            // generate_join already adds a leading space
7321            self.generate_join(join)?;
7322        }
7323
7324        // Teradata: FROM clause comes before SET
7325        let teradata_from_before_set = matches!(self.config.dialect, Some(DialectType::Teradata));
7326        if teradata_from_before_set && !mysql_like_update_from {
7327            if let Some(ref from_clause) = update.from_clause {
7328                self.write_space();
7329                self.write_keyword("FROM");
7330                self.write_space();
7331                for (i, table_expr) in from_clause.expressions.iter().enumerate() {
7332                    if i > 0 {
7333                        self.write(", ");
7334                    }
7335                    self.generate_expression(table_expr)?;
7336                }
7337            }
7338            for join in &update.from_joins {
7339                self.generate_join(join)?;
7340            }
7341        }
7342
7343        self.write_space();
7344        self.write_keyword("SET");
7345        self.write_space();
7346
7347        for (i, (col, val)) in set_pairs.iter().enumerate() {
7348            if i > 0 {
7349                self.write(", ");
7350            }
7351            self.generate_identifier(col)?;
7352            self.write(" = ");
7353            self.generate_expression(val)?;
7354        }
7355
7356        // OUTPUT clause (TSQL)
7357        if let Some(ref output) = update.output {
7358            self.generate_output_clause(output)?;
7359        }
7360
7361        // FROM clause (after SET for non-Teradata, non-MySQL dialects)
7362        if !mysql_like_update_from && !teradata_from_before_set {
7363            if let Some(ref from_clause) = update.from_clause {
7364                self.write_space();
7365                self.write_keyword("FROM");
7366                self.write_space();
7367                // Generate each table in the FROM clause
7368                for (i, table_expr) in from_clause.expressions.iter().enumerate() {
7369                    if i > 0 {
7370                        self.write(", ");
7371                    }
7372                    self.generate_expression(table_expr)?;
7373                }
7374            }
7375        }
7376
7377        if !mysql_like_update_from && !teradata_from_before_set {
7378            // JOINs after FROM clause (PostgreSQL, Snowflake, SQL Server syntax)
7379            for join in &update.from_joins {
7380                self.generate_join(join)?;
7381            }
7382        }
7383
7384        if let Some(where_clause) = &update.where_clause {
7385            self.write_space();
7386            self.write_keyword("WHERE");
7387            self.write_space();
7388            self.generate_expression(&where_clause.this)?;
7389        }
7390
7391        // RETURNING clause
7392        if !update.returning.is_empty() {
7393            self.write_space();
7394            self.write_keyword("RETURNING");
7395            self.write_space();
7396            for (i, expr) in update.returning.iter().enumerate() {
7397                if i > 0 {
7398                    self.write(", ");
7399                }
7400                self.generate_expression(expr)?;
7401            }
7402        }
7403
7404        // ORDER BY clause (MySQL)
7405        if let Some(ref order_by) = update.order_by {
7406            self.write_space();
7407            self.generate_order_by(order_by)?;
7408        }
7409
7410        // LIMIT clause (MySQL)
7411        if let Some(ref limit) = update.limit {
7412            self.write_space();
7413            self.write_keyword("LIMIT");
7414            self.write_space();
7415            self.generate_expression(limit)?;
7416        }
7417
7418        Ok(())
7419    }
7420
7421    fn generate_delete(&mut self, delete: &Delete) -> Result<()> {
7422        // Output WITH clause if present
7423        if let Some(with) = &delete.with {
7424            self.generate_with(with)?;
7425            self.write_space();
7426        }
7427
7428        // Output leading comments before DELETE
7429        for comment in &delete.leading_comments {
7430            self.write_formatted_comment(comment);
7431            self.write(" ");
7432        }
7433
7434        // MySQL multi-table DELETE or TSQL DELETE with OUTPUT before FROM
7435        if !delete.tables.is_empty() && !delete.tables_from_using {
7436            // DELETE t1[, t2] [OUTPUT ...] FROM ... syntax (tables before FROM)
7437            self.write_keyword("DELETE");
7438            if let Some(hint) = &delete.hint {
7439                self.generate_hint(hint)?;
7440            }
7441            self.write_space();
7442            for (i, tbl) in delete.tables.iter().enumerate() {
7443                if i > 0 {
7444                    self.write(", ");
7445                }
7446                self.generate_table(tbl)?;
7447            }
7448            // TSQL: OUTPUT clause between target table and FROM
7449            if let Some(ref output) = delete.output {
7450                self.generate_output_clause(output)?;
7451            }
7452            self.write_space();
7453            self.write_keyword("FROM");
7454            self.write_space();
7455            self.generate_table(&delete.table)?;
7456        } else if !delete.tables.is_empty() && delete.tables_from_using {
7457            // DELETE FROM t1, t2 USING ... syntax (tables after FROM)
7458            self.write_keyword("DELETE");
7459            if let Some(hint) = &delete.hint {
7460                self.generate_hint(hint)?;
7461            }
7462            self.write_space();
7463            self.write_keyword("FROM");
7464            self.write_space();
7465            for (i, tbl) in delete.tables.iter().enumerate() {
7466                if i > 0 {
7467                    self.write(", ");
7468                }
7469                self.generate_table(tbl)?;
7470            }
7471        } else if delete.no_from && matches!(self.config.dialect, Some(DialectType::BigQuery)) {
7472            // BigQuery-style DELETE without FROM keyword
7473            self.write_keyword("DELETE");
7474            if let Some(hint) = &delete.hint {
7475                self.generate_hint(hint)?;
7476            }
7477            self.write_space();
7478            self.generate_table(&delete.table)?;
7479        } else {
7480            self.write_keyword("DELETE");
7481            if let Some(hint) = &delete.hint {
7482                self.generate_hint(hint)?;
7483            }
7484            self.write_space();
7485            self.write_keyword("FROM");
7486            self.write_space();
7487            self.generate_table(&delete.table)?;
7488        }
7489
7490        // ClickHouse: ON CLUSTER clause
7491        if let Some(ref on_cluster) = delete.on_cluster {
7492            self.write_space();
7493            self.generate_on_cluster(on_cluster)?;
7494        }
7495
7496        // FORCE INDEX hint (MySQL)
7497        if let Some(ref idx) = delete.force_index {
7498            self.write_space();
7499            self.write_keyword("FORCE INDEX");
7500            self.write(" (");
7501            self.write(idx);
7502            self.write(")");
7503        }
7504
7505        // Optional alias
7506        if let Some(ref alias) = delete.alias {
7507            self.write_space();
7508            if delete.alias_explicit_as
7509                || matches!(self.config.dialect, Some(DialectType::BigQuery))
7510            {
7511                self.write_keyword("AS");
7512                self.write_space();
7513            }
7514            self.generate_identifier(alias)?;
7515        }
7516
7517        // JOINs (MySQL multi-table) - when NOT tables_from_using, JOINs come before USING
7518        if !delete.tables_from_using {
7519            for join in &delete.joins {
7520                self.generate_join(join)?;
7521            }
7522        }
7523
7524        // USING clause (PostgreSQL/DuckDB/MySQL)
7525        if !delete.using.is_empty() {
7526            self.write_space();
7527            self.write_keyword("USING");
7528            for (i, table) in delete.using.iter().enumerate() {
7529                if i > 0 {
7530                    self.write(",");
7531                }
7532                self.write_space();
7533                // Check if the table has subquery hints (DuckDB USING with subquery)
7534                if !table.hints.is_empty() && table.name.is_empty() {
7535                    // Subquery in USING: (VALUES ...) AS alias(cols)
7536                    self.generate_expression(&table.hints[0])?;
7537                    if let Some(ref alias) = table.alias {
7538                        self.write_space();
7539                        if table.alias_explicit_as {
7540                            self.write_keyword("AS");
7541                            self.write_space();
7542                        }
7543                        self.generate_identifier(alias)?;
7544                        if !table.column_aliases.is_empty() {
7545                            self.write("(");
7546                            for (j, col_alias) in table.column_aliases.iter().enumerate() {
7547                                if j > 0 {
7548                                    self.write(", ");
7549                                }
7550                                self.generate_identifier(col_alias)?;
7551                            }
7552                            self.write(")");
7553                        }
7554                    }
7555                } else {
7556                    self.generate_table(table)?;
7557                }
7558            }
7559        }
7560
7561        // JOINs (MySQL multi-table) - when tables_from_using, JOINs come after USING
7562        if delete.tables_from_using {
7563            for join in &delete.joins {
7564                self.generate_join(join)?;
7565            }
7566        }
7567
7568        // OUTPUT clause (TSQL) - only if not already emitted in the early position
7569        let output_already_emitted =
7570            !delete.tables.is_empty() && !delete.tables_from_using && delete.output.is_some();
7571        if !output_already_emitted {
7572            if let Some(ref output) = delete.output {
7573                self.generate_output_clause(output)?;
7574            }
7575        }
7576
7577        if let Some(where_clause) = &delete.where_clause {
7578            self.write_space();
7579            self.write_keyword("WHERE");
7580            self.write_space();
7581            self.generate_expression(&where_clause.this)?;
7582        }
7583
7584        // ORDER BY clause (MySQL)
7585        if let Some(ref order_by) = delete.order_by {
7586            self.write_space();
7587            self.generate_order_by(order_by)?;
7588        }
7589
7590        // LIMIT clause (MySQL)
7591        if let Some(ref limit) = delete.limit {
7592            self.write_space();
7593            self.write_keyword("LIMIT");
7594            self.write_space();
7595            self.generate_expression(limit)?;
7596        }
7597
7598        // RETURNING clause (PostgreSQL)
7599        if !delete.returning.is_empty() {
7600            self.write_space();
7601            self.write_keyword("RETURNING");
7602            self.write_space();
7603            for (i, expr) in delete.returning.iter().enumerate() {
7604                if i > 0 {
7605                    self.write(", ");
7606                }
7607                self.generate_expression(expr)?;
7608            }
7609        }
7610
7611        Ok(())
7612    }
7613
7614    // ==================== DDL Generation ====================
7615
7616    fn generate_create_table(&mut self, ct: &CreateTable) -> Result<()> {
7617        // Athena: Determine if this is Hive-style DDL or Trino-style DML
7618        // CREATE TABLE AS SELECT uses Trino (double quotes)
7619        // CREATE TABLE (without AS SELECT) and CREATE EXTERNAL TABLE use Hive (backticks)
7620        let saved_athena_hive_context = self.athena_hive_context;
7621        let is_clickhouse = matches!(self.config.dialect, Some(DialectType::ClickHouse));
7622        if matches!(
7623            self.config.dialect,
7624            Some(crate::dialects::DialectType::Athena)
7625        ) {
7626            // Use Hive context if:
7627            // 1. It's an EXTERNAL table, OR
7628            // 2. There's no AS SELECT clause
7629            let is_external = ct
7630                .table_modifier
7631                .as_ref()
7632                .map(|m| m.eq_ignore_ascii_case("EXTERNAL"))
7633                .unwrap_or(false);
7634            let has_as_select = ct.as_select.is_some();
7635            self.athena_hive_context = is_external || !has_as_select;
7636        }
7637
7638        // TSQL: Convert CREATE TABLE AS SELECT to SELECT * INTO table FROM (subquery) AS temp
7639        if matches!(
7640            self.config.dialect,
7641            Some(crate::dialects::DialectType::TSQL)
7642        ) {
7643            if let Some(ref query) = ct.as_select {
7644                // Output WITH CTE clause if present
7645                if let Some(with_cte) = &ct.with_cte {
7646                    self.generate_with(with_cte)?;
7647                    self.write_space();
7648                }
7649
7650                // Generate: SELECT * INTO [table] FROM (subquery) AS temp
7651                self.write_keyword("SELECT");
7652                self.write(" * ");
7653                self.write_keyword("INTO");
7654                self.write_space();
7655
7656                // If temporary, prefix with # for TSQL temp table
7657                if ct.temporary {
7658                    self.write("#");
7659                }
7660                self.generate_table(&ct.name)?;
7661
7662                self.write_space();
7663                self.write_keyword("FROM");
7664                self.write(" (");
7665                // For TSQL, add aliases to select columns to preserve column names
7666                let aliased_query = Self::add_column_aliases_to_query(query.clone());
7667                self.generate_expression(&aliased_query)?;
7668                self.write(") ");
7669                self.write_keyword("AS");
7670                self.write(" temp");
7671                return Ok(());
7672            }
7673        }
7674
7675        // Output WITH CTE clause if present
7676        if let Some(with_cte) = &ct.with_cte {
7677            self.generate_with(with_cte)?;
7678            self.write_space();
7679        }
7680
7681        // Output leading comments before CREATE
7682        for comment in &ct.leading_comments {
7683            self.write_formatted_comment(comment);
7684            self.write(" ");
7685        }
7686        self.write_keyword("CREATE");
7687
7688        if ct.or_replace {
7689            self.write_space();
7690            self.write_keyword("OR REPLACE");
7691        }
7692
7693        if ct.temporary {
7694            self.write_space();
7695            // Oracle uses GLOBAL TEMPORARY TABLE syntax
7696            if matches!(self.config.dialect, Some(DialectType::Oracle)) {
7697                self.write_keyword("GLOBAL TEMPORARY");
7698            } else {
7699                self.write_keyword("TEMPORARY");
7700            }
7701        }
7702
7703        // Table modifier: DYNAMIC, ICEBERG, EXTERNAL, HYBRID, TRANSIENT
7704        let is_dictionary = ct
7705            .table_modifier
7706            .as_ref()
7707            .map(|m| m.eq_ignore_ascii_case("DICTIONARY"))
7708            .unwrap_or(false);
7709        if let Some(ref modifier) = ct.table_modifier {
7710            // TRANSIENT is Snowflake-specific - skip for other dialects
7711            let skip_transient = modifier.eq_ignore_ascii_case("TRANSIENT")
7712                && !matches!(self.config.dialect, Some(DialectType::Snowflake) | None);
7713            // Teradata-specific modifiers: VOLATILE, SET, MULTISET, SET TABLE combinations
7714            let is_teradata_modifier = modifier.eq_ignore_ascii_case("VOLATILE")
7715                || modifier.eq_ignore_ascii_case("SET")
7716                || modifier.eq_ignore_ascii_case("MULTISET")
7717                || modifier.to_ascii_uppercase().contains("VOLATILE")
7718                || modifier.to_ascii_uppercase().starts_with("SET ")
7719                || modifier.to_ascii_uppercase().starts_with("MULTISET ");
7720            let skip_teradata =
7721                is_teradata_modifier && !matches!(self.config.dialect, Some(DialectType::Teradata));
7722            if !skip_transient && !skip_teradata {
7723                self.write_space();
7724                self.write_keyword(modifier);
7725            }
7726        }
7727
7728        if !is_dictionary {
7729            self.write_space();
7730            self.write_keyword("TABLE");
7731        }
7732
7733        if ct.if_not_exists {
7734            self.write_space();
7735            self.write_keyword("IF NOT EXISTS");
7736        }
7737
7738        self.write_space();
7739        self.generate_table(&ct.name)?;
7740
7741        // ClickHouse: UUID 'xxx' clause after table name
7742        if let Some(ref uuid) = ct.uuid {
7743            self.write_space();
7744            self.write_keyword("UUID");
7745            self.write(" '");
7746            self.write(uuid);
7747            self.write("'");
7748        }
7749
7750        // ClickHouse: ON CLUSTER clause
7751        if let Some(ref on_cluster) = ct.on_cluster {
7752            self.write_space();
7753            self.generate_on_cluster(on_cluster)?;
7754        }
7755
7756        // Teradata: options after table name before column list (comma-separated)
7757        if matches!(
7758            self.config.dialect,
7759            Some(crate::dialects::DialectType::Teradata)
7760        ) && !ct.teradata_post_name_options.is_empty()
7761        {
7762            for opt in &ct.teradata_post_name_options {
7763                self.write(", ");
7764                self.write(opt);
7765            }
7766        }
7767
7768        // Snowflake: COPY GRANTS clause
7769        if ct.copy_grants {
7770            self.write_space();
7771            self.write_keyword("COPY GRANTS");
7772        }
7773
7774        // Snowflake: USING TEMPLATE clause (before columns or AS SELECT)
7775        if let Some(ref using_template) = ct.using_template {
7776            self.write_space();
7777            self.write_keyword("USING TEMPLATE");
7778            self.write_space();
7779            self.generate_expression(using_template)?;
7780            return Ok(());
7781        }
7782
7783        // ClickHouse uses CREATE TABLE target AS source [ENGINE ...] for table-structure copies.
7784        if is_clickhouse {
7785            if let Some(ref clone_source) = ct.clone_source {
7786                self.write_space();
7787                self.write_keyword("AS");
7788                self.write_space();
7789                self.generate_table(clone_source)?;
7790            }
7791        }
7792
7793        // Handle [SHALLOW | DEEP] CLONE/COPY source_table [AT(...) | BEFORE(...)]
7794        if !is_clickhouse {
7795            if let Some(ref clone_source) = ct.clone_source {
7796                self.write_space();
7797                if ct.is_copy && self.config.supports_table_copy {
7798                    // BigQuery uses COPY
7799                    self.write_keyword("COPY");
7800                } else if ct.shallow_clone {
7801                    self.write_keyword("SHALLOW CLONE");
7802                } else if ct.deep_clone {
7803                    self.write_keyword("DEEP CLONE");
7804                } else {
7805                    self.write_keyword("CLONE");
7806                }
7807                self.write_space();
7808                self.generate_table(clone_source)?;
7809                // Generate AT/BEFORE time travel clause (stored as Raw expression)
7810                if let Some(ref at_clause) = ct.clone_at_clause {
7811                    self.write_space();
7812                    self.generate_expression(at_clause)?;
7813                }
7814                return Ok(());
7815            }
7816        }
7817
7818        // Handle PARTITION OF property
7819        // Output order: PARTITION OF <table> (<columns/constraints>) FOR VALUES ...
7820        // Columns/constraints must appear BETWEEN the table name and the partition bound spec
7821        if let Some(ref partition_of) = ct.partition_of {
7822            self.write_space();
7823
7824            // Extract the PartitionedOfProperty parts to generate them separately
7825            if let Expression::PartitionedOfProperty(ref pop) = partition_of {
7826                // Output: PARTITION OF <table>
7827                self.write_keyword("PARTITION OF");
7828                self.write_space();
7829                self.generate_expression(&pop.this)?;
7830
7831                // Output columns/constraints if present (e.g., (unitsales DEFAULT 0) or (CONSTRAINT ...))
7832                if !ct.columns.is_empty() || !ct.constraints.is_empty() {
7833                    self.write(" (");
7834                    let mut first = true;
7835                    for col in &ct.columns {
7836                        if !first {
7837                            self.write(", ");
7838                        }
7839                        first = false;
7840                        self.generate_column_def(col)?;
7841                    }
7842                    for constraint in &ct.constraints {
7843                        if !first {
7844                            self.write(", ");
7845                        }
7846                        first = false;
7847                        self.generate_table_constraint(constraint)?;
7848                    }
7849                    self.write(")");
7850                }
7851
7852                // Output partition bound spec: FOR VALUES ... or DEFAULT
7853                if let Expression::PartitionBoundSpec(_) = pop.expression.as_ref() {
7854                    self.write_space();
7855                    self.write_keyword("FOR VALUES");
7856                    self.write_space();
7857                    self.generate_expression(&pop.expression)?;
7858                } else {
7859                    self.write_space();
7860                    self.write_keyword("DEFAULT");
7861                }
7862            } else {
7863                // Fallback: generate the whole expression if it's not a PartitionedOfProperty
7864                self.generate_expression(partition_of)?;
7865
7866                // Output columns/constraints if present
7867                if !ct.columns.is_empty() || !ct.constraints.is_empty() {
7868                    self.write(" (");
7869                    let mut first = true;
7870                    for col in &ct.columns {
7871                        if !first {
7872                            self.write(", ");
7873                        }
7874                        first = false;
7875                        self.generate_column_def(col)?;
7876                    }
7877                    for constraint in &ct.constraints {
7878                        if !first {
7879                            self.write(", ");
7880                        }
7881                        first = false;
7882                        self.generate_table_constraint(constraint)?;
7883                    }
7884                    self.write(")");
7885                }
7886            }
7887
7888            // Output table properties (e.g., PARTITION BY RANGE(population))
7889            for prop in &ct.properties {
7890                self.write_space();
7891                self.generate_expression(prop)?;
7892            }
7893
7894            return Ok(());
7895        }
7896
7897        // SQLite: Inline single-column PRIMARY KEY constraints into column definition
7898        // This matches Python sqlglot's behavior for SQLite dialect
7899        self.sqlite_inline_pk_columns.clear();
7900        if matches!(
7901            self.config.dialect,
7902            Some(crate::dialects::DialectType::SQLite)
7903        ) {
7904            for constraint in &ct.constraints {
7905                if let TableConstraint::PrimaryKey { columns, name, .. } = constraint {
7906                    // Only inline if: single column, no constraint name, and column exists in table
7907                    if columns.len() == 1 && name.is_none() {
7908                        let pk_col_name = columns[0].name.to_ascii_lowercase();
7909                        // Check if this column exists in the table
7910                        if ct
7911                            .columns
7912                            .iter()
7913                            .any(|c| c.name.name.to_ascii_lowercase() == pk_col_name)
7914                        {
7915                            self.sqlite_inline_pk_columns.insert(pk_col_name);
7916                        }
7917                    }
7918                }
7919            }
7920        }
7921
7922        // Output columns if present (even for CTAS with columns)
7923        if !ct.columns.is_empty() {
7924            if self.config.pretty {
7925                // Pretty print: each column on new line
7926                self.write(" (");
7927                self.write_newline();
7928                self.indent_level += 1;
7929                for (i, col) in ct.columns.iter().enumerate() {
7930                    if i > 0 {
7931                        self.write(",");
7932                        self.write_newline();
7933                    }
7934                    self.write_indent();
7935                    self.generate_column_def(col)?;
7936                }
7937                // Table constraints (skip inlined PRIMARY KEY for SQLite)
7938                for constraint in &ct.constraints {
7939                    // Skip single-column PRIMARY KEY that was inlined for SQLite
7940                    if let TableConstraint::PrimaryKey { columns, name, .. } = constraint {
7941                        if columns.len() == 1
7942                            && name.is_none()
7943                            && self
7944                                .sqlite_inline_pk_columns
7945                                .contains(&columns[0].name.to_ascii_lowercase())
7946                        {
7947                            continue;
7948                        }
7949                    }
7950                    self.write(",");
7951                    self.write_newline();
7952                    self.write_indent();
7953                    self.generate_table_constraint(constraint)?;
7954                }
7955                self.indent_level -= 1;
7956                self.write_newline();
7957                self.write(")");
7958            } else {
7959                self.write(" (");
7960                for (i, col) in ct.columns.iter().enumerate() {
7961                    if i > 0 {
7962                        self.write(", ");
7963                    }
7964                    self.generate_column_def(col)?;
7965                }
7966                // Table constraints (skip inlined PRIMARY KEY for SQLite)
7967                let mut first_constraint = true;
7968                for constraint in &ct.constraints {
7969                    // Skip single-column PRIMARY KEY that was inlined for SQLite
7970                    if let TableConstraint::PrimaryKey { columns, name, .. } = constraint {
7971                        if columns.len() == 1
7972                            && name.is_none()
7973                            && self
7974                                .sqlite_inline_pk_columns
7975                                .contains(&columns[0].name.to_ascii_lowercase())
7976                        {
7977                            continue;
7978                        }
7979                    }
7980                    if first_constraint {
7981                        self.write(", ");
7982                        first_constraint = false;
7983                    } else {
7984                        self.write(", ");
7985                    }
7986                    self.generate_table_constraint(constraint)?;
7987                }
7988                self.write(")");
7989            }
7990        } else if !ct.constraints.is_empty() {
7991            // No columns but constraints exist (e.g., CREATE TABLE A LIKE B or CREATE TABLE A TAG (...))
7992            let has_like_only = ct
7993                .constraints
7994                .iter()
7995                .all(|c| matches!(c, TableConstraint::Like { .. }));
7996            let has_tags_only = ct
7997                .constraints
7998                .iter()
7999                .all(|c| matches!(c, TableConstraint::Tags(_)));
8000            // PostgreSQL: CREATE TABLE A (LIKE B INCLUDING ALL) (with parens)
8001            // Most dialects: CREATE TABLE A LIKE B (no parens)
8002            // Snowflake: CREATE TABLE A TAG (...) (no outer parens, but TAG has its own)
8003            let is_pg_like = matches!(
8004                self.config.dialect,
8005                Some(crate::dialects::DialectType::PostgreSQL)
8006                    | Some(crate::dialects::DialectType::CockroachDB)
8007                    | Some(crate::dialects::DialectType::Materialize)
8008                    | Some(crate::dialects::DialectType::RisingWave)
8009                    | Some(crate::dialects::DialectType::Redshift)
8010                    | Some(crate::dialects::DialectType::Presto)
8011                    | Some(crate::dialects::DialectType::Trino)
8012                    | Some(crate::dialects::DialectType::Athena)
8013            );
8014            let use_parens = if has_like_only {
8015                is_pg_like
8016            } else {
8017                !has_tags_only
8018            };
8019            if self.config.pretty && use_parens {
8020                self.write(" (");
8021                self.write_newline();
8022                self.indent_level += 1;
8023                for (i, constraint) in ct.constraints.iter().enumerate() {
8024                    if i > 0 {
8025                        self.write(",");
8026                        self.write_newline();
8027                    }
8028                    self.write_indent();
8029                    self.generate_table_constraint(constraint)?;
8030                }
8031                self.indent_level -= 1;
8032                self.write_newline();
8033                self.write(")");
8034            } else {
8035                if use_parens {
8036                    self.write(" (");
8037                } else {
8038                    self.write_space();
8039                }
8040                for (i, constraint) in ct.constraints.iter().enumerate() {
8041                    if i > 0 {
8042                        self.write(", ");
8043                    }
8044                    self.generate_table_constraint(constraint)?;
8045                }
8046                if use_parens {
8047                    self.write(")");
8048                }
8049            }
8050        }
8051
8052        // TSQL ON filegroup or ON filegroup (partition_column) clause
8053        if let Some(ref on_prop) = ct.on_property {
8054            self.write(" ");
8055            self.write_keyword("ON");
8056            self.write(" ");
8057            self.generate_expression(&on_prop.this)?;
8058        }
8059
8060        // BigQuery: WITH PARTITION COLUMNS (col_name col_type, ...)
8061        if !ct.with_partition_columns.is_empty() {
8062            if self.config.pretty {
8063                self.write_newline();
8064            } else {
8065                self.write_space();
8066            }
8067            self.write_keyword("WITH PARTITION COLUMNS");
8068            self.write(" (");
8069            if self.config.pretty {
8070                self.write_newline();
8071                self.indent_level += 1;
8072                for (i, col) in ct.with_partition_columns.iter().enumerate() {
8073                    if i > 0 {
8074                        self.write(",");
8075                        self.write_newline();
8076                    }
8077                    self.write_indent();
8078                    self.generate_column_def(col)?;
8079                }
8080                self.indent_level -= 1;
8081                self.write_newline();
8082            } else {
8083                for (i, col) in ct.with_partition_columns.iter().enumerate() {
8084                    if i > 0 {
8085                        self.write(", ");
8086                    }
8087                    self.generate_column_def(col)?;
8088                }
8089            }
8090            self.write(")");
8091        }
8092
8093        // BigQuery: WITH CONNECTION `project.region.connection`
8094        if let Some(ref conn) = ct.with_connection {
8095            if self.config.pretty {
8096                self.write_newline();
8097            } else {
8098                self.write_space();
8099            }
8100            self.write_keyword("WITH CONNECTION");
8101            self.write_space();
8102            self.generate_table(conn)?;
8103        }
8104
8105        // Output SchemaCommentProperty BEFORE WITH properties (Presto/Hive/Spark style)
8106        // For ClickHouse, SchemaCommentProperty goes after AS SELECT, handled later
8107        if !is_clickhouse {
8108            for prop in &ct.properties {
8109                if let Expression::SchemaCommentProperty(_) = prop {
8110                    if self.config.pretty {
8111                        self.write_newline();
8112                    } else {
8113                        self.write_space();
8114                    }
8115                    self.generate_expression(prop)?;
8116                }
8117            }
8118        }
8119
8120        // WITH properties (output after columns if columns exist, otherwise before AS)
8121        if !ct.with_properties.is_empty() {
8122            // Snowflake ICEBERG/DYNAMIC TABLE: output properties inline (space-separated, no WITH wrapper)
8123            let is_snowflake_special_table = matches!(
8124                self.config.dialect,
8125                Some(crate::dialects::DialectType::Snowflake)
8126            ) && (ct.table_modifier.as_deref() == Some("ICEBERG")
8127                || ct.table_modifier.as_deref() == Some("DYNAMIC"));
8128            if is_snowflake_special_table {
8129                for (key, value) in &ct.with_properties {
8130                    self.write_space();
8131                    self.write(key);
8132                    self.write("=");
8133                    self.write(value);
8134                }
8135            } else if self.config.pretty {
8136                self.write_newline();
8137                self.write_keyword("WITH");
8138                self.write(" (");
8139                self.write_newline();
8140                self.indent_level += 1;
8141                for (i, (key, value)) in ct.with_properties.iter().enumerate() {
8142                    if i > 0 {
8143                        self.write(",");
8144                        self.write_newline();
8145                    }
8146                    self.write_indent();
8147                    self.write(key);
8148                    self.write("=");
8149                    self.write(value);
8150                }
8151                self.indent_level -= 1;
8152                self.write_newline();
8153                self.write(")");
8154            } else {
8155                self.write_space();
8156                self.write_keyword("WITH");
8157                self.write(" (");
8158                for (i, (key, value)) in ct.with_properties.iter().enumerate() {
8159                    if i > 0 {
8160                        self.write(", ");
8161                    }
8162                    self.write(key);
8163                    self.write("=");
8164                    self.write(value);
8165                }
8166                self.write(")");
8167            }
8168        }
8169
8170        let (pre_as_properties, post_as_properties): (Vec<&Expression>, Vec<&Expression>) =
8171            if is_clickhouse && ct.as_select.is_some() {
8172                let mut pre = Vec::new();
8173                let mut post = Vec::new();
8174                for prop in &ct.properties {
8175                    if matches!(prop, Expression::SchemaCommentProperty(_)) {
8176                        post.push(prop);
8177                    } else {
8178                        pre.push(prop);
8179                    }
8180                }
8181                (pre, post)
8182            } else {
8183                (ct.properties.iter().collect(), Vec::new())
8184            };
8185
8186        // Table properties like DEFAULT COLLATE (BigQuery), OPTIONS (...), TBLPROPERTIES (...), or PROPERTIES (...)
8187        for prop in pre_as_properties {
8188            // SchemaCommentProperty was already output before WITH properties (except for ClickHouse)
8189            if !is_clickhouse && matches!(prop, Expression::SchemaCommentProperty(_)) {
8190                continue;
8191            }
8192            if self.config.pretty {
8193                self.write_newline();
8194            } else {
8195                self.write_space();
8196            }
8197            // BigQuery: Properties containing OPTIONS should be wrapped with OPTIONS (...)
8198            // Hive: Properties should be wrapped with TBLPROPERTIES (...)
8199            // Doris/StarRocks: Properties should be wrapped with PROPERTIES (...)
8200            if let Expression::Properties(props) = prop {
8201                let is_hive_dialect = matches!(
8202                    self.config.dialect,
8203                    Some(crate::dialects::DialectType::Hive)
8204                        | Some(crate::dialects::DialectType::Spark)
8205                        | Some(crate::dialects::DialectType::Databricks)
8206                        | Some(crate::dialects::DialectType::Athena)
8207                );
8208                let is_doris_starrocks = matches!(
8209                    self.config.dialect,
8210                    Some(crate::dialects::DialectType::Doris)
8211                        | Some(crate::dialects::DialectType::StarRocks)
8212                );
8213                if is_hive_dialect {
8214                    self.generate_tblproperties_clause(&props.expressions)?;
8215                } else if is_doris_starrocks {
8216                    self.generate_properties_clause(&props.expressions)?;
8217                } else {
8218                    self.generate_options_clause(&props.expressions)?;
8219                }
8220            } else {
8221                self.generate_expression(prop)?;
8222            }
8223        }
8224
8225        // Post-table properties like TSQL WITH(SYSTEM_VERSIONING=ON(...)) or Doris PROPERTIES
8226        for prop in &ct.post_table_properties {
8227            if let Expression::WithSystemVersioningProperty(ref svp) = prop {
8228                self.write(" WITH(");
8229                self.generate_system_versioning_content(svp)?;
8230                self.write(")");
8231            } else if let Expression::Properties(props) = prop {
8232                // Doris/StarRocks: PROPERTIES ('key'='value', ...) in post_table_properties
8233                let is_doris_starrocks = matches!(
8234                    self.config.dialect,
8235                    Some(crate::dialects::DialectType::Doris)
8236                        | Some(crate::dialects::DialectType::StarRocks)
8237                );
8238                self.write_space();
8239                if is_doris_starrocks {
8240                    self.generate_properties_clause(&props.expressions)?;
8241                } else {
8242                    self.generate_options_clause(&props.expressions)?;
8243                }
8244            } else {
8245                self.write_space();
8246                self.generate_expression(prop)?;
8247            }
8248        }
8249
8250        // StarRocks ROLLUP property: ROLLUP (r1(col1, col2), r2(col1))
8251        // Only output for StarRocks target
8252        if let Some(ref rollup) = ct.rollup {
8253            if matches!(self.config.dialect, Some(DialectType::StarRocks)) {
8254                self.write_space();
8255                self.generate_rollup_property(rollup)?;
8256            }
8257        }
8258
8259        // MySQL table options (ENGINE=val, AUTO_INCREMENT=val, etc.)
8260        // Only output for MySQL-compatible dialects; strip for others during transpilation
8261        // COMMENT is also used by Hive/Spark so we selectively preserve it
8262        let is_mysql_compatible = matches!(
8263            self.config.dialect,
8264            Some(DialectType::MySQL)
8265                | Some(DialectType::SingleStore)
8266                | Some(DialectType::Doris)
8267                | Some(DialectType::StarRocks)
8268                | None
8269        );
8270        let is_hive_compatible = matches!(
8271            self.config.dialect,
8272            Some(DialectType::Hive)
8273                | Some(DialectType::Spark)
8274                | Some(DialectType::Databricks)
8275                | Some(DialectType::Athena)
8276        );
8277        let mysql_pretty_options =
8278            self.config.pretty && matches!(self.config.dialect, Some(DialectType::MySQL));
8279        for (key, value) in &ct.mysql_table_options {
8280            // Skip non-MySQL-specific options for non-MySQL targets
8281            let should_output = if is_mysql_compatible {
8282                true
8283            } else if is_hive_compatible && key == "COMMENT" {
8284                true // COMMENT is valid in Hive/Spark table definitions
8285            } else {
8286                false
8287            };
8288            if should_output {
8289                if mysql_pretty_options {
8290                    self.write_newline();
8291                    self.write_indent();
8292                } else {
8293                    self.write_space();
8294                }
8295                self.write_keyword(key);
8296                // StarRocks/Doris: COMMENT 'value' (no =), others: COMMENT='value'
8297                if key == "COMMENT" && !self.config.schema_comment_with_eq {
8298                    self.write_space();
8299                } else {
8300                    self.write("=");
8301                }
8302                self.write(value);
8303            }
8304        }
8305
8306        // Spark/Databricks: USING PARQUET for temporary tables that don't already have a storage format
8307        if ct.temporary
8308            && matches!(
8309                self.config.dialect,
8310                Some(DialectType::Spark) | Some(DialectType::Databricks)
8311            )
8312            && ct.as_select.is_none()
8313        {
8314            self.write_space();
8315            self.write_keyword("USING PARQUET");
8316        }
8317
8318        // PostgreSQL INHERITS clause
8319        if !ct.inherits.is_empty() {
8320            self.write_space();
8321            self.write_keyword("INHERITS");
8322            self.write(" (");
8323            for (i, parent) in ct.inherits.iter().enumerate() {
8324                if i > 0 {
8325                    self.write(", ");
8326                }
8327                self.generate_table(parent)?;
8328            }
8329            self.write(")");
8330        }
8331
8332        // CREATE TABLE AS SELECT
8333        if let Some(ref query) = ct.as_select {
8334            self.write_space();
8335            self.write_keyword("AS");
8336            self.write_space();
8337            let source_is_clickhouse =
8338                matches!(self.config.source_dialect, Some(DialectType::ClickHouse));
8339            let wrap_as_select =
8340                ct.as_select_parenthesized && !(is_clickhouse && source_is_clickhouse);
8341            if wrap_as_select {
8342                self.write("(");
8343            }
8344            self.generate_expression(query)?;
8345            if wrap_as_select {
8346                self.write(")");
8347            }
8348
8349            // Teradata: WITH DATA / WITH NO DATA
8350            if let Some(with_data) = ct.with_data {
8351                self.write_space();
8352                self.write_keyword("WITH");
8353                if !with_data {
8354                    self.write_space();
8355                    self.write_keyword("NO");
8356                }
8357                self.write_space();
8358                self.write_keyword("DATA");
8359            }
8360
8361            // Teradata: AND STATISTICS / AND NO STATISTICS
8362            if let Some(with_statistics) = ct.with_statistics {
8363                self.write_space();
8364                self.write_keyword("AND");
8365                if !with_statistics {
8366                    self.write_space();
8367                    self.write_keyword("NO");
8368                }
8369                self.write_space();
8370                self.write_keyword("STATISTICS");
8371            }
8372
8373            // Teradata: Index specifications
8374            for index in &ct.teradata_indexes {
8375                self.write_space();
8376                match index.kind {
8377                    TeradataIndexKind::NoPrimary => {
8378                        self.write_keyword("NO PRIMARY INDEX");
8379                    }
8380                    TeradataIndexKind::Primary => {
8381                        self.write_keyword("PRIMARY INDEX");
8382                    }
8383                    TeradataIndexKind::PrimaryAmp => {
8384                        self.write_keyword("PRIMARY AMP INDEX");
8385                    }
8386                    TeradataIndexKind::Unique => {
8387                        self.write_keyword("UNIQUE INDEX");
8388                    }
8389                    TeradataIndexKind::UniquePrimary => {
8390                        self.write_keyword("UNIQUE PRIMARY INDEX");
8391                    }
8392                    TeradataIndexKind::Secondary => {
8393                        self.write_keyword("INDEX");
8394                    }
8395                }
8396                // Output index name if present
8397                if let Some(ref name) = index.name {
8398                    self.write_space();
8399                    self.write(name);
8400                }
8401                // Output columns if present
8402                if !index.columns.is_empty() {
8403                    self.write(" (");
8404                    for (i, col) in index.columns.iter().enumerate() {
8405                        if i > 0 {
8406                            self.write(", ");
8407                        }
8408                        self.write(col);
8409                    }
8410                    self.write(")");
8411                }
8412            }
8413
8414            // Teradata: ON COMMIT behavior for volatile tables
8415            if let Some(ref on_commit) = ct.on_commit {
8416                self.write_space();
8417                self.write_keyword("ON COMMIT");
8418                self.write_space();
8419                match on_commit {
8420                    OnCommit::PreserveRows => self.write_keyword("PRESERVE ROWS"),
8421                    OnCommit::DeleteRows => self.write_keyword("DELETE ROWS"),
8422                }
8423            }
8424
8425            if !post_as_properties.is_empty() {
8426                for prop in post_as_properties {
8427                    self.write_space();
8428                    self.generate_expression(prop)?;
8429                }
8430            }
8431
8432            // Restore Athena Hive context before early return
8433            self.athena_hive_context = saved_athena_hive_context;
8434            return Ok(());
8435        }
8436
8437        // ON COMMIT behavior (for non-CTAS tables)
8438        if let Some(ref on_commit) = ct.on_commit {
8439            self.write_space();
8440            self.write_keyword("ON COMMIT");
8441            self.write_space();
8442            match on_commit {
8443                OnCommit::PreserveRows => self.write_keyword("PRESERVE ROWS"),
8444                OnCommit::DeleteRows => self.write_keyword("DELETE ROWS"),
8445            }
8446        }
8447
8448        // Restore Athena Hive context
8449        self.athena_hive_context = saved_athena_hive_context;
8450
8451        Ok(())
8452    }
8453
8454    /// Generate column definition as an expression (for ROWS FROM alias columns, XMLTABLE/JSON_TABLE)
8455    /// Outputs: "col_name" TYPE [PATH 'xpath'] (not the full CREATE TABLE column definition)
8456    fn generate_column_def_expr(&mut self, col: &ColumnDef) -> Result<()> {
8457        // Output column name
8458        self.generate_identifier(&col.name)?;
8459        // Output data type if known
8460        if !matches!(col.data_type, DataType::Unknown) {
8461            self.write_space();
8462            self.generate_data_type(&col.data_type)?;
8463        }
8464        // Output PATH constraint if present (for XMLTABLE/JSON_TABLE columns)
8465        for constraint in &col.constraints {
8466            if let ColumnConstraint::Path(path_expr) = constraint {
8467                self.write_space();
8468                self.write_keyword("PATH");
8469                self.write_space();
8470                self.generate_expression(path_expr)?;
8471            }
8472        }
8473        Ok(())
8474    }
8475
8476    fn generate_column_def(&mut self, col: &ColumnDef) -> Result<()> {
8477        // Check if this is a TSQL computed column (no data type)
8478        let has_computed_no_type = matches!(&col.data_type, DataType::Custom { name } if name.is_empty())
8479            && col
8480                .constraints
8481                .iter()
8482                .any(|c| matches!(c, ColumnConstraint::ComputedColumn(_)));
8483        // Some dialects (notably TSQL/Fabric) do not include an explicit type for computed columns.
8484        let omit_computed_type = !self.config.computed_column_with_type
8485            && col
8486                .constraints
8487                .iter()
8488                .any(|c| matches!(c, ColumnConstraint::ComputedColumn(_)));
8489
8490        // Check if this is a partition column spec (no data type, type is Unknown)
8491        // This is used in PostgreSQL PARTITION OF syntax where columns only have constraints
8492        let is_partition_column_spec = matches!(col.data_type, DataType::Unknown);
8493
8494        // Check if this is a DYNAMIC TABLE column (no data type, empty Custom name, no constraints)
8495        // Also check the no_type flag for SQLite columns without types
8496        let has_no_type = col.no_type
8497            || (matches!(&col.data_type, DataType::Custom { name } if name.is_empty())
8498                && col.constraints.is_empty());
8499
8500        self.generate_identifier(&col.name)?;
8501
8502        // Check for SERIAL/BIGSERIAL/SMALLSERIAL expansion for Materialize and PostgreSQL
8503        let serial_expansion = if matches!(
8504            self.config.dialect,
8505            Some(DialectType::Materialize) | Some(DialectType::PostgreSQL)
8506        ) {
8507            if let DataType::Custom { ref name } = col.data_type {
8508                if name.eq_ignore_ascii_case("SERIAL") {
8509                    Some("INT")
8510                } else if name.eq_ignore_ascii_case("BIGSERIAL") {
8511                    Some("BIGINT")
8512                } else if name.eq_ignore_ascii_case("SMALLSERIAL") {
8513                    Some("SMALLINT")
8514                } else {
8515                    None
8516                }
8517            } else {
8518                None
8519            }
8520        } else {
8521            None
8522        };
8523
8524        if !has_computed_no_type && !omit_computed_type && !is_partition_column_spec && !has_no_type
8525        {
8526            self.write_space();
8527            // ClickHouse CREATE TABLE column types: suppress automatic Nullable wrapping
8528            // since ClickHouse uses explicit Nullable() in its type system.
8529            let saved_nullable_depth = self.clickhouse_nullable_depth;
8530            if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
8531                self.clickhouse_nullable_depth = -1;
8532            }
8533            if let Some(int_type) = serial_expansion {
8534                // SERIAL -> INT (+ constraints added below)
8535                self.write_keyword(int_type);
8536            } else if col.unsigned && matches!(self.config.dialect, Some(DialectType::DuckDB)) {
8537                // For DuckDB: convert unsigned integer types to their unsigned equivalents
8538                let unsigned_type = match &col.data_type {
8539                    DataType::Int { .. } => Some("UINTEGER"),
8540                    DataType::BigInt { .. } => Some("UBIGINT"),
8541                    DataType::SmallInt { .. } => Some("USMALLINT"),
8542                    DataType::TinyInt { .. } => Some("UTINYINT"),
8543                    _ => None,
8544                };
8545                if let Some(utype) = unsigned_type {
8546                    self.write_keyword(utype);
8547                } else {
8548                    self.generate_data_type(&col.data_type)?;
8549                }
8550            } else {
8551                self.generate_data_type(&col.data_type)?;
8552            }
8553            self.clickhouse_nullable_depth = saved_nullable_depth;
8554        }
8555
8556        // MySQL type modifiers (must come right after data type)
8557        // Skip UNSIGNED for DuckDB (already mapped to unsigned type above)
8558        if col.unsigned && !matches!(self.config.dialect, Some(DialectType::DuckDB)) {
8559            self.write_space();
8560            self.write_keyword("UNSIGNED");
8561        }
8562        if col.zerofill {
8563            self.write_space();
8564            self.write_keyword("ZEROFILL");
8565        }
8566
8567        // Teradata column attributes (must come right after data type, in specific order)
8568        // ORDER: CHARACTER SET, UPPERCASE, CASESPECIFIC, FORMAT, TITLE, INLINE LENGTH, COMPRESS
8569
8570        if let Some(ref charset) = col.character_set {
8571            self.write_space();
8572            self.write_keyword("CHARACTER SET");
8573            self.write_space();
8574            self.write(charset);
8575        }
8576
8577        if col.uppercase {
8578            self.write_space();
8579            self.write_keyword("UPPERCASE");
8580        }
8581
8582        if let Some(casespecific) = col.casespecific {
8583            self.write_space();
8584            if casespecific {
8585                self.write_keyword("CASESPECIFIC");
8586            } else {
8587                self.write_keyword("NOT CASESPECIFIC");
8588            }
8589        }
8590
8591        if let Some(ref format) = col.format {
8592            self.write_space();
8593            self.write_keyword("FORMAT");
8594            self.write(" '");
8595            self.write(format);
8596            self.write("'");
8597        }
8598
8599        if let Some(ref title) = col.title {
8600            self.write_space();
8601            self.write_keyword("TITLE");
8602            self.write(" '");
8603            self.write(title);
8604            self.write("'");
8605        }
8606
8607        if let Some(length) = col.inline_length {
8608            self.write_space();
8609            self.write_keyword("INLINE LENGTH");
8610            self.write(" ");
8611            self.write(&length.to_string());
8612        }
8613
8614        if let Some(ref compress) = col.compress {
8615            self.write_space();
8616            self.write_keyword("COMPRESS");
8617            if !compress.is_empty() {
8618                // Single string literal: output without parentheses (Teradata syntax)
8619                if compress.len() == 1 {
8620                    if let Expression::Literal(lit) = &compress[0] {
8621                        if let Literal::String(_) = lit.as_ref() {
8622                            self.write_space();
8623                            self.generate_expression(&compress[0])?;
8624                        }
8625                    } else {
8626                        self.write(" (");
8627                        self.generate_expression(&compress[0])?;
8628                        self.write(")");
8629                    }
8630                } else {
8631                    self.write(" (");
8632                    for (i, val) in compress.iter().enumerate() {
8633                        if i > 0 {
8634                            self.write(", ");
8635                        }
8636                        self.generate_expression(val)?;
8637                    }
8638                    self.write(")");
8639                }
8640            }
8641        }
8642
8643        // Column constraints - output in original order if constraint_order is populated
8644        // Otherwise fall back to legacy fixed order for backward compatibility
8645        if !col.constraint_order.is_empty() {
8646            // Use constraint_order for original ordering
8647            // Track indices for constraints stored in the constraints Vec
8648            let mut references_idx = 0;
8649            let mut check_idx = 0;
8650            let mut generated_idx = 0;
8651            let mut collate_idx = 0;
8652            let mut comment_idx = 0;
8653            // The preprocessing in dialects/mod.rs now handles the correct ordering of
8654            // NOT NULL relative to IDENTITY for PostgreSQL, so no deferral needed here.
8655            let defer_not_null_after_identity = false;
8656            let mut pending_not_null_after_identity = false;
8657
8658            for constraint_type in &col.constraint_order {
8659                match constraint_type {
8660                    ConstraintType::PrimaryKey => {
8661                        // Materialize doesn't support PRIMARY KEY column constraints
8662                        if col.primary_key
8663                            && !matches!(self.config.dialect, Some(DialectType::Materialize))
8664                        {
8665                            if let Some(ref cname) = col.primary_key_constraint_name {
8666                                self.write_space();
8667                                self.write_keyword("CONSTRAINT");
8668                                self.write_space();
8669                                self.write(cname);
8670                            }
8671                            self.write_space();
8672                            self.write_keyword("PRIMARY KEY");
8673                            if let Some(ref order) = col.primary_key_order {
8674                                self.write_space();
8675                                match order {
8676                                    SortOrder::Asc => self.write_keyword("ASC"),
8677                                    SortOrder::Desc => self.write_keyword("DESC"),
8678                                }
8679                            }
8680                        }
8681                    }
8682                    ConstraintType::Unique => {
8683                        if col.unique {
8684                            if let Some(ref cname) = col.unique_constraint_name {
8685                                self.write_space();
8686                                self.write_keyword("CONSTRAINT");
8687                                self.write_space();
8688                                self.write(cname);
8689                            }
8690                            self.write_space();
8691                            self.write_keyword("UNIQUE");
8692                            // PostgreSQL 15+: NULLS NOT DISTINCT
8693                            if col.unique_nulls_not_distinct {
8694                                self.write(" NULLS NOT DISTINCT");
8695                            }
8696                        }
8697                    }
8698                    ConstraintType::NotNull => {
8699                        if col.nullable == Some(false) {
8700                            if defer_not_null_after_identity {
8701                                pending_not_null_after_identity = true;
8702                                continue;
8703                            }
8704                            if let Some(ref cname) = col.not_null_constraint_name {
8705                                self.write_space();
8706                                self.write_keyword("CONSTRAINT");
8707                                self.write_space();
8708                                self.write(cname);
8709                            }
8710                            self.write_space();
8711                            self.write_keyword("NOT NULL");
8712                        }
8713                    }
8714                    ConstraintType::Null => {
8715                        if col.nullable == Some(true) {
8716                            self.write_space();
8717                            self.write_keyword("NULL");
8718                        }
8719                    }
8720                    ConstraintType::Default => {
8721                        if let Some(ref default) = col.default {
8722                            self.write_space();
8723                            self.write_keyword("DEFAULT");
8724                            self.write_space();
8725                            self.generate_expression(default)?;
8726                        }
8727                    }
8728                    ConstraintType::AutoIncrement => {
8729                        if col.auto_increment {
8730                            // DuckDB doesn't support AUTO_INCREMENT - skip entirely
8731                            if matches!(
8732                                self.config.dialect,
8733                                Some(crate::dialects::DialectType::DuckDB)
8734                            ) {
8735                                // Skip - DuckDB uses sequences or rowid instead
8736                            } else if matches!(
8737                                self.config.dialect,
8738                                Some(crate::dialects::DialectType::Materialize)
8739                            ) {
8740                                // Materialize strips AUTO_INCREMENT but adds NOT NULL
8741                                if !matches!(col.nullable, Some(false)) {
8742                                    self.write_space();
8743                                    self.write_keyword("NOT NULL");
8744                                }
8745                            } else if matches!(
8746                                self.config.dialect,
8747                                Some(crate::dialects::DialectType::PostgreSQL)
8748                            ) {
8749                                // PostgreSQL: AUTO_INCREMENT -> GENERATED BY DEFAULT AS IDENTITY
8750                                self.write_space();
8751                                self.generate_auto_increment_keyword(col)?;
8752                            } else {
8753                                self.write_space();
8754                                self.generate_auto_increment_keyword(col)?;
8755                                if pending_not_null_after_identity {
8756                                    self.write_space();
8757                                    self.write_keyword("NOT NULL");
8758                                    pending_not_null_after_identity = false;
8759                                }
8760                            }
8761                        } // close else for DuckDB skip
8762                    }
8763                    ConstraintType::References => {
8764                        // Find next References constraint
8765                        while references_idx < col.constraints.len() {
8766                            if let ColumnConstraint::References(fk_ref) =
8767                                &col.constraints[references_idx]
8768                            {
8769                                // CONSTRAINT name if present
8770                                if let Some(ref name) = fk_ref.constraint_name {
8771                                    self.write_space();
8772                                    self.write_keyword("CONSTRAINT");
8773                                    self.write_space();
8774                                    self.write(name);
8775                                }
8776                                self.write_space();
8777                                if fk_ref.has_foreign_key_keywords {
8778                                    self.write_keyword("FOREIGN KEY");
8779                                    self.write_space();
8780                                }
8781                                self.write_keyword("REFERENCES");
8782                                self.write_space();
8783                                self.generate_table(&fk_ref.table)?;
8784                                if !fk_ref.columns.is_empty() {
8785                                    self.write(" (");
8786                                    for (i, c) in fk_ref.columns.iter().enumerate() {
8787                                        if i > 0 {
8788                                            self.write(", ");
8789                                        }
8790                                        self.generate_identifier(c)?;
8791                                    }
8792                                    self.write(")");
8793                                }
8794                                self.generate_referential_actions(fk_ref)?;
8795                                references_idx += 1;
8796                                break;
8797                            }
8798                            references_idx += 1;
8799                        }
8800                    }
8801                    ConstraintType::Check => {
8802                        // Find next Check constraint
8803                        while check_idx < col.constraints.len() {
8804                            if let ColumnConstraint::Check(expr) = &col.constraints[check_idx] {
8805                                // Output CONSTRAINT name if present (only for first CHECK)
8806                                if check_idx == 0 {
8807                                    if let Some(ref cname) = col.check_constraint_name {
8808                                        self.write_space();
8809                                        self.write_keyword("CONSTRAINT");
8810                                        self.write_space();
8811                                        self.write(cname);
8812                                    }
8813                                }
8814                                self.write_space();
8815                                self.write_keyword("CHECK");
8816                                self.write(" (");
8817                                self.generate_expression(expr)?;
8818                                self.write(")");
8819                                check_idx += 1;
8820                                break;
8821                            }
8822                            check_idx += 1;
8823                        }
8824                    }
8825                    ConstraintType::GeneratedAsIdentity => {
8826                        // Find next GeneratedAsIdentity constraint
8827                        while generated_idx < col.constraints.len() {
8828                            if let ColumnConstraint::GeneratedAsIdentity(gen) =
8829                                &col.constraints[generated_idx]
8830                            {
8831                                self.write_space();
8832                                // Redshift uses IDENTITY(start, increment) syntax
8833                                if matches!(
8834                                    self.config.dialect,
8835                                    Some(crate::dialects::DialectType::Redshift)
8836                                ) {
8837                                    self.write_keyword("IDENTITY");
8838                                    self.write("(");
8839                                    if let Some(ref start) = gen.start {
8840                                        self.generate_expression(start)?;
8841                                    } else {
8842                                        self.write("0");
8843                                    }
8844                                    self.write(", ");
8845                                    if let Some(ref incr) = gen.increment {
8846                                        self.generate_expression(incr)?;
8847                                    } else {
8848                                        self.write("1");
8849                                    }
8850                                    self.write(")");
8851                                } else {
8852                                    self.write_keyword("GENERATED");
8853                                    if gen.always {
8854                                        self.write_space();
8855                                        self.write_keyword("ALWAYS");
8856                                    } else {
8857                                        self.write_space();
8858                                        self.write_keyword("BY DEFAULT");
8859                                        if gen.on_null {
8860                                            self.write_space();
8861                                            self.write_keyword("ON NULL");
8862                                        }
8863                                    }
8864                                    self.write_space();
8865                                    self.write_keyword("AS IDENTITY");
8866
8867                                    let has_options = gen.start.is_some()
8868                                        || gen.increment.is_some()
8869                                        || gen.minvalue.is_some()
8870                                        || gen.maxvalue.is_some()
8871                                        || gen.cycle.is_some();
8872                                    if has_options {
8873                                        self.write(" (");
8874                                        let mut first = true;
8875                                        if let Some(ref start) = gen.start {
8876                                            if !first {
8877                                                self.write(" ");
8878                                            }
8879                                            first = false;
8880                                            self.write_keyword("START WITH");
8881                                            self.write_space();
8882                                            self.generate_expression(start)?;
8883                                        }
8884                                        if let Some(ref incr) = gen.increment {
8885                                            if !first {
8886                                                self.write(" ");
8887                                            }
8888                                            first = false;
8889                                            self.write_keyword("INCREMENT BY");
8890                                            self.write_space();
8891                                            self.generate_expression(incr)?;
8892                                        }
8893                                        if let Some(ref minv) = gen.minvalue {
8894                                            if !first {
8895                                                self.write(" ");
8896                                            }
8897                                            first = false;
8898                                            self.write_keyword("MINVALUE");
8899                                            self.write_space();
8900                                            self.generate_expression(minv)?;
8901                                        }
8902                                        if let Some(ref maxv) = gen.maxvalue {
8903                                            if !first {
8904                                                self.write(" ");
8905                                            }
8906                                            first = false;
8907                                            self.write_keyword("MAXVALUE");
8908                                            self.write_space();
8909                                            self.generate_expression(maxv)?;
8910                                        }
8911                                        if let Some(cycle) = gen.cycle {
8912                                            if !first {
8913                                                self.write(" ");
8914                                            }
8915                                            if cycle {
8916                                                self.write_keyword("CYCLE");
8917                                            } else {
8918                                                self.write_keyword("NO CYCLE");
8919                                            }
8920                                        }
8921                                        self.write(")");
8922                                    }
8923                                }
8924                                generated_idx += 1;
8925                                break;
8926                            }
8927                            generated_idx += 1;
8928                        }
8929                    }
8930                    ConstraintType::Collate => {
8931                        // Find next Collate constraint
8932                        while collate_idx < col.constraints.len() {
8933                            if let ColumnConstraint::Collate(collation) =
8934                                &col.constraints[collate_idx]
8935                            {
8936                                self.write_space();
8937                                self.write_keyword("COLLATE");
8938                                self.write_space();
8939                                self.generate_identifier(collation)?;
8940                                collate_idx += 1;
8941                                break;
8942                            }
8943                            collate_idx += 1;
8944                        }
8945                    }
8946                    ConstraintType::Comment => {
8947                        // Find next Comment constraint
8948                        while comment_idx < col.constraints.len() {
8949                            if let ColumnConstraint::Comment(comment) =
8950                                &col.constraints[comment_idx]
8951                            {
8952                                self.write_space();
8953                                self.write_keyword("COMMENT");
8954                                self.write_space();
8955                                self.generate_string_literal(comment)?;
8956                                comment_idx += 1;
8957                                break;
8958                            }
8959                            comment_idx += 1;
8960                        }
8961                    }
8962                    ConstraintType::Tags => {
8963                        // Find next Tags constraint (Snowflake)
8964                        for constraint in &col.constraints {
8965                            if let ColumnConstraint::Tags(tags) = constraint {
8966                                self.write_space();
8967                                self.write_keyword("TAG");
8968                                self.write(" (");
8969                                for (i, expr) in tags.expressions.iter().enumerate() {
8970                                    if i > 0 {
8971                                        self.write(", ");
8972                                    }
8973                                    self.generate_expression(expr)?;
8974                                }
8975                                self.write(")");
8976                                break;
8977                            }
8978                        }
8979                    }
8980                    ConstraintType::ComputedColumn => {
8981                        // Find next ComputedColumn constraint
8982                        for constraint in &col.constraints {
8983                            if let ColumnConstraint::ComputedColumn(cc) = constraint {
8984                                self.write_space();
8985                                self.generate_computed_column_inline(cc)?;
8986                                break;
8987                            }
8988                        }
8989                    }
8990                    ConstraintType::GeneratedAsRow => {
8991                        // Find next GeneratedAsRow constraint
8992                        for constraint in &col.constraints {
8993                            if let ColumnConstraint::GeneratedAsRow(gar) = constraint {
8994                                self.write_space();
8995                                self.generate_generated_as_row_inline(gar)?;
8996                                break;
8997                            }
8998                        }
8999                    }
9000                    ConstraintType::OnUpdate => {
9001                        if let Some(ref expr) = col.on_update {
9002                            self.write_space();
9003                            self.write_keyword("ON UPDATE");
9004                            self.write_space();
9005                            self.generate_expression(expr)?;
9006                        }
9007                    }
9008                    ConstraintType::Encode => {
9009                        if let Some(ref encoding) = col.encoding {
9010                            self.write_space();
9011                            self.write_keyword("ENCODE");
9012                            self.write_space();
9013                            self.write(encoding);
9014                        }
9015                    }
9016                    ConstraintType::Path => {
9017                        // Find next Path constraint
9018                        for constraint in &col.constraints {
9019                            if let ColumnConstraint::Path(path_expr) = constraint {
9020                                self.write_space();
9021                                self.write_keyword("PATH");
9022                                self.write_space();
9023                                self.generate_expression(path_expr)?;
9024                                break;
9025                            }
9026                        }
9027                    }
9028                }
9029            }
9030            if pending_not_null_after_identity {
9031                self.write_space();
9032                self.write_keyword("NOT NULL");
9033            }
9034        } else {
9035            // Legacy fixed order for backward compatibility
9036            if col.primary_key {
9037                self.write_space();
9038                self.write_keyword("PRIMARY KEY");
9039                if let Some(ref order) = col.primary_key_order {
9040                    self.write_space();
9041                    match order {
9042                        SortOrder::Asc => self.write_keyword("ASC"),
9043                        SortOrder::Desc => self.write_keyword("DESC"),
9044                    }
9045                }
9046            }
9047
9048            if col.unique {
9049                self.write_space();
9050                self.write_keyword("UNIQUE");
9051                // PostgreSQL 15+: NULLS NOT DISTINCT
9052                if col.unique_nulls_not_distinct {
9053                    self.write(" NULLS NOT DISTINCT");
9054                }
9055            }
9056
9057            match col.nullable {
9058                Some(false) => {
9059                    self.write_space();
9060                    self.write_keyword("NOT NULL");
9061                }
9062                Some(true) => {
9063                    self.write_space();
9064                    self.write_keyword("NULL");
9065                }
9066                None => {}
9067            }
9068
9069            if let Some(ref default) = col.default {
9070                self.write_space();
9071                self.write_keyword("DEFAULT");
9072                self.write_space();
9073                self.generate_expression(default)?;
9074            }
9075
9076            if col.auto_increment {
9077                self.write_space();
9078                self.generate_auto_increment_keyword(col)?;
9079            }
9080
9081            // Column-level constraints from Vec
9082            for constraint in &col.constraints {
9083                match constraint {
9084                    ColumnConstraint::References(fk_ref) => {
9085                        self.write_space();
9086                        if fk_ref.has_foreign_key_keywords {
9087                            self.write_keyword("FOREIGN KEY");
9088                            self.write_space();
9089                        }
9090                        self.write_keyword("REFERENCES");
9091                        self.write_space();
9092                        self.generate_table(&fk_ref.table)?;
9093                        if !fk_ref.columns.is_empty() {
9094                            self.write(" (");
9095                            for (i, c) in fk_ref.columns.iter().enumerate() {
9096                                if i > 0 {
9097                                    self.write(", ");
9098                                }
9099                                self.generate_identifier(c)?;
9100                            }
9101                            self.write(")");
9102                        }
9103                        self.generate_referential_actions(fk_ref)?;
9104                    }
9105                    ColumnConstraint::Check(expr) => {
9106                        self.write_space();
9107                        self.write_keyword("CHECK");
9108                        self.write(" (");
9109                        self.generate_expression(expr)?;
9110                        self.write(")");
9111                    }
9112                    ColumnConstraint::GeneratedAsIdentity(gen) => {
9113                        self.write_space();
9114                        // Redshift uses IDENTITY(start, increment) syntax
9115                        if matches!(
9116                            self.config.dialect,
9117                            Some(crate::dialects::DialectType::Redshift)
9118                        ) {
9119                            self.write_keyword("IDENTITY");
9120                            self.write("(");
9121                            if let Some(ref start) = gen.start {
9122                                self.generate_expression(start)?;
9123                            } else {
9124                                self.write("0");
9125                            }
9126                            self.write(", ");
9127                            if let Some(ref incr) = gen.increment {
9128                                self.generate_expression(incr)?;
9129                            } else {
9130                                self.write("1");
9131                            }
9132                            self.write(")");
9133                        } else {
9134                            self.write_keyword("GENERATED");
9135                            if gen.always {
9136                                self.write_space();
9137                                self.write_keyword("ALWAYS");
9138                            } else {
9139                                self.write_space();
9140                                self.write_keyword("BY DEFAULT");
9141                                if gen.on_null {
9142                                    self.write_space();
9143                                    self.write_keyword("ON NULL");
9144                                }
9145                            }
9146                            self.write_space();
9147                            self.write_keyword("AS IDENTITY");
9148
9149                            let has_options = gen.start.is_some()
9150                                || gen.increment.is_some()
9151                                || gen.minvalue.is_some()
9152                                || gen.maxvalue.is_some()
9153                                || gen.cycle.is_some();
9154                            if has_options {
9155                                self.write(" (");
9156                                let mut first = true;
9157                                if let Some(ref start) = gen.start {
9158                                    if !first {
9159                                        self.write(" ");
9160                                    }
9161                                    first = false;
9162                                    self.write_keyword("START WITH");
9163                                    self.write_space();
9164                                    self.generate_expression(start)?;
9165                                }
9166                                if let Some(ref incr) = gen.increment {
9167                                    if !first {
9168                                        self.write(" ");
9169                                    }
9170                                    first = false;
9171                                    self.write_keyword("INCREMENT BY");
9172                                    self.write_space();
9173                                    self.generate_expression(incr)?;
9174                                }
9175                                if let Some(ref minv) = gen.minvalue {
9176                                    if !first {
9177                                        self.write(" ");
9178                                    }
9179                                    first = false;
9180                                    self.write_keyword("MINVALUE");
9181                                    self.write_space();
9182                                    self.generate_expression(minv)?;
9183                                }
9184                                if let Some(ref maxv) = gen.maxvalue {
9185                                    if !first {
9186                                        self.write(" ");
9187                                    }
9188                                    first = false;
9189                                    self.write_keyword("MAXVALUE");
9190                                    self.write_space();
9191                                    self.generate_expression(maxv)?;
9192                                }
9193                                if let Some(cycle) = gen.cycle {
9194                                    if !first {
9195                                        self.write(" ");
9196                                    }
9197                                    if cycle {
9198                                        self.write_keyword("CYCLE");
9199                                    } else {
9200                                        self.write_keyword("NO CYCLE");
9201                                    }
9202                                }
9203                                self.write(")");
9204                            }
9205                        }
9206                    }
9207                    ColumnConstraint::Collate(collation) => {
9208                        self.write_space();
9209                        self.write_keyword("COLLATE");
9210                        self.write_space();
9211                        self.generate_identifier(collation)?;
9212                    }
9213                    ColumnConstraint::Comment(comment) => {
9214                        self.write_space();
9215                        self.write_keyword("COMMENT");
9216                        self.write_space();
9217                        self.generate_string_literal(comment)?;
9218                    }
9219                    ColumnConstraint::Path(path_expr) => {
9220                        self.write_space();
9221                        self.write_keyword("PATH");
9222                        self.write_space();
9223                        self.generate_expression(path_expr)?;
9224                    }
9225                    _ => {} // Other constraints handled above
9226                }
9227            }
9228
9229            // Redshift: ENCODE encoding_type (legacy path)
9230            if let Some(ref encoding) = col.encoding {
9231                self.write_space();
9232                self.write_keyword("ENCODE");
9233                self.write_space();
9234                self.write(encoding);
9235            }
9236        }
9237
9238        // ClickHouse: CODEC(...)
9239        if let Some(ref codec) = col.codec {
9240            self.write_space();
9241            self.write_keyword("CODEC");
9242            self.write("(");
9243            self.write(codec);
9244            self.write(")");
9245        }
9246
9247        if let Some(visible) = col.visible {
9248            self.write_space();
9249            if visible {
9250                self.write_keyword("VISIBLE");
9251            } else {
9252                self.write_keyword("INVISIBLE");
9253            }
9254        }
9255
9256        // ClickHouse: EPHEMERAL [expr]
9257        if let Some(ref ephemeral) = col.ephemeral {
9258            self.write_space();
9259            self.write_keyword("EPHEMERAL");
9260            if let Some(ref expr) = ephemeral {
9261                self.write_space();
9262                self.generate_expression(expr)?;
9263            }
9264        }
9265
9266        // ClickHouse: MATERIALIZED expr
9267        if let Some(ref mat_expr) = col.materialized_expr {
9268            self.write_space();
9269            self.write_keyword("MATERIALIZED");
9270            self.write_space();
9271            self.generate_expression(mat_expr)?;
9272        }
9273
9274        // ClickHouse: ALIAS expr
9275        if let Some(ref alias_expr) = col.alias_expr {
9276            self.write_space();
9277            self.write_keyword("ALIAS");
9278            self.write_space();
9279            self.generate_expression(alias_expr)?;
9280        }
9281
9282        // ClickHouse: TTL expr
9283        if let Some(ref ttl_expr) = col.ttl_expr {
9284            self.write_space();
9285            self.write_keyword("TTL");
9286            self.write_space();
9287            self.generate_expression(ttl_expr)?;
9288        }
9289
9290        // TSQL: NOT FOR REPLICATION
9291        if col.not_for_replication
9292            && matches!(
9293                self.config.dialect,
9294                Some(crate::dialects::DialectType::TSQL)
9295                    | Some(crate::dialects::DialectType::Fabric)
9296            )
9297        {
9298            self.write_space();
9299            self.write_keyword("NOT FOR REPLICATION");
9300        }
9301
9302        // BigQuery: OPTIONS (key=value, ...) on column - comes after all constraints
9303        if !col.options.is_empty() {
9304            self.write_space();
9305            self.generate_options_clause(&col.options)?;
9306        }
9307
9308        // SQLite: Inline PRIMARY KEY from table constraint
9309        // This comes at the end, after all existing column constraints
9310        if !col.primary_key
9311            && self
9312                .sqlite_inline_pk_columns
9313                .contains(&col.name.name.to_ascii_lowercase())
9314        {
9315            self.write_space();
9316            self.write_keyword("PRIMARY KEY");
9317        }
9318
9319        // SERIAL expansion: add GENERATED BY DEFAULT AS IDENTITY NOT NULL for PostgreSQL,
9320        // just NOT NULL for Materialize (which strips GENERATED AS IDENTITY)
9321        if serial_expansion.is_some() {
9322            if matches!(self.config.dialect, Some(DialectType::PostgreSQL)) {
9323                self.write_space();
9324                self.write_keyword("GENERATED BY DEFAULT AS IDENTITY NOT NULL");
9325            } else if matches!(self.config.dialect, Some(DialectType::Materialize)) {
9326                self.write_space();
9327                self.write_keyword("NOT NULL");
9328            }
9329        }
9330
9331        Ok(())
9332    }
9333
9334    fn generate_table_constraint(&mut self, constraint: &TableConstraint) -> Result<()> {
9335        match constraint {
9336            TableConstraint::PrimaryKey {
9337                name,
9338                columns,
9339                include_columns,
9340                modifiers,
9341                has_constraint_keyword,
9342            } => {
9343                if let Some(ref n) = name {
9344                    if *has_constraint_keyword {
9345                        self.write_keyword("CONSTRAINT");
9346                        self.write_space();
9347                        self.generate_identifier(n)?;
9348                        self.write_space();
9349                    }
9350                }
9351                self.write_keyword("PRIMARY KEY");
9352                // TSQL CLUSTERED/NONCLUSTERED modifier (before columns)
9353                if let Some(ref clustered) = modifiers.clustered {
9354                    self.write_space();
9355                    self.write_keyword(clustered);
9356                }
9357                // MySQL format: PRIMARY KEY name (cols) when no CONSTRAINT keyword
9358                if let Some(ref n) = name {
9359                    if !*has_constraint_keyword {
9360                        self.write_space();
9361                        self.generate_identifier(n)?;
9362                    }
9363                }
9364                self.write(" (");
9365                for (i, col) in columns.iter().enumerate() {
9366                    if i > 0 {
9367                        self.write(", ");
9368                    }
9369                    self.generate_identifier(col)?;
9370                }
9371                self.write(")");
9372                if !include_columns.is_empty() {
9373                    self.write_space();
9374                    self.write_keyword("INCLUDE");
9375                    self.write(" (");
9376                    for (i, col) in include_columns.iter().enumerate() {
9377                        if i > 0 {
9378                            self.write(", ");
9379                        }
9380                        self.generate_identifier(col)?;
9381                    }
9382                    self.write(")");
9383                }
9384                self.generate_constraint_modifiers(modifiers);
9385            }
9386            TableConstraint::Unique {
9387                name,
9388                columns,
9389                columns_parenthesized,
9390                modifiers,
9391                has_constraint_keyword,
9392                nulls_not_distinct,
9393            } => {
9394                if let Some(ref n) = name {
9395                    if *has_constraint_keyword {
9396                        self.write_keyword("CONSTRAINT");
9397                        self.write_space();
9398                        self.generate_identifier(n)?;
9399                        self.write_space();
9400                    }
9401                }
9402                self.write_keyword("UNIQUE");
9403                // TSQL CLUSTERED/NONCLUSTERED modifier (before columns)
9404                if let Some(ref clustered) = modifiers.clustered {
9405                    self.write_space();
9406                    self.write_keyword(clustered);
9407                }
9408                // PostgreSQL 15+: NULLS NOT DISTINCT
9409                if *nulls_not_distinct {
9410                    self.write(" NULLS NOT DISTINCT");
9411                }
9412                // MySQL format: UNIQUE name (cols) when no CONSTRAINT keyword
9413                if let Some(ref n) = name {
9414                    if !*has_constraint_keyword {
9415                        self.write_space();
9416                        self.generate_identifier(n)?;
9417                    }
9418                }
9419                if *columns_parenthesized {
9420                    self.write(" (");
9421                    for (i, col) in columns.iter().enumerate() {
9422                        if i > 0 {
9423                            self.write(", ");
9424                        }
9425                        self.generate_identifier(col)?;
9426                    }
9427                    self.write(")");
9428                } else {
9429                    // UNIQUE without parentheses (e.g., UNIQUE idx_name)
9430                    for col in columns.iter() {
9431                        self.write_space();
9432                        self.generate_identifier(col)?;
9433                    }
9434                }
9435                self.generate_constraint_modifiers(modifiers);
9436            }
9437            TableConstraint::ForeignKey {
9438                name,
9439                columns,
9440                references,
9441                on_delete,
9442                on_update,
9443                modifiers,
9444            } => {
9445                if let Some(ref n) = name {
9446                    self.write_keyword("CONSTRAINT");
9447                    self.write_space();
9448                    self.generate_identifier(n)?;
9449                    self.write_space();
9450                }
9451                self.write_keyword("FOREIGN KEY");
9452                self.write(" (");
9453                for (i, col) in columns.iter().enumerate() {
9454                    if i > 0 {
9455                        self.write(", ");
9456                    }
9457                    self.generate_identifier(col)?;
9458                }
9459                self.write(")");
9460                if let Some(ref refs) = references {
9461                    self.write(" ");
9462                    self.write_keyword("REFERENCES");
9463                    self.write_space();
9464                    self.generate_table(&refs.table)?;
9465                    if !refs.columns.is_empty() {
9466                        if self.config.pretty {
9467                            self.write(" (");
9468                            self.write_newline();
9469                            self.indent_level += 1;
9470                            for (i, col) in refs.columns.iter().enumerate() {
9471                                if i > 0 {
9472                                    self.write(",");
9473                                    self.write_newline();
9474                                }
9475                                self.write_indent();
9476                                self.generate_identifier(col)?;
9477                            }
9478                            self.indent_level -= 1;
9479                            self.write_newline();
9480                            self.write_indent();
9481                            self.write(")");
9482                        } else {
9483                            self.write(" (");
9484                            for (i, col) in refs.columns.iter().enumerate() {
9485                                if i > 0 {
9486                                    self.write(", ");
9487                                }
9488                                self.generate_identifier(col)?;
9489                            }
9490                            self.write(")");
9491                        }
9492                    }
9493                    self.generate_referential_actions(refs)?;
9494                } else {
9495                    // No REFERENCES - output ON DELETE/ON UPDATE directly
9496                    if let Some(ref action) = on_delete {
9497                        self.write_space();
9498                        self.write_keyword("ON DELETE");
9499                        self.write_space();
9500                        self.generate_referential_action(action);
9501                    }
9502                    if let Some(ref action) = on_update {
9503                        self.write_space();
9504                        self.write_keyword("ON UPDATE");
9505                        self.write_space();
9506                        self.generate_referential_action(action);
9507                    }
9508                }
9509                self.generate_constraint_modifiers(modifiers);
9510            }
9511            TableConstraint::Check {
9512                name,
9513                expression,
9514                modifiers,
9515            } => {
9516                if let Some(ref n) = name {
9517                    self.write_keyword("CONSTRAINT");
9518                    self.write_space();
9519                    self.generate_identifier(n)?;
9520                    self.write_space();
9521                }
9522                self.write_keyword("CHECK");
9523                self.write(" (");
9524                self.generate_expression(expression)?;
9525                self.write(")");
9526                self.generate_constraint_modifiers(modifiers);
9527            }
9528            TableConstraint::Assume { name, expression } => {
9529                if let Some(ref n) = name {
9530                    self.write_keyword("CONSTRAINT");
9531                    self.write_space();
9532                    self.generate_identifier(n)?;
9533                    self.write_space();
9534                }
9535                self.write_keyword("ASSUME");
9536                self.write(" (");
9537                self.generate_expression(expression)?;
9538                self.write(")");
9539            }
9540            TableConstraint::Default {
9541                name,
9542                expression,
9543                column,
9544            } => {
9545                if let Some(ref n) = name {
9546                    self.write_keyword("CONSTRAINT");
9547                    self.write_space();
9548                    self.generate_identifier(n)?;
9549                    self.write_space();
9550                }
9551                self.write_keyword("DEFAULT");
9552                self.write_space();
9553                self.generate_expression(expression)?;
9554                self.write_space();
9555                self.write_keyword("FOR");
9556                self.write_space();
9557                self.generate_identifier(column)?;
9558            }
9559            TableConstraint::Index {
9560                name,
9561                columns,
9562                kind,
9563                modifiers,
9564                use_key_keyword,
9565                expression,
9566                index_type,
9567                granularity,
9568            } => {
9569                // ClickHouse-style INDEX: INDEX name expr TYPE type_func GRANULARITY n
9570                if expression.is_some() {
9571                    self.write_keyword("INDEX");
9572                    if let Some(ref n) = name {
9573                        self.write_space();
9574                        self.generate_identifier(n)?;
9575                    }
9576                    if let Some(ref expr) = expression {
9577                        self.write_space();
9578                        self.generate_expression(expr)?;
9579                    }
9580                    if let Some(ref idx_type) = index_type {
9581                        self.write_space();
9582                        self.write_keyword("TYPE");
9583                        self.write_space();
9584                        self.generate_expression(idx_type)?;
9585                    }
9586                    if let Some(ref gran) = granularity {
9587                        self.write_space();
9588                        self.write_keyword("GRANULARITY");
9589                        self.write_space();
9590                        self.generate_expression(gran)?;
9591                    }
9592                } else {
9593                    // Standard INDEX syntax
9594                    // Determine the index keyword to use
9595                    // MySQL normalizes KEY to INDEX
9596                    use crate::dialects::DialectType;
9597                    let index_keyword = if *use_key_keyword
9598                        && !matches!(self.config.dialect, Some(DialectType::MySQL))
9599                    {
9600                        "KEY"
9601                    } else {
9602                        "INDEX"
9603                    };
9604
9605                    // Output kind (UNIQUE, FULLTEXT, SPATIAL) if present
9606                    if let Some(ref k) = kind {
9607                        self.write_keyword(k);
9608                        // For UNIQUE, don't add INDEX/KEY keyword
9609                        if k != "UNIQUE" {
9610                            self.write_space();
9611                            self.write_keyword(index_keyword);
9612                        }
9613                    } else {
9614                        self.write_keyword(index_keyword);
9615                    }
9616
9617                    // Output USING before name if using_before_columns is true and there's no name
9618                    if modifiers.using_before_columns && name.is_none() {
9619                        if let Some(ref using) = modifiers.using {
9620                            self.write_space();
9621                            self.write_keyword("USING");
9622                            self.write_space();
9623                            self.write_keyword(using);
9624                        }
9625                    }
9626
9627                    // Output index name if present
9628                    if let Some(ref n) = name {
9629                        self.write_space();
9630                        self.generate_identifier(n)?;
9631                    }
9632
9633                    // Output USING after name but before columns if using_before_columns and there's a name
9634                    if modifiers.using_before_columns && name.is_some() {
9635                        if let Some(ref using) = modifiers.using {
9636                            self.write_space();
9637                            self.write_keyword("USING");
9638                            self.write_space();
9639                            self.write_keyword(using);
9640                        }
9641                    }
9642
9643                    // Output columns
9644                    self.write(" (");
9645                    for (i, col) in columns.iter().enumerate() {
9646                        if i > 0 {
9647                            self.write(", ");
9648                        }
9649                        self.generate_identifier(col)?;
9650                    }
9651                    self.write(")");
9652
9653                    // Output USING after columns if not using_before_columns
9654                    if !modifiers.using_before_columns {
9655                        if let Some(ref using) = modifiers.using {
9656                            self.write_space();
9657                            self.write_keyword("USING");
9658                            self.write_space();
9659                            self.write_keyword(using);
9660                        }
9661                    }
9662
9663                    // Output other constraint modifiers (but skip USING since we already handled it)
9664                    self.generate_constraint_modifiers_without_using(modifiers);
9665                }
9666            }
9667            TableConstraint::Projection { name, expression } => {
9668                // ClickHouse: PROJECTION name (SELECT ...)
9669                self.write_keyword("PROJECTION");
9670                self.write_space();
9671                self.generate_identifier(name)?;
9672                self.write(" (");
9673                self.generate_expression(expression)?;
9674                self.write(")");
9675            }
9676            TableConstraint::Like { source, options } => {
9677                self.write_keyword("LIKE");
9678                self.write_space();
9679                self.generate_table(source)?;
9680                for (action, prop) in options {
9681                    self.write_space();
9682                    match action {
9683                        LikeOptionAction::Including => self.write_keyword("INCLUDING"),
9684                        LikeOptionAction::Excluding => self.write_keyword("EXCLUDING"),
9685                    }
9686                    self.write_space();
9687                    self.write_keyword(prop);
9688                }
9689            }
9690            TableConstraint::PeriodForSystemTime { start_col, end_col } => {
9691                self.write_keyword("PERIOD FOR SYSTEM_TIME");
9692                self.write(" (");
9693                self.generate_identifier(start_col)?;
9694                self.write(", ");
9695                self.generate_identifier(end_col)?;
9696                self.write(")");
9697            }
9698            TableConstraint::Exclude {
9699                name,
9700                using,
9701                elements,
9702                include_columns,
9703                where_clause,
9704                with_params,
9705                using_index_tablespace,
9706                modifiers: _,
9707            } => {
9708                if let Some(ref n) = name {
9709                    self.write_keyword("CONSTRAINT");
9710                    self.write_space();
9711                    self.generate_identifier(n)?;
9712                    self.write_space();
9713                }
9714                self.write_keyword("EXCLUDE");
9715                if let Some(ref method) = using {
9716                    self.write_space();
9717                    self.write_keyword("USING");
9718                    self.write_space();
9719                    self.write(method);
9720                    self.write("(");
9721                } else {
9722                    self.write(" (");
9723                }
9724                for (i, elem) in elements.iter().enumerate() {
9725                    if i > 0 {
9726                        self.write(", ");
9727                    }
9728                    self.write(&elem.expression);
9729                    self.write_space();
9730                    self.write_keyword("WITH");
9731                    self.write_space();
9732                    self.write(&elem.operator);
9733                }
9734                self.write(")");
9735                if !include_columns.is_empty() {
9736                    self.write_space();
9737                    self.write_keyword("INCLUDE");
9738                    self.write(" (");
9739                    for (i, col) in include_columns.iter().enumerate() {
9740                        if i > 0 {
9741                            self.write(", ");
9742                        }
9743                        self.generate_identifier(col)?;
9744                    }
9745                    self.write(")");
9746                }
9747                if !with_params.is_empty() {
9748                    self.write_space();
9749                    self.write_keyword("WITH");
9750                    self.write(" (");
9751                    for (i, (key, val)) in with_params.iter().enumerate() {
9752                        if i > 0 {
9753                            self.write(", ");
9754                        }
9755                        self.write(key);
9756                        self.write("=");
9757                        self.write(val);
9758                    }
9759                    self.write(")");
9760                }
9761                if let Some(ref tablespace) = using_index_tablespace {
9762                    self.write_space();
9763                    self.write_keyword("USING INDEX TABLESPACE");
9764                    self.write_space();
9765                    self.write(tablespace);
9766                }
9767                if let Some(ref where_expr) = where_clause {
9768                    self.write_space();
9769                    self.write_keyword("WHERE");
9770                    self.write(" (");
9771                    self.generate_expression(where_expr)?;
9772                    self.write(")");
9773                }
9774            }
9775            TableConstraint::Tags(tags) => {
9776                self.write_keyword("TAG");
9777                self.write(" (");
9778                for (i, expr) in tags.expressions.iter().enumerate() {
9779                    if i > 0 {
9780                        self.write(", ");
9781                    }
9782                    self.generate_expression(expr)?;
9783                }
9784                self.write(")");
9785            }
9786            TableConstraint::InitiallyDeferred { deferred } => {
9787                self.write_keyword("INITIALLY");
9788                self.write_space();
9789                if *deferred {
9790                    self.write_keyword("DEFERRED");
9791                } else {
9792                    self.write_keyword("IMMEDIATE");
9793                }
9794            }
9795        }
9796        Ok(())
9797    }
9798
9799    fn generate_constraint_modifiers(&mut self, modifiers: &ConstraintModifiers) {
9800        // Output USING BTREE/HASH (MySQL) - comes first
9801        if let Some(using) = &modifiers.using {
9802            self.write_space();
9803            self.write_keyword("USING");
9804            self.write_space();
9805            self.write_keyword(using);
9806        }
9807        // Output ENFORCED/NOT ENFORCED
9808        if let Some(enforced) = modifiers.enforced {
9809            self.write_space();
9810            if enforced {
9811                self.write_keyword("ENFORCED");
9812            } else {
9813                self.write_keyword("NOT ENFORCED");
9814            }
9815        }
9816        // Output DEFERRABLE/NOT DEFERRABLE
9817        if let Some(deferrable) = modifiers.deferrable {
9818            self.write_space();
9819            if deferrable {
9820                self.write_keyword("DEFERRABLE");
9821            } else {
9822                self.write_keyword("NOT DEFERRABLE");
9823            }
9824        }
9825        // Output INITIALLY DEFERRED/INITIALLY IMMEDIATE
9826        if let Some(initially_deferred) = modifiers.initially_deferred {
9827            self.write_space();
9828            if initially_deferred {
9829                self.write_keyword("INITIALLY DEFERRED");
9830            } else {
9831                self.write_keyword("INITIALLY IMMEDIATE");
9832            }
9833        }
9834        // Output NORELY
9835        if modifiers.norely {
9836            self.write_space();
9837            self.write_keyword("NORELY");
9838        }
9839        // Output RELY
9840        if modifiers.rely {
9841            self.write_space();
9842            self.write_keyword("RELY");
9843        }
9844        // Output NOT VALID (PostgreSQL)
9845        if modifiers.not_valid {
9846            self.write_space();
9847            self.write_keyword("NOT VALID");
9848        }
9849        // Output ON CONFLICT (SQLite)
9850        if let Some(on_conflict) = &modifiers.on_conflict {
9851            self.write_space();
9852            self.write_keyword("ON CONFLICT");
9853            self.write_space();
9854            self.write_keyword(on_conflict);
9855        }
9856        // Output TSQL WITH options (PAD_INDEX=ON, STATISTICS_NORECOMPUTE=OFF, ...)
9857        if !modifiers.with_options.is_empty() {
9858            self.write_space();
9859            self.write_keyword("WITH");
9860            self.write(" (");
9861            for (i, (key, value)) in modifiers.with_options.iter().enumerate() {
9862                if i > 0 {
9863                    self.write(", ");
9864                }
9865                self.write(key);
9866                self.write("=");
9867                self.write(value);
9868            }
9869            self.write(")");
9870        }
9871        // Output TSQL ON filegroup
9872        if let Some(ref fg) = modifiers.on_filegroup {
9873            self.write_space();
9874            self.write_keyword("ON");
9875            self.write_space();
9876            let _ = self.generate_identifier(fg);
9877        }
9878    }
9879
9880    /// Generate constraint modifiers without USING (for Index constraints where USING is handled separately)
9881    fn generate_constraint_modifiers_without_using(&mut self, modifiers: &ConstraintModifiers) {
9882        // Output ENFORCED/NOT ENFORCED
9883        if let Some(enforced) = modifiers.enforced {
9884            self.write_space();
9885            if enforced {
9886                self.write_keyword("ENFORCED");
9887            } else {
9888                self.write_keyword("NOT ENFORCED");
9889            }
9890        }
9891        // Output DEFERRABLE/NOT DEFERRABLE
9892        if let Some(deferrable) = modifiers.deferrable {
9893            self.write_space();
9894            if deferrable {
9895                self.write_keyword("DEFERRABLE");
9896            } else {
9897                self.write_keyword("NOT DEFERRABLE");
9898            }
9899        }
9900        // Output INITIALLY DEFERRED/INITIALLY IMMEDIATE
9901        if let Some(initially_deferred) = modifiers.initially_deferred {
9902            self.write_space();
9903            if initially_deferred {
9904                self.write_keyword("INITIALLY DEFERRED");
9905            } else {
9906                self.write_keyword("INITIALLY IMMEDIATE");
9907            }
9908        }
9909        // Output NORELY
9910        if modifiers.norely {
9911            self.write_space();
9912            self.write_keyword("NORELY");
9913        }
9914        // Output RELY
9915        if modifiers.rely {
9916            self.write_space();
9917            self.write_keyword("RELY");
9918        }
9919        // Output NOT VALID (PostgreSQL)
9920        if modifiers.not_valid {
9921            self.write_space();
9922            self.write_keyword("NOT VALID");
9923        }
9924        // Output ON CONFLICT (SQLite)
9925        if let Some(on_conflict) = &modifiers.on_conflict {
9926            self.write_space();
9927            self.write_keyword("ON CONFLICT");
9928            self.write_space();
9929            self.write_keyword(on_conflict);
9930        }
9931        // Output MySQL index-specific modifiers
9932        self.generate_index_specific_modifiers(modifiers);
9933    }
9934
9935    /// Generate MySQL index-specific modifiers (COMMENT, VISIBLE, ENGINE_ATTRIBUTE, WITH PARSER)
9936    fn generate_index_specific_modifiers(&mut self, modifiers: &ConstraintModifiers) {
9937        if let Some(ref comment) = modifiers.comment {
9938            self.write_space();
9939            self.write_keyword("COMMENT");
9940            self.write(" '");
9941            self.write(comment);
9942            self.write("'");
9943        }
9944        if let Some(visible) = modifiers.visible {
9945            self.write_space();
9946            if visible {
9947                self.write_keyword("VISIBLE");
9948            } else {
9949                self.write_keyword("INVISIBLE");
9950            }
9951        }
9952        if let Some(ref attr) = modifiers.engine_attribute {
9953            self.write_space();
9954            self.write_keyword("ENGINE_ATTRIBUTE");
9955            self.write(" = '");
9956            self.write(attr);
9957            self.write("'");
9958        }
9959        if let Some(ref parser) = modifiers.with_parser {
9960            self.write_space();
9961            self.write_keyword("WITH PARSER");
9962            self.write_space();
9963            self.write(parser);
9964        }
9965    }
9966
9967    fn generate_referential_actions(&mut self, fk_ref: &ForeignKeyRef) -> Result<()> {
9968        // MATCH clause before ON DELETE/ON UPDATE (default position, e.g. PostgreSQL)
9969        if !fk_ref.match_after_actions {
9970            if let Some(ref match_type) = fk_ref.match_type {
9971                self.write_space();
9972                self.write_keyword("MATCH");
9973                self.write_space();
9974                match match_type {
9975                    MatchType::Full => self.write_keyword("FULL"),
9976                    MatchType::Partial => self.write_keyword("PARTIAL"),
9977                    MatchType::Simple => self.write_keyword("SIMPLE"),
9978                }
9979            }
9980        }
9981
9982        // Output ON UPDATE and ON DELETE in the original order
9983        if fk_ref.on_update_first {
9984            if let Some(ref action) = fk_ref.on_update {
9985                self.write_space();
9986                self.write_keyword("ON UPDATE");
9987                self.write_space();
9988                self.generate_referential_action(action);
9989            }
9990            if let Some(ref action) = fk_ref.on_delete {
9991                self.write_space();
9992                self.write_keyword("ON DELETE");
9993                self.write_space();
9994                self.generate_referential_action(action);
9995            }
9996        } else {
9997            if let Some(ref action) = fk_ref.on_delete {
9998                self.write_space();
9999                self.write_keyword("ON DELETE");
10000                self.write_space();
10001                self.generate_referential_action(action);
10002            }
10003            if let Some(ref action) = fk_ref.on_update {
10004                self.write_space();
10005                self.write_keyword("ON UPDATE");
10006                self.write_space();
10007                self.generate_referential_action(action);
10008            }
10009        }
10010
10011        // MATCH clause after ON DELETE/ON UPDATE (when original SQL had it after)
10012        if fk_ref.match_after_actions {
10013            if let Some(ref match_type) = fk_ref.match_type {
10014                self.write_space();
10015                self.write_keyword("MATCH");
10016                self.write_space();
10017                match match_type {
10018                    MatchType::Full => self.write_keyword("FULL"),
10019                    MatchType::Partial => self.write_keyword("PARTIAL"),
10020                    MatchType::Simple => self.write_keyword("SIMPLE"),
10021                }
10022            }
10023        }
10024
10025        // DEFERRABLE / NOT DEFERRABLE
10026        if let Some(deferrable) = fk_ref.deferrable {
10027            self.write_space();
10028            if deferrable {
10029                self.write_keyword("DEFERRABLE");
10030            } else {
10031                self.write_keyword("NOT DEFERRABLE");
10032            }
10033        }
10034
10035        Ok(())
10036    }
10037
10038    fn generate_referential_action(&mut self, action: &ReferentialAction) {
10039        match action {
10040            ReferentialAction::Cascade => self.write_keyword("CASCADE"),
10041            ReferentialAction::SetNull => self.write_keyword("SET NULL"),
10042            ReferentialAction::SetDefault => self.write_keyword("SET DEFAULT"),
10043            ReferentialAction::Restrict => self.write_keyword("RESTRICT"),
10044            ReferentialAction::NoAction => self.write_keyword("NO ACTION"),
10045        }
10046    }
10047
10048    fn generate_drop_table(&mut self, dt: &DropTable) -> Result<()> {
10049        // TSQL: IF NOT OBJECT_ID(...) IS NULL BEGIN DROP TABLE ...; END
10050        if let Some(ref object_id_args) = dt.object_id_args {
10051            if matches!(
10052                self.config.dialect,
10053                Some(crate::dialects::DialectType::TSQL)
10054                    | Some(crate::dialects::DialectType::Fabric)
10055            ) {
10056                self.write_keyword("IF NOT OBJECT_ID");
10057                self.write("(");
10058                self.write(object_id_args);
10059                self.write(")");
10060                self.write_space();
10061                self.write_keyword("IS NULL BEGIN DROP TABLE");
10062                self.write_space();
10063                for (i, table) in dt.names.iter().enumerate() {
10064                    if i > 0 {
10065                        self.write(", ");
10066                    }
10067                    self.generate_table(table)?;
10068                }
10069                self.write("; ");
10070                self.write_keyword("END");
10071                return Ok(());
10072            }
10073        }
10074
10075        // Athena: DROP TABLE uses Hive engine (backticks)
10076        let saved_athena_hive_context = self.athena_hive_context;
10077        if matches!(
10078            self.config.dialect,
10079            Some(crate::dialects::DialectType::Athena)
10080        ) {
10081            self.athena_hive_context = true;
10082        }
10083
10084        // Output leading comments (e.g., "-- comment\nDROP TABLE ...")
10085        for comment in &dt.leading_comments {
10086            self.write_formatted_comment(comment);
10087            self.write_space();
10088        }
10089        if dt.iceberg {
10090            self.write_keyword("DROP ICEBERG TABLE");
10091        } else {
10092            self.write_keyword("DROP TABLE");
10093        }
10094
10095        if dt.if_exists {
10096            self.write_space();
10097            self.write_keyword("IF EXISTS");
10098        }
10099
10100        self.write_space();
10101        for (i, table) in dt.names.iter().enumerate() {
10102            if i > 0 {
10103                self.write(", ");
10104            }
10105            self.generate_table(table)?;
10106        }
10107
10108        if dt.cascade_constraints {
10109            self.write_space();
10110            self.write_keyword("CASCADE CONSTRAINTS");
10111        } else if dt.cascade {
10112            self.write_space();
10113            self.write_keyword("CASCADE");
10114        }
10115
10116        if dt.restrict {
10117            self.write_space();
10118            self.write_keyword("RESTRICT");
10119        }
10120
10121        if dt.purge {
10122            self.write_space();
10123            self.write_keyword("PURGE");
10124        }
10125
10126        if dt.sync {
10127            self.write_space();
10128            self.write_keyword("SYNC");
10129        }
10130
10131        // Restore Athena Hive context
10132        self.athena_hive_context = saved_athena_hive_context;
10133
10134        Ok(())
10135    }
10136
10137    fn generate_undrop(&mut self, u: &Undrop) -> Result<()> {
10138        self.write_keyword("UNDROP");
10139        self.write_space();
10140        self.write_keyword(&u.kind);
10141        if u.if_exists {
10142            self.write_space();
10143            self.write_keyword("IF EXISTS");
10144        }
10145        self.write_space();
10146        self.generate_table(&u.name)?;
10147        Ok(())
10148    }
10149
10150    fn generate_alter_table(&mut self, at: &AlterTable) -> Result<()> {
10151        // Athena: ALTER TABLE uses Hive engine (backticks)
10152        let saved_athena_hive_context = self.athena_hive_context;
10153        if matches!(
10154            self.config.dialect,
10155            Some(crate::dialects::DialectType::Athena)
10156        ) {
10157            self.athena_hive_context = true;
10158        }
10159
10160        self.write_keyword("ALTER");
10161        // Write table modifier (e.g., ICEBERG) unless target is DuckDB
10162        if let Some(ref modifier) = at.table_modifier {
10163            if !matches!(
10164                self.config.dialect,
10165                Some(crate::dialects::DialectType::DuckDB)
10166            ) {
10167                self.write_space();
10168                self.write_keyword(modifier);
10169            }
10170        }
10171        self.write(" ");
10172        self.write_keyword("TABLE");
10173        if at.if_exists {
10174            self.write_space();
10175            self.write_keyword("IF EXISTS");
10176        }
10177        self.write_space();
10178        self.generate_table(&at.name)?;
10179
10180        // ClickHouse: ON CLUSTER clause
10181        if let Some(ref on_cluster) = at.on_cluster {
10182            self.write_space();
10183            self.generate_on_cluster(on_cluster)?;
10184        }
10185
10186        // Hive: PARTITION(key=value, ...) clause
10187        if let Some(ref partition) = at.partition {
10188            self.write_space();
10189            self.write_keyword("PARTITION");
10190            self.write("(");
10191            for (i, (key, value)) in partition.iter().enumerate() {
10192                if i > 0 {
10193                    self.write(", ");
10194                }
10195                self.generate_identifier(key)?;
10196                self.write(" = ");
10197                self.generate_expression(value)?;
10198            }
10199            self.write(")");
10200        }
10201
10202        // TSQL: WITH CHECK / WITH NOCHECK modifier
10203        if let Some(ref with_check) = at.with_check {
10204            self.write_space();
10205            self.write_keyword(with_check);
10206        }
10207
10208        if self.config.pretty {
10209            // In pretty mode, format actions with newlines and indentation
10210            self.write_newline();
10211            self.indent_level += 1;
10212            for (i, action) in at.actions.iter().enumerate() {
10213                // Check if this is a continuation of previous ADD COLUMN or ADD CONSTRAINT
10214                let is_continuation = i > 0
10215                    && matches!(
10216                        (&at.actions[i - 1], action),
10217                        (
10218                            AlterTableAction::AddColumn { .. },
10219                            AlterTableAction::AddColumn { .. }
10220                        ) | (
10221                            AlterTableAction::AddConstraint(_),
10222                            AlterTableAction::AddConstraint(_)
10223                        )
10224                    );
10225                if i > 0 {
10226                    self.write(",");
10227                    self.write_newline();
10228                }
10229                self.write_indent();
10230                self.generate_alter_action_with_continuation(action, is_continuation)?;
10231            }
10232            self.indent_level -= 1;
10233        } else {
10234            for (i, action) in at.actions.iter().enumerate() {
10235                // Check if this is a continuation of previous ADD COLUMN or ADD CONSTRAINT
10236                let is_continuation = i > 0
10237                    && matches!(
10238                        (&at.actions[i - 1], action),
10239                        (
10240                            AlterTableAction::AddColumn { .. },
10241                            AlterTableAction::AddColumn { .. }
10242                        ) | (
10243                            AlterTableAction::AddConstraint(_),
10244                            AlterTableAction::AddConstraint(_)
10245                        )
10246                    );
10247                if i > 0 {
10248                    self.write(",");
10249                }
10250                self.write_space();
10251                self.generate_alter_action_with_continuation(action, is_continuation)?;
10252            }
10253        }
10254
10255        // MySQL ALTER TABLE trailing options
10256        if let Some(ref algorithm) = at.algorithm {
10257            self.write(", ");
10258            self.write_keyword("ALGORITHM");
10259            self.write("=");
10260            self.write_keyword(algorithm);
10261        }
10262        if let Some(ref lock) = at.lock {
10263            self.write(", ");
10264            self.write_keyword("LOCK");
10265            self.write("=");
10266            self.write_keyword(lock);
10267        }
10268
10269        // Restore Athena Hive context
10270        self.athena_hive_context = saved_athena_hive_context;
10271
10272        Ok(())
10273    }
10274
10275    fn generate_alter_action_with_continuation(
10276        &mut self,
10277        action: &AlterTableAction,
10278        is_continuation: bool,
10279    ) -> Result<()> {
10280        match action {
10281            AlterTableAction::AddColumn {
10282                column,
10283                if_not_exists,
10284                position,
10285            } => {
10286                use crate::dialects::DialectType;
10287                // For Snowflake: consecutive ADD COLUMN actions are combined with commas
10288                // e.g., "ADD col1, col2" instead of "ADD col1, ADD col2"
10289                // For other dialects, repeat ADD COLUMN for each
10290                let is_snowflake = matches!(self.config.dialect, Some(DialectType::Snowflake));
10291                let is_tsql_like = matches!(
10292                    self.config.dialect,
10293                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
10294                );
10295                // Athena uses "ADD COLUMNS (col_def)" instead of "ADD COLUMN col_def"
10296                let is_athena = matches!(self.config.dialect, Some(DialectType::Athena));
10297
10298                if is_continuation && (is_snowflake || is_tsql_like) {
10299                    // Don't write ADD keyword for continuation in Snowflake/TSQL
10300                } else if is_snowflake {
10301                    self.write_keyword("ADD");
10302                    self.write_space();
10303                } else if is_athena {
10304                    // Athena uses ADD COLUMNS (col_def) syntax
10305                    self.write_keyword("ADD COLUMNS");
10306                    self.write(" (");
10307                } else if self.config.alter_table_include_column_keyword {
10308                    self.write_keyword("ADD COLUMN");
10309                    self.write_space();
10310                } else {
10311                    // Dialects like Oracle and TSQL don't use COLUMN keyword
10312                    self.write_keyword("ADD");
10313                    self.write_space();
10314                }
10315
10316                if *if_not_exists {
10317                    self.write_keyword("IF NOT EXISTS");
10318                    self.write_space();
10319                }
10320                self.generate_column_def(column)?;
10321
10322                // Close parenthesis for Athena
10323                if is_athena {
10324                    self.write(")");
10325                }
10326
10327                // Column position (FIRST or AFTER)
10328                if let Some(pos) = position {
10329                    self.write_space();
10330                    match pos {
10331                        ColumnPosition::First => self.write_keyword("FIRST"),
10332                        ColumnPosition::After(col_name) => {
10333                            self.write_keyword("AFTER");
10334                            self.write_space();
10335                            self.generate_identifier(col_name)?;
10336                        }
10337                    }
10338                }
10339            }
10340            AlterTableAction::DropColumn {
10341                name,
10342                if_exists,
10343                cascade,
10344            } => {
10345                self.write_keyword("DROP COLUMN");
10346                if *if_exists {
10347                    self.write_space();
10348                    self.write_keyword("IF EXISTS");
10349                }
10350                self.write_space();
10351                self.generate_identifier(name)?;
10352                if *cascade {
10353                    self.write_space();
10354                    self.write_keyword("CASCADE");
10355                }
10356            }
10357            AlterTableAction::DropColumns { names } => {
10358                self.write_keyword("DROP COLUMNS");
10359                self.write(" (");
10360                for (i, name) in names.iter().enumerate() {
10361                    if i > 0 {
10362                        self.write(", ");
10363                    }
10364                    self.generate_identifier(name)?;
10365                }
10366                self.write(")");
10367            }
10368            AlterTableAction::RenameColumn {
10369                old_name,
10370                new_name,
10371                if_exists,
10372            } => {
10373                self.write_keyword("RENAME COLUMN");
10374                if *if_exists {
10375                    self.write_space();
10376                    self.write_keyword("IF EXISTS");
10377                }
10378                self.write_space();
10379                self.generate_identifier(old_name)?;
10380                self.write_space();
10381                self.write_keyword("TO");
10382                self.write_space();
10383                self.generate_identifier(new_name)?;
10384            }
10385            AlterTableAction::AlterColumn {
10386                name,
10387                action,
10388                use_modify_keyword,
10389            } => {
10390                use crate::dialects::DialectType;
10391                // MySQL uses MODIFY COLUMN for type changes (SetDataType)
10392                // but ALTER COLUMN for SET DEFAULT, DROP DEFAULT, etc.
10393                let use_modify = *use_modify_keyword
10394                    || (matches!(self.config.dialect, Some(DialectType::MySQL))
10395                        && matches!(action, AlterColumnAction::SetDataType { .. }));
10396                if use_modify {
10397                    self.write_keyword("MODIFY COLUMN");
10398                    self.write_space();
10399                    self.generate_identifier(name)?;
10400                    // For MODIFY COLUMN, output the type directly
10401                    if let AlterColumnAction::SetDataType {
10402                        data_type,
10403                        using: _,
10404                        collate,
10405                    } = action
10406                    {
10407                        self.write_space();
10408                        self.generate_data_type(data_type)?;
10409                        // Output COLLATE clause if present
10410                        if let Some(collate_name) = collate {
10411                            self.write_space();
10412                            self.write_keyword("COLLATE");
10413                            self.write_space();
10414                            // Output as single-quoted string
10415                            self.write(&format!("'{}'", collate_name));
10416                        }
10417                    } else {
10418                        self.write_space();
10419                        self.generate_alter_column_action(action)?;
10420                    }
10421                } else if matches!(self.config.dialect, Some(DialectType::Hive))
10422                    && matches!(action, AlterColumnAction::SetDataType { .. })
10423                {
10424                    // Hive uses CHANGE COLUMN col_name col_name NEW_TYPE
10425                    self.write_keyword("CHANGE COLUMN");
10426                    self.write_space();
10427                    self.generate_identifier(name)?;
10428                    self.write_space();
10429                    self.generate_identifier(name)?;
10430                    if let AlterColumnAction::SetDataType { data_type, .. } = action {
10431                        self.write_space();
10432                        self.generate_data_type(data_type)?;
10433                    }
10434                } else {
10435                    self.write_keyword("ALTER COLUMN");
10436                    self.write_space();
10437                    self.generate_identifier(name)?;
10438                    self.write_space();
10439                    self.generate_alter_column_action(action)?;
10440                }
10441            }
10442            AlterTableAction::RenameTable(new_name) => {
10443                // MySQL-like dialects (MySQL, Doris, StarRocks) use RENAME without TO
10444                let mysql_like = matches!(
10445                    self.config.dialect,
10446                    Some(DialectType::MySQL)
10447                        | Some(DialectType::Doris)
10448                        | Some(DialectType::StarRocks)
10449                        | Some(DialectType::SingleStore)
10450                );
10451                if mysql_like {
10452                    self.write_keyword("RENAME");
10453                } else {
10454                    self.write_keyword("RENAME TO");
10455                }
10456                self.write_space();
10457                // Doris, DuckDB, BigQuery, PostgreSQL strip schema/catalog from target table
10458                let rename_table_with_db = !matches!(
10459                    self.config.dialect,
10460                    Some(DialectType::Doris)
10461                        | Some(DialectType::DuckDB)
10462                        | Some(DialectType::BigQuery)
10463                        | Some(DialectType::PostgreSQL)
10464                );
10465                if !rename_table_with_db {
10466                    let mut stripped = new_name.clone();
10467                    stripped.schema = None;
10468                    stripped.catalog = None;
10469                    self.generate_table(&stripped)?;
10470                } else {
10471                    self.generate_table(new_name)?;
10472                }
10473            }
10474            AlterTableAction::AddConstraint(constraint) => {
10475                // For consecutive ADD CONSTRAINT actions (is_continuation=true), skip ADD keyword
10476                // to produce: ADD CONSTRAINT c1 ..., CONSTRAINT c2 ...
10477                if !is_continuation {
10478                    self.write_keyword("ADD");
10479                    self.write_space();
10480                }
10481                self.generate_table_constraint(constraint)?;
10482            }
10483            AlterTableAction::DropConstraint { name, if_exists } => {
10484                self.write_keyword("DROP CONSTRAINT");
10485                if *if_exists {
10486                    self.write_space();
10487                    self.write_keyword("IF EXISTS");
10488                }
10489                self.write_space();
10490                self.generate_identifier(name)?;
10491            }
10492            AlterTableAction::DropForeignKey { name } => {
10493                self.write_keyword("DROP FOREIGN KEY");
10494                self.write_space();
10495                self.generate_identifier(name)?;
10496            }
10497            AlterTableAction::DropPartition {
10498                partitions,
10499                if_exists,
10500            } => {
10501                self.write_keyword("DROP");
10502                if *if_exists {
10503                    self.write_space();
10504                    self.write_keyword("IF EXISTS");
10505                }
10506                for (i, partition) in partitions.iter().enumerate() {
10507                    if i > 0 {
10508                        self.write(",");
10509                    }
10510                    self.write_space();
10511                    self.write_keyword("PARTITION");
10512                    // Check for special ClickHouse partition formats
10513                    if partition.len() == 1 && partition[0].0.name == "__expr__" {
10514                        // ClickHouse: PARTITION <expression>
10515                        self.write_space();
10516                        self.generate_expression(&partition[0].1)?;
10517                    } else if partition.len() == 1 && partition[0].0.name == "ALL" {
10518                        // ClickHouse: PARTITION ALL
10519                        self.write_space();
10520                        self.write_keyword("ALL");
10521                    } else if partition.len() == 1 && partition[0].0.name == "ID" {
10522                        // ClickHouse: PARTITION ID 'string'
10523                        self.write_space();
10524                        self.write_keyword("ID");
10525                        self.write_space();
10526                        self.generate_expression(&partition[0].1)?;
10527                    } else {
10528                        // Standard SQL: PARTITION(key=value, ...)
10529                        self.write("(");
10530                        for (j, (key, value)) in partition.iter().enumerate() {
10531                            if j > 0 {
10532                                self.write(", ");
10533                            }
10534                            self.generate_identifier(key)?;
10535                            self.write(" = ");
10536                            self.generate_expression(value)?;
10537                        }
10538                        self.write(")");
10539                    }
10540                }
10541            }
10542            AlterTableAction::Delete { where_clause } => {
10543                self.write_keyword("DELETE");
10544                self.write_space();
10545                self.write_keyword("WHERE");
10546                self.write_space();
10547                self.generate_expression(where_clause)?;
10548            }
10549            AlterTableAction::SwapWith(target) => {
10550                self.write_keyword("SWAP WITH");
10551                self.write_space();
10552                self.generate_table(target)?;
10553            }
10554            AlterTableAction::SetProperty { properties } => {
10555                use crate::dialects::DialectType;
10556                self.write_keyword("SET");
10557                // Trino/Presto use SET PROPERTIES syntax with spaces around =
10558                let is_trino_presto = matches!(
10559                    self.config.dialect,
10560                    Some(DialectType::Trino) | Some(DialectType::Presto)
10561                );
10562                if is_trino_presto {
10563                    self.write_space();
10564                    self.write_keyword("PROPERTIES");
10565                }
10566                let eq = if is_trino_presto { " = " } else { "=" };
10567                for (i, (key, value)) in properties.iter().enumerate() {
10568                    if i > 0 {
10569                        self.write(",");
10570                    }
10571                    self.write_space();
10572                    // Handle quoted property names for Trino
10573                    if key.contains(' ') {
10574                        self.generate_string_literal(key)?;
10575                    } else {
10576                        self.write(key);
10577                    }
10578                    self.write(eq);
10579                    self.generate_expression(value)?;
10580                }
10581            }
10582            AlterTableAction::UnsetProperty { properties } => {
10583                self.write_keyword("UNSET");
10584                for (i, name) in properties.iter().enumerate() {
10585                    if i > 0 {
10586                        self.write(",");
10587                    }
10588                    self.write_space();
10589                    self.write(name);
10590                }
10591            }
10592            AlterTableAction::ClusterBy { expressions } => {
10593                self.write_keyword("CLUSTER BY");
10594                self.write(" (");
10595                for (i, expr) in expressions.iter().enumerate() {
10596                    if i > 0 {
10597                        self.write(", ");
10598                    }
10599                    self.generate_expression(expr)?;
10600                }
10601                self.write(")");
10602            }
10603            AlterTableAction::SetTag { expressions } => {
10604                self.write_keyword("SET TAG");
10605                for (i, (key, value)) in expressions.iter().enumerate() {
10606                    if i > 0 {
10607                        self.write(",");
10608                    }
10609                    self.write_space();
10610                    self.write(key);
10611                    self.write(" = ");
10612                    self.generate_expression(value)?;
10613                }
10614            }
10615            AlterTableAction::UnsetTag { names } => {
10616                self.write_keyword("UNSET TAG");
10617                for (i, name) in names.iter().enumerate() {
10618                    if i > 0 {
10619                        self.write(",");
10620                    }
10621                    self.write_space();
10622                    self.write(name);
10623                }
10624            }
10625            AlterTableAction::SetOptions { expressions } => {
10626                self.write_keyword("SET");
10627                self.write(" (");
10628                for (i, expr) in expressions.iter().enumerate() {
10629                    if i > 0 {
10630                        self.write(", ");
10631                    }
10632                    self.generate_expression(expr)?;
10633                }
10634                self.write(")");
10635            }
10636            AlterTableAction::AlterIndex { name, visible } => {
10637                self.write_keyword("ALTER INDEX");
10638                self.write_space();
10639                self.generate_identifier(name)?;
10640                self.write_space();
10641                if *visible {
10642                    self.write_keyword("VISIBLE");
10643                } else {
10644                    self.write_keyword("INVISIBLE");
10645                }
10646            }
10647            AlterTableAction::SetAttribute { attribute } => {
10648                self.write_keyword("SET");
10649                self.write_space();
10650                self.write_keyword(attribute);
10651            }
10652            AlterTableAction::SetStageFileFormat { options } => {
10653                self.write_keyword("SET");
10654                self.write_space();
10655                self.write_keyword("STAGE_FILE_FORMAT");
10656                self.write(" = (");
10657                if let Some(opts) = options {
10658                    self.generate_space_separated_properties(opts)?;
10659                }
10660                self.write(")");
10661            }
10662            AlterTableAction::SetStageCopyOptions { options } => {
10663                self.write_keyword("SET");
10664                self.write_space();
10665                self.write_keyword("STAGE_COPY_OPTIONS");
10666                self.write(" = (");
10667                if let Some(opts) = options {
10668                    self.generate_space_separated_properties(opts)?;
10669                }
10670                self.write(")");
10671            }
10672            AlterTableAction::AddColumns { columns, cascade } => {
10673                // Oracle uses ADD (...) without COLUMNS keyword
10674                // Hive/Spark uses ADD COLUMNS (...)
10675                let is_oracle = matches!(self.config.dialect, Some(DialectType::Oracle));
10676                if is_oracle {
10677                    self.write_keyword("ADD");
10678                } else {
10679                    self.write_keyword("ADD COLUMNS");
10680                }
10681                self.write(" (");
10682                for (i, col) in columns.iter().enumerate() {
10683                    if i > 0 {
10684                        self.write(", ");
10685                    }
10686                    self.generate_column_def(col)?;
10687                }
10688                self.write(")");
10689                if *cascade {
10690                    self.write_space();
10691                    self.write_keyword("CASCADE");
10692                }
10693            }
10694            AlterTableAction::ChangeColumn {
10695                old_name,
10696                new_name,
10697                data_type,
10698                comment,
10699                cascade,
10700            } => {
10701                use crate::dialects::DialectType;
10702                let is_spark = matches!(
10703                    self.config.dialect,
10704                    Some(DialectType::Spark) | Some(DialectType::Databricks)
10705                );
10706                let is_rename = old_name.name != new_name.name;
10707
10708                if is_spark {
10709                    if is_rename {
10710                        // Spark: RENAME COLUMN old TO new
10711                        self.write_keyword("RENAME COLUMN");
10712                        self.write_space();
10713                        self.generate_identifier(old_name)?;
10714                        self.write_space();
10715                        self.write_keyword("TO");
10716                        self.write_space();
10717                        self.generate_identifier(new_name)?;
10718                    } else if comment.is_some() {
10719                        // Spark: ALTER COLUMN old COMMENT 'comment'
10720                        self.write_keyword("ALTER COLUMN");
10721                        self.write_space();
10722                        self.generate_identifier(old_name)?;
10723                        self.write_space();
10724                        self.write_keyword("COMMENT");
10725                        self.write_space();
10726                        self.write("'");
10727                        self.write(comment.as_ref().unwrap());
10728                        self.write("'");
10729                    } else if data_type.is_some() {
10730                        // Spark: ALTER COLUMN old TYPE data_type
10731                        self.write_keyword("ALTER COLUMN");
10732                        self.write_space();
10733                        self.generate_identifier(old_name)?;
10734                        self.write_space();
10735                        self.write_keyword("TYPE");
10736                        self.write_space();
10737                        self.generate_data_type(data_type.as_ref().unwrap())?;
10738                    } else {
10739                        // Fallback to CHANGE COLUMN
10740                        self.write_keyword("CHANGE COLUMN");
10741                        self.write_space();
10742                        self.generate_identifier(old_name)?;
10743                        self.write_space();
10744                        self.generate_identifier(new_name)?;
10745                    }
10746                } else {
10747                    // Hive/MySQL/default: CHANGE [COLUMN] old new [type] [COMMENT '...'] [CASCADE]
10748                    if data_type.is_some() {
10749                        self.write_keyword("CHANGE COLUMN");
10750                    } else {
10751                        self.write_keyword("CHANGE");
10752                    }
10753                    self.write_space();
10754                    self.generate_identifier(old_name)?;
10755                    self.write_space();
10756                    self.generate_identifier(new_name)?;
10757                    if let Some(ref dt) = data_type {
10758                        self.write_space();
10759                        self.generate_data_type(dt)?;
10760                    }
10761                    if let Some(ref c) = comment {
10762                        self.write_space();
10763                        self.write_keyword("COMMENT");
10764                        self.write_space();
10765                        self.write("'");
10766                        self.write(c);
10767                        self.write("'");
10768                    }
10769                    if *cascade {
10770                        self.write_space();
10771                        self.write_keyword("CASCADE");
10772                    }
10773                }
10774            }
10775            AlterTableAction::AddPartition {
10776                partition,
10777                if_not_exists,
10778                location,
10779            } => {
10780                self.write_keyword("ADD");
10781                self.write_space();
10782                if *if_not_exists {
10783                    self.write_keyword("IF NOT EXISTS");
10784                    self.write_space();
10785                }
10786                self.generate_expression(partition)?;
10787                if let Some(ref loc) = location {
10788                    self.write_space();
10789                    self.write_keyword("LOCATION");
10790                    self.write_space();
10791                    self.generate_expression(loc)?;
10792                }
10793            }
10794            AlterTableAction::AlterSortKey {
10795                this,
10796                expressions,
10797                compound,
10798            } => {
10799                // Redshift: ALTER [COMPOUND] SORTKEY AUTO|NONE|(col1, col2)
10800                self.write_keyword("ALTER");
10801                if *compound {
10802                    self.write_space();
10803                    self.write_keyword("COMPOUND");
10804                }
10805                self.write_space();
10806                self.write_keyword("SORTKEY");
10807                self.write_space();
10808                if let Some(style) = this {
10809                    self.write_keyword(style);
10810                } else if !expressions.is_empty() {
10811                    self.write("(");
10812                    for (i, expr) in expressions.iter().enumerate() {
10813                        if i > 0 {
10814                            self.write(", ");
10815                        }
10816                        self.generate_expression(expr)?;
10817                    }
10818                    self.write(")");
10819                }
10820            }
10821            AlterTableAction::AlterDistStyle { style, distkey } => {
10822                // Redshift: ALTER DISTSTYLE ALL|EVEN|AUTO|KEY [DISTKEY col]
10823                self.write_keyword("ALTER");
10824                self.write_space();
10825                self.write_keyword("DISTSTYLE");
10826                self.write_space();
10827                self.write_keyword(style);
10828                if let Some(col) = distkey {
10829                    self.write_space();
10830                    self.write_keyword("DISTKEY");
10831                    self.write_space();
10832                    self.generate_identifier(col)?;
10833                }
10834            }
10835            AlterTableAction::SetTableProperties { properties } => {
10836                // Redshift: SET TABLE PROPERTIES ('a' = '5', 'b' = 'c')
10837                self.write_keyword("SET TABLE PROPERTIES");
10838                self.write(" (");
10839                for (i, (key, value)) in properties.iter().enumerate() {
10840                    if i > 0 {
10841                        self.write(", ");
10842                    }
10843                    self.generate_expression(key)?;
10844                    self.write(" = ");
10845                    self.generate_expression(value)?;
10846                }
10847                self.write(")");
10848            }
10849            AlterTableAction::SetLocation { location } => {
10850                // Redshift: SET LOCATION 's3://bucket/folder/'
10851                self.write_keyword("SET LOCATION");
10852                self.write_space();
10853                self.write("'");
10854                self.write(location);
10855                self.write("'");
10856            }
10857            AlterTableAction::SetFileFormat { format } => {
10858                // Redshift: SET FILE FORMAT AVRO
10859                self.write_keyword("SET FILE FORMAT");
10860                self.write_space();
10861                self.write_keyword(format);
10862            }
10863            AlterTableAction::ReplacePartition { partition, source } => {
10864                // ClickHouse: REPLACE PARTITION expr FROM source
10865                self.write_keyword("REPLACE PARTITION");
10866                self.write_space();
10867                self.generate_expression(partition)?;
10868                if let Some(src) = source {
10869                    self.write_space();
10870                    self.write_keyword("FROM");
10871                    self.write_space();
10872                    self.generate_expression(src)?;
10873                }
10874            }
10875            AlterTableAction::Raw { sql } => {
10876                self.write(sql);
10877            }
10878        }
10879        Ok(())
10880    }
10881
10882    fn generate_alter_column_action(&mut self, action: &AlterColumnAction) -> Result<()> {
10883        match action {
10884            AlterColumnAction::SetDataType {
10885                data_type,
10886                using,
10887                collate,
10888            } => {
10889                use crate::dialects::DialectType;
10890                // Dialect-specific type change syntax:
10891                // - TSQL/Fabric/Hive: no prefix (ALTER COLUMN col datatype)
10892                // - Redshift/Spark: TYPE (ALTER COLUMN col TYPE datatype)
10893                // - Default: SET DATA TYPE (ALTER COLUMN col SET DATA TYPE datatype)
10894                let is_no_prefix = matches!(
10895                    self.config.dialect,
10896                    Some(DialectType::TSQL) | Some(DialectType::Fabric) | Some(DialectType::Hive)
10897                );
10898                let is_type_only = matches!(
10899                    self.config.dialect,
10900                    Some(DialectType::Redshift)
10901                        | Some(DialectType::Spark)
10902                        | Some(DialectType::Databricks)
10903                );
10904                if is_type_only {
10905                    self.write_keyword("TYPE");
10906                    self.write_space();
10907                } else if !is_no_prefix {
10908                    self.write_keyword("SET DATA TYPE");
10909                    self.write_space();
10910                }
10911                self.generate_data_type(data_type)?;
10912                if let Some(ref collation) = collate {
10913                    self.write_space();
10914                    self.write_keyword("COLLATE");
10915                    self.write_space();
10916                    self.write(collation);
10917                }
10918                if let Some(ref using_expr) = using {
10919                    self.write_space();
10920                    self.write_keyword("USING");
10921                    self.write_space();
10922                    self.generate_expression(using_expr)?;
10923                }
10924            }
10925            AlterColumnAction::SetDefault(expr) => {
10926                self.write_keyword("SET DEFAULT");
10927                self.write_space();
10928                self.generate_expression(expr)?;
10929            }
10930            AlterColumnAction::DropDefault => {
10931                self.write_keyword("DROP DEFAULT");
10932            }
10933            AlterColumnAction::SetNotNull => {
10934                self.write_keyword("SET NOT NULL");
10935            }
10936            AlterColumnAction::DropNotNull => {
10937                self.write_keyword("DROP NOT NULL");
10938            }
10939            AlterColumnAction::Comment(comment) => {
10940                self.write_keyword("COMMENT");
10941                self.write_space();
10942                self.generate_string_literal(comment)?;
10943            }
10944            AlterColumnAction::SetVisible => {
10945                self.write_keyword("SET VISIBLE");
10946            }
10947            AlterColumnAction::SetInvisible => {
10948                self.write_keyword("SET INVISIBLE");
10949            }
10950        }
10951        Ok(())
10952    }
10953
10954    fn generate_create_index(&mut self, ci: &CreateIndex) -> Result<()> {
10955        self.write_keyword("CREATE");
10956
10957        if ci.unique {
10958            self.write_space();
10959            self.write_keyword("UNIQUE");
10960        }
10961
10962        // TSQL CLUSTERED/NONCLUSTERED modifier
10963        if let Some(ref clustered) = ci.clustered {
10964            self.write_space();
10965            self.write_keyword(clustered);
10966        }
10967
10968        self.write_space();
10969        self.write_keyword("INDEX");
10970
10971        // PostgreSQL CONCURRENTLY modifier
10972        if ci.concurrently {
10973            self.write_space();
10974            self.write_keyword("CONCURRENTLY");
10975        }
10976
10977        if ci.if_not_exists {
10978            self.write_space();
10979            self.write_keyword("IF NOT EXISTS");
10980        }
10981
10982        // Index name is optional in PostgreSQL when IF NOT EXISTS is specified
10983        if !ci.name.name.is_empty() {
10984            self.write_space();
10985            self.generate_identifier(&ci.name)?;
10986        }
10987        self.write_space();
10988        self.write_keyword("ON");
10989        // Hive uses ON TABLE
10990        if matches!(self.config.dialect, Some(DialectType::Hive)) {
10991            self.write_space();
10992            self.write_keyword("TABLE");
10993        }
10994        self.write_space();
10995        self.generate_table(&ci.table)?;
10996
10997        // Column list (optional for COLUMNSTORE indexes)
10998        // Standard SQL convention: ON t(a) without space before paren
10999        if !ci.columns.is_empty() || ci.using.is_some() {
11000            let space_before_paren = false;
11001
11002            if let Some(ref using) = ci.using {
11003                self.write_space();
11004                self.write_keyword("USING");
11005                self.write_space();
11006                self.write(using);
11007                if space_before_paren {
11008                    self.write(" (");
11009                } else {
11010                    self.write("(");
11011                }
11012            } else {
11013                if space_before_paren {
11014                    self.write(" (");
11015                } else {
11016                    self.write("(");
11017                }
11018            }
11019            for (i, col) in ci.columns.iter().enumerate() {
11020                if i > 0 {
11021                    self.write(", ");
11022                }
11023                self.generate_identifier(&col.column)?;
11024                if let Some(ref opclass) = col.opclass {
11025                    self.write_space();
11026                    self.write(opclass);
11027                }
11028                if col.desc {
11029                    self.write_space();
11030                    self.write_keyword("DESC");
11031                } else if col.asc {
11032                    self.write_space();
11033                    self.write_keyword("ASC");
11034                }
11035                if let Some(nulls_first) = col.nulls_first {
11036                    self.write_space();
11037                    self.write_keyword("NULLS");
11038                    self.write_space();
11039                    self.write_keyword(if nulls_first { "FIRST" } else { "LAST" });
11040                }
11041            }
11042            self.write(")");
11043        }
11044
11045        // PostgreSQL INCLUDE (col1, col2) clause
11046        if !ci.include_columns.is_empty() {
11047            self.write_space();
11048            self.write_keyword("INCLUDE");
11049            self.write(" (");
11050            for (i, col) in ci.include_columns.iter().enumerate() {
11051                if i > 0 {
11052                    self.write(", ");
11053                }
11054                self.generate_identifier(col)?;
11055            }
11056            self.write(")");
11057        }
11058
11059        // TSQL: WITH (option=value, ...) clause
11060        if !ci.with_options.is_empty() {
11061            self.write_space();
11062            self.write_keyword("WITH");
11063            self.write(" (");
11064            for (i, (key, value)) in ci.with_options.iter().enumerate() {
11065                if i > 0 {
11066                    self.write(", ");
11067                }
11068                self.write(key);
11069                self.write("=");
11070                self.write(value);
11071            }
11072            self.write(")");
11073        }
11074
11075        // PostgreSQL WHERE clause for partial indexes
11076        if let Some(ref where_clause) = ci.where_clause {
11077            self.write_space();
11078            self.write_keyword("WHERE");
11079            self.write_space();
11080            self.generate_expression(where_clause)?;
11081        }
11082
11083        // TSQL: ON filegroup or partition scheme clause
11084        if let Some(ref on_fg) = ci.on_filegroup {
11085            self.write_space();
11086            self.write_keyword("ON");
11087            self.write_space();
11088            self.write(on_fg);
11089        }
11090
11091        Ok(())
11092    }
11093
11094    fn generate_drop_index(&mut self, di: &DropIndex) -> Result<()> {
11095        self.write_keyword("DROP INDEX");
11096
11097        if di.concurrently {
11098            self.write_space();
11099            self.write_keyword("CONCURRENTLY");
11100        }
11101
11102        if di.if_exists {
11103            self.write_space();
11104            self.write_keyword("IF EXISTS");
11105        }
11106
11107        self.write_space();
11108        self.generate_identifier(&di.name)?;
11109
11110        if let Some(ref table) = di.table {
11111            self.write_space();
11112            self.write_keyword("ON");
11113            self.write_space();
11114            self.generate_table(table)?;
11115        }
11116
11117        Ok(())
11118    }
11119
11120    fn generate_create_view(&mut self, cv: &CreateView) -> Result<()> {
11121        self.write_keyword("CREATE");
11122
11123        // MySQL: ALGORITHM=...
11124        if let Some(ref algorithm) = cv.algorithm {
11125            self.write_space();
11126            self.write_keyword("ALGORITHM");
11127            self.write("=");
11128            self.write_keyword(algorithm);
11129        }
11130
11131        // MySQL: DEFINER=...
11132        if let Some(ref definer) = cv.definer {
11133            self.write_space();
11134            self.write_keyword("DEFINER");
11135            self.write("=");
11136            self.write(definer);
11137        }
11138
11139        // MySQL: SQL SECURITY DEFINER/INVOKER (before VIEW keyword, unless it appeared after view name)
11140        if cv.security_sql_style && !cv.security_after_name {
11141            if let Some(ref security) = cv.security {
11142                self.write_space();
11143                self.write_keyword("SQL SECURITY");
11144                self.write_space();
11145                match security {
11146                    FunctionSecurity::Definer => self.write_keyword("DEFINER"),
11147                    FunctionSecurity::Invoker => self.write_keyword("INVOKER"),
11148                    FunctionSecurity::None => self.write_keyword("NONE"),
11149                }
11150            }
11151        }
11152
11153        if cv.or_alter {
11154            self.write_space();
11155            self.write_keyword("OR ALTER");
11156        } else if cv.or_replace {
11157            self.write_space();
11158            self.write_keyword("OR REPLACE");
11159        }
11160
11161        if cv.temporary {
11162            self.write_space();
11163            self.write_keyword("TEMPORARY");
11164        }
11165
11166        if cv.materialized {
11167            self.write_space();
11168            self.write_keyword("MATERIALIZED");
11169        }
11170
11171        // Snowflake: SECURE VIEW
11172        if cv.secure {
11173            self.write_space();
11174            self.write_keyword("SECURE");
11175        }
11176
11177        self.write_space();
11178        self.write_keyword("VIEW");
11179
11180        if cv.if_not_exists {
11181            self.write_space();
11182            self.write_keyword("IF NOT EXISTS");
11183        }
11184
11185        self.write_space();
11186        self.generate_table(&cv.name)?;
11187
11188        // ClickHouse: ON CLUSTER clause
11189        if let Some(ref on_cluster) = cv.on_cluster {
11190            self.write_space();
11191            self.generate_on_cluster(on_cluster)?;
11192        }
11193
11194        // ClickHouse: TO destination_table
11195        if let Some(ref to_table) = cv.to_table {
11196            self.write_space();
11197            self.write_keyword("TO");
11198            self.write_space();
11199            self.generate_table(to_table)?;
11200        }
11201
11202        // For regular VIEW: columns come before COPY GRANTS
11203        // For MATERIALIZED VIEW: COPY GRANTS comes before columns
11204        if !cv.materialized {
11205            // Regular VIEW: columns first
11206            if !cv.columns.is_empty() {
11207                self.write(" (");
11208                for (i, col) in cv.columns.iter().enumerate() {
11209                    if i > 0 {
11210                        self.write(", ");
11211                    }
11212                    self.generate_identifier(&col.name)?;
11213                    // BigQuery: OPTIONS (key=value, ...) on view column
11214                    if !col.options.is_empty() {
11215                        self.write_space();
11216                        self.generate_options_clause(&col.options)?;
11217                    }
11218                    if let Some(ref comment) = col.comment {
11219                        self.write_space();
11220                        self.write_keyword("COMMENT");
11221                        self.write_space();
11222                        self.generate_string_literal(comment)?;
11223                    }
11224                }
11225                self.write(")");
11226            }
11227
11228            // Presto/Trino/StarRocks: SECURITY DEFINER/INVOKER/NONE (after columns)
11229            // Also handles SQL SECURITY after view name (security_after_name)
11230            if !cv.security_sql_style || cv.security_after_name {
11231                if let Some(ref security) = cv.security {
11232                    self.write_space();
11233                    if cv.security_sql_style {
11234                        self.write_keyword("SQL SECURITY");
11235                    } else {
11236                        self.write_keyword("SECURITY");
11237                    }
11238                    self.write_space();
11239                    match security {
11240                        FunctionSecurity::Definer => self.write_keyword("DEFINER"),
11241                        FunctionSecurity::Invoker => self.write_keyword("INVOKER"),
11242                        FunctionSecurity::None => self.write_keyword("NONE"),
11243                    }
11244                }
11245            }
11246
11247            // Snowflake: COPY GRANTS
11248            if cv.copy_grants {
11249                self.write_space();
11250                self.write_keyword("COPY GRANTS");
11251            }
11252        } else {
11253            // MATERIALIZED VIEW: COPY GRANTS first
11254            if cv.copy_grants {
11255                self.write_space();
11256                self.write_keyword("COPY GRANTS");
11257            }
11258
11259            // Doris: If we have a schema (typed columns), generate that instead
11260            if let Some(ref schema) = cv.schema {
11261                self.write(" (");
11262                for (i, expr) in schema.expressions.iter().enumerate() {
11263                    if i > 0 {
11264                        self.write(", ");
11265                    }
11266                    self.generate_expression(expr)?;
11267                }
11268                self.write(")");
11269            } else if !cv.columns.is_empty() {
11270                // Then columns (simple column names without types)
11271                self.write(" (");
11272                for (i, col) in cv.columns.iter().enumerate() {
11273                    if i > 0 {
11274                        self.write(", ");
11275                    }
11276                    self.generate_identifier(&col.name)?;
11277                    // BigQuery: OPTIONS (key=value, ...) on view column
11278                    if !col.options.is_empty() {
11279                        self.write_space();
11280                        self.generate_options_clause(&col.options)?;
11281                    }
11282                    if let Some(ref comment) = col.comment {
11283                        self.write_space();
11284                        self.write_keyword("COMMENT");
11285                        self.write_space();
11286                        self.generate_string_literal(comment)?;
11287                    }
11288                }
11289                self.write(")");
11290            }
11291
11292            // Doris: KEY (columns) for materialized views
11293            if let Some(ref unique_key) = cv.unique_key {
11294                self.write_space();
11295                self.write_keyword("KEY");
11296                self.write(" (");
11297                for (i, expr) in unique_key.expressions.iter().enumerate() {
11298                    if i > 0 {
11299                        self.write(", ");
11300                    }
11301                    self.generate_expression(expr)?;
11302                }
11303                self.write(")");
11304            }
11305        }
11306
11307        if let Some(ref row_access_policy) = cv.row_access_policy {
11308            self.write_space();
11309            self.write_keyword("WITH");
11310            self.write_space();
11311            self.write(row_access_policy);
11312        }
11313
11314        // Snowflake: COMMENT = 'text'
11315        if let Some(ref comment) = cv.comment {
11316            self.write_space();
11317            self.write_keyword("COMMENT");
11318            self.write("=");
11319            self.generate_string_literal(comment)?;
11320        }
11321
11322        // Snowflake: TAG (name='value', ...)
11323        if !cv.tags.is_empty() {
11324            self.write_space();
11325            self.write_keyword("TAG");
11326            self.write(" (");
11327            for (i, (name, value)) in cv.tags.iter().enumerate() {
11328                if i > 0 {
11329                    self.write(", ");
11330                }
11331                self.write(name);
11332                self.write("='");
11333                self.write(value);
11334                self.write("'");
11335            }
11336            self.write(")");
11337        }
11338
11339        // BigQuery: OPTIONS (key=value, ...)
11340        if !cv.options.is_empty() {
11341            self.write_space();
11342            self.generate_options_clause(&cv.options)?;
11343        }
11344
11345        // Doris: BUILD IMMEDIATE/DEFERRED for materialized views
11346        if let Some(ref build) = cv.build {
11347            self.write_space();
11348            self.write_keyword("BUILD");
11349            self.write_space();
11350            self.write_keyword(build);
11351        }
11352
11353        // Doris: REFRESH clause for materialized views
11354        if let Some(ref refresh) = cv.refresh {
11355            self.write_space();
11356            self.generate_refresh_trigger_property(refresh)?;
11357        }
11358
11359        // Redshift: AUTO REFRESH YES|NO for materialized views
11360        if let Some(auto_refresh) = cv.auto_refresh {
11361            self.write_space();
11362            self.write_keyword("AUTO REFRESH");
11363            self.write_space();
11364            if auto_refresh {
11365                self.write_keyword("YES");
11366            } else {
11367                self.write_keyword("NO");
11368            }
11369        }
11370
11371        // ClickHouse: Table properties (ENGINE, ORDER BY, SAMPLE, SETTINGS, TTL, etc.)
11372        for prop in &cv.table_properties {
11373            self.write_space();
11374            self.generate_expression(prop)?;
11375        }
11376
11377        // ClickHouse: POPULATE / EMPTY before AS
11378        if let Some(ref population) = cv.clickhouse_population {
11379            self.write_space();
11380            self.write_keyword(population);
11381        }
11382
11383        // Only output AS clause if there's a real query (not just NULL placeholder)
11384        if !matches!(&cv.query, Expression::Null(_)) {
11385            self.write_space();
11386            self.write_keyword("AS");
11387            self.write_space();
11388
11389            // Teradata: LOCKING clause (between AS and query)
11390            if let Some(ref mode) = cv.locking_mode {
11391                self.write_keyword("LOCKING");
11392                self.write_space();
11393                self.write_keyword(mode);
11394                if let Some(ref access) = cv.locking_access {
11395                    self.write_space();
11396                    self.write_keyword("FOR");
11397                    self.write_space();
11398                    self.write_keyword(access);
11399                }
11400                self.write_space();
11401            }
11402
11403            if cv.query_parenthesized {
11404                self.write("(");
11405            }
11406            self.generate_expression(&cv.query)?;
11407            if cv.query_parenthesized {
11408                self.write(")");
11409            }
11410        }
11411
11412        // Redshift: WITH NO SCHEMA BINDING (after query)
11413        if cv.no_schema_binding {
11414            self.write_space();
11415            self.write_keyword("WITH NO SCHEMA BINDING");
11416        }
11417
11418        Ok(())
11419    }
11420
11421    fn generate_drop_view(&mut self, dv: &DropView) -> Result<()> {
11422        self.write_keyword("DROP");
11423
11424        if dv.materialized {
11425            self.write_space();
11426            self.write_keyword("MATERIALIZED");
11427        }
11428
11429        self.write_space();
11430        self.write_keyword("VIEW");
11431
11432        if dv.if_exists {
11433            self.write_space();
11434            self.write_keyword("IF EXISTS");
11435        }
11436
11437        self.write_space();
11438        self.generate_table(&dv.name)?;
11439
11440        Ok(())
11441    }
11442
11443    fn generate_truncate(&mut self, tr: &Truncate) -> Result<()> {
11444        match tr.target {
11445            TruncateTarget::Database => self.write_keyword("TRUNCATE DATABASE"),
11446            TruncateTarget::Table => self.write_keyword("TRUNCATE TABLE"),
11447        }
11448        if tr.if_exists {
11449            self.write_space();
11450            self.write_keyword("IF EXISTS");
11451        }
11452        self.write_space();
11453        self.generate_table(&tr.table)?;
11454
11455        // ClickHouse: ON CLUSTER clause
11456        if let Some(ref on_cluster) = tr.on_cluster {
11457            self.write_space();
11458            self.generate_on_cluster(on_cluster)?;
11459        }
11460
11461        // Check if first table has a * (multi-table with star)
11462        if !tr.extra_tables.is_empty() {
11463            // Check if the first entry matches the main table (star case)
11464            let skip_first = if let Some(first) = tr.extra_tables.first() {
11465                first.table.name == tr.table.name && first.star
11466            } else {
11467                false
11468            };
11469
11470            // PostgreSQL normalizes away the * suffix (it's the default behavior)
11471            let strip_star = matches!(
11472                self.config.dialect,
11473                Some(crate::dialects::DialectType::PostgreSQL)
11474                    | Some(crate::dialects::DialectType::Redshift)
11475            );
11476            if skip_first && !strip_star {
11477                self.write("*");
11478            }
11479
11480            // Generate additional tables
11481            for (i, entry) in tr.extra_tables.iter().enumerate() {
11482                if i == 0 && skip_first {
11483                    continue; // Already handled the star for first table
11484                }
11485                self.write(", ");
11486                self.generate_table(&entry.table)?;
11487                if entry.star && !strip_star {
11488                    self.write("*");
11489                }
11490            }
11491        }
11492
11493        // RESTART/CONTINUE IDENTITY
11494        if let Some(identity) = &tr.identity {
11495            self.write_space();
11496            match identity {
11497                TruncateIdentity::Restart => self.write_keyword("RESTART IDENTITY"),
11498                TruncateIdentity::Continue => self.write_keyword("CONTINUE IDENTITY"),
11499            }
11500        }
11501
11502        if tr.cascade {
11503            self.write_space();
11504            self.write_keyword("CASCADE");
11505        }
11506
11507        if tr.restrict {
11508            self.write_space();
11509            self.write_keyword("RESTRICT");
11510        }
11511
11512        // Output Hive PARTITION clause
11513        if let Some(ref partition) = tr.partition {
11514            self.write_space();
11515            self.generate_expression(partition)?;
11516        }
11517
11518        Ok(())
11519    }
11520
11521    fn generate_use(&mut self, u: &Use) -> Result<()> {
11522        // Teradata uses "DATABASE <name>" instead of "USE <name>"
11523        if matches!(self.config.dialect, Some(DialectType::Teradata)) {
11524            self.write_keyword("DATABASE");
11525            self.write_space();
11526            self.generate_identifier(&u.this)?;
11527            return Ok(());
11528        }
11529
11530        self.write_keyword("USE");
11531
11532        if let Some(kind) = &u.kind {
11533            self.write_space();
11534            match kind {
11535                UseKind::Database => self.write_keyword("DATABASE"),
11536                UseKind::Schema => self.write_keyword("SCHEMA"),
11537                UseKind::Role => self.write_keyword("ROLE"),
11538                UseKind::Warehouse => self.write_keyword("WAREHOUSE"),
11539                UseKind::Catalog => self.write_keyword("CATALOG"),
11540                UseKind::SecondaryRoles => self.write_keyword("SECONDARY ROLES"),
11541            }
11542        }
11543
11544        self.write_space();
11545        // For SECONDARY ROLES, write the value as-is (ALL, NONE, or role names)
11546        // without quoting, since these are keywords not identifiers
11547        if matches!(&u.kind, Some(UseKind::SecondaryRoles)) {
11548            self.write(&u.this.name);
11549        } else {
11550            self.generate_identifier(&u.this)?;
11551        }
11552        Ok(())
11553    }
11554
11555    fn generate_cache(&mut self, c: &Cache) -> Result<()> {
11556        self.write_keyword("CACHE");
11557        if c.lazy {
11558            self.write_space();
11559            self.write_keyword("LAZY");
11560        }
11561        self.write_space();
11562        self.write_keyword("TABLE");
11563        self.write_space();
11564        self.generate_identifier(&c.table)?;
11565
11566        // OPTIONS clause
11567        if !c.options.is_empty() {
11568            self.write_space();
11569            self.write_keyword("OPTIONS");
11570            self.write("(");
11571            for (i, (key, value)) in c.options.iter().enumerate() {
11572                if i > 0 {
11573                    self.write(", ");
11574                }
11575                self.generate_expression(key)?;
11576                self.write(" = ");
11577                self.generate_expression(value)?;
11578            }
11579            self.write(")");
11580        }
11581
11582        // AS query
11583        if let Some(query) = &c.query {
11584            self.write_space();
11585            self.write_keyword("AS");
11586            self.write_space();
11587            self.generate_expression(query)?;
11588        }
11589
11590        Ok(())
11591    }
11592
11593    fn generate_uncache(&mut self, u: &Uncache) -> Result<()> {
11594        self.write_keyword("UNCACHE TABLE");
11595        if u.if_exists {
11596            self.write_space();
11597            self.write_keyword("IF EXISTS");
11598        }
11599        self.write_space();
11600        self.generate_identifier(&u.table)?;
11601        Ok(())
11602    }
11603
11604    fn generate_load_data(&mut self, l: &LoadData) -> Result<()> {
11605        self.write_keyword("LOAD DATA");
11606        if l.local {
11607            self.write_space();
11608            self.write_keyword("LOCAL");
11609        }
11610        self.write_space();
11611        self.write_keyword("INPATH");
11612        self.write_space();
11613        self.write("'");
11614        self.write(&l.inpath);
11615        self.write("'");
11616
11617        if l.overwrite {
11618            self.write_space();
11619            self.write_keyword("OVERWRITE");
11620        }
11621
11622        self.write_space();
11623        self.write_keyword("INTO TABLE");
11624        self.write_space();
11625        self.generate_expression(&l.table)?;
11626
11627        // PARTITION clause
11628        if !l.partition.is_empty() {
11629            self.write_space();
11630            self.write_keyword("PARTITION");
11631            self.write("(");
11632            for (i, (col, val)) in l.partition.iter().enumerate() {
11633                if i > 0 {
11634                    self.write(", ");
11635                }
11636                self.generate_identifier(col)?;
11637                self.write(" = ");
11638                self.generate_expression(val)?;
11639            }
11640            self.write(")");
11641        }
11642
11643        // INPUTFORMAT clause
11644        if let Some(fmt) = &l.input_format {
11645            self.write_space();
11646            self.write_keyword("INPUTFORMAT");
11647            self.write_space();
11648            self.write("'");
11649            self.write(fmt);
11650            self.write("'");
11651        }
11652
11653        // SERDE clause
11654        if let Some(serde) = &l.serde {
11655            self.write_space();
11656            self.write_keyword("SERDE");
11657            self.write_space();
11658            self.write("'");
11659            self.write(serde);
11660            self.write("'");
11661        }
11662
11663        Ok(())
11664    }
11665
11666    fn generate_pragma(&mut self, p: &Pragma) -> Result<()> {
11667        self.write_keyword("PRAGMA");
11668        self.write_space();
11669
11670        // Schema prefix if present
11671        if let Some(schema) = &p.schema {
11672            self.generate_identifier(schema)?;
11673            self.write(".");
11674        }
11675
11676        // Pragma name
11677        self.generate_identifier(&p.name)?;
11678
11679        // Value assignment or function call
11680        if p.use_assignment_syntax {
11681            self.write(" = ");
11682            if let Some(value) = &p.value {
11683                self.generate_expression(value)?;
11684            } else if let Some(arg) = p.args.first() {
11685                self.generate_expression(arg)?;
11686            }
11687        } else if !p.args.is_empty() {
11688            self.write("(");
11689            for (i, arg) in p.args.iter().enumerate() {
11690                if i > 0 {
11691                    self.write(", ");
11692                }
11693                self.generate_expression(arg)?;
11694            }
11695            self.write(")");
11696        }
11697
11698        Ok(())
11699    }
11700
11701    fn generate_grant(&mut self, g: &Grant) -> Result<()> {
11702        self.write_keyword("GRANT");
11703        self.write_space();
11704
11705        // Privileges (with optional column lists)
11706        for (i, privilege) in g.privileges.iter().enumerate() {
11707            if i > 0 {
11708                self.write(", ");
11709            }
11710            self.write_keyword(&privilege.name);
11711            // Output column list if present: SELECT(col1, col2)
11712            if !privilege.columns.is_empty() {
11713                self.write("(");
11714                for (j, col) in privilege.columns.iter().enumerate() {
11715                    if j > 0 {
11716                        self.write(", ");
11717                    }
11718                    self.write(col);
11719                }
11720                self.write(")");
11721            }
11722        }
11723
11724        self.write_space();
11725        self.write_keyword("ON");
11726        self.write_space();
11727
11728        // Object kind (TABLE, SCHEMA, etc.)
11729        if let Some(kind) = &g.kind {
11730            self.write_keyword(kind);
11731            self.write_space();
11732        }
11733
11734        // Securable - normalize function/procedure names to uppercase for PostgreSQL family
11735        {
11736            use crate::dialects::DialectType;
11737            let should_upper = matches!(
11738                self.config.dialect,
11739                Some(DialectType::PostgreSQL)
11740                    | Some(DialectType::CockroachDB)
11741                    | Some(DialectType::Materialize)
11742                    | Some(DialectType::RisingWave)
11743            ) && (g.kind.as_deref() == Some("FUNCTION")
11744                || g.kind.as_deref() == Some("PROCEDURE"));
11745            if should_upper {
11746                use crate::expressions::Identifier;
11747                let upper_id = Identifier {
11748                    name: g.securable.name.to_ascii_uppercase(),
11749                    quoted: g.securable.quoted,
11750                    ..g.securable.clone()
11751                };
11752                self.generate_identifier(&upper_id)?;
11753            } else {
11754                self.generate_identifier(&g.securable)?;
11755            }
11756        }
11757
11758        // Function parameter types (if present)
11759        if !g.function_params.is_empty() {
11760            self.write("(");
11761            for (i, param) in g.function_params.iter().enumerate() {
11762                if i > 0 {
11763                    self.write(", ");
11764                }
11765                self.write(param);
11766            }
11767            self.write(")");
11768        }
11769
11770        self.write_space();
11771        self.write_keyword("TO");
11772        self.write_space();
11773
11774        // Principals
11775        for (i, principal) in g.principals.iter().enumerate() {
11776            if i > 0 {
11777                self.write(", ");
11778            }
11779            if principal.is_role {
11780                self.write_keyword("ROLE");
11781                self.write_space();
11782            } else if principal.is_group {
11783                self.write_keyword("GROUP");
11784                self.write_space();
11785            } else if principal.is_share {
11786                self.write_keyword("SHARE");
11787                self.write_space();
11788            }
11789            self.generate_identifier(&principal.name)?;
11790        }
11791
11792        // WITH GRANT OPTION
11793        if g.grant_option {
11794            self.write_space();
11795            self.write_keyword("WITH GRANT OPTION");
11796        }
11797
11798        // TSQL: AS principal
11799        if let Some(ref principal) = g.as_principal {
11800            self.write_space();
11801            self.write_keyword("AS");
11802            self.write_space();
11803            self.generate_identifier(principal)?;
11804        }
11805
11806        Ok(())
11807    }
11808
11809    fn generate_revoke(&mut self, r: &Revoke) -> Result<()> {
11810        self.write_keyword("REVOKE");
11811        self.write_space();
11812
11813        // GRANT OPTION FOR
11814        if r.grant_option {
11815            self.write_keyword("GRANT OPTION FOR");
11816            self.write_space();
11817        }
11818
11819        // Privileges (with optional column lists)
11820        for (i, privilege) in r.privileges.iter().enumerate() {
11821            if i > 0 {
11822                self.write(", ");
11823            }
11824            self.write_keyword(&privilege.name);
11825            // Output column list if present: SELECT(col1, col2)
11826            if !privilege.columns.is_empty() {
11827                self.write("(");
11828                for (j, col) in privilege.columns.iter().enumerate() {
11829                    if j > 0 {
11830                        self.write(", ");
11831                    }
11832                    self.write(col);
11833                }
11834                self.write(")");
11835            }
11836        }
11837
11838        self.write_space();
11839        self.write_keyword("ON");
11840        self.write_space();
11841
11842        // Object kind
11843        if let Some(kind) = &r.kind {
11844            self.write_keyword(kind);
11845            self.write_space();
11846        }
11847
11848        // Securable - normalize function/procedure names to uppercase for PostgreSQL family
11849        {
11850            use crate::dialects::DialectType;
11851            let should_upper = matches!(
11852                self.config.dialect,
11853                Some(DialectType::PostgreSQL)
11854                    | Some(DialectType::CockroachDB)
11855                    | Some(DialectType::Materialize)
11856                    | Some(DialectType::RisingWave)
11857            ) && (r.kind.as_deref() == Some("FUNCTION")
11858                || r.kind.as_deref() == Some("PROCEDURE"));
11859            if should_upper {
11860                use crate::expressions::Identifier;
11861                let upper_id = Identifier {
11862                    name: r.securable.name.to_ascii_uppercase(),
11863                    quoted: r.securable.quoted,
11864                    ..r.securable.clone()
11865                };
11866                self.generate_identifier(&upper_id)?;
11867            } else {
11868                self.generate_identifier(&r.securable)?;
11869            }
11870        }
11871
11872        // Function parameter types (if present)
11873        if !r.function_params.is_empty() {
11874            self.write("(");
11875            for (i, param) in r.function_params.iter().enumerate() {
11876                if i > 0 {
11877                    self.write(", ");
11878                }
11879                self.write(param);
11880            }
11881            self.write(")");
11882        }
11883
11884        self.write_space();
11885        self.write_keyword("FROM");
11886        self.write_space();
11887
11888        // Principals
11889        for (i, principal) in r.principals.iter().enumerate() {
11890            if i > 0 {
11891                self.write(", ");
11892            }
11893            if principal.is_role {
11894                self.write_keyword("ROLE");
11895                self.write_space();
11896            } else if principal.is_group {
11897                self.write_keyword("GROUP");
11898                self.write_space();
11899            } else if principal.is_share {
11900                self.write_keyword("SHARE");
11901                self.write_space();
11902            }
11903            self.generate_identifier(&principal.name)?;
11904        }
11905
11906        // CASCADE or RESTRICT
11907        if r.cascade {
11908            self.write_space();
11909            self.write_keyword("CASCADE");
11910        } else if r.restrict {
11911            self.write_space();
11912            self.write_keyword("RESTRICT");
11913        }
11914
11915        Ok(())
11916    }
11917
11918    fn generate_comment(&mut self, c: &Comment) -> Result<()> {
11919        self.write_keyword("COMMENT");
11920
11921        // IF EXISTS
11922        if c.exists {
11923            self.write_space();
11924            self.write_keyword("IF EXISTS");
11925        }
11926
11927        self.write_space();
11928        self.write_keyword("ON");
11929
11930        // MATERIALIZED
11931        if c.materialized {
11932            self.write_space();
11933            self.write_keyword("MATERIALIZED");
11934        }
11935
11936        self.write_space();
11937        self.write_keyword(&c.kind);
11938        self.write_space();
11939
11940        // Object name
11941        self.generate_expression(&c.this)?;
11942
11943        self.write_space();
11944        self.write_keyword("IS");
11945        self.write_space();
11946
11947        // Comment expression
11948        self.generate_expression(&c.expression)?;
11949
11950        Ok(())
11951    }
11952
11953    fn generate_set_statement(&mut self, s: &SetStatement) -> Result<()> {
11954        self.write_keyword("SET");
11955
11956        for (i, item) in s.items.iter().enumerate() {
11957            if i > 0 {
11958                self.write(",");
11959            }
11960            self.write_space();
11961
11962            // Kind modifier (GLOBAL, LOCAL, SESSION, PERSIST, PERSIST_ONLY, VARIABLE)
11963            let has_variable_kind = item.kind.as_deref() == Some("VARIABLE");
11964            if let Some(ref kind) = item.kind {
11965                // For VARIABLE kind, only output the keyword for dialects that require it
11966                // (Spark, Databricks, DuckDB) - matching Python sqlglot's
11967                // SET_ASSIGNMENT_REQUIRES_VARIABLE_KEYWORD flag
11968                if has_variable_kind {
11969                    if matches!(
11970                        self.config.dialect,
11971                        Some(DialectType::Spark | DialectType::Databricks | DialectType::DuckDB)
11972                    ) {
11973                        self.write_keyword("VARIABLE");
11974                        self.write_space();
11975                    }
11976                } else {
11977                    self.write_keyword(kind);
11978                    self.write_space();
11979                }
11980            }
11981
11982            // Check for special SET forms by name
11983            let name_str = match &item.name {
11984                Expression::Identifier(id) => Some(id.name.as_str()),
11985                _ => None,
11986            };
11987
11988            let is_transaction = name_str == Some("TRANSACTION");
11989            let is_character_set = name_str == Some("CHARACTER SET");
11990            let is_names = name_str == Some("NAMES");
11991            let is_collate = name_str == Some("COLLATE");
11992            let is_value_only =
11993                matches!(&item.value, Expression::Identifier(id) if id.name.is_empty());
11994
11995            if is_transaction {
11996                // Output: SET [GLOBAL|SESSION] TRANSACTION <characteristics>
11997                self.write_keyword("TRANSACTION");
11998                if let Expression::Identifier(id) = &item.value {
11999                    if !id.name.is_empty() {
12000                        self.write_space();
12001                        self.write(&id.name);
12002                    }
12003                }
12004            } else if is_character_set {
12005                // Output: SET CHARACTER SET <charset>
12006                self.write_keyword("CHARACTER SET");
12007                self.write_space();
12008                self.generate_set_value(&item.value)?;
12009            } else if is_names {
12010                // Output: SET NAMES <charset>
12011                self.write_keyword("NAMES");
12012                self.write_space();
12013                self.generate_set_value(&item.value)?;
12014            } else if is_collate {
12015                // Output: COLLATE <collation> (part of SET NAMES ... COLLATE ...)
12016                self.write_keyword("COLLATE");
12017                self.write_space();
12018                self.generate_set_value(&item.value)?;
12019            } else if has_variable_kind {
12020                // Output: SET [VARIABLE] <name> = <value>
12021                // VARIABLE keyword already written above if dialect requires it
12022                if let Some(ns) = name_str {
12023                    self.write(ns);
12024                } else {
12025                    self.generate_expression(&item.name)?;
12026                }
12027                self.write(" = ");
12028                self.generate_set_value(&item.value)?;
12029            } else if is_value_only {
12030                // SET <name> ON/OFF without = (TSQL: SET XACT_ABORT ON)
12031                self.generate_expression(&item.name)?;
12032            } else if item.no_equals && matches!(self.config.dialect, Some(DialectType::TSQL)) {
12033                // SET key value without = (TSQL style)
12034                self.generate_expression(&item.name)?;
12035                self.write_space();
12036                self.generate_set_value(&item.value)?;
12037            } else {
12038                // Standard: variable = value
12039                // SET item names should not be quoted (they are config parameter names, not column refs)
12040                match &item.name {
12041                    Expression::Identifier(id) => {
12042                        self.write(&id.name);
12043                    }
12044                    _ => {
12045                        self.generate_expression(&item.name)?;
12046                    }
12047                }
12048                self.write(" = ");
12049                self.generate_set_value(&item.value)?;
12050            }
12051        }
12052
12053        Ok(())
12054    }
12055
12056    /// Generate a SET statement value, writing keyword values (DEFAULT, ON, OFF)
12057    /// directly to avoid reserved keyword quoting.
12058    fn generate_set_value(&mut self, value: &Expression) -> Result<()> {
12059        if let Expression::Identifier(id) = value {
12060            match id.name.as_str() {
12061                "DEFAULT" | "ON" | "OFF" => {
12062                    self.write_keyword(&id.name);
12063                    return Ok(());
12064                }
12065                _ => {}
12066            }
12067        }
12068        self.generate_expression(value)
12069    }
12070
12071    // ==================== Phase 4: Additional DDL Generation ====================
12072
12073    fn generate_alter_view(&mut self, av: &AlterView) -> Result<()> {
12074        self.write_keyword("ALTER");
12075        // MySQL modifiers before VIEW
12076        if let Some(ref algorithm) = av.algorithm {
12077            self.write_space();
12078            self.write_keyword("ALGORITHM");
12079            self.write(" = ");
12080            self.write_keyword(algorithm);
12081        }
12082        if let Some(ref definer) = av.definer {
12083            self.write_space();
12084            self.write_keyword("DEFINER");
12085            self.write(" = ");
12086            self.write(definer);
12087        }
12088        if let Some(ref sql_security) = av.sql_security {
12089            self.write_space();
12090            self.write_keyword("SQL SECURITY");
12091            self.write(" = ");
12092            self.write_keyword(sql_security);
12093        }
12094        self.write_space();
12095        self.write_keyword("VIEW");
12096        self.write_space();
12097        self.generate_table(&av.name)?;
12098
12099        // Hive: Column aliases with optional COMMENT
12100        if !av.columns.is_empty() {
12101            self.write(" (");
12102            for (i, col) in av.columns.iter().enumerate() {
12103                if i > 0 {
12104                    self.write(", ");
12105                }
12106                self.generate_identifier(&col.name)?;
12107                if let Some(ref comment) = col.comment {
12108                    self.write_space();
12109                    self.write_keyword("COMMENT");
12110                    self.write(" ");
12111                    self.generate_string_literal(comment)?;
12112                }
12113            }
12114            self.write(")");
12115        }
12116
12117        // TSQL: WITH option before actions
12118        if let Some(ref opt) = av.with_option {
12119            self.write_space();
12120            self.write_keyword("WITH");
12121            self.write_space();
12122            self.write_keyword(opt);
12123        }
12124
12125        for action in &av.actions {
12126            self.write_space();
12127            match action {
12128                AlterViewAction::Rename(new_name) => {
12129                    self.write_keyword("RENAME TO");
12130                    self.write_space();
12131                    self.generate_table(new_name)?;
12132                }
12133                AlterViewAction::OwnerTo(owner) => {
12134                    self.write_keyword("OWNER TO");
12135                    self.write_space();
12136                    self.generate_identifier(owner)?;
12137                }
12138                AlterViewAction::SetSchema(schema) => {
12139                    self.write_keyword("SET SCHEMA");
12140                    self.write_space();
12141                    self.generate_identifier(schema)?;
12142                }
12143                AlterViewAction::SetAuthorization(auth) => {
12144                    self.write_keyword("SET AUTHORIZATION");
12145                    self.write_space();
12146                    self.write(auth);
12147                }
12148                AlterViewAction::AlterColumn { name, action } => {
12149                    self.write_keyword("ALTER COLUMN");
12150                    self.write_space();
12151                    self.generate_identifier(name)?;
12152                    self.write_space();
12153                    self.generate_alter_column_action(action)?;
12154                }
12155                AlterViewAction::AsSelect(query) => {
12156                    self.write_keyword("AS");
12157                    self.write_space();
12158                    self.generate_expression(query)?;
12159                }
12160                AlterViewAction::SetTblproperties(props) => {
12161                    self.write_keyword("SET TBLPROPERTIES");
12162                    self.write(" (");
12163                    for (i, (key, value)) in props.iter().enumerate() {
12164                        if i > 0 {
12165                            self.write(", ");
12166                        }
12167                        self.generate_string_literal(key)?;
12168                        self.write("=");
12169                        self.generate_string_literal(value)?;
12170                    }
12171                    self.write(")");
12172                }
12173                AlterViewAction::UnsetTblproperties(keys) => {
12174                    self.write_keyword("UNSET TBLPROPERTIES");
12175                    self.write(" (");
12176                    for (i, key) in keys.iter().enumerate() {
12177                        if i > 0 {
12178                            self.write(", ");
12179                        }
12180                        self.generate_string_literal(key)?;
12181                    }
12182                    self.write(")");
12183                }
12184            }
12185        }
12186
12187        Ok(())
12188    }
12189
12190    fn generate_alter_index(&mut self, ai: &AlterIndex) -> Result<()> {
12191        self.write_keyword("ALTER INDEX");
12192        self.write_space();
12193        self.generate_identifier(&ai.name)?;
12194
12195        if let Some(table) = &ai.table {
12196            self.write_space();
12197            self.write_keyword("ON");
12198            self.write_space();
12199            self.generate_table(table)?;
12200        }
12201
12202        for action in &ai.actions {
12203            self.write_space();
12204            match action {
12205                AlterIndexAction::Rename(new_name) => {
12206                    self.write_keyword("RENAME TO");
12207                    self.write_space();
12208                    self.generate_identifier(new_name)?;
12209                }
12210                AlterIndexAction::SetTablespace(tablespace) => {
12211                    self.write_keyword("SET TABLESPACE");
12212                    self.write_space();
12213                    self.generate_identifier(tablespace)?;
12214                }
12215                AlterIndexAction::Visible(visible) => {
12216                    if *visible {
12217                        self.write_keyword("VISIBLE");
12218                    } else {
12219                        self.write_keyword("INVISIBLE");
12220                    }
12221                }
12222            }
12223        }
12224
12225        Ok(())
12226    }
12227
12228    fn generate_create_schema(&mut self, cs: &CreateSchema) -> Result<()> {
12229        // Output leading comments
12230        for comment in &cs.leading_comments {
12231            self.write_formatted_comment(comment);
12232            self.write_space();
12233        }
12234
12235        // Athena: CREATE SCHEMA uses Hive engine (backticks)
12236        let saved_athena_hive_context = self.athena_hive_context;
12237        if matches!(
12238            self.config.dialect,
12239            Some(crate::dialects::DialectType::Athena)
12240        ) {
12241            self.athena_hive_context = true;
12242        }
12243
12244        self.write_keyword("CREATE SCHEMA");
12245
12246        if cs.if_not_exists {
12247            self.write_space();
12248            self.write_keyword("IF NOT EXISTS");
12249        }
12250
12251        self.write_space();
12252        for (i, part) in cs.name.iter().enumerate() {
12253            if i > 0 {
12254                self.write(".");
12255            }
12256            self.generate_identifier(part)?;
12257        }
12258
12259        if let Some(ref clone_parts) = cs.clone_from {
12260            self.write_keyword(" CLONE ");
12261            for (i, part) in clone_parts.iter().enumerate() {
12262                if i > 0 {
12263                    self.write(".");
12264                }
12265                self.generate_identifier(part)?;
12266            }
12267        }
12268
12269        if let Some(ref at_clause) = cs.at_clause {
12270            self.write_space();
12271            self.generate_expression(at_clause)?;
12272        }
12273
12274        if let Some(auth) = &cs.authorization {
12275            self.write_space();
12276            self.write_keyword("AUTHORIZATION");
12277            self.write_space();
12278            self.generate_identifier(auth)?;
12279        }
12280
12281        // Generate schema properties (e.g., DEFAULT COLLATE or WITH (props))
12282        // Separate WITH properties from other properties
12283        let with_properties: Vec<_> = cs
12284            .properties
12285            .iter()
12286            .filter(|p| matches!(p, Expression::Property(_)))
12287            .collect();
12288        let other_properties: Vec<_> = cs
12289            .properties
12290            .iter()
12291            .filter(|p| !matches!(p, Expression::Property(_)))
12292            .collect();
12293
12294        // Generate WITH (props) if we have Property expressions
12295        if !with_properties.is_empty() {
12296            self.write_space();
12297            self.write_keyword("WITH");
12298            self.write(" (");
12299            for (i, prop) in with_properties.iter().enumerate() {
12300                if i > 0 {
12301                    self.write(", ");
12302                }
12303                self.generate_expression(prop)?;
12304            }
12305            self.write(")");
12306        }
12307
12308        // Generate other properties (like DEFAULT COLLATE)
12309        for prop in other_properties {
12310            self.write_space();
12311            self.generate_expression(prop)?;
12312        }
12313
12314        // Restore Athena Hive context
12315        self.athena_hive_context = saved_athena_hive_context;
12316
12317        Ok(())
12318    }
12319
12320    fn generate_drop_schema(&mut self, ds: &DropSchema) -> Result<()> {
12321        self.write_keyword("DROP SCHEMA");
12322
12323        if ds.if_exists {
12324            self.write_space();
12325            self.write_keyword("IF EXISTS");
12326        }
12327
12328        self.write_space();
12329        self.generate_identifier(&ds.name)?;
12330
12331        if ds.cascade {
12332            self.write_space();
12333            self.write_keyword("CASCADE");
12334        }
12335
12336        Ok(())
12337    }
12338
12339    fn generate_drop_namespace(&mut self, dn: &DropNamespace) -> Result<()> {
12340        self.write_keyword("DROP NAMESPACE");
12341
12342        if dn.if_exists {
12343            self.write_space();
12344            self.write_keyword("IF EXISTS");
12345        }
12346
12347        self.write_space();
12348        self.generate_identifier(&dn.name)?;
12349
12350        if dn.cascade {
12351            self.write_space();
12352            self.write_keyword("CASCADE");
12353        }
12354
12355        Ok(())
12356    }
12357
12358    fn generate_create_database(&mut self, cd: &CreateDatabase) -> Result<()> {
12359        self.write_keyword("CREATE DATABASE");
12360
12361        if cd.if_not_exists {
12362            self.write_space();
12363            self.write_keyword("IF NOT EXISTS");
12364        }
12365
12366        self.write_space();
12367        self.generate_identifier(&cd.name)?;
12368
12369        if let Some(ref clone_src) = cd.clone_from {
12370            self.write_keyword(" CLONE ");
12371            self.generate_identifier(clone_src)?;
12372        }
12373
12374        // AT/BEFORE clause for time travel (Snowflake)
12375        if let Some(ref at_clause) = cd.at_clause {
12376            self.write_space();
12377            self.generate_expression(at_clause)?;
12378        }
12379
12380        for option in &cd.options {
12381            self.write_space();
12382            match option {
12383                DatabaseOption::CharacterSet(charset) => {
12384                    self.write_keyword("CHARACTER SET");
12385                    self.write(" = ");
12386                    self.write(&format!("'{}'", charset));
12387                }
12388                DatabaseOption::Collate(collate) => {
12389                    self.write_keyword("COLLATE");
12390                    self.write(" = ");
12391                    self.write(&format!("'{}'", collate));
12392                }
12393                DatabaseOption::Owner(owner) => {
12394                    self.write_keyword("OWNER");
12395                    self.write(" = ");
12396                    self.generate_identifier(owner)?;
12397                }
12398                DatabaseOption::Template(template) => {
12399                    self.write_keyword("TEMPLATE");
12400                    self.write(" = ");
12401                    self.generate_identifier(template)?;
12402                }
12403                DatabaseOption::Encoding(encoding) => {
12404                    self.write_keyword("ENCODING");
12405                    self.write(" = ");
12406                    self.write(&format!("'{}'", encoding));
12407                }
12408                DatabaseOption::Location(location) => {
12409                    self.write_keyword("LOCATION");
12410                    self.write(" = ");
12411                    self.write(&format!("'{}'", location));
12412                }
12413            }
12414        }
12415
12416        Ok(())
12417    }
12418
12419    fn generate_drop_database(&mut self, dd: &DropDatabase) -> Result<()> {
12420        self.write_keyword("DROP DATABASE");
12421
12422        if dd.if_exists {
12423            self.write_space();
12424            self.write_keyword("IF EXISTS");
12425        }
12426
12427        self.write_space();
12428        self.generate_identifier(&dd.name)?;
12429
12430        if dd.sync {
12431            self.write_space();
12432            self.write_keyword("SYNC");
12433        }
12434
12435        Ok(())
12436    }
12437
12438    fn generate_create_function(&mut self, cf: &CreateFunction) -> Result<()> {
12439        self.write_keyword("CREATE");
12440
12441        if cf.or_alter {
12442            self.write_space();
12443            self.write_keyword("OR ALTER");
12444        } else if cf.or_replace {
12445            self.write_space();
12446            self.write_keyword("OR REPLACE");
12447        }
12448
12449        if cf.temporary {
12450            self.write_space();
12451            self.write_keyword("TEMPORARY");
12452        }
12453
12454        self.write_space();
12455        if cf.is_table_function {
12456            self.write_keyword("TABLE FUNCTION");
12457        } else {
12458            self.write_keyword("FUNCTION");
12459        }
12460
12461        if cf.if_not_exists {
12462            self.write_space();
12463            self.write_keyword("IF NOT EXISTS");
12464        }
12465
12466        self.write_space();
12467        self.generate_table(&cf.name)?;
12468        if cf.has_parens {
12469            let func_multiline = self.config.pretty
12470                && matches!(
12471                    self.config.dialect,
12472                    Some(crate::dialects::DialectType::TSQL)
12473                        | Some(crate::dialects::DialectType::Fabric)
12474                )
12475                && !cf.parameters.is_empty();
12476            if func_multiline {
12477                self.write("(\n");
12478                self.indent_level += 2;
12479                self.write_indent();
12480                self.generate_function_parameters(&cf.parameters)?;
12481                self.write("\n");
12482                self.indent_level -= 2;
12483                self.write(")");
12484            } else {
12485                self.write("(");
12486                self.generate_function_parameters(&cf.parameters)?;
12487                self.write(")");
12488            }
12489        }
12490
12491        // Output RETURNS clause (always comes first after parameters)
12492        // BigQuery and TSQL use multiline formatting for CREATE FUNCTION structure
12493        let use_multiline = self.config.pretty
12494            && matches!(
12495                self.config.dialect,
12496                Some(crate::dialects::DialectType::BigQuery)
12497                    | Some(crate::dialects::DialectType::TSQL)
12498                    | Some(crate::dialects::DialectType::Fabric)
12499            );
12500
12501        if cf.language_first {
12502            // LANGUAGE first, then SQL data access, then RETURNS
12503            if let Some(lang) = &cf.language {
12504                if use_multiline {
12505                    self.write_newline();
12506                } else {
12507                    self.write_space();
12508                }
12509                self.write_keyword("LANGUAGE");
12510                self.write_space();
12511                self.write(lang);
12512            }
12513
12514            // SQL data access comes after LANGUAGE in this case
12515            if let Some(sql_data) = &cf.sql_data_access {
12516                self.write_space();
12517                match sql_data {
12518                    SqlDataAccess::NoSql => self.write_keyword("NO SQL"),
12519                    SqlDataAccess::ContainsSql => self.write_keyword("CONTAINS SQL"),
12520                    SqlDataAccess::ReadsSqlData => self.write_keyword("READS SQL DATA"),
12521                    SqlDataAccess::ModifiesSqlData => self.write_keyword("MODIFIES SQL DATA"),
12522                }
12523            }
12524
12525            if let Some(ref rtb) = cf.returns_table_body {
12526                if use_multiline {
12527                    self.write_newline();
12528                } else {
12529                    self.write_space();
12530                }
12531                self.write_keyword("RETURNS");
12532                self.write_space();
12533                self.write(rtb);
12534            } else if let Some(return_type) = &cf.return_type {
12535                if use_multiline {
12536                    self.write_newline();
12537                } else {
12538                    self.write_space();
12539                }
12540                self.write_keyword("RETURNS");
12541                self.write_space();
12542                self.generate_data_type(return_type)?;
12543            }
12544        } else {
12545            // RETURNS first (default)
12546            // DuckDB macros: skip RETURNS output (empty marker in returns_table_body means TABLE return)
12547            let is_duckdb = matches!(
12548                self.config.dialect,
12549                Some(crate::dialects::DialectType::DuckDB)
12550            );
12551            if let Some(ref rtb) = cf.returns_table_body {
12552                if !(is_duckdb && rtb.is_empty()) {
12553                    if use_multiline {
12554                        self.write_newline();
12555                    } else {
12556                        self.write_space();
12557                    }
12558                    self.write_keyword("RETURNS");
12559                    self.write_space();
12560                    self.write(rtb);
12561                }
12562            } else if let Some(return_type) = &cf.return_type {
12563                // DuckDB: skip all RETURNS (DuckDB macros don't use RETURNS clause)
12564                if !is_duckdb {
12565                    let is_table_return = matches!(return_type, crate::expressions::DataType::Custom { ref name } if name.eq_ignore_ascii_case("TABLE"));
12566                    if use_multiline {
12567                        self.write_newline();
12568                    } else {
12569                        self.write_space();
12570                    }
12571                    self.write_keyword("RETURNS");
12572                    self.write_space();
12573                    if is_table_return {
12574                        self.write_keyword("TABLE");
12575                    } else {
12576                        self.generate_data_type(return_type)?;
12577                    }
12578                }
12579            }
12580        }
12581
12582        // If we have property_order, use it to output properties in original order
12583        if !cf.property_order.is_empty() {
12584            // For BigQuery, OPTIONS must come before AS - reorder if needed
12585            let is_bigquery = matches!(
12586                self.config.dialect,
12587                Some(crate::dialects::DialectType::BigQuery)
12588            );
12589            let property_order = if is_bigquery {
12590                // Move Options before As if both are present
12591                let mut reordered = Vec::new();
12592                let mut has_as = false;
12593                let mut has_options = false;
12594                for prop in &cf.property_order {
12595                    match prop {
12596                        FunctionPropertyKind::As => has_as = true,
12597                        FunctionPropertyKind::Options => has_options = true,
12598                        _ => {}
12599                    }
12600                }
12601                if has_as && has_options {
12602                    // Output all props except As and Options, then Options, then As
12603                    for prop in &cf.property_order {
12604                        if *prop != FunctionPropertyKind::As
12605                            && *prop != FunctionPropertyKind::Options
12606                        {
12607                            reordered.push(*prop);
12608                        }
12609                    }
12610                    reordered.push(FunctionPropertyKind::Options);
12611                    reordered.push(FunctionPropertyKind::As);
12612                    reordered
12613                } else {
12614                    cf.property_order.clone()
12615                }
12616            } else {
12617                cf.property_order.clone()
12618            };
12619
12620            for prop in &property_order {
12621                match prop {
12622                    FunctionPropertyKind::Set => {
12623                        self.generate_function_set_options(cf)?;
12624                    }
12625                    FunctionPropertyKind::As => {
12626                        self.generate_function_body(cf)?;
12627                    }
12628                    FunctionPropertyKind::Using => {
12629                        self.generate_function_using_resources(cf)?;
12630                    }
12631                    FunctionPropertyKind::Language => {
12632                        if !cf.language_first {
12633                            // Only output here if not already output above
12634                            if let Some(lang) = &cf.language {
12635                                // Only BigQuery uses multiline formatting
12636                                let use_multiline = self.config.pretty
12637                                    && matches!(
12638                                        self.config.dialect,
12639                                        Some(crate::dialects::DialectType::BigQuery)
12640                                    );
12641                                if use_multiline {
12642                                    self.write_newline();
12643                                } else {
12644                                    self.write_space();
12645                                }
12646                                self.write_keyword("LANGUAGE");
12647                                self.write_space();
12648                                self.write(lang);
12649                            }
12650                        }
12651                    }
12652                    FunctionPropertyKind::Determinism => {
12653                        self.generate_function_determinism(cf)?;
12654                    }
12655                    FunctionPropertyKind::NullInput => {
12656                        self.generate_function_null_input(cf)?;
12657                    }
12658                    FunctionPropertyKind::Security => {
12659                        self.generate_function_security(cf)?;
12660                    }
12661                    FunctionPropertyKind::SqlDataAccess => {
12662                        if !cf.language_first {
12663                            // Only output here if not already output above
12664                            self.generate_function_sql_data_access(cf)?;
12665                        }
12666                    }
12667                    FunctionPropertyKind::Options => {
12668                        if !cf.options.is_empty() {
12669                            self.write_space();
12670                            self.generate_options_clause(&cf.options)?;
12671                        }
12672                    }
12673                    FunctionPropertyKind::Environment => {
12674                        if !cf.environment.is_empty() {
12675                            self.write_space();
12676                            self.generate_environment_clause(&cf.environment)?;
12677                        }
12678                    }
12679                    FunctionPropertyKind::Handler => {
12680                        if let Some(ref h) = cf.handler {
12681                            self.write_space();
12682                            self.write_keyword("HANDLER");
12683                            if cf.handler_uses_eq {
12684                                self.write(" = ");
12685                            } else {
12686                                self.write_space();
12687                            }
12688                            self.write("'");
12689                            self.write(h);
12690                            self.write("'");
12691                        }
12692                    }
12693                    FunctionPropertyKind::RuntimeVersion => {
12694                        if let Some(ref runtime_version) = cf.runtime_version {
12695                            self.write_space();
12696                            self.write_keyword("RUNTIME_VERSION");
12697                            self.write("='");
12698                            self.write(runtime_version);
12699                            self.write("'");
12700                        }
12701                    }
12702                    FunctionPropertyKind::Packages => {
12703                        if let Some(ref packages) = cf.packages {
12704                            self.write_space();
12705                            self.write_keyword("PACKAGES");
12706                            self.write("=(");
12707                            for (i, package) in packages.iter().enumerate() {
12708                                if i > 0 {
12709                                    self.write(", ");
12710                                }
12711                                self.write("'");
12712                                self.write(package);
12713                                self.write("'");
12714                            }
12715                            self.write(")");
12716                        }
12717                    }
12718                    FunctionPropertyKind::ParameterStyle => {
12719                        if let Some(ref ps) = cf.parameter_style {
12720                            self.write_space();
12721                            self.write_keyword("PARAMETER STYLE");
12722                            self.write_space();
12723                            self.write_keyword(ps);
12724                        }
12725                    }
12726                }
12727            }
12728
12729            // Output OPTIONS if not tracked in property_order (legacy)
12730            if !cf.options.is_empty() && !cf.property_order.contains(&FunctionPropertyKind::Options)
12731            {
12732                self.write_space();
12733                self.generate_options_clause(&cf.options)?;
12734            }
12735
12736            // Output ENVIRONMENT if not tracked in property_order (legacy)
12737            if !cf.environment.is_empty()
12738                && !cf
12739                    .property_order
12740                    .contains(&FunctionPropertyKind::Environment)
12741            {
12742                self.write_space();
12743                self.generate_environment_clause(&cf.environment)?;
12744            }
12745        } else {
12746            // Legacy behavior when property_order is empty
12747            // BigQuery: DETERMINISTIC/NOT DETERMINISTIC comes before LANGUAGE
12748            if matches!(
12749                self.config.dialect,
12750                Some(crate::dialects::DialectType::BigQuery)
12751            ) {
12752                self.generate_function_determinism(cf)?;
12753            }
12754
12755            // Only BigQuery uses multiline formatting for CREATE FUNCTION structure
12756            let use_multiline = self.config.pretty
12757                && matches!(
12758                    self.config.dialect,
12759                    Some(crate::dialects::DialectType::BigQuery)
12760                );
12761
12762            if !cf.language_first {
12763                if let Some(lang) = &cf.language {
12764                    if use_multiline {
12765                        self.write_newline();
12766                    } else {
12767                        self.write_space();
12768                    }
12769                    self.write_keyword("LANGUAGE");
12770                    self.write_space();
12771                    self.write(lang);
12772                }
12773
12774                // SQL data access characteristic comes after LANGUAGE
12775                self.generate_function_sql_data_access(cf)?;
12776            }
12777
12778            // For non-BigQuery dialects, output DETERMINISTIC/IMMUTABLE/VOLATILE here
12779            if !matches!(
12780                self.config.dialect,
12781                Some(crate::dialects::DialectType::BigQuery)
12782            ) {
12783                self.generate_function_determinism(cf)?;
12784            }
12785
12786            self.generate_function_null_input(cf)?;
12787            self.generate_function_security(cf)?;
12788            self.generate_function_set_options(cf)?;
12789
12790            // BigQuery: OPTIONS (key=value, ...) - comes before AS
12791            if !cf.options.is_empty() {
12792                self.write_space();
12793                self.generate_options_clause(&cf.options)?;
12794            }
12795
12796            // Databricks: ENVIRONMENT (dependencies = '...', ...) - comes before AS
12797            if !cf.environment.is_empty() {
12798                self.write_space();
12799                self.generate_environment_clause(&cf.environment)?;
12800            }
12801
12802            if let Some(ref h) = cf.handler {
12803                self.write_space();
12804                self.write_keyword("HANDLER");
12805                if cf.handler_uses_eq {
12806                    self.write(" = ");
12807                } else {
12808                    self.write_space();
12809                }
12810                self.write("'");
12811                self.write(h);
12812                self.write("'");
12813            }
12814
12815            if let Some(ref runtime_version) = cf.runtime_version {
12816                self.write_space();
12817                self.write_keyword("RUNTIME_VERSION");
12818                self.write("='");
12819                self.write(runtime_version);
12820                self.write("'");
12821            }
12822
12823            if let Some(ref packages) = cf.packages {
12824                self.write_space();
12825                self.write_keyword("PACKAGES");
12826                self.write("=(");
12827                for (i, package) in packages.iter().enumerate() {
12828                    if i > 0 {
12829                        self.write(", ");
12830                    }
12831                    self.write("'");
12832                    self.write(package);
12833                    self.write("'");
12834                }
12835                self.write(")");
12836            }
12837
12838            self.generate_function_body(cf)?;
12839            self.generate_function_using_resources(cf)?;
12840        }
12841
12842        Ok(())
12843    }
12844
12845    /// Generate SET options for CREATE FUNCTION
12846    fn generate_function_set_options(&mut self, cf: &CreateFunction) -> Result<()> {
12847        for opt in &cf.set_options {
12848            self.write_space();
12849            self.write_keyword("SET");
12850            self.write_space();
12851            self.write(&opt.name);
12852            match &opt.value {
12853                FunctionSetValue::Value { value, use_to } => {
12854                    if *use_to {
12855                        self.write(" TO ");
12856                    } else {
12857                        self.write(" = ");
12858                    }
12859                    self.write(value);
12860                }
12861                FunctionSetValue::FromCurrent => {
12862                    self.write_space();
12863                    self.write_keyword("FROM CURRENT");
12864                }
12865            }
12866        }
12867        Ok(())
12868    }
12869
12870    fn generate_function_using_resources(&mut self, cf: &CreateFunction) -> Result<()> {
12871        if cf.using_resources.is_empty() {
12872            return Ok(());
12873        }
12874
12875        self.write_space();
12876        self.write_keyword("USING");
12877        for resource in &cf.using_resources {
12878            self.write_space();
12879            self.write_keyword(&resource.kind);
12880            self.write_space();
12881            self.generate_string_literal(&resource.uri)?;
12882        }
12883        Ok(())
12884    }
12885
12886    /// Generate function body (AS clause)
12887    fn generate_function_body(&mut self, cf: &CreateFunction) -> Result<()> {
12888        if let Some(body) = &cf.body {
12889            // AS stays on same line as previous content (e.g., LANGUAGE js AS)
12890            self.write_space();
12891            // Only BigQuery uses multiline formatting for CREATE FUNCTION body
12892            let use_multiline = self.config.pretty
12893                && matches!(
12894                    self.config.dialect,
12895                    Some(crate::dialects::DialectType::BigQuery)
12896                );
12897            match body {
12898                FunctionBody::Block(block) => {
12899                    self.write_keyword("AS");
12900                    if matches!(
12901                        self.config.dialect,
12902                        Some(crate::dialects::DialectType::TSQL)
12903                    ) {
12904                        self.write(" BEGIN ");
12905                        self.write(block);
12906                        self.write(" END");
12907                    } else if matches!(
12908                        self.config.dialect,
12909                        Some(crate::dialects::DialectType::PostgreSQL)
12910                    ) {
12911                        self.write(" $$");
12912                        self.write(block);
12913                        self.write("$$");
12914                    } else {
12915                        // Escape content for single-quoted output
12916                        let escaped = self.escape_block_for_single_quote(block);
12917                        // In BigQuery pretty mode, body content goes on new line
12918                        if use_multiline {
12919                            self.write_newline();
12920                        } else {
12921                            self.write(" ");
12922                        }
12923                        self.write("'");
12924                        self.write(&escaped);
12925                        self.write("'");
12926                    }
12927                }
12928                FunctionBody::StringLiteral(s) => {
12929                    self.write_keyword("AS");
12930                    // In BigQuery pretty mode, body content goes on new line
12931                    if use_multiline {
12932                        self.write_newline();
12933                    } else {
12934                        self.write(" ");
12935                    }
12936                    self.write("'");
12937                    self.write(s);
12938                    self.write("'");
12939                }
12940                FunctionBody::Expression(expr) => {
12941                    self.write_keyword("AS");
12942                    self.write_space();
12943                    self.generate_expression(expr)?;
12944                }
12945                FunctionBody::External(name) => {
12946                    self.write_keyword("EXTERNAL NAME");
12947                    self.write(" '");
12948                    self.write(name);
12949                    self.write("'");
12950                }
12951                FunctionBody::Return(expr) => {
12952                    if matches!(
12953                        self.config.dialect,
12954                        Some(crate::dialects::DialectType::DuckDB)
12955                    ) {
12956                        // DuckDB macro syntax: AS [TABLE] expression (no RETURN keyword)
12957                        self.write_keyword("AS");
12958                        self.write_space();
12959                        // Check both returns_table_body marker and return_type = Custom "TABLE"
12960                        let is_table_return = cf.returns_table_body.is_some()
12961                            || matches!(&cf.return_type, Some(crate::expressions::DataType::Custom { ref name }) if name.eq_ignore_ascii_case("TABLE"));
12962                        if is_table_return {
12963                            self.write_keyword("TABLE");
12964                            self.write_space();
12965                        }
12966                        self.generate_expression(expr)?;
12967                    } else {
12968                        if self.config.create_function_return_as {
12969                            self.write_keyword("AS");
12970                            // TSQL pretty: newline between AS and RETURN
12971                            if self.config.pretty
12972                                && matches!(
12973                                    self.config.dialect,
12974                                    Some(crate::dialects::DialectType::TSQL)
12975                                        | Some(crate::dialects::DialectType::Fabric)
12976                                )
12977                            {
12978                                self.write_newline();
12979                            } else {
12980                                self.write_space();
12981                            }
12982                        }
12983                        self.write_keyword("RETURN");
12984                        self.write_space();
12985                        self.generate_expression(expr)?;
12986                    }
12987                }
12988                FunctionBody::Statements(stmts) => {
12989                    self.write_keyword("AS");
12990                    self.write(" BEGIN ");
12991                    for (i, stmt) in stmts.iter().enumerate() {
12992                        if i > 0 {
12993                            self.write(" ");
12994                        }
12995                        self.generate_expression(stmt)?;
12996                        self.write(";");
12997                    }
12998                    self.write(" END");
12999                }
13000                FunctionBody::RawBlock(text) => {
13001                    self.write_newline();
13002                    self.write(text);
13003                }
13004                FunctionBody::DollarQuoted { content, tag } => {
13005                    self.write_keyword("AS");
13006                    self.write(" ");
13007                    // Dialects that support dollar-quoted strings: PostgreSQL, Databricks, Redshift, DuckDB
13008                    let supports_dollar_quoting = matches!(
13009                        self.config.dialect,
13010                        Some(crate::dialects::DialectType::PostgreSQL)
13011                            | Some(crate::dialects::DialectType::Databricks)
13012                            | Some(crate::dialects::DialectType::Redshift)
13013                            | Some(crate::dialects::DialectType::DuckDB)
13014                    );
13015                    if supports_dollar_quoting {
13016                        // Output in dollar-quoted format
13017                        self.write("$");
13018                        if let Some(t) = tag {
13019                            self.write(t);
13020                        }
13021                        self.write("$");
13022                        self.write(content);
13023                        self.write("$");
13024                        if let Some(t) = tag {
13025                            self.write(t);
13026                        }
13027                        self.write("$");
13028                    } else {
13029                        // Convert to single-quoted string for other dialects
13030                        let escaped = self.escape_block_for_single_quote(content);
13031                        self.write("'");
13032                        self.write(&escaped);
13033                        self.write("'");
13034                    }
13035                }
13036            }
13037        }
13038        Ok(())
13039    }
13040
13041    /// Generate determinism clause (IMMUTABLE/VOLATILE/DETERMINISTIC)
13042    fn generate_function_determinism(&mut self, cf: &CreateFunction) -> Result<()> {
13043        if let Some(det) = cf.deterministic {
13044            self.write_space();
13045            if matches!(
13046                self.config.dialect,
13047                Some(crate::dialects::DialectType::BigQuery)
13048            ) {
13049                // BigQuery uses DETERMINISTIC/NOT DETERMINISTIC
13050                if det {
13051                    self.write_keyword("DETERMINISTIC");
13052                } else {
13053                    self.write_keyword("NOT DETERMINISTIC");
13054                }
13055            } else {
13056                // PostgreSQL and others use IMMUTABLE/VOLATILE
13057                if det {
13058                    self.write_keyword("IMMUTABLE");
13059                } else {
13060                    self.write_keyword("VOLATILE");
13061                }
13062            }
13063        }
13064        Ok(())
13065    }
13066
13067    /// Generate null input handling clause
13068    fn generate_function_null_input(&mut self, cf: &CreateFunction) -> Result<()> {
13069        if let Some(returns_null) = cf.returns_null_on_null_input {
13070            self.write_space();
13071            if returns_null {
13072                if cf.strict {
13073                    self.write_keyword("STRICT");
13074                } else {
13075                    self.write_keyword("RETURNS NULL ON NULL INPUT");
13076                }
13077            } else {
13078                self.write_keyword("CALLED ON NULL INPUT");
13079            }
13080        }
13081        Ok(())
13082    }
13083
13084    /// Generate security clause
13085    fn generate_function_security(&mut self, cf: &CreateFunction) -> Result<()> {
13086        if let Some(security) = &cf.security {
13087            self.write_space();
13088            // MySQL uses SQL SECURITY prefix
13089            if matches!(
13090                self.config.dialect,
13091                Some(crate::dialects::DialectType::MySQL)
13092            ) {
13093                self.write_keyword("SQL SECURITY");
13094            } else {
13095                self.write_keyword("SECURITY");
13096            }
13097            self.write_space();
13098            match security {
13099                FunctionSecurity::Definer => self.write_keyword("DEFINER"),
13100                FunctionSecurity::Invoker => self.write_keyword("INVOKER"),
13101                FunctionSecurity::None => self.write_keyword("NONE"),
13102            }
13103        }
13104        Ok(())
13105    }
13106
13107    /// Generate SQL data access clause
13108    fn generate_function_sql_data_access(&mut self, cf: &CreateFunction) -> Result<()> {
13109        if let Some(sql_data) = &cf.sql_data_access {
13110            self.write_space();
13111            match sql_data {
13112                SqlDataAccess::NoSql => self.write_keyword("NO SQL"),
13113                SqlDataAccess::ContainsSql => self.write_keyword("CONTAINS SQL"),
13114                SqlDataAccess::ReadsSqlData => self.write_keyword("READS SQL DATA"),
13115                SqlDataAccess::ModifiesSqlData => self.write_keyword("MODIFIES SQL DATA"),
13116            }
13117        }
13118        Ok(())
13119    }
13120
13121    fn generate_function_parameters(&mut self, params: &[FunctionParameter]) -> Result<()> {
13122        for (i, param) in params.iter().enumerate() {
13123            if i > 0 {
13124                self.write(", ");
13125            }
13126
13127            if let Some(mode) = &param.mode {
13128                if let Some(text) = &param.mode_text {
13129                    self.write(text);
13130                } else {
13131                    match mode {
13132                        ParameterMode::In => self.write_keyword("IN"),
13133                        ParameterMode::Out => self.write_keyword("OUT"),
13134                        ParameterMode::InOut => self.write_keyword("INOUT"),
13135                        ParameterMode::Variadic => self.write_keyword("VARIADIC"),
13136                    }
13137                }
13138                self.write_space();
13139            }
13140
13141            if let Some(name) = &param.name {
13142                self.generate_identifier(name)?;
13143                // Skip space and type for empty Custom types (e.g., DuckDB macros)
13144                let skip_type =
13145                    matches!(&param.data_type, DataType::Custom { name } if name.is_empty());
13146                if !skip_type {
13147                    self.write_space();
13148                    self.generate_data_type(&param.data_type)?;
13149                }
13150            } else {
13151                self.generate_data_type(&param.data_type)?;
13152            }
13153
13154            if let Some(default) = &param.default {
13155                if self.config.parameter_default_equals {
13156                    self.write(" = ");
13157                } else {
13158                    self.write(" DEFAULT ");
13159                }
13160                self.generate_expression(default)?;
13161            }
13162        }
13163
13164        Ok(())
13165    }
13166
13167    fn generate_drop_function(&mut self, df: &DropFunction) -> Result<()> {
13168        self.write_keyword("DROP FUNCTION");
13169
13170        if df.if_exists {
13171            self.write_space();
13172            self.write_keyword("IF EXISTS");
13173        }
13174
13175        self.write_space();
13176        self.generate_table(&df.name)?;
13177
13178        if let Some(params) = &df.parameters {
13179            self.write(" (");
13180            for (i, dt) in params.iter().enumerate() {
13181                if i > 0 {
13182                    self.write(", ");
13183                }
13184                self.generate_data_type(dt)?;
13185            }
13186            self.write(")");
13187        }
13188
13189        if df.cascade {
13190            self.write_space();
13191            self.write_keyword("CASCADE");
13192        }
13193
13194        Ok(())
13195    }
13196
13197    fn generate_create_procedure(&mut self, cp: &CreateProcedure) -> Result<()> {
13198        self.write_keyword("CREATE");
13199
13200        if cp.or_alter {
13201            self.write_space();
13202            self.write_keyword("OR ALTER");
13203        } else if cp.or_replace {
13204            self.write_space();
13205            self.write_keyword("OR REPLACE");
13206        }
13207
13208        self.write_space();
13209        if cp.use_proc_keyword {
13210            self.write_keyword("PROC");
13211        } else {
13212            self.write_keyword("PROCEDURE");
13213        }
13214
13215        if cp.if_not_exists {
13216            self.write_space();
13217            self.write_keyword("IF NOT EXISTS");
13218        }
13219
13220        self.write_space();
13221        self.generate_table(&cp.name)?;
13222        if cp.has_parens {
13223            self.write("(");
13224            self.generate_function_parameters(&cp.parameters)?;
13225            self.write(")");
13226        } else if !cp.parameters.is_empty() {
13227            // TSQL: unparenthesized parameters
13228            self.write_space();
13229            self.generate_function_parameters(&cp.parameters)?;
13230        }
13231
13232        // RETURNS clause (Snowflake)
13233        if let Some(return_type) = &cp.return_type {
13234            self.write_space();
13235            self.write_keyword("RETURNS");
13236            self.write_space();
13237            self.generate_data_type(return_type)?;
13238        }
13239
13240        // EXECUTE AS clause (Snowflake)
13241        if let Some(execute_as) = &cp.execute_as {
13242            self.write_space();
13243            self.write_keyword("EXECUTE AS");
13244            self.write_space();
13245            self.write_keyword(execute_as);
13246        }
13247
13248        if let Some(lang) = &cp.language {
13249            self.write_space();
13250            self.write_keyword("LANGUAGE");
13251            self.write_space();
13252            self.write(lang);
13253        }
13254
13255        if let Some(security) = &cp.security {
13256            self.write_space();
13257            self.write_keyword("SECURITY");
13258            self.write_space();
13259            match security {
13260                FunctionSecurity::Definer => self.write_keyword("DEFINER"),
13261                FunctionSecurity::Invoker => self.write_keyword("INVOKER"),
13262                FunctionSecurity::None => self.write_keyword("NONE"),
13263            }
13264        }
13265
13266        // TSQL WITH options (ENCRYPTION, RECOMPILE, etc.)
13267        if !cp.with_options.is_empty() {
13268            self.write_space();
13269            self.write_keyword("WITH");
13270            self.write_space();
13271            for (i, opt) in cp.with_options.iter().enumerate() {
13272                if i > 0 {
13273                    self.write(", ");
13274                }
13275                self.write(opt);
13276            }
13277        }
13278
13279        if let Some(body) = &cp.body {
13280            self.write_space();
13281            match body {
13282                FunctionBody::Block(block) => {
13283                    self.write_keyword("AS");
13284                    if matches!(
13285                        self.config.dialect,
13286                        Some(crate::dialects::DialectType::TSQL)
13287                    ) {
13288                        self.write(" BEGIN ");
13289                        self.write(block);
13290                        self.write(" END");
13291                    } else if matches!(
13292                        self.config.dialect,
13293                        Some(crate::dialects::DialectType::PostgreSQL)
13294                    ) {
13295                        self.write(" $$");
13296                        self.write(block);
13297                        self.write("$$");
13298                    } else {
13299                        // Escape content for single-quoted output
13300                        let escaped = self.escape_block_for_single_quote(block);
13301                        self.write(" '");
13302                        self.write(&escaped);
13303                        self.write("'");
13304                    }
13305                }
13306                FunctionBody::StringLiteral(s) => {
13307                    self.write_keyword("AS");
13308                    self.write(" '");
13309                    self.write(s);
13310                    self.write("'");
13311                }
13312                FunctionBody::Expression(expr) => {
13313                    self.write_keyword("AS");
13314                    self.write_space();
13315                    self.generate_expression(expr)?;
13316                }
13317                FunctionBody::External(name) => {
13318                    self.write_keyword("EXTERNAL NAME");
13319                    self.write(" '");
13320                    self.write(name);
13321                    self.write("'");
13322                }
13323                FunctionBody::Return(expr) => {
13324                    self.write_keyword("RETURN");
13325                    self.write_space();
13326                    self.generate_expression(expr)?;
13327                }
13328                FunctionBody::Statements(stmts) => {
13329                    self.write_keyword("AS");
13330                    self.write(" BEGIN ");
13331                    for (i, stmt) in stmts.iter().enumerate() {
13332                        if i > 0 {
13333                            self.write(" ");
13334                        }
13335                        self.generate_expression(stmt)?;
13336                        self.write(";");
13337                    }
13338                    self.write(" END");
13339                }
13340                FunctionBody::RawBlock(text) => {
13341                    self.write_newline();
13342                    self.write(text);
13343                }
13344                FunctionBody::DollarQuoted { content, tag } => {
13345                    self.write_keyword("AS");
13346                    self.write(" ");
13347                    // Dialects that support dollar-quoted strings: PostgreSQL, Databricks, Redshift, DuckDB
13348                    let supports_dollar_quoting = matches!(
13349                        self.config.dialect,
13350                        Some(crate::dialects::DialectType::PostgreSQL)
13351                            | Some(crate::dialects::DialectType::Databricks)
13352                            | Some(crate::dialects::DialectType::Redshift)
13353                            | Some(crate::dialects::DialectType::DuckDB)
13354                    );
13355                    if supports_dollar_quoting {
13356                        // Output in dollar-quoted format
13357                        self.write("$");
13358                        if let Some(t) = tag {
13359                            self.write(t);
13360                        }
13361                        self.write("$");
13362                        self.write(content);
13363                        self.write("$");
13364                        if let Some(t) = tag {
13365                            self.write(t);
13366                        }
13367                        self.write("$");
13368                    } else {
13369                        // Convert to single-quoted string for other dialects
13370                        let escaped = self.escape_block_for_single_quote(content);
13371                        self.write("'");
13372                        self.write(&escaped);
13373                        self.write("'");
13374                    }
13375                }
13376            }
13377        }
13378
13379        Ok(())
13380    }
13381
13382    fn generate_drop_procedure(&mut self, dp: &DropProcedure) -> Result<()> {
13383        self.write_keyword("DROP PROCEDURE");
13384
13385        if dp.if_exists {
13386            self.write_space();
13387            self.write_keyword("IF EXISTS");
13388        }
13389
13390        self.write_space();
13391        self.generate_table(&dp.name)?;
13392
13393        if let Some(params) = &dp.parameters {
13394            self.write(" (");
13395            for (i, dt) in params.iter().enumerate() {
13396                if i > 0 {
13397                    self.write(", ");
13398                }
13399                self.generate_data_type(dt)?;
13400            }
13401            self.write(")");
13402        }
13403
13404        if dp.cascade {
13405            self.write_space();
13406            self.write_keyword("CASCADE");
13407        }
13408
13409        Ok(())
13410    }
13411
13412    fn generate_create_sequence(&mut self, cs: &CreateSequence) -> Result<()> {
13413        self.write_keyword("CREATE");
13414
13415        if cs.or_replace {
13416            self.write_space();
13417            self.write_keyword("OR REPLACE");
13418        }
13419
13420        if cs.temporary {
13421            self.write_space();
13422            self.write_keyword("TEMPORARY");
13423        }
13424
13425        self.write_space();
13426        self.write_keyword("SEQUENCE");
13427
13428        if cs.if_not_exists {
13429            self.write_space();
13430            self.write_keyword("IF NOT EXISTS");
13431        }
13432
13433        self.write_space();
13434        self.generate_table(&cs.name)?;
13435
13436        // Output AS <type> if present
13437        if let Some(as_type) = &cs.as_type {
13438            self.write_space();
13439            self.write_keyword("AS");
13440            self.write_space();
13441            self.generate_data_type(as_type)?;
13442        }
13443
13444        // Output COMMENT first (Snowflake convention: COMMENT comes before other properties)
13445        if let Some(comment) = &cs.comment {
13446            self.write_space();
13447            self.write_keyword("COMMENT");
13448            self.write("=");
13449            self.generate_string_literal(comment)?;
13450        }
13451
13452        // If property_order is available, use it to preserve original order
13453        if !cs.property_order.is_empty() {
13454            for prop in &cs.property_order {
13455                match prop {
13456                    SeqPropKind::Start => {
13457                        if let Some(start) = cs.start {
13458                            self.write_space();
13459                            self.write_keyword("START WITH");
13460                            self.write(&format!(" {}", start));
13461                        }
13462                    }
13463                    SeqPropKind::Increment => {
13464                        if let Some(inc) = cs.increment {
13465                            self.write_space();
13466                            self.write_keyword("INCREMENT BY");
13467                            self.write(&format!(" {}", inc));
13468                        }
13469                    }
13470                    SeqPropKind::Minvalue => {
13471                        if let Some(min) = &cs.minvalue {
13472                            self.write_space();
13473                            match min {
13474                                SequenceBound::Value(v) => {
13475                                    self.write_keyword("MINVALUE");
13476                                    self.write(&format!(" {}", v));
13477                                }
13478                                SequenceBound::None => {
13479                                    self.write_keyword("NO MINVALUE");
13480                                }
13481                            }
13482                        }
13483                    }
13484                    SeqPropKind::Maxvalue => {
13485                        if let Some(max) = &cs.maxvalue {
13486                            self.write_space();
13487                            match max {
13488                                SequenceBound::Value(v) => {
13489                                    self.write_keyword("MAXVALUE");
13490                                    self.write(&format!(" {}", v));
13491                                }
13492                                SequenceBound::None => {
13493                                    self.write_keyword("NO MAXVALUE");
13494                                }
13495                            }
13496                        }
13497                    }
13498                    SeqPropKind::Cache => {
13499                        if let Some(cache) = cs.cache {
13500                            self.write_space();
13501                            self.write_keyword("CACHE");
13502                            self.write(&format!(" {}", cache));
13503                        }
13504                    }
13505                    SeqPropKind::NoCache => {
13506                        self.write_space();
13507                        self.write_keyword("NO CACHE");
13508                    }
13509                    SeqPropKind::NoCacheWord => {
13510                        self.write_space();
13511                        self.write_keyword("NOCACHE");
13512                    }
13513                    SeqPropKind::Cycle => {
13514                        self.write_space();
13515                        self.write_keyword("CYCLE");
13516                    }
13517                    SeqPropKind::NoCycle => {
13518                        self.write_space();
13519                        self.write_keyword("NO CYCLE");
13520                    }
13521                    SeqPropKind::NoCycleWord => {
13522                        self.write_space();
13523                        self.write_keyword("NOCYCLE");
13524                    }
13525                    SeqPropKind::OwnedBy => {
13526                        // Skip OWNED BY NONE (it's a no-op)
13527                        if !cs.owned_by_none {
13528                            if let Some(owned) = &cs.owned_by {
13529                                self.write_space();
13530                                self.write_keyword("OWNED BY");
13531                                self.write_space();
13532                                self.generate_table(owned)?;
13533                            }
13534                        }
13535                    }
13536                    SeqPropKind::Order => {
13537                        self.write_space();
13538                        self.write_keyword("ORDER");
13539                    }
13540                    SeqPropKind::NoOrder => {
13541                        self.write_space();
13542                        self.write_keyword("NOORDER");
13543                    }
13544                    SeqPropKind::Comment => {
13545                        // COMMENT is output above, before property_order iteration
13546                    }
13547                    SeqPropKind::Sharing => {
13548                        if let Some(val) = &cs.sharing {
13549                            self.write_space();
13550                            self.write(&format!("SHARING={}", val));
13551                        }
13552                    }
13553                    SeqPropKind::Keep => {
13554                        self.write_space();
13555                        self.write_keyword("KEEP");
13556                    }
13557                    SeqPropKind::NoKeep => {
13558                        self.write_space();
13559                        self.write_keyword("NOKEEP");
13560                    }
13561                    SeqPropKind::Scale => {
13562                        self.write_space();
13563                        self.write_keyword("SCALE");
13564                        if let Some(modifier) = &cs.scale_modifier {
13565                            if !modifier.is_empty() {
13566                                self.write_space();
13567                                self.write_keyword(modifier);
13568                            }
13569                        }
13570                    }
13571                    SeqPropKind::NoScale => {
13572                        self.write_space();
13573                        self.write_keyword("NOSCALE");
13574                    }
13575                    SeqPropKind::Shard => {
13576                        self.write_space();
13577                        self.write_keyword("SHARD");
13578                        if let Some(modifier) = &cs.shard_modifier {
13579                            if !modifier.is_empty() {
13580                                self.write_space();
13581                                self.write_keyword(modifier);
13582                            }
13583                        }
13584                    }
13585                    SeqPropKind::NoShard => {
13586                        self.write_space();
13587                        self.write_keyword("NOSHARD");
13588                    }
13589                    SeqPropKind::Session => {
13590                        self.write_space();
13591                        self.write_keyword("SESSION");
13592                    }
13593                    SeqPropKind::Global => {
13594                        self.write_space();
13595                        self.write_keyword("GLOBAL");
13596                    }
13597                    SeqPropKind::NoMinvalueWord => {
13598                        self.write_space();
13599                        self.write_keyword("NOMINVALUE");
13600                    }
13601                    SeqPropKind::NoMaxvalueWord => {
13602                        self.write_space();
13603                        self.write_keyword("NOMAXVALUE");
13604                    }
13605                }
13606            }
13607        } else {
13608            // Fallback: default order for backwards compatibility
13609            if let Some(inc) = cs.increment {
13610                self.write_space();
13611                self.write_keyword("INCREMENT BY");
13612                self.write(&format!(" {}", inc));
13613            }
13614
13615            if let Some(min) = &cs.minvalue {
13616                self.write_space();
13617                match min {
13618                    SequenceBound::Value(v) => {
13619                        self.write_keyword("MINVALUE");
13620                        self.write(&format!(" {}", v));
13621                    }
13622                    SequenceBound::None => {
13623                        self.write_keyword("NO MINVALUE");
13624                    }
13625                }
13626            }
13627
13628            if let Some(max) = &cs.maxvalue {
13629                self.write_space();
13630                match max {
13631                    SequenceBound::Value(v) => {
13632                        self.write_keyword("MAXVALUE");
13633                        self.write(&format!(" {}", v));
13634                    }
13635                    SequenceBound::None => {
13636                        self.write_keyword("NO MAXVALUE");
13637                    }
13638                }
13639            }
13640
13641            if let Some(start) = cs.start {
13642                self.write_space();
13643                self.write_keyword("START WITH");
13644                self.write(&format!(" {}", start));
13645            }
13646
13647            if let Some(cache) = cs.cache {
13648                self.write_space();
13649                self.write_keyword("CACHE");
13650                self.write(&format!(" {}", cache));
13651            }
13652
13653            if cs.cycle {
13654                self.write_space();
13655                self.write_keyword("CYCLE");
13656            }
13657
13658            if let Some(owned) = &cs.owned_by {
13659                self.write_space();
13660                self.write_keyword("OWNED BY");
13661                self.write_space();
13662                self.generate_table(owned)?;
13663            }
13664        }
13665
13666        Ok(())
13667    }
13668
13669    fn generate_drop_sequence(&mut self, ds: &DropSequence) -> Result<()> {
13670        self.write_keyword("DROP SEQUENCE");
13671
13672        if ds.if_exists {
13673            self.write_space();
13674            self.write_keyword("IF EXISTS");
13675        }
13676
13677        self.write_space();
13678        self.generate_table(&ds.name)?;
13679
13680        if ds.cascade {
13681            self.write_space();
13682            self.write_keyword("CASCADE");
13683        }
13684
13685        Ok(())
13686    }
13687
13688    fn generate_alter_sequence(&mut self, als: &AlterSequence) -> Result<()> {
13689        self.write_keyword("ALTER SEQUENCE");
13690
13691        if als.if_exists {
13692            self.write_space();
13693            self.write_keyword("IF EXISTS");
13694        }
13695
13696        self.write_space();
13697        self.generate_table(&als.name)?;
13698
13699        if let Some(inc) = als.increment {
13700            self.write_space();
13701            self.write_keyword("INCREMENT BY");
13702            self.write(&format!(" {}", inc));
13703        }
13704
13705        if let Some(min) = &als.minvalue {
13706            self.write_space();
13707            match min {
13708                SequenceBound::Value(v) => {
13709                    self.write_keyword("MINVALUE");
13710                    self.write(&format!(" {}", v));
13711                }
13712                SequenceBound::None => {
13713                    self.write_keyword("NO MINVALUE");
13714                }
13715            }
13716        }
13717
13718        if let Some(max) = &als.maxvalue {
13719            self.write_space();
13720            match max {
13721                SequenceBound::Value(v) => {
13722                    self.write_keyword("MAXVALUE");
13723                    self.write(&format!(" {}", v));
13724                }
13725                SequenceBound::None => {
13726                    self.write_keyword("NO MAXVALUE");
13727                }
13728            }
13729        }
13730
13731        if let Some(start) = als.start {
13732            self.write_space();
13733            self.write_keyword("START WITH");
13734            self.write(&format!(" {}", start));
13735        }
13736
13737        if let Some(restart) = &als.restart {
13738            self.write_space();
13739            self.write_keyword("RESTART");
13740            if let Some(val) = restart {
13741                self.write_keyword(" WITH");
13742                self.write(&format!(" {}", val));
13743            }
13744        }
13745
13746        if let Some(cache) = als.cache {
13747            self.write_space();
13748            self.write_keyword("CACHE");
13749            self.write(&format!(" {}", cache));
13750        }
13751
13752        if let Some(cycle) = als.cycle {
13753            self.write_space();
13754            if cycle {
13755                self.write_keyword("CYCLE");
13756            } else {
13757                self.write_keyword("NO CYCLE");
13758            }
13759        }
13760
13761        if let Some(owned) = &als.owned_by {
13762            self.write_space();
13763            self.write_keyword("OWNED BY");
13764            self.write_space();
13765            if let Some(table) = owned {
13766                self.generate_table(table)?;
13767            } else {
13768                self.write_keyword("NONE");
13769            }
13770        }
13771
13772        Ok(())
13773    }
13774
13775    fn generate_create_trigger(&mut self, ct: &CreateTrigger) -> Result<()> {
13776        self.write_keyword("CREATE");
13777
13778        if ct.or_alter {
13779            self.write_space();
13780            self.write_keyword("OR ALTER");
13781        } else if ct.or_replace {
13782            self.write_space();
13783            self.write_keyword("OR REPLACE");
13784        }
13785
13786        if ct.constraint {
13787            self.write_space();
13788            self.write_keyword("CONSTRAINT");
13789        }
13790
13791        self.write_space();
13792        self.write_keyword("TRIGGER");
13793        self.write_space();
13794        self.generate_identifier(&ct.name)?;
13795
13796        self.write_space();
13797        match ct.timing {
13798            TriggerTiming::Before => self.write_keyword("BEFORE"),
13799            TriggerTiming::After => self.write_keyword("AFTER"),
13800            TriggerTiming::InsteadOf => self.write_keyword("INSTEAD OF"),
13801        }
13802
13803        // Events
13804        for (i, event) in ct.events.iter().enumerate() {
13805            if i > 0 {
13806                self.write_keyword(" OR");
13807            }
13808            self.write_space();
13809            match event {
13810                TriggerEvent::Insert => self.write_keyword("INSERT"),
13811                TriggerEvent::Update(cols) => {
13812                    self.write_keyword("UPDATE");
13813                    if let Some(cols) = cols {
13814                        self.write_space();
13815                        self.write_keyword("OF");
13816                        for (j, col) in cols.iter().enumerate() {
13817                            if j > 0 {
13818                                self.write(",");
13819                            }
13820                            self.write_space();
13821                            self.generate_identifier(col)?;
13822                        }
13823                    }
13824                }
13825                TriggerEvent::Delete => self.write_keyword("DELETE"),
13826                TriggerEvent::Truncate => self.write_keyword("TRUNCATE"),
13827            }
13828        }
13829
13830        self.write_space();
13831        self.write_keyword("ON");
13832        self.write_space();
13833        self.generate_table(&ct.table)?;
13834
13835        // Referencing clause
13836        if let Some(ref_clause) = &ct.referencing {
13837            self.write_space();
13838            self.write_keyword("REFERENCING");
13839            if let Some(old_table) = &ref_clause.old_table {
13840                self.write_space();
13841                self.write_keyword("OLD TABLE AS");
13842                self.write_space();
13843                self.generate_identifier(old_table)?;
13844            }
13845            if let Some(new_table) = &ref_clause.new_table {
13846                self.write_space();
13847                self.write_keyword("NEW TABLE AS");
13848                self.write_space();
13849                self.generate_identifier(new_table)?;
13850            }
13851            if let Some(old_row) = &ref_clause.old_row {
13852                self.write_space();
13853                self.write_keyword("OLD ROW AS");
13854                self.write_space();
13855                self.generate_identifier(old_row)?;
13856            }
13857            if let Some(new_row) = &ref_clause.new_row {
13858                self.write_space();
13859                self.write_keyword("NEW ROW AS");
13860                self.write_space();
13861                self.generate_identifier(new_row)?;
13862            }
13863        }
13864
13865        // Deferrable options for constraint triggers (must come before FOR EACH)
13866        if let Some(deferrable) = ct.deferrable {
13867            self.write_space();
13868            if deferrable {
13869                self.write_keyword("DEFERRABLE");
13870            } else {
13871                self.write_keyword("NOT DEFERRABLE");
13872            }
13873        }
13874
13875        if let Some(initially) = ct.initially_deferred {
13876            self.write_space();
13877            self.write_keyword("INITIALLY");
13878            self.write_space();
13879            if initially {
13880                self.write_keyword("DEFERRED");
13881            } else {
13882                self.write_keyword("IMMEDIATE");
13883            }
13884        }
13885
13886        if let Some(for_each) = ct.for_each {
13887            self.write_space();
13888            self.write_keyword("FOR EACH");
13889            self.write_space();
13890            match for_each {
13891                TriggerForEach::Row => self.write_keyword("ROW"),
13892                TriggerForEach::Statement => self.write_keyword("STATEMENT"),
13893            }
13894        }
13895
13896        // When clause
13897        if let Some(when) = &ct.when {
13898            self.write_space();
13899            self.write_keyword("WHEN");
13900            if ct.when_paren {
13901                self.write(" (");
13902                self.generate_expression(when)?;
13903                self.write(")");
13904            } else {
13905                self.write_space();
13906                self.generate_expression(when)?;
13907            }
13908        }
13909
13910        // Body
13911        self.write_space();
13912        match &ct.body {
13913            TriggerBody::Execute { function, args } => {
13914                self.write_keyword("EXECUTE FUNCTION");
13915                self.write_space();
13916                self.generate_table(function)?;
13917                self.write("(");
13918                for (i, arg) in args.iter().enumerate() {
13919                    if i > 0 {
13920                        self.write(", ");
13921                    }
13922                    self.generate_expression(arg)?;
13923                }
13924                self.write(")");
13925            }
13926            TriggerBody::Block(block) => {
13927                self.write_keyword("BEGIN");
13928                self.write_space();
13929                self.write(block);
13930                self.write_space();
13931                self.write_keyword("END");
13932            }
13933        }
13934
13935        Ok(())
13936    }
13937
13938    fn generate_drop_trigger(&mut self, dt: &DropTrigger) -> Result<()> {
13939        self.write_keyword("DROP TRIGGER");
13940
13941        if dt.if_exists {
13942            self.write_space();
13943            self.write_keyword("IF EXISTS");
13944        }
13945
13946        self.write_space();
13947        self.generate_identifier(&dt.name)?;
13948
13949        if let Some(table) = &dt.table {
13950            self.write_space();
13951            self.write_keyword("ON");
13952            self.write_space();
13953            self.generate_table(table)?;
13954        }
13955
13956        if dt.cascade {
13957            self.write_space();
13958            self.write_keyword("CASCADE");
13959        }
13960
13961        Ok(())
13962    }
13963
13964    fn generate_create_type(&mut self, ct: &CreateType) -> Result<()> {
13965        self.write_keyword("CREATE TYPE");
13966
13967        if ct.if_not_exists {
13968            self.write_space();
13969            self.write_keyword("IF NOT EXISTS");
13970        }
13971
13972        self.write_space();
13973        self.generate_table(&ct.name)?;
13974
13975        self.write_space();
13976        self.write_keyword("AS");
13977        self.write_space();
13978
13979        match &ct.definition {
13980            TypeDefinition::Enum(values) => {
13981                self.write_keyword("ENUM");
13982                self.write(" (");
13983                for (i, val) in values.iter().enumerate() {
13984                    if i > 0 {
13985                        self.write(", ");
13986                    }
13987                    self.write(&format!("'{}'", val));
13988                }
13989                self.write(")");
13990            }
13991            TypeDefinition::Composite(attrs) => {
13992                self.write("(");
13993                for (i, attr) in attrs.iter().enumerate() {
13994                    if i > 0 {
13995                        self.write(", ");
13996                    }
13997                    self.generate_identifier(&attr.name)?;
13998                    self.write_space();
13999                    self.generate_data_type(&attr.data_type)?;
14000                    if let Some(collate) = &attr.collate {
14001                        self.write_space();
14002                        self.write_keyword("COLLATE");
14003                        self.write_space();
14004                        self.generate_identifier(collate)?;
14005                    }
14006                }
14007                self.write(")");
14008            }
14009            TypeDefinition::Range {
14010                subtype,
14011                subtype_diff,
14012                canonical,
14013            } => {
14014                self.write_keyword("RANGE");
14015                self.write(" (");
14016                self.write_keyword("SUBTYPE");
14017                self.write(" = ");
14018                self.generate_data_type(subtype)?;
14019                if let Some(diff) = subtype_diff {
14020                    self.write(", ");
14021                    self.write_keyword("SUBTYPE_DIFF");
14022                    self.write(" = ");
14023                    self.write(diff);
14024                }
14025                if let Some(canon) = canonical {
14026                    self.write(", ");
14027                    self.write_keyword("CANONICAL");
14028                    self.write(" = ");
14029                    self.write(canon);
14030                }
14031                self.write(")");
14032            }
14033            TypeDefinition::Base {
14034                input,
14035                output,
14036                internallength,
14037            } => {
14038                self.write("(");
14039                self.write_keyword("INPUT");
14040                self.write(" = ");
14041                self.write(input);
14042                self.write(", ");
14043                self.write_keyword("OUTPUT");
14044                self.write(" = ");
14045                self.write(output);
14046                if let Some(len) = internallength {
14047                    self.write(", ");
14048                    self.write_keyword("INTERNALLENGTH");
14049                    self.write(" = ");
14050                    self.write(&len.to_string());
14051                }
14052                self.write(")");
14053            }
14054            TypeDefinition::Domain {
14055                base_type,
14056                default,
14057                constraints,
14058            } => {
14059                self.generate_data_type(base_type)?;
14060                if let Some(def) = default {
14061                    self.write_space();
14062                    self.write_keyword("DEFAULT");
14063                    self.write_space();
14064                    self.generate_expression(def)?;
14065                }
14066                for constr in constraints {
14067                    self.write_space();
14068                    if let Some(name) = &constr.name {
14069                        self.write_keyword("CONSTRAINT");
14070                        self.write_space();
14071                        self.generate_identifier(name)?;
14072                        self.write_space();
14073                    }
14074                    self.write_keyword("CHECK");
14075                    self.write(" (");
14076                    self.generate_expression(&constr.check)?;
14077                    self.write(")");
14078                }
14079            }
14080        }
14081
14082        Ok(())
14083    }
14084
14085    fn generate_create_task(&mut self, task: &crate::expressions::CreateTask) -> Result<()> {
14086        self.write_keyword("CREATE");
14087        if task.or_replace {
14088            self.write_space();
14089            self.write_keyword("OR REPLACE");
14090        }
14091        self.write_space();
14092        self.write_keyword("TASK");
14093        if task.if_not_exists {
14094            self.write_space();
14095            self.write_keyword("IF NOT EXISTS");
14096        }
14097        self.write_space();
14098        self.write(&task.name);
14099        if !task.properties.is_empty() {
14100            // Properties already include leading whitespace from tokens_to_sql
14101            if !task.properties.starts_with('\n') && !task.properties.starts_with(' ') {
14102                self.write_space();
14103            }
14104            self.write(&task.properties);
14105        }
14106        self.write_space();
14107        self.write_keyword("AS");
14108        self.write_space();
14109        self.generate_expression(&task.body)?;
14110        Ok(())
14111    }
14112
14113    fn generate_try_catch(&mut self, try_catch: &TryCatch) -> Result<()> {
14114        self.write_keyword("BEGIN TRY");
14115        self.generate_tsql_block_statements(&try_catch.try_body)?;
14116        self.write_keyword("END TRY");
14117
14118        if let Some(catch_body) = &try_catch.catch_body {
14119            if self.config.pretty {
14120                self.write_newline();
14121                self.write_indent();
14122            } else {
14123                self.write_space();
14124            }
14125            self.write_keyword("BEGIN CATCH");
14126            self.generate_tsql_block_statements(catch_body)?;
14127            self.write_keyword("END CATCH");
14128        }
14129
14130        Ok(())
14131    }
14132
14133    fn generate_tsql_block_statements(&mut self, statements: &[Expression]) -> Result<()> {
14134        if statements.is_empty() {
14135            self.write_space();
14136            return Ok(());
14137        }
14138
14139        if self.config.pretty {
14140            self.indent_level += 1;
14141            for stmt in statements {
14142                self.write_newline();
14143                self.write_indent();
14144                self.generate_expression(stmt)?;
14145                self.write(";");
14146            }
14147            self.indent_level -= 1;
14148            self.write_newline();
14149            self.write_indent();
14150        } else {
14151            self.write_space();
14152            for (i, stmt) in statements.iter().enumerate() {
14153                if i > 0 {
14154                    self.write_space();
14155                }
14156                self.generate_expression(stmt)?;
14157                self.write(";");
14158            }
14159            self.write_space();
14160        }
14161
14162        Ok(())
14163    }
14164
14165    fn generate_drop_type(&mut self, dt: &DropType) -> Result<()> {
14166        self.write_keyword("DROP TYPE");
14167
14168        if dt.if_exists {
14169            self.write_space();
14170            self.write_keyword("IF EXISTS");
14171        }
14172
14173        self.write_space();
14174        self.generate_table(&dt.name)?;
14175
14176        if dt.cascade {
14177            self.write_space();
14178            self.write_keyword("CASCADE");
14179        }
14180
14181        Ok(())
14182    }
14183
14184    fn generate_describe(&mut self, d: &Describe) -> Result<()> {
14185        // Athena: DESCRIBE uses Hive engine (backticks)
14186        let saved_athena_hive_context = self.athena_hive_context;
14187        if matches!(
14188            self.config.dialect,
14189            Some(crate::dialects::DialectType::Athena)
14190        ) {
14191            self.athena_hive_context = true;
14192        }
14193
14194        // Output leading comments before DESCRIBE
14195        for comment in &d.leading_comments {
14196            self.write_formatted_comment(comment);
14197            self.write(" ");
14198        }
14199
14200        self.write_keyword("DESCRIBE");
14201
14202        if d.extended {
14203            self.write_space();
14204            self.write_keyword("EXTENDED");
14205        } else if d.formatted {
14206            self.write_space();
14207            self.write_keyword("FORMATTED");
14208        }
14209
14210        // Output style like ANALYZE, HISTORY
14211        if let Some(ref style) = d.style {
14212            self.write_space();
14213            self.write_keyword(style);
14214        }
14215
14216        // Handle object kind (TABLE, VIEW) based on dialect
14217        let should_output_kind = match self.config.dialect {
14218            // Spark doesn't use TABLE/VIEW after DESCRIBE
14219            Some(DialectType::Spark) | Some(DialectType::Databricks) | Some(DialectType::Hive) => {
14220                false
14221            }
14222            // Snowflake always includes TABLE
14223            Some(DialectType::Snowflake) => true,
14224            _ => d.kind.is_some(),
14225        };
14226        if should_output_kind {
14227            if let Some(ref kind) = d.kind {
14228                self.write_space();
14229                self.write_keyword(kind);
14230            } else if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
14231                self.write_space();
14232                self.write_keyword("TABLE");
14233            }
14234        }
14235
14236        self.write_space();
14237        self.generate_expression(&d.target)?;
14238
14239        // Output parenthesized parameter types for PROCEDURE/FUNCTION
14240        if !d.params.is_empty() {
14241            self.write("(");
14242            for (i, param) in d.params.iter().enumerate() {
14243                if i > 0 {
14244                    self.write(", ");
14245                }
14246                self.write(param);
14247            }
14248            self.write(")");
14249        }
14250
14251        // Output PARTITION clause if present (the Partition expression outputs its own PARTITION keyword)
14252        if let Some(ref partition) = d.partition {
14253            self.write_space();
14254            self.generate_expression(partition)?;
14255        }
14256
14257        // Databricks: AS JSON
14258        if d.as_json {
14259            self.write_space();
14260            self.write_keyword("AS JSON");
14261        }
14262
14263        // Output properties like type=stage
14264        for (name, value) in &d.properties {
14265            self.write_space();
14266            self.write(name);
14267            self.write("=");
14268            self.write(value);
14269        }
14270
14271        // Restore Athena Hive context
14272        self.athena_hive_context = saved_athena_hive_context;
14273
14274        Ok(())
14275    }
14276
14277    /// Generate SHOW statement (Snowflake, MySQL, etc.)
14278    /// SHOW [TERSE] <object_type> [HISTORY] [LIKE pattern] [IN <scope>] [STARTS WITH pattern] [LIMIT n] [FROM object]
14279    fn generate_show(&mut self, s: &Show) -> Result<()> {
14280        self.write_keyword("SHOW");
14281        self.write_space();
14282
14283        // TERSE keyword - but not for PRIMARY KEYS, UNIQUE KEYS, IMPORTED KEYS
14284        // where TERSE is syntactically valid but has no effect on output
14285        let show_terse = s.terse
14286            && !matches!(
14287                s.this.as_str(),
14288                "PRIMARY KEYS" | "UNIQUE KEYS" | "IMPORTED KEYS"
14289            );
14290        if show_terse {
14291            self.write_keyword("TERSE");
14292            self.write_space();
14293        }
14294
14295        // Object type (USERS, TABLES, DATABASES, etc.)
14296        self.write_keyword(&s.this);
14297
14298        // Target identifier (MySQL: engine name in SHOW ENGINE, preserved case)
14299        if let Some(ref target_expr) = s.target {
14300            self.write_space();
14301            self.generate_expression(target_expr)?;
14302        }
14303
14304        // HISTORY keyword
14305        if s.history {
14306            self.write_space();
14307            self.write_keyword("HISTORY");
14308        }
14309
14310        // FOR target (MySQL: SHOW GRANTS FOR foo, SHOW PROFILE ... FOR QUERY 5)
14311        if let Some(ref for_target) = s.for_target {
14312            self.write_space();
14313            self.write_keyword("FOR");
14314            self.write_space();
14315            self.generate_expression(for_target)?;
14316        }
14317
14318        // Determine ordering based on dialect:
14319        // - Snowflake: LIKE, IN, STARTS WITH, LIMIT, FROM
14320        // - MySQL: IN, FROM, LIKE (when FROM is present)
14321        use crate::dialects::DialectType;
14322        let is_snowflake = matches!(self.config.dialect, Some(DialectType::Snowflake));
14323        let is_mysql = matches!(self.config.dialect, Some(DialectType::MySQL));
14324        let mysql_tables_scope_as_from = is_mysql
14325            && matches!(s.this.as_str(), "TABLES" | "FULL TABLES")
14326            && s.scope_kind.as_deref() == Some("SCHEMA")
14327            && s.scope.is_some()
14328            && s.from.is_none();
14329
14330        if !is_snowflake && s.from.is_some() {
14331            // MySQL ordering: IN, FROM, LIKE
14332
14333            // IN scope_kind [scope]
14334            if let Some(ref scope_kind) = s.scope_kind {
14335                self.write_space();
14336                self.write_keyword("IN");
14337                self.write_space();
14338                self.write_keyword(scope_kind);
14339                if let Some(ref scope) = s.scope {
14340                    self.write_space();
14341                    self.generate_expression(scope)?;
14342                }
14343            } else if let Some(ref scope) = s.scope {
14344                self.write_space();
14345                self.write_keyword("IN");
14346                self.write_space();
14347                self.generate_expression(scope)?;
14348            }
14349
14350            // FROM clause
14351            if let Some(ref from) = s.from {
14352                self.write_space();
14353                self.write_keyword("FROM");
14354                self.write_space();
14355                self.generate_expression(from)?;
14356            }
14357
14358            // Second FROM clause (db name)
14359            if let Some(ref db) = s.db {
14360                self.write_space();
14361                self.write_keyword("FROM");
14362                self.write_space();
14363                self.generate_expression(db)?;
14364            }
14365
14366            // LIKE pattern
14367            if let Some(ref like) = s.like {
14368                self.write_space();
14369                self.write_keyword("LIKE");
14370                self.write_space();
14371                self.generate_expression(like)?;
14372            }
14373        } else {
14374            // Snowflake ordering: LIKE, IN, STARTS WITH, LIMIT, FROM
14375
14376            // LIKE pattern
14377            if let Some(ref like) = s.like {
14378                self.write_space();
14379                self.write_keyword("LIKE");
14380                self.write_space();
14381                self.generate_expression(like)?;
14382            }
14383
14384            // IN scope_kind [scope]
14385            if mysql_tables_scope_as_from {
14386                self.write_space();
14387                self.write_keyword("FROM");
14388                self.write_space();
14389                self.generate_expression(s.scope.as_ref().unwrap())?;
14390            } else if let Some(ref scope_kind) = s.scope_kind {
14391                self.write_space();
14392                self.write_keyword("IN");
14393                self.write_space();
14394                self.write_keyword(scope_kind);
14395                if let Some(ref scope) = s.scope {
14396                    self.write_space();
14397                    self.generate_expression(scope)?;
14398                }
14399            } else if let Some(ref scope) = s.scope {
14400                self.write_space();
14401                self.write_keyword("IN");
14402                self.write_space();
14403                self.generate_expression(scope)?;
14404            }
14405        }
14406
14407        // STARTS WITH pattern
14408        if let Some(ref starts_with) = s.starts_with {
14409            self.write_space();
14410            self.write_keyword("STARTS WITH");
14411            self.write_space();
14412            self.generate_expression(starts_with)?;
14413        }
14414
14415        // LIMIT clause
14416        if let Some(ref limit) = s.limit {
14417            self.write_space();
14418            self.generate_limit(limit)?;
14419        }
14420
14421        // FROM clause (for Snowflake, FROM comes after STARTS WITH and LIMIT)
14422        if is_snowflake {
14423            if let Some(ref from) = s.from {
14424                self.write_space();
14425                self.write_keyword("FROM");
14426                self.write_space();
14427                self.generate_expression(from)?;
14428            }
14429        }
14430
14431        // WHERE clause (MySQL: SHOW STATUS WHERE condition)
14432        if let Some(ref where_clause) = s.where_clause {
14433            self.write_space();
14434            self.write_keyword("WHERE");
14435            self.write_space();
14436            self.generate_expression(where_clause)?;
14437        }
14438
14439        // MUTEX/STATUS suffix (MySQL: SHOW ENGINE foo STATUS/MUTEX)
14440        if let Some(is_mutex) = s.mutex {
14441            self.write_space();
14442            if is_mutex {
14443                self.write_keyword("MUTEX");
14444            } else {
14445                self.write_keyword("STATUS");
14446            }
14447        }
14448
14449        // WITH PRIVILEGES clause (Snowflake: SHOW ... WITH PRIVILEGES USAGE, MODIFY)
14450        if !s.privileges.is_empty() {
14451            self.write_space();
14452            self.write_keyword("WITH PRIVILEGES");
14453            self.write_space();
14454            for (i, priv_name) in s.privileges.iter().enumerate() {
14455                if i > 0 {
14456                    self.write(", ");
14457                }
14458                self.write_keyword(priv_name);
14459            }
14460        }
14461
14462        Ok(())
14463    }
14464
14465    // ==================== End DDL Generation ====================
14466
14467    fn generate_literal(&mut self, lit: &Literal) -> Result<()> {
14468        use crate::dialects::DialectType;
14469        match lit {
14470            Literal::String(s) => {
14471                self.generate_string_literal(s)?;
14472            }
14473            Literal::Number(n) => {
14474                if matches!(self.config.dialect, Some(DialectType::MySQL))
14475                    && n.len() > 2
14476                    && (n.starts_with("0x") || n.starts_with("0X"))
14477                    && !n[2..].chars().all(|c| c.is_ascii_hexdigit())
14478                {
14479                    return self.generate_identifier(&Identifier {
14480                        name: n.clone(),
14481                        quoted: true,
14482                        trailing_comments: Vec::new(),
14483                        span: None,
14484                    });
14485                }
14486                // Strip underscore digit separators (e.g., 1_000_000 -> 1000000)
14487                // for dialects that don't support them (MySQL interprets as identifier).
14488                // ClickHouse, DuckDB, PostgreSQL, and Hive/Spark/Databricks support them.
14489                let n = if n.contains('_')
14490                    && !matches!(
14491                        self.config.dialect,
14492                        Some(DialectType::ClickHouse)
14493                            | Some(DialectType::DuckDB)
14494                            | Some(DialectType::PostgreSQL)
14495                            | Some(DialectType::Hive)
14496                            | Some(DialectType::Spark)
14497                            | Some(DialectType::Databricks)
14498                    ) {
14499                    std::borrow::Cow::Owned(n.replace('_', ""))
14500                } else {
14501                    std::borrow::Cow::Borrowed(n.as_str())
14502                };
14503                // Normalize numbers starting with decimal point to have leading zero
14504                // e.g., .25 -> 0.25 (matches sqlglot behavior)
14505                if n.starts_with('.') {
14506                    self.write("0");
14507                    self.write(&n);
14508                } else if n.starts_with("-.") {
14509                    // Handle negative numbers like -.25 -> -0.25
14510                    self.write("-0");
14511                    self.write(&n[1..]);
14512                } else {
14513                    self.write(&n);
14514                }
14515            }
14516            Literal::HexString(h) => {
14517                // Most dialects use lowercase x'...' for hex literals; Spark/Databricks/Teradata use uppercase X'...'
14518                match self.config.dialect {
14519                    Some(DialectType::Spark)
14520                    | Some(DialectType::Databricks)
14521                    | Some(DialectType::Teradata) => self.write("X'"),
14522                    _ => self.write("x'"),
14523                }
14524                self.write(h);
14525                self.write("'");
14526            }
14527            Literal::HexNumber(h) => {
14528                // Hex number (0xA) - integer in hex notation (from BigQuery)
14529                // For BigQuery, TSQL, Fabric output as 0xHEX (native hex notation)
14530                // For other dialects, convert to decimal integer
14531                match self.config.dialect {
14532                    Some(DialectType::BigQuery)
14533                    | Some(DialectType::ClickHouse)
14534                    | Some(DialectType::TSQL)
14535                    | Some(DialectType::Fabric) => {
14536                        self.write("0x");
14537                        self.write(h);
14538                    }
14539                    _ => {
14540                        // Convert hex to decimal
14541                        if let Ok(val) = u64::from_str_radix(h, 16) {
14542                            self.write(&val.to_string());
14543                        } else {
14544                            // Fallback: keep as 0x notation
14545                            self.write("0x");
14546                            self.write(h);
14547                        }
14548                    }
14549                }
14550            }
14551            Literal::BitString(b) => {
14552                // Bit string B'0101...'
14553                self.write("B'");
14554                self.write(b);
14555                self.write("'");
14556            }
14557            Literal::ByteString(b) => {
14558                // Byte string b'...' (BigQuery style)
14559                self.write("b'");
14560                // Escape special characters for output
14561                self.write_escaped_byte_string(b);
14562                self.write("'");
14563            }
14564            Literal::NationalString(s) => {
14565                // N'string' is supported by TSQL, Oracle, MySQL, and generic SQL
14566                // Other dialects strip the N prefix and output as regular string
14567                let keep_n_prefix = matches!(
14568                    self.config.dialect,
14569                    Some(DialectType::TSQL)
14570                        | Some(DialectType::Oracle)
14571                        | Some(DialectType::MySQL)
14572                        | None
14573                );
14574                if keep_n_prefix {
14575                    self.write("N'");
14576                } else {
14577                    self.write("'");
14578                }
14579                self.write(s);
14580                self.write("'");
14581            }
14582            Literal::Date(d) => {
14583                self.generate_date_literal(d)?;
14584            }
14585            Literal::Time(t) => {
14586                self.generate_time_literal(t)?;
14587            }
14588            Literal::Timestamp(ts) => {
14589                self.generate_timestamp_literal(ts)?;
14590            }
14591            Literal::Datetime(dt) => {
14592                self.generate_datetime_literal(dt)?;
14593            }
14594            Literal::TripleQuotedString(s, _quote_char) => {
14595                // For BigQuery and other dialects that don't support triple-quote, normalize to regular strings
14596                if matches!(
14597                    self.config.dialect,
14598                    Some(crate::dialects::DialectType::BigQuery)
14599                        | Some(crate::dialects::DialectType::DuckDB)
14600                        | Some(crate::dialects::DialectType::Snowflake)
14601                        | Some(crate::dialects::DialectType::Spark)
14602                        | Some(crate::dialects::DialectType::Hive)
14603                        | Some(crate::dialects::DialectType::Presto)
14604                        | Some(crate::dialects::DialectType::Trino)
14605                        | Some(crate::dialects::DialectType::PostgreSQL)
14606                        | Some(crate::dialects::DialectType::MySQL)
14607                        | Some(crate::dialects::DialectType::Redshift)
14608                        | Some(crate::dialects::DialectType::TSQL)
14609                        | Some(crate::dialects::DialectType::Oracle)
14610                        | Some(crate::dialects::DialectType::ClickHouse)
14611                        | Some(crate::dialects::DialectType::Databricks)
14612                        | Some(crate::dialects::DialectType::SQLite)
14613                ) {
14614                    self.generate_string_literal(s)?;
14615                } else {
14616                    // Preserve triple-quoted string syntax for generic/unknown dialects
14617                    let quotes = format!("{0}{0}{0}", _quote_char);
14618                    self.write(&quotes);
14619                    self.write(s);
14620                    self.write(&quotes);
14621                }
14622            }
14623            Literal::EscapeString(s) => {
14624                // PostgreSQL escape string: e'...' or E'...'
14625                // Token text format is "e:content" or "E:content"
14626                // Normalize escape sequences: \' -> '' (standard SQL doubled quote)
14627                use crate::dialects::DialectType;
14628                let content = if let Some(c) = s.strip_prefix("e:") {
14629                    c
14630                } else if let Some(c) = s.strip_prefix("E:") {
14631                    c
14632                } else {
14633                    s.as_str()
14634                };
14635
14636                // MySQL: output the content without quotes or prefix
14637                if matches!(
14638                    self.config.dialect,
14639                    Some(DialectType::MySQL) | Some(DialectType::TiDB)
14640                ) {
14641                    self.write(content);
14642                } else {
14643                    // Some dialects use lowercase e' prefix
14644                    let prefix = if matches!(
14645                        self.config.dialect,
14646                        Some(DialectType::SingleStore)
14647                            | Some(DialectType::DuckDB)
14648                            | Some(DialectType::PostgreSQL)
14649                            | Some(DialectType::CockroachDB)
14650                            | Some(DialectType::Materialize)
14651                            | Some(DialectType::RisingWave)
14652                    ) {
14653                        "e'"
14654                    } else {
14655                        "E'"
14656                    };
14657
14658                    // Normalize \' to '' for output
14659                    let normalized = content.replace("\\'", "''");
14660                    self.write(prefix);
14661                    self.write(&normalized);
14662                    self.write("'");
14663                }
14664            }
14665            Literal::DollarString(s) => {
14666                // Convert dollar-quoted strings to single-quoted strings
14667                // (like Python sqlglot's rawstring_sql)
14668                use crate::dialects::DialectType;
14669                // Extract content from tag\x00content format
14670                let (_tag, content) = crate::tokens::parse_dollar_string_token(s);
14671                // Step 1: Escape backslashes if the dialect uses backslash as a string escape
14672                let escape_backslash = matches!(
14673                    self.config.dialect,
14674                    Some(DialectType::ClickHouse) | Some(DialectType::Snowflake)
14675                );
14676                // Step 2: Determine quote escaping style
14677                // Snowflake: ' -> \' (backslash escape)
14678                // PostgreSQL, DuckDB, others: ' -> '' (doubled quote)
14679                let use_backslash_quote =
14680                    matches!(self.config.dialect, Some(DialectType::Snowflake));
14681
14682                let mut escaped = String::with_capacity(content.len() + 4);
14683                for ch in content.chars() {
14684                    if escape_backslash && ch == '\\' {
14685                        // Escape backslash first (before quote escaping)
14686                        escaped.push('\\');
14687                        escaped.push('\\');
14688                    } else if ch == '\'' {
14689                        if use_backslash_quote {
14690                            escaped.push('\\');
14691                            escaped.push('\'');
14692                        } else {
14693                            escaped.push('\'');
14694                            escaped.push('\'');
14695                        }
14696                    } else {
14697                        escaped.push(ch);
14698                    }
14699                }
14700                self.write("'");
14701                self.write(&escaped);
14702                self.write("'");
14703            }
14704            Literal::RawString(s) => {
14705                // Raw strings (r"..." or r'...') contain literal backslashes.
14706                // When converting to a regular string, this follows Python sqlglot's rawstring_sql:
14707                // 1. If \\ is in STRING_ESCAPES, double all backslashes
14708                // 2. Apply ESCAPED_SEQUENCES for special chars (but NOT for backslash itself)
14709                // 3. Escape quotes using STRING_ESCAPES[0] + quote_char
14710                use crate::dialects::DialectType;
14711
14712                // Dialects where \\ is in STRING_ESCAPES (backslashes need doubling)
14713                let escape_backslash = matches!(
14714                    self.config.dialect,
14715                    Some(DialectType::BigQuery)
14716                        | Some(DialectType::MySQL)
14717                        | Some(DialectType::SingleStore)
14718                        | Some(DialectType::TiDB)
14719                        | Some(DialectType::Hive)
14720                        | Some(DialectType::Spark)
14721                        | Some(DialectType::Databricks)
14722                        | Some(DialectType::Drill)
14723                        | Some(DialectType::Snowflake)
14724                        | Some(DialectType::Redshift)
14725                        | Some(DialectType::ClickHouse)
14726                );
14727
14728                // Dialects where backslash is the PRIMARY string escape (STRING_ESCAPES[0] = "\\")
14729                // These escape quotes as \' instead of ''
14730                let backslash_escapes_quote = matches!(
14731                    self.config.dialect,
14732                    Some(DialectType::BigQuery)
14733                        | Some(DialectType::Hive)
14734                        | Some(DialectType::Spark)
14735                        | Some(DialectType::Databricks)
14736                        | Some(DialectType::Drill)
14737                        | Some(DialectType::Snowflake)
14738                        | Some(DialectType::Redshift)
14739                );
14740
14741                // Whether this dialect supports escaped sequences (ESCAPED_SEQUENCES mapping)
14742                // This is True when \\ is in STRING_ESCAPES (same as escape_backslash)
14743                let supports_escape_sequences = escape_backslash;
14744
14745                let mut escaped = String::with_capacity(s.len() + 4);
14746                for ch in s.chars() {
14747                    if escape_backslash && ch == '\\' {
14748                        // Double the backslash for the target dialect
14749                        escaped.push('\\');
14750                        escaped.push('\\');
14751                    } else if ch == '\'' {
14752                        if backslash_escapes_quote {
14753                            // Use backslash to escape the quote: \'
14754                            escaped.push('\\');
14755                            escaped.push('\'');
14756                        } else {
14757                            // Use SQL standard quote doubling: ''
14758                            escaped.push('\'');
14759                            escaped.push('\'');
14760                        }
14761                    } else if supports_escape_sequences {
14762                        // Apply ESCAPED_SEQUENCES mapping for special chars
14763                        // (escape_backslash=False in rawstring_sql, so \\ is NOT escaped here)
14764                        match ch {
14765                            '\n' => {
14766                                escaped.push('\\');
14767                                escaped.push('n');
14768                            }
14769                            '\r' => {
14770                                escaped.push('\\');
14771                                escaped.push('r');
14772                            }
14773                            '\t' => {
14774                                escaped.push('\\');
14775                                escaped.push('t');
14776                            }
14777                            '\x07' => {
14778                                escaped.push('\\');
14779                                escaped.push('a');
14780                            }
14781                            '\x08' => {
14782                                escaped.push('\\');
14783                                escaped.push('b');
14784                            }
14785                            '\x0C' => {
14786                                escaped.push('\\');
14787                                escaped.push('f');
14788                            }
14789                            '\x0B' => {
14790                                escaped.push('\\');
14791                                escaped.push('v');
14792                            }
14793                            _ => escaped.push(ch),
14794                        }
14795                    } else {
14796                        escaped.push(ch);
14797                    }
14798                }
14799                self.write("'");
14800                self.write(&escaped);
14801                self.write("'");
14802            }
14803        }
14804        Ok(())
14805    }
14806
14807    /// Generate a DATE literal with dialect-specific formatting
14808    fn generate_date_literal(&mut self, d: &str) -> Result<()> {
14809        use crate::dialects::DialectType;
14810
14811        match self.config.dialect {
14812            // SQL Server / Fabric use CONVERT or CAST
14813            Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
14814                self.write("CAST('");
14815                self.write(d);
14816                self.write("' AS DATE)");
14817            }
14818            // BigQuery uses CAST syntax for type literals
14819            // DATE 'value' -> CAST('value' AS DATE)
14820            Some(DialectType::BigQuery) => {
14821                self.write("CAST('");
14822                self.write(d);
14823                self.write("' AS DATE)");
14824            }
14825            // Exasol uses CAST syntax for DATE literals
14826            // DATE 'value' -> CAST('value' AS DATE)
14827            Some(DialectType::Exasol) => {
14828                self.write("CAST('");
14829                self.write(d);
14830                self.write("' AS DATE)");
14831            }
14832            // Snowflake uses CAST syntax for DATE literals
14833            // DATE 'value' -> CAST('value' AS DATE)
14834            Some(DialectType::Snowflake) => {
14835                self.write("CAST('");
14836                self.write(d);
14837                self.write("' AS DATE)");
14838            }
14839            // PostgreSQL, MySQL, Redshift: DATE 'value' -> CAST('value' AS DATE)
14840            Some(DialectType::PostgreSQL)
14841            | Some(DialectType::MySQL)
14842            | Some(DialectType::SingleStore)
14843            | Some(DialectType::TiDB)
14844            | Some(DialectType::Redshift) => {
14845                self.write("CAST('");
14846                self.write(d);
14847                self.write("' AS DATE)");
14848            }
14849            // DuckDB, Presto, Trino, Spark: DATE 'value' -> CAST('value' AS DATE)
14850            Some(DialectType::DuckDB)
14851            | Some(DialectType::Presto)
14852            | Some(DialectType::Trino)
14853            | Some(DialectType::Athena)
14854            | Some(DialectType::Spark)
14855            | Some(DialectType::Databricks)
14856            | Some(DialectType::Hive) => {
14857                self.write("CAST('");
14858                self.write(d);
14859                self.write("' AS DATE)");
14860            }
14861            // Oracle: DATE 'value' -> TO_DATE('value', 'YYYY-MM-DD')
14862            Some(DialectType::Oracle) => {
14863                self.write("TO_DATE('");
14864                self.write(d);
14865                self.write("', 'YYYY-MM-DD')");
14866            }
14867            // Standard SQL: DATE '...'
14868            _ => {
14869                self.write_keyword("DATE");
14870                self.write(" '");
14871                self.write(d);
14872                self.write("'");
14873            }
14874        }
14875        Ok(())
14876    }
14877
14878    /// Generate a TIME literal with dialect-specific formatting
14879    fn generate_time_literal(&mut self, t: &str) -> Result<()> {
14880        use crate::dialects::DialectType;
14881
14882        match self.config.dialect {
14883            // SQL Server uses CONVERT or CAST
14884            Some(DialectType::TSQL) => {
14885                self.write("CAST('");
14886                self.write(t);
14887                self.write("' AS TIME)");
14888            }
14889            // Standard SQL: TIME '...'
14890            _ => {
14891                self.write_keyword("TIME");
14892                self.write(" '");
14893                self.write(t);
14894                self.write("'");
14895            }
14896        }
14897        Ok(())
14898    }
14899
14900    /// Generate a date expression for Dremio, converting DATE literals to CAST
14901    fn generate_dremio_date_expression(&mut self, expr: &Expression) -> Result<()> {
14902        use crate::expressions::Literal;
14903
14904        match expr {
14905            Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Date(_)) => {
14906                let Literal::Date(d) = lit.as_ref() else {
14907                    unreachable!()
14908                };
14909                // DATE 'value' -> CAST('value' AS DATE)
14910                self.write("CAST('");
14911                self.write(d);
14912                self.write("' AS DATE)");
14913            }
14914            _ => {
14915                // For all other expressions, generate normally
14916                self.generate_expression(expr)?;
14917            }
14918        }
14919        Ok(())
14920    }
14921
14922    /// Generate a TIMESTAMP literal with dialect-specific formatting
14923    fn generate_timestamp_literal(&mut self, ts: &str) -> Result<()> {
14924        use crate::dialects::DialectType;
14925
14926        match self.config.dialect {
14927            // SQL Server uses CONVERT or CAST
14928            Some(DialectType::TSQL) => {
14929                self.write("CAST('");
14930                self.write(ts);
14931                self.write("' AS DATETIME2)");
14932            }
14933            // BigQuery uses CAST syntax for type literals
14934            // TIMESTAMP 'value' -> CAST('value' AS TIMESTAMP)
14935            Some(DialectType::BigQuery) => {
14936                self.write("CAST('");
14937                self.write(ts);
14938                self.write("' AS TIMESTAMP)");
14939            }
14940            // Snowflake uses CAST syntax for TIMESTAMP literals
14941            // TIMESTAMP 'value' -> CAST('value' AS TIMESTAMP)
14942            Some(DialectType::Snowflake) => {
14943                self.write("CAST('");
14944                self.write(ts);
14945                self.write("' AS TIMESTAMP)");
14946            }
14947            // Dremio uses CAST syntax for TIMESTAMP literals
14948            // TIMESTAMP 'value' -> CAST('value' AS TIMESTAMP)
14949            Some(DialectType::Dremio) => {
14950                self.write("CAST('");
14951                self.write(ts);
14952                self.write("' AS TIMESTAMP)");
14953            }
14954            // Exasol uses CAST syntax for TIMESTAMP literals
14955            // TIMESTAMP 'value' -> CAST('value' AS TIMESTAMP)
14956            Some(DialectType::Exasol) => {
14957                self.write("CAST('");
14958                self.write(ts);
14959                self.write("' AS TIMESTAMP)");
14960            }
14961            // Oracle prefers TO_TIMESTAMP function call
14962            // TIMESTAMP 'value' -> TO_TIMESTAMP('value', 'YYYY-MM-DD HH24:MI:SS.FF6')
14963            Some(DialectType::Oracle) => {
14964                self.write("TO_TIMESTAMP('");
14965                self.write(ts);
14966                self.write("', 'YYYY-MM-DD HH24:MI:SS.FF6')");
14967            }
14968            // Presto/Trino: always use CAST for TIMESTAMP literals
14969            Some(DialectType::Presto) | Some(DialectType::Trino) => {
14970                if Self::timestamp_has_timezone(ts) {
14971                    self.write("CAST('");
14972                    self.write(ts);
14973                    self.write("' AS TIMESTAMP WITH TIME ZONE)");
14974                } else {
14975                    self.write("CAST('");
14976                    self.write(ts);
14977                    self.write("' AS TIMESTAMP)");
14978                }
14979            }
14980            // ClickHouse: CAST('...' AS Nullable(DateTime))
14981            Some(DialectType::ClickHouse) => {
14982                self.write("CAST('");
14983                self.write(ts);
14984                self.write("' AS Nullable(DateTime))");
14985            }
14986            // Spark: CAST('...' AS TIMESTAMP)
14987            Some(DialectType::Spark) => {
14988                self.write("CAST('");
14989                self.write(ts);
14990                self.write("' AS TIMESTAMP)");
14991            }
14992            // Redshift: CAST('...' AS TIMESTAMP) for regular timestamps,
14993            // but TIMESTAMP '...' for special values like 'epoch'
14994            Some(DialectType::Redshift) => {
14995                if ts == "epoch" {
14996                    self.write_keyword("TIMESTAMP");
14997                    self.write(" '");
14998                    self.write(ts);
14999                    self.write("'");
15000                } else {
15001                    self.write("CAST('");
15002                    self.write(ts);
15003                    self.write("' AS TIMESTAMP)");
15004                }
15005            }
15006            // PostgreSQL, Hive, DuckDB, etc.: CAST('...' AS TIMESTAMP)
15007            Some(DialectType::PostgreSQL)
15008            | Some(DialectType::Hive)
15009            | Some(DialectType::SQLite)
15010            | Some(DialectType::DuckDB)
15011            | Some(DialectType::Athena)
15012            | Some(DialectType::Drill)
15013            | Some(DialectType::Teradata) => {
15014                self.write("CAST('");
15015                self.write(ts);
15016                self.write("' AS TIMESTAMP)");
15017            }
15018            // MySQL/StarRocks: CAST('...' AS DATETIME)
15019            Some(DialectType::MySQL) | Some(DialectType::StarRocks) | Some(DialectType::Doris) => {
15020                self.write("CAST('");
15021                self.write(ts);
15022                self.write("' AS DATETIME)");
15023            }
15024            // Databricks: CAST('...' AS TIMESTAMP_NTZ)
15025            Some(DialectType::Databricks) => {
15026                self.write("CAST('");
15027                self.write(ts);
15028                self.write("' AS TIMESTAMP_NTZ)");
15029            }
15030            // Standard SQL: TIMESTAMP '...'
15031            _ => {
15032                self.write_keyword("TIMESTAMP");
15033                self.write(" '");
15034                self.write(ts);
15035                self.write("'");
15036            }
15037        }
15038        Ok(())
15039    }
15040
15041    /// Check if a timestamp string contains a timezone identifier
15042    /// This detects IANA timezone names like Europe/Prague, America/New_York, etc.
15043    fn timestamp_has_timezone(ts: &str) -> bool {
15044        // Check for common IANA timezone patterns: Continent/City format
15045        // Examples: Europe/Prague, America/New_York, Asia/Tokyo, etc.
15046        // Also handles: UTC, GMT, Etc/GMT+0, etc.
15047        let ts_lower = ts.to_ascii_lowercase();
15048
15049        // Check for Continent/City pattern (most common)
15050        let continent_prefixes = [
15051            "africa/",
15052            "america/",
15053            "antarctica/",
15054            "arctic/",
15055            "asia/",
15056            "atlantic/",
15057            "australia/",
15058            "europe/",
15059            "indian/",
15060            "pacific/",
15061            "etc/",
15062            "brazil/",
15063            "canada/",
15064            "chile/",
15065            "mexico/",
15066            "us/",
15067        ];
15068
15069        for prefix in &continent_prefixes {
15070            if ts_lower.contains(prefix) {
15071                return true;
15072            }
15073        }
15074
15075        // Check for standalone timezone abbreviations at the end
15076        // These typically appear after the time portion
15077        let tz_abbrevs = [
15078            " utc", " gmt", " cet", " cest", " eet", " eest", " wet", " west", " est", " edt",
15079            " cst", " cdt", " mst", " mdt", " pst", " pdt", " ist", " bst", " jst", " kst", " hkt",
15080            " sgt", " aest", " aedt", " acst", " acdt", " awst",
15081        ];
15082
15083        for abbrev in &tz_abbrevs {
15084            if ts_lower.ends_with(abbrev) {
15085                return true;
15086            }
15087        }
15088
15089        // Check for numeric timezone offsets: +N, -N, +NN:NN, -NN:NN
15090        // Examples: "2012-10-31 01:00 -2", "2012-10-31 01:00 +02:00"
15091        // Look for pattern: space followed by + or - and digits (optionally with :)
15092        let trimmed = ts.trim();
15093        if let Some(last_space) = trimmed.rfind(' ') {
15094            let suffix = &trimmed[last_space + 1..];
15095            if (suffix.starts_with('+') || suffix.starts_with('-')) && suffix.len() > 1 {
15096                // Check if rest is numeric (possibly with : for hh:mm format)
15097                let rest = &suffix[1..];
15098                if rest.chars().all(|c| c.is_ascii_digit() || c == ':') {
15099                    return true;
15100                }
15101            }
15102        }
15103
15104        false
15105    }
15106
15107    /// Generate a DATETIME literal with dialect-specific formatting
15108    fn generate_datetime_literal(&mut self, dt: &str) -> Result<()> {
15109        use crate::dialects::DialectType;
15110
15111        match self.config.dialect {
15112            // BigQuery uses CAST syntax for type literals
15113            // DATETIME 'value' -> CAST('value' AS DATETIME)
15114            Some(DialectType::BigQuery) => {
15115                self.write("CAST('");
15116                self.write(dt);
15117                self.write("' AS DATETIME)");
15118            }
15119            // DuckDB: DATETIME -> CAST('value' AS TIMESTAMP)
15120            Some(DialectType::DuckDB) => {
15121                self.write("CAST('");
15122                self.write(dt);
15123                self.write("' AS TIMESTAMP)");
15124            }
15125            // DATETIME is primarily a BigQuery type
15126            // Output as DATETIME '...' for dialects that support it
15127            _ => {
15128                self.write_keyword("DATETIME");
15129                self.write(" '");
15130                self.write(dt);
15131                self.write("'");
15132            }
15133        }
15134        Ok(())
15135    }
15136
15137    /// Generate a string literal with dialect-specific escaping
15138    fn generate_string_literal(&mut self, s: &str) -> Result<()> {
15139        use crate::dialects::DialectType;
15140
15141        match self.config.dialect {
15142            // MySQL/Hive: Uses SQL standard quote escaping ('') for quotes,
15143            // and backslash escaping for special characters like newlines
15144            // Hive STRING_ESCAPES = ["\\"] - uses backslash escapes
15145            Some(DialectType::Hive) | Some(DialectType::Spark) | Some(DialectType::Databricks) => {
15146                // Hive/Spark use backslash escaping for quotes (\') and special chars
15147                self.write("'");
15148                for c in s.chars() {
15149                    match c {
15150                        '\'' => self.write("\\'"),
15151                        '\\' => self.write("\\\\"),
15152                        '\n' => self.write("\\n"),
15153                        '\r' => self.write("\\r"),
15154                        '\t' => self.write("\\t"),
15155                        '\0' => self.write("\\0"),
15156                        _ => self.output.push(c),
15157                    }
15158                }
15159                self.write("'");
15160            }
15161            Some(DialectType::Drill) => {
15162                // Drill uses SQL-standard quote doubling ('') for quotes,
15163                // but backslash escaping for special characters
15164                self.write("'");
15165                for c in s.chars() {
15166                    match c {
15167                        '\'' => self.write("''"),
15168                        '\\' => self.write("\\\\"),
15169                        '\n' => self.write("\\n"),
15170                        '\r' => self.write("\\r"),
15171                        '\t' => self.write("\\t"),
15172                        '\0' => self.write("\\0"),
15173                        _ => self.output.push(c),
15174                    }
15175                }
15176                self.write("'");
15177            }
15178            Some(DialectType::MySQL) | Some(DialectType::SingleStore) | Some(DialectType::TiDB) => {
15179                self.write("'");
15180                for c in s.chars() {
15181                    match c {
15182                        // MySQL uses SQL standard quote doubling
15183                        '\'' => self.write("''"),
15184                        '\\' => self.write("\\\\"),
15185                        '\n' => self.write("\\n"),
15186                        '\r' => self.write("\\r"),
15187                        '\t' => self.write("\\t"),
15188                        // sqlglot writes a literal NUL for this case
15189                        '\0' => self.output.push('\0'),
15190                        _ => self.output.push(c),
15191                    }
15192                }
15193                self.write("'");
15194            }
15195            // BigQuery: Uses backslash escaping
15196            Some(DialectType::BigQuery) => {
15197                self.write("'");
15198                for c in s.chars() {
15199                    match c {
15200                        '\'' => self.write("\\'"),
15201                        '\\' => self.write("\\\\"),
15202                        '\n' => self.write("\\n"),
15203                        '\r' => self.write("\\r"),
15204                        '\t' => self.write("\\t"),
15205                        '\0' => self.write("\\0"),
15206                        '\x07' => self.write("\\a"),
15207                        '\x08' => self.write("\\b"),
15208                        '\x0C' => self.write("\\f"),
15209                        '\x0B' => self.write("\\v"),
15210                        _ => self.output.push(c),
15211                    }
15212                }
15213                self.write("'");
15214            }
15215            // Athena: Uses different escaping for DDL (Hive) vs DML (Trino)
15216            // In Hive context (DDL): backslash escaping for single quotes (\') and backslashes (\\)
15217            // In Trino context (DML): SQL-standard escaping ('') and literal backslashes
15218            Some(DialectType::Athena) => {
15219                if self.athena_hive_context {
15220                    // Hive-style: backslash escaping
15221                    self.write("'");
15222                    for c in s.chars() {
15223                        match c {
15224                            '\'' => self.write("\\'"),
15225                            '\\' => self.write("\\\\"),
15226                            '\n' => self.write("\\n"),
15227                            '\r' => self.write("\\r"),
15228                            '\t' => self.write("\\t"),
15229                            '\0' => self.write("\\0"),
15230                            _ => self.output.push(c),
15231                        }
15232                    }
15233                    self.write("'");
15234                } else {
15235                    // Trino-style: SQL-standard escaping, preserve backslashes
15236                    self.write("'");
15237                    for c in s.chars() {
15238                        match c {
15239                            '\'' => self.write("''"),
15240                            // Preserve backslashes literally (no re-escaping)
15241                            _ => self.output.push(c),
15242                        }
15243                    }
15244                    self.write("'");
15245                }
15246            }
15247            // Snowflake: Uses backslash escaping (STRING_ESCAPES = ["\\", "'"])
15248            // The tokenizer preserves backslash escape sequences literally (e.g., input '\\'
15249            // becomes string value '\\'), so we should NOT re-escape backslashes.
15250            // We only need to escape single quotes.
15251            Some(DialectType::Snowflake) => {
15252                self.write("'");
15253                for c in s.chars() {
15254                    match c {
15255                        '\'' => self.write("\\'"),
15256                        // Backslashes are already escaped in the tokenized string, don't re-escape
15257                        // Only escape special characters that might not have been escaped
15258                        '\n' => self.write("\\n"),
15259                        '\r' => self.write("\\r"),
15260                        '\t' => self.write("\\t"),
15261                        _ => self.output.push(c),
15262                    }
15263                }
15264                self.write("'");
15265            }
15266            // PostgreSQL: Output special characters as literal chars in strings (no E-string prefix)
15267            Some(DialectType::PostgreSQL) => {
15268                self.write("'");
15269                for c in s.chars() {
15270                    match c {
15271                        '\'' => self.write("''"),
15272                        _ => self.output.push(c),
15273                    }
15274                }
15275                self.write("'");
15276            }
15277            // Redshift: Uses backslash escaping for single quotes
15278            Some(DialectType::Redshift) => {
15279                self.write("'");
15280                for c in s.chars() {
15281                    match c {
15282                        '\'' => self.write("\\'"),
15283                        _ => self.output.push(c),
15284                    }
15285                }
15286                self.write("'");
15287            }
15288            // Oracle: Uses standard double single-quote escaping
15289            Some(DialectType::Oracle) => {
15290                self.write("'");
15291                for ch in s.chars() {
15292                    if ch == '\'' {
15293                        self.output.push_str("''");
15294                    } else {
15295                        self.output.push(ch);
15296                    }
15297                }
15298                self.write("'");
15299            }
15300            // ClickHouse: Uses SQL-standard quote doubling ('') for quotes,
15301            // backslash escaping for backslashes and special characters
15302            Some(DialectType::ClickHouse) => {
15303                self.write("'");
15304                for c in s.chars() {
15305                    match c {
15306                        '\'' => self.write("''"),
15307                        '\\' => self.write("\\\\"),
15308                        '\n' => self.write("\\n"),
15309                        '\r' => self.write("\\r"),
15310                        '\t' => self.write("\\t"),
15311                        '\0' => self.write("\\0"),
15312                        '\x07' => self.write("\\a"),
15313                        '\x08' => self.write("\\b"),
15314                        '\x0C' => self.write("\\f"),
15315                        '\x0B' => self.write("\\v"),
15316                        // Non-printable characters: emit as \xNN hex escapes
15317                        c if c.is_control() || (c as u32) < 0x20 => {
15318                            let byte = c as u32;
15319                            if byte < 256 {
15320                                self.write(&format!("\\x{:02X}", byte));
15321                            } else {
15322                                self.output.push(c);
15323                            }
15324                        }
15325                        _ => self.output.push(c),
15326                    }
15327                }
15328                self.write("'");
15329            }
15330            // Default: SQL standard double single quotes (works for most dialects)
15331            // PostgreSQL, Snowflake, DuckDB, TSQL, etc.
15332            _ => {
15333                self.write("'");
15334                for ch in s.chars() {
15335                    if ch == '\'' {
15336                        self.output.push_str("''");
15337                    } else {
15338                        self.output.push(ch);
15339                    }
15340                }
15341                self.write("'");
15342            }
15343        }
15344        Ok(())
15345    }
15346
15347    /// Write a byte string with proper escaping for BigQuery-style byte literals
15348    /// Escapes characters as \xNN hex escapes where needed
15349    fn write_escaped_byte_string(&mut self, s: &str) {
15350        for c in s.chars() {
15351            match c {
15352                // Escape single quotes
15353                '\'' => self.write("\\'"),
15354                // Escape backslashes
15355                '\\' => self.write("\\\\"),
15356                // Keep all printable characters (including non-ASCII) as-is
15357                _ if !c.is_control() => self.output.push(c),
15358                // Escape control characters as hex
15359                _ => {
15360                    let byte = c as u32;
15361                    if byte < 256 {
15362                        self.write(&format!("\\x{:02x}", byte));
15363                    } else {
15364                        // For unicode characters, write each UTF-8 byte
15365                        for b in c.to_string().as_bytes() {
15366                            self.write(&format!("\\x{:02x}", b));
15367                        }
15368                    }
15369                }
15370            }
15371        }
15372    }
15373
15374    fn generate_boolean(&mut self, b: &BooleanLiteral) -> Result<()> {
15375        use crate::dialects::DialectType;
15376
15377        // Different dialects have different boolean literal formats
15378        match self.config.dialect {
15379            // SQL Server typically uses 1/0 for boolean literals in many contexts
15380            // However, TRUE/FALSE also works in modern versions
15381            Some(DialectType::TSQL) => {
15382                self.write(if b.value { "1" } else { "0" });
15383            }
15384            // Oracle traditionally uses 1/0 (no native boolean until recent versions)
15385            Some(DialectType::Oracle) => {
15386                self.write(if b.value { "1" } else { "0" });
15387            }
15388            // MySQL accepts TRUE/FALSE as aliases for 1/0
15389            Some(DialectType::MySQL) => {
15390                self.write_keyword(if b.value { "TRUE" } else { "FALSE" });
15391            }
15392            // Most other dialects support TRUE/FALSE
15393            _ => {
15394                self.write_keyword(if b.value { "TRUE" } else { "FALSE" });
15395            }
15396        }
15397        Ok(())
15398    }
15399
15400    /// Generate an identifier that's used as an alias name
15401    /// This quotes reserved keywords in addition to already-quoted identifiers
15402    fn generate_alias_identifier(&mut self, id: &Identifier) -> Result<()> {
15403        let name = &id.name;
15404        let quote_style = &self.config.identifier_quote_style;
15405
15406        // For aliases, quote if:
15407        // 1. The identifier was explicitly quoted in the source
15408        // 2. The identifier is a reserved keyword for the current dialect
15409        let needs_quoting = id.quoted || self.is_reserved_keyword(name);
15410
15411        // Normalize identifier if configured
15412        let output_name = if self.config.normalize_identifiers && !id.quoted {
15413            name.to_ascii_lowercase()
15414        } else {
15415            name.to_string()
15416        };
15417
15418        if needs_quoting {
15419            let quote_style = if matches!(self.config.dialect, Some(DialectType::ClickHouse))
15420                && matches!(self.config.source_dialect, Some(DialectType::ClickHouse))
15421                && quote_style.start == '"'
15422                && output_name.contains('"')
15423            {
15424                &IdentifierQuoteStyle::BACKTICK
15425            } else {
15426                quote_style
15427            };
15428            // Escape any quote characters within the identifier
15429            let escaped_name = if quote_style.start == quote_style.end {
15430                output_name.replace(
15431                    quote_style.end,
15432                    &format!("{}{}", quote_style.end, quote_style.end),
15433                )
15434            } else {
15435                output_name.replace(
15436                    quote_style.end,
15437                    &format!("{}{}", quote_style.end, quote_style.end),
15438                )
15439            };
15440            self.write(&format!(
15441                "{}{}{}",
15442                quote_style.start, escaped_name, quote_style.end
15443            ));
15444        } else {
15445            self.write(&output_name);
15446        }
15447
15448        // Output trailing comments
15449        for comment in &id.trailing_comments {
15450            self.write(" ");
15451            self.write_formatted_comment(comment);
15452        }
15453        Ok(())
15454    }
15455
15456    fn generate_identifier(&mut self, id: &Identifier) -> Result<()> {
15457        use crate::dialects::DialectType;
15458
15459        let name = &id.name;
15460
15461        // For Athena, use backticks in Hive context, double quotes in Trino context
15462        let quote_style = if matches!(self.config.dialect, Some(DialectType::Athena))
15463            && self.athena_hive_context
15464        {
15465            &IdentifierQuoteStyle::BACKTICK
15466        } else {
15467            &self.config.identifier_quote_style
15468        };
15469
15470        // Quote if:
15471        // 1. The identifier was explicitly quoted in the source
15472        // 2. The identifier is a reserved keyword for the current dialect
15473        // 3. The config says to always quote identifiers (e.g., Athena/Presto)
15474        // This matches Python sqlglot's identifier_sql behavior
15475        // Also quote identifiers starting with digits if the target dialect doesn't support them
15476        let starts_with_digit = name.chars().next().map_or(false, |c| c.is_ascii_digit());
15477        let needs_digit_quoting = starts_with_digit
15478            && !self.config.identifiers_can_start_with_digit
15479            && self.config.dialect.is_some();
15480        let mysql_invalid_hex_identifier = matches!(self.config.dialect, Some(DialectType::MySQL))
15481            && name.len() > 2
15482            && (name.starts_with("0x") || name.starts_with("0X"))
15483            && !name[2..].chars().all(|c| c.is_ascii_hexdigit());
15484        let clickhouse_unsafe_identifier =
15485            matches!(self.config.dialect, Some(DialectType::ClickHouse))
15486                && matches!(self.config.source_dialect, Some(DialectType::ClickHouse))
15487                && !name.starts_with('{')
15488                && !name.contains('(')
15489                && !name.contains(')')
15490                && name != "?"
15491                && name
15492                    .chars()
15493                    .any(|c| !(c.is_ascii_alphanumeric() || c == '_'));
15494        let needs_quoting = id.quoted
15495            || self.is_reserved_keyword(name)
15496            || self.config.always_quote_identifiers
15497            || needs_digit_quoting
15498            || mysql_invalid_hex_identifier
15499            || clickhouse_unsafe_identifier;
15500
15501        // Check for MySQL index column prefix length: name(16) or name(16) ASC/DESC
15502        // When quoted, we need to output `name`(16) not `name(16)`
15503        let (base_name, suffix) = if needs_quoting {
15504            // Try to extract prefix length from identifier: name(number) or name(number) ASC/DESC
15505            if let Some(paren_pos) = name.find('(') {
15506                let base = &name[..paren_pos];
15507                let rest = &name[paren_pos..];
15508                // Verify it looks like (digits) or (digits) ASC/DESC
15509                if rest.starts_with('(')
15510                    && (rest.ends_with(')') || rest.ends_with(") ASC") || rest.ends_with(") DESC"))
15511                {
15512                    // Check if content between parens is all digits
15513                    let close_paren = rest.find(')').unwrap_or(rest.len());
15514                    let inside = &rest[1..close_paren];
15515                    if inside.chars().all(|c| c.is_ascii_digit()) {
15516                        (base.to_string(), rest.to_string())
15517                    } else {
15518                        (name.to_string(), String::new())
15519                    }
15520                } else {
15521                    (name.to_string(), String::new())
15522                }
15523            } else if name.ends_with(" ASC") {
15524                let base = &name[..name.len() - 4];
15525                (base.to_string(), " ASC".to_string())
15526            } else if name.ends_with(" DESC") {
15527                let base = &name[..name.len() - 5];
15528                (base.to_string(), " DESC".to_string())
15529            } else {
15530                (name.to_string(), String::new())
15531            }
15532        } else {
15533            (name.to_string(), String::new())
15534        };
15535
15536        // Normalize identifier if configured, with special handling for Exasol
15537        // Exasol uses UPPERCASE normalization strategy, so reserved keywords that need quoting
15538        // should be uppercased when not already quoted (to match Python sqlglot behavior)
15539        let output_name = if self.config.normalize_identifiers && !id.quoted {
15540            base_name.to_ascii_lowercase()
15541        } else if matches!(self.config.dialect, Some(DialectType::Exasol))
15542            && !id.quoted
15543            && self.is_reserved_keyword(name)
15544        {
15545            // Exasol: uppercase reserved keywords when quoting them
15546            // This matches Python sqlglot's behavior with NORMALIZATION_STRATEGY = UPPERCASE
15547            base_name.to_ascii_uppercase()
15548        } else {
15549            base_name
15550        };
15551
15552        if needs_quoting {
15553            // Escape any quote characters within the identifier
15554            let escaped_name = if quote_style.start == quote_style.end {
15555                // Same start/end char (e.g., " or `) - double the quote char
15556                output_name.replace(
15557                    quote_style.end,
15558                    &format!("{}{}", quote_style.end, quote_style.end),
15559                )
15560            } else {
15561                // Different start/end (e.g., [ and ]) - escape only the end char
15562                output_name.replace(
15563                    quote_style.end,
15564                    &format!("{}{}", quote_style.end, quote_style.end),
15565                )
15566            };
15567            self.write(&format!(
15568                "{}{}{}{}",
15569                quote_style.start, escaped_name, quote_style.end, suffix
15570            ));
15571        } else {
15572            self.write(&output_name);
15573        }
15574
15575        // Output trailing comments
15576        for comment in &id.trailing_comments {
15577            self.write(" ");
15578            self.write_formatted_comment(comment);
15579        }
15580        Ok(())
15581    }
15582
15583    fn generate_column(&mut self, col: &Column) -> Result<()> {
15584        use crate::dialects::DialectType;
15585
15586        if let Some(table) = &col.table {
15587            // Exasol special case: LOCAL as column table prefix should NOT be quoted
15588            // LOCAL is a special keyword in Exasol for referencing aliases from the current scope
15589            // Only applies when: dialect is Exasol, name is "LOCAL" (case-insensitive), and not already quoted
15590            let is_exasol_local_prefix = matches!(self.config.dialect, Some(DialectType::Exasol))
15591                && !table.quoted
15592                && table.name.eq_ignore_ascii_case("LOCAL");
15593
15594            if is_exasol_local_prefix {
15595                // Write LOCAL unquoted (this is special Exasol syntax, not a table reference)
15596                self.write("LOCAL");
15597            } else {
15598                self.generate_identifier(table)?;
15599            }
15600            self.write(".");
15601        }
15602        self.generate_identifier(&col.name)?;
15603        // Oracle-style join marker (+)
15604        // Only output if dialect supports it (Oracle, Exasol)
15605        if col.join_mark && self.config.supports_column_join_marks {
15606            self.write(" (+)");
15607        }
15608        // Output trailing comments
15609        for comment in &col.trailing_comments {
15610            self.write_space();
15611            self.write_formatted_comment(comment);
15612        }
15613        Ok(())
15614    }
15615
15616    /// Generate a pseudocolumn (Oracle ROWNUM, ROWID, LEVEL, etc.)
15617    /// Pseudocolumns should NEVER be quoted, as quoting breaks them in Oracle
15618    fn generate_pseudocolumn(&mut self, pc: &Pseudocolumn) -> Result<()> {
15619        use crate::dialects::DialectType;
15620        use crate::expressions::PseudocolumnType;
15621
15622        // SYSDATE -> CURRENT_TIMESTAMP for non-Oracle/Redshift dialects
15623        if pc.kind == PseudocolumnType::Sysdate
15624            && !matches!(
15625                self.config.dialect,
15626                Some(DialectType::Oracle) | Some(DialectType::Redshift) | None
15627            )
15628        {
15629            self.write_keyword("CURRENT_TIMESTAMP");
15630            // Add () for dialects that expect it
15631            if matches!(
15632                self.config.dialect,
15633                Some(DialectType::MySQL)
15634                    | Some(DialectType::ClickHouse)
15635                    | Some(DialectType::Spark)
15636                    | Some(DialectType::Databricks)
15637                    | Some(DialectType::Hive)
15638            ) {
15639                self.write("()");
15640            }
15641        } else {
15642            self.write(pc.kind.as_str());
15643        }
15644        Ok(())
15645    }
15646
15647    /// Generate CONNECT BY clause (Oracle hierarchical queries)
15648    fn generate_connect(&mut self, connect: &Connect) -> Result<()> {
15649        use crate::dialects::DialectType;
15650
15651        // Generate native CONNECT BY for Oracle and Snowflake
15652        // For other dialects, add a comment noting manual conversion needed
15653        let supports_connect_by = matches!(
15654            self.config.dialect,
15655            Some(DialectType::Oracle) | Some(DialectType::Snowflake)
15656        );
15657
15658        if !supports_connect_by && self.config.dialect.is_some() {
15659            // Add comment for unsupported dialects
15660            if self.config.pretty {
15661                self.write_newline();
15662            } else {
15663                self.write_space();
15664            }
15665            self.write_unsupported_comment(
15666                "CONNECT BY requires manual conversion to recursive CTE",
15667            )?;
15668        }
15669
15670        // Generate START WITH if present (before CONNECT BY)
15671        if let Some(start) = &connect.start {
15672            if self.config.pretty {
15673                self.write_newline();
15674            } else {
15675                self.write_space();
15676            }
15677            self.write_keyword("START WITH");
15678            self.write_space();
15679            self.generate_expression(start)?;
15680        }
15681
15682        // Generate CONNECT BY
15683        if self.config.pretty {
15684            self.write_newline();
15685        } else {
15686            self.write_space();
15687        }
15688        self.write_keyword("CONNECT BY");
15689        if connect.nocycle {
15690            self.write_space();
15691            self.write_keyword("NOCYCLE");
15692        }
15693        self.write_space();
15694        self.generate_expression(&connect.connect)?;
15695
15696        Ok(())
15697    }
15698
15699    /// Generate Connect expression (for Expression::Connect variant)
15700    fn generate_connect_expr(&mut self, connect: &Connect) -> Result<()> {
15701        self.generate_connect(connect)
15702    }
15703
15704    /// Generate PRIOR expression
15705    fn generate_prior(&mut self, prior: &Prior) -> Result<()> {
15706        self.write_keyword("PRIOR");
15707        self.write_space();
15708        self.generate_expression(&prior.this)?;
15709        Ok(())
15710    }
15711
15712    /// Generate CONNECT_BY_ROOT function
15713    /// Syntax: CONNECT_BY_ROOT column (no parentheses)
15714    fn generate_connect_by_root(&mut self, cbr: &ConnectByRoot) -> Result<()> {
15715        self.write_keyword("CONNECT_BY_ROOT");
15716        self.write_space();
15717        self.generate_expression(&cbr.this)?;
15718        Ok(())
15719    }
15720
15721    /// Generate MATCH_RECOGNIZE clause
15722    fn generate_match_recognize(&mut self, mr: &MatchRecognize) -> Result<()> {
15723        use crate::dialects::DialectType;
15724
15725        // MATCH_RECOGNIZE is supported in Oracle, Snowflake, Presto, and Trino
15726        let supports_match_recognize = matches!(
15727            self.config.dialect,
15728            Some(DialectType::Oracle)
15729                | Some(DialectType::Snowflake)
15730                | Some(DialectType::Presto)
15731                | Some(DialectType::Trino)
15732        );
15733
15734        // Generate the source table first
15735        if let Some(source) = &mr.this {
15736            self.generate_expression(source)?;
15737        }
15738
15739        if !supports_match_recognize {
15740            self.write_unsupported_comment("MATCH_RECOGNIZE not supported in this dialect")?;
15741            return Ok(());
15742        }
15743
15744        // In pretty mode, MATCH_RECOGNIZE should be on a new line
15745        if self.config.pretty {
15746            self.write_newline();
15747        } else {
15748            self.write_space();
15749        }
15750
15751        self.write_keyword("MATCH_RECOGNIZE");
15752        self.write(" (");
15753
15754        if self.config.pretty {
15755            self.indent_level += 1;
15756        }
15757
15758        let mut needs_separator = false;
15759
15760        // PARTITION BY
15761        if let Some(partition_by) = &mr.partition_by {
15762            if !partition_by.is_empty() {
15763                if self.config.pretty {
15764                    self.write_newline();
15765                    self.write_indent();
15766                }
15767                self.write_keyword("PARTITION BY");
15768                self.write_space();
15769                for (i, expr) in partition_by.iter().enumerate() {
15770                    if i > 0 {
15771                        self.write(", ");
15772                    }
15773                    self.generate_expression(expr)?;
15774                }
15775                needs_separator = true;
15776            }
15777        }
15778
15779        // ORDER BY
15780        if let Some(order_by) = &mr.order_by {
15781            if !order_by.is_empty() {
15782                if needs_separator {
15783                    if self.config.pretty {
15784                        self.write_newline();
15785                        self.write_indent();
15786                    } else {
15787                        self.write_space();
15788                    }
15789                } else if self.config.pretty {
15790                    self.write_newline();
15791                    self.write_indent();
15792                }
15793                self.write_keyword("ORDER BY");
15794                // In pretty mode, put each ORDER BY column on a new indented line
15795                if self.config.pretty {
15796                    self.indent_level += 1;
15797                    for (i, ordered) in order_by.iter().enumerate() {
15798                        if i > 0 {
15799                            self.write(",");
15800                        }
15801                        self.write_newline();
15802                        self.write_indent();
15803                        self.generate_ordered(ordered)?;
15804                    }
15805                    self.indent_level -= 1;
15806                } else {
15807                    self.write_space();
15808                    for (i, ordered) in order_by.iter().enumerate() {
15809                        if i > 0 {
15810                            self.write(", ");
15811                        }
15812                        self.generate_ordered(ordered)?;
15813                    }
15814                }
15815                needs_separator = true;
15816            }
15817        }
15818
15819        // MEASURES
15820        if let Some(measures) = &mr.measures {
15821            if !measures.is_empty() {
15822                if needs_separator {
15823                    if self.config.pretty {
15824                        self.write_newline();
15825                        self.write_indent();
15826                    } else {
15827                        self.write_space();
15828                    }
15829                } else if self.config.pretty {
15830                    self.write_newline();
15831                    self.write_indent();
15832                }
15833                self.write_keyword("MEASURES");
15834                // In pretty mode, put each MEASURE on a new indented line
15835                if self.config.pretty {
15836                    self.indent_level += 1;
15837                    for (i, measure) in measures.iter().enumerate() {
15838                        if i > 0 {
15839                            self.write(",");
15840                        }
15841                        self.write_newline();
15842                        self.write_indent();
15843                        // Handle RUNNING/FINAL prefix
15844                        if let Some(semantics) = &measure.window_frame {
15845                            match semantics {
15846                                MatchRecognizeSemantics::Running => {
15847                                    self.write_keyword("RUNNING");
15848                                    self.write_space();
15849                                }
15850                                MatchRecognizeSemantics::Final => {
15851                                    self.write_keyword("FINAL");
15852                                    self.write_space();
15853                                }
15854                            }
15855                        }
15856                        self.generate_expression(&measure.this)?;
15857                    }
15858                    self.indent_level -= 1;
15859                } else {
15860                    self.write_space();
15861                    for (i, measure) in measures.iter().enumerate() {
15862                        if i > 0 {
15863                            self.write(", ");
15864                        }
15865                        // Handle RUNNING/FINAL prefix
15866                        if let Some(semantics) = &measure.window_frame {
15867                            match semantics {
15868                                MatchRecognizeSemantics::Running => {
15869                                    self.write_keyword("RUNNING");
15870                                    self.write_space();
15871                                }
15872                                MatchRecognizeSemantics::Final => {
15873                                    self.write_keyword("FINAL");
15874                                    self.write_space();
15875                                }
15876                            }
15877                        }
15878                        self.generate_expression(&measure.this)?;
15879                    }
15880                }
15881                needs_separator = true;
15882            }
15883        }
15884
15885        // Row semantics (ONE ROW PER MATCH, ALL ROWS PER MATCH, etc.)
15886        if let Some(rows) = &mr.rows {
15887            if needs_separator {
15888                if self.config.pretty {
15889                    self.write_newline();
15890                    self.write_indent();
15891                } else {
15892                    self.write_space();
15893                }
15894            } else if self.config.pretty {
15895                self.write_newline();
15896                self.write_indent();
15897            }
15898            match rows {
15899                MatchRecognizeRows::OneRowPerMatch => {
15900                    self.write_keyword("ONE ROW PER MATCH");
15901                }
15902                MatchRecognizeRows::AllRowsPerMatch => {
15903                    self.write_keyword("ALL ROWS PER MATCH");
15904                }
15905                MatchRecognizeRows::AllRowsPerMatchShowEmptyMatches => {
15906                    self.write_keyword("ALL ROWS PER MATCH SHOW EMPTY MATCHES");
15907                }
15908                MatchRecognizeRows::AllRowsPerMatchOmitEmptyMatches => {
15909                    self.write_keyword("ALL ROWS PER MATCH OMIT EMPTY MATCHES");
15910                }
15911                MatchRecognizeRows::AllRowsPerMatchWithUnmatchedRows => {
15912                    self.write_keyword("ALL ROWS PER MATCH WITH UNMATCHED ROWS");
15913                }
15914            }
15915            needs_separator = true;
15916        }
15917
15918        // AFTER MATCH SKIP
15919        if let Some(after) = &mr.after {
15920            if needs_separator {
15921                if self.config.pretty {
15922                    self.write_newline();
15923                    self.write_indent();
15924                } else {
15925                    self.write_space();
15926                }
15927            } else if self.config.pretty {
15928                self.write_newline();
15929                self.write_indent();
15930            }
15931            match after {
15932                MatchRecognizeAfter::PastLastRow => {
15933                    self.write_keyword("AFTER MATCH SKIP PAST LAST ROW");
15934                }
15935                MatchRecognizeAfter::ToNextRow => {
15936                    self.write_keyword("AFTER MATCH SKIP TO NEXT ROW");
15937                }
15938                MatchRecognizeAfter::ToFirst(ident) => {
15939                    self.write_keyword("AFTER MATCH SKIP TO FIRST");
15940                    self.write_space();
15941                    self.generate_identifier(ident)?;
15942                }
15943                MatchRecognizeAfter::ToLast(ident) => {
15944                    self.write_keyword("AFTER MATCH SKIP TO LAST");
15945                    self.write_space();
15946                    self.generate_identifier(ident)?;
15947                }
15948            }
15949            needs_separator = true;
15950        }
15951
15952        // PATTERN
15953        if let Some(pattern) = &mr.pattern {
15954            if needs_separator {
15955                if self.config.pretty {
15956                    self.write_newline();
15957                    self.write_indent();
15958                } else {
15959                    self.write_space();
15960                }
15961            } else if self.config.pretty {
15962                self.write_newline();
15963                self.write_indent();
15964            }
15965            self.write_keyword("PATTERN");
15966            self.write_space();
15967            self.write("(");
15968            self.write(pattern);
15969            self.write(")");
15970            needs_separator = true;
15971        }
15972
15973        // DEFINE
15974        if let Some(define) = &mr.define {
15975            if !define.is_empty() {
15976                if needs_separator {
15977                    if self.config.pretty {
15978                        self.write_newline();
15979                        self.write_indent();
15980                    } else {
15981                        self.write_space();
15982                    }
15983                } else if self.config.pretty {
15984                    self.write_newline();
15985                    self.write_indent();
15986                }
15987                self.write_keyword("DEFINE");
15988                // In pretty mode, put each DEFINE on a new indented line
15989                if self.config.pretty {
15990                    self.indent_level += 1;
15991                    for (i, (name, expr)) in define.iter().enumerate() {
15992                        if i > 0 {
15993                            self.write(",");
15994                        }
15995                        self.write_newline();
15996                        self.write_indent();
15997                        self.generate_identifier(name)?;
15998                        self.write(" AS ");
15999                        self.generate_expression(expr)?;
16000                    }
16001                    self.indent_level -= 1;
16002                } else {
16003                    self.write_space();
16004                    for (i, (name, expr)) in define.iter().enumerate() {
16005                        if i > 0 {
16006                            self.write(", ");
16007                        }
16008                        self.generate_identifier(name)?;
16009                        self.write(" AS ");
16010                        self.generate_expression(expr)?;
16011                    }
16012                }
16013            }
16014        }
16015
16016        if self.config.pretty {
16017            self.indent_level -= 1;
16018            self.write_newline();
16019        }
16020        self.write(")");
16021
16022        // Alias - only include AS if it was explicitly present in the input
16023        if let Some(alias) = &mr.alias {
16024            self.write(" ");
16025            if mr.alias_explicit_as {
16026                self.write_keyword("AS");
16027                self.write(" ");
16028            }
16029            self.generate_identifier(alias)?;
16030        }
16031
16032        Ok(())
16033    }
16034
16035    /// Generate a query hint /*+ ... */
16036    fn generate_hint(&mut self, hint: &Hint) -> Result<()> {
16037        use crate::dialects::DialectType;
16038
16039        // Output hints for dialects that support them, or when no dialect is specified (identity tests)
16040        let supports_hints = matches!(
16041            self.config.dialect,
16042            None |  // No dialect = preserve everything
16043            Some(DialectType::Oracle) | Some(DialectType::MySQL) |
16044            Some(DialectType::Spark) | Some(DialectType::Hive) |
16045            Some(DialectType::Databricks) | Some(DialectType::PostgreSQL)
16046        );
16047
16048        if !supports_hints || hint.expressions.is_empty() {
16049            return Ok(());
16050        }
16051
16052        // First, expand raw hint text into individual hint strings
16053        // This handles the case where the parser stored multiple hints as a single raw string
16054        let mut hint_strings: Vec<String> = Vec::new();
16055        for expr in &hint.expressions {
16056            match expr {
16057                HintExpression::Raw(text) => {
16058                    // Parse raw hint text into individual hint function calls
16059                    let parsed = self.parse_raw_hint_text(text);
16060                    hint_strings.extend(parsed);
16061                }
16062                _ => {
16063                    hint_strings.push(self.hint_expression_to_string(expr)?);
16064                }
16065            }
16066        }
16067
16068        // In pretty mode with multiple hints, always use multiline format
16069        // This matches Python sqlglot's behavior where expressions() with default dynamic=False
16070        // always joins with newlines in pretty mode
16071        let use_multiline = self.config.pretty && hint_strings.len() > 1;
16072
16073        if use_multiline {
16074            // Pretty print with each hint on its own line
16075            self.write(" /*+ ");
16076            for (i, hint_str) in hint_strings.iter().enumerate() {
16077                if i > 0 {
16078                    self.write_newline();
16079                    self.write("  "); // 2-space indent within hint block
16080                }
16081                self.write(hint_str);
16082            }
16083            self.write(" */");
16084        } else {
16085            // Single line format
16086            self.write(" /*+ ");
16087            let sep = match self.config.dialect {
16088                Some(DialectType::Spark) | Some(DialectType::Databricks) => ", ",
16089                _ => " ",
16090            };
16091            for (i, hint_str) in hint_strings.iter().enumerate() {
16092                if i > 0 {
16093                    self.write(sep);
16094                }
16095                self.write(hint_str);
16096            }
16097            self.write(" */");
16098        }
16099
16100        Ok(())
16101    }
16102
16103    /// Parse raw hint text into individual hint function calls
16104    /// e.g., "LEADING(a b) USE_NL(c)" -> ["LEADING(a b)", "USE_NL(c)"]
16105    /// If the hint contains unparseable content (like SQL keywords), return as single raw string
16106    fn parse_raw_hint_text(&self, text: &str) -> Vec<String> {
16107        let mut results = Vec::new();
16108        let mut chars = text.chars().peekable();
16109        let mut current = String::new();
16110        let mut paren_depth = 0;
16111        let mut has_unparseable_content = false;
16112        let mut position_after_last_function = 0;
16113        let mut char_position = 0;
16114
16115        while let Some(c) = chars.next() {
16116            char_position += c.len_utf8();
16117            match c {
16118                '(' => {
16119                    paren_depth += 1;
16120                    current.push(c);
16121                }
16122                ')' => {
16123                    paren_depth -= 1;
16124                    current.push(c);
16125                    // When we close the outer parenthesis, we've completed a hint function
16126                    if paren_depth == 0 {
16127                        let trimmed = current.trim().to_string();
16128                        if !trimmed.is_empty() {
16129                            // Format this hint for pretty printing if needed
16130                            let formatted = self.format_hint_function(&trimmed);
16131                            results.push(formatted);
16132                        }
16133                        current.clear();
16134                        position_after_last_function = char_position;
16135                    }
16136                }
16137                ' ' | '\t' | '\n' | ',' if paren_depth == 0 => {
16138                    // Space/comma/whitespace outside parentheses - skip
16139                }
16140                _ if paren_depth == 0 => {
16141                    // Character outside parentheses - accumulate for potential hint name
16142                    current.push(c);
16143                }
16144                _ => {
16145                    current.push(c);
16146                }
16147            }
16148        }
16149
16150        // Check if there's remaining text after the last function call
16151        let remaining_text = text[position_after_last_function..].trim();
16152        if !remaining_text.is_empty() {
16153            // Check if it looks like valid hint function names
16154            // Valid hint identifiers typically are uppercase alphanumeric with underscores
16155            // If we see multiple words without parens, it's likely unparseable
16156            let words: Vec<&str> = remaining_text.split_whitespace().collect();
16157            let looks_like_hint_functions = words.iter().all(|word| {
16158                // A valid hint name followed by opening paren, or a standalone uppercase identifier
16159                word.contains('(') || (word.chars().all(|c| c.is_ascii_uppercase() || c == '_'))
16160            });
16161
16162            if !looks_like_hint_functions && words.len() > 1 {
16163                has_unparseable_content = true;
16164            }
16165        }
16166
16167        // If we detected unparseable content (like SQL keywords), return the whole hint as-is
16168        if has_unparseable_content {
16169            return vec![text.trim().to_string()];
16170        }
16171
16172        // If we couldn't parse anything, return the original text as a single hint
16173        if results.is_empty() {
16174            results.push(text.trim().to_string());
16175        }
16176
16177        results
16178    }
16179
16180    /// Format a hint function for pretty printing
16181    /// e.g., "LEADING(aaa bbb ccc ddd)" -> multiline if args are too wide
16182    fn format_hint_function(&self, hint: &str) -> String {
16183        if !self.config.pretty {
16184            return hint.to_string();
16185        }
16186
16187        // Try to parse NAME(args) pattern
16188        if let Some(paren_pos) = hint.find('(') {
16189            if hint.ends_with(')') {
16190                let name = &hint[..paren_pos];
16191                let args_str = &hint[paren_pos + 1..hint.len() - 1];
16192
16193                // Parse arguments (space-separated for Oracle hints)
16194                let args: Vec<&str> = args_str.split_whitespace().collect();
16195
16196                // Calculate total width of arguments
16197                let total_args_width: usize =
16198                    args.iter().map(|s| s.len()).sum::<usize>() + args.len().saturating_sub(1); // spaces between args
16199
16200                // If too wide, format on multiple lines
16201                if total_args_width > self.config.max_text_width && !args.is_empty() {
16202                    let mut result = format!("{}(\n", name);
16203                    for arg in &args {
16204                        result.push_str("    "); // 4-space indent for args
16205                        result.push_str(arg);
16206                        result.push('\n');
16207                    }
16208                    result.push_str("  )"); // 2-space indent for closing paren
16209                    return result;
16210                }
16211            }
16212        }
16213
16214        hint.to_string()
16215    }
16216
16217    /// Convert a hint expression to a string, handling multiline formatting for long arguments
16218    fn hint_expression_to_string(&mut self, expr: &HintExpression) -> Result<String> {
16219        match expr {
16220            HintExpression::Function { name, args } => {
16221                // Generate each argument to a string
16222                let arg_strings: Vec<String> = args
16223                    .iter()
16224                    .map(|arg| {
16225                        let mut gen = Generator::with_arc_config(self.config.clone());
16226                        gen.generate_expression(arg)?;
16227                        Ok(gen.output)
16228                    })
16229                    .collect::<Result<Vec<_>>>()?;
16230
16231                // Oracle hints use space-separated arguments, not comma-separated
16232                let total_args_width: usize = arg_strings.iter().map(|s| s.len()).sum::<usize>()
16233                    + arg_strings.len().saturating_sub(1); // spaces between args
16234
16235                // Check if function args need multiline formatting
16236                // Use too_wide check for argument formatting
16237                let args_multiline =
16238                    self.config.pretty && total_args_width > self.config.max_text_width;
16239
16240                if args_multiline && !arg_strings.is_empty() {
16241                    // Multiline format for long argument lists
16242                    let mut result = format!("{}(\n", name);
16243                    for arg_str in &arg_strings {
16244                        result.push_str("    "); // 4-space indent for args
16245                        result.push_str(arg_str);
16246                        result.push('\n');
16247                    }
16248                    result.push_str("  )"); // 2-space indent for closing paren
16249                    Ok(result)
16250                } else {
16251                    // Single line format with space-separated args (Oracle style)
16252                    let args_str = arg_strings.join(" ");
16253                    Ok(format!("{}({})", name, args_str))
16254                }
16255            }
16256            HintExpression::Identifier(name) => Ok(name.clone()),
16257            HintExpression::Raw(text) => {
16258                // For pretty printing, try to format the raw text
16259                if self.config.pretty {
16260                    Ok(self.format_hint_function(text))
16261                } else {
16262                    Ok(text.clone())
16263                }
16264            }
16265        }
16266    }
16267
16268    fn generate_table(&mut self, table: &TableRef) -> Result<()> {
16269        // PostgreSQL ONLY modifier: prevents scanning child tables
16270        if table.only {
16271            self.write_keyword("ONLY");
16272            self.write_space();
16273        }
16274
16275        // Check for IDENTIFIER() (Snowflake) or OPENDATASOURCE(...).db.schema.table (TSQL)
16276        if let Some(ref identifier_func) = table.identifier_func {
16277            self.generate_expression(identifier_func)?;
16278            // If table name parts are present, emit .catalog.schema.name after the function
16279            if !table.name.name.is_empty() {
16280                if let Some(catalog) = &table.catalog {
16281                    self.write(".");
16282                    self.generate_identifier(catalog)?;
16283                }
16284                if let Some(schema) = &table.schema {
16285                    self.write(".");
16286                    self.generate_identifier(schema)?;
16287                }
16288                self.write(".");
16289                self.generate_identifier(&table.name)?;
16290            }
16291        } else {
16292            if let Some(catalog) = &table.catalog {
16293                self.generate_identifier(catalog)?;
16294                self.write(".");
16295            }
16296            if let Some(schema) = &table.schema {
16297                self.generate_identifier(schema)?;
16298                self.write(".");
16299            }
16300            self.generate_identifier(&table.name)?;
16301        }
16302
16303        // Output Snowflake CHANGES clause (before partition, includes its own AT/BEFORE/END)
16304        if let Some(changes) = &table.changes {
16305            self.write(" ");
16306            self.generate_changes(changes)?;
16307        }
16308
16309        // Output MySQL PARTITION clause: t1 PARTITION(p0, p1)
16310        if !table.partitions.is_empty() {
16311            self.write_space();
16312            self.write_keyword("PARTITION");
16313            self.write("(");
16314            for (i, partition) in table.partitions.iter().enumerate() {
16315                if i > 0 {
16316                    self.write(", ");
16317                }
16318                self.generate_identifier(partition)?;
16319            }
16320            self.write(")");
16321        }
16322
16323        // Output time travel clause: BEFORE (STATEMENT => ...) or AT (TIMESTAMP => ...)
16324        // Skip if CHANGES clause is present (CHANGES includes its own time travel)
16325        if table.changes.is_none() {
16326            if let Some(when) = &table.when {
16327                self.write_space();
16328                self.generate_historical_data(when)?;
16329            }
16330        }
16331
16332        // Output TSQL FOR SYSTEM_TIME temporal clause (before alias, except BigQuery)
16333        let system_time_post_alias = matches!(self.config.dialect, Some(DialectType::BigQuery));
16334        if !system_time_post_alias {
16335            if let Some(ref system_time) = table.system_time {
16336                self.write_space();
16337                self.write(system_time);
16338            }
16339        }
16340
16341        // Output Presto/Trino time travel: FOR VERSION AS OF / FOR TIMESTAMP AS OF
16342        if let Some(ref version) = table.version {
16343            self.write_space();
16344            self.generate_version(version)?;
16345        }
16346
16347        // When alias_post_tablesample is true, the order is: table TABLESAMPLE (...) alias
16348        // When alias_post_tablesample is false (default), the order is: table alias TABLESAMPLE (...)
16349        // Oracle, Hive, Spark use ALIAS_POST_TABLESAMPLE = true (alias comes after sample)
16350        let alias_post_tablesample = self.config.alias_post_tablesample;
16351
16352        if alias_post_tablesample {
16353            // TABLESAMPLE before alias (Oracle, Hive, Spark)
16354            self.generate_table_sample_clause(table)?;
16355        }
16356
16357        // Output table hints (TSQL: WITH (TABLOCK, INDEX(myindex), ...))
16358        // For SQLite, INDEXED BY hints come after the alias, so skip here
16359        let is_sqlite_hint = matches!(self.config.dialect, Some(DialectType::SQLite))
16360            && table.hints.iter().any(|h| {
16361                if let Expression::Identifier(id) = h {
16362                    id.name.starts_with("INDEXED BY") || id.name == "NOT INDEXED"
16363                } else {
16364                    false
16365                }
16366            });
16367        if !table.hints.is_empty() && !is_sqlite_hint {
16368            for hint in &table.hints {
16369                self.write_space();
16370                self.generate_expression(hint)?;
16371            }
16372        }
16373
16374        if let Some(alias) = &table.alias {
16375            self.write_space();
16376            // Output AS if it was explicitly present in the input, OR for certain dialects/cases
16377            // Generic mode and most dialects always use AS for table aliases
16378            let always_use_as = self.config.dialect.is_none()
16379                || matches!(
16380                    self.config.dialect,
16381                    Some(DialectType::Generic)
16382                        | Some(DialectType::PostgreSQL)
16383                        | Some(DialectType::Redshift)
16384                        | Some(DialectType::Snowflake)
16385                        | Some(DialectType::BigQuery)
16386                        | Some(DialectType::DuckDB)
16387                        | Some(DialectType::Presto)
16388                        | Some(DialectType::Trino)
16389                        | Some(DialectType::TSQL)
16390                        | Some(DialectType::Fabric)
16391                        | Some(DialectType::MySQL)
16392                        | Some(DialectType::Spark)
16393                        | Some(DialectType::Hive)
16394                        | Some(DialectType::SQLite)
16395                        | Some(DialectType::Drill)
16396                );
16397            let is_stage_ref = table.name.name.starts_with('@');
16398            // Oracle never uses AS for table aliases
16399            let suppress_as = matches!(self.config.dialect, Some(DialectType::Oracle));
16400            if !suppress_as && (table.alias_explicit_as || always_use_as || is_stage_ref) {
16401                self.write_keyword("AS");
16402                self.write_space();
16403            }
16404            self.generate_identifier(alias)?;
16405
16406            // Output column aliases if present: AS t(c1, c2)
16407            // Skip for dialects that don't support table alias columns (BigQuery, SQLite)
16408            if !table.column_aliases.is_empty() && self.config.supports_table_alias_columns {
16409                self.write("(");
16410                for (i, col_alias) in table.column_aliases.iter().enumerate() {
16411                    if i > 0 {
16412                        self.write(", ");
16413                    }
16414                    self.generate_identifier(col_alias)?;
16415                }
16416                self.write(")");
16417            }
16418        }
16419
16420        // BigQuery: FOR SYSTEM_TIME AS OF after alias
16421        if system_time_post_alias {
16422            if let Some(ref system_time) = table.system_time {
16423                self.write_space();
16424                self.write(system_time);
16425            }
16426        }
16427
16428        // For default behavior (alias_post_tablesample = false), output TABLESAMPLE after alias
16429        if !alias_post_tablesample {
16430            self.generate_table_sample_clause(table)?;
16431        }
16432
16433        // Output SQLite INDEXED BY / NOT INDEXED hints after alias
16434        if is_sqlite_hint {
16435            for hint in &table.hints {
16436                self.write_space();
16437                self.generate_expression(hint)?;
16438            }
16439        }
16440
16441        // ClickHouse FINAL modifier
16442        if table.final_ && matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
16443            self.write_space();
16444            self.write_keyword("FINAL");
16445        }
16446
16447        // Output trailing comments
16448        for comment in &table.trailing_comments {
16449            self.write_space();
16450            self.write_formatted_comment(comment);
16451        }
16452        // Note: leading_comments (from before table in FROM clause) are intentionally NOT
16453        // output here - they are output by the FROM/PIVOT generator after the full expression
16454
16455        Ok(())
16456    }
16457
16458    /// Helper to output TABLESAMPLE clause for a table reference
16459    fn generate_table_sample_clause(&mut self, table: &TableRef) -> Result<()> {
16460        if let Some(ref ts) = table.table_sample {
16461            self.write_space();
16462            if ts.is_using_sample {
16463                self.write_keyword("USING SAMPLE");
16464            } else {
16465                // Use the configured tablesample keyword (e.g., "TABLESAMPLE" or "SAMPLE")
16466                self.write_keyword(self.config.tablesample_keywords);
16467            }
16468            self.generate_sample_body(ts)?;
16469            // Seed for table-level sample - use dialect's configured keyword
16470            if let Some(ref seed) = ts.seed {
16471                self.write_space();
16472                self.write_keyword(self.config.tablesample_seed_keyword);
16473                self.write(" (");
16474                self.generate_expression(seed)?;
16475                self.write(")");
16476            }
16477        }
16478        Ok(())
16479    }
16480
16481    fn generate_stage_reference(&mut self, sr: &StageReference) -> Result<()> {
16482        // Output: '@stage_name/path' if quoted, or @stage_name/path otherwise
16483        // Optionally followed by (FILE_FORMAT => 'fmt', PATTERN => '*.csv')
16484
16485        if sr.quoted {
16486            self.write("'");
16487        }
16488
16489        self.write(&sr.name);
16490        if let Some(path) = &sr.path {
16491            self.write(path);
16492        }
16493
16494        if sr.quoted {
16495            self.write("'");
16496        }
16497
16498        // Output FILE_FORMAT and PATTERN if present
16499        let has_options = sr.file_format.is_some() || sr.pattern.is_some();
16500        if has_options {
16501            self.write(" (");
16502            let mut first = true;
16503
16504            if let Some(file_format) = &sr.file_format {
16505                if !first {
16506                    self.write(", ");
16507                }
16508                self.write_keyword("FILE_FORMAT");
16509                self.write(" => ");
16510                self.generate_expression(file_format)?;
16511                first = false;
16512            }
16513
16514            if let Some(pattern) = &sr.pattern {
16515                if !first {
16516                    self.write(", ");
16517                }
16518                self.write_keyword("PATTERN");
16519                self.write(" => '");
16520                self.write(pattern);
16521                self.write("'");
16522            }
16523
16524            self.write(")");
16525        }
16526        Ok(())
16527    }
16528
16529    fn generate_star(&mut self, star: &Star) -> Result<()> {
16530        use crate::dialects::DialectType;
16531
16532        if let Some(table) = &star.table {
16533            self.generate_identifier(table)?;
16534            self.write(".");
16535        }
16536        self.write("*");
16537
16538        // Generate EXCLUDE/EXCEPT clause based on dialect
16539        if let Some(except) = &star.except {
16540            if !except.is_empty() {
16541                self.write_space();
16542                // Use dialect-appropriate keyword
16543                match self.config.dialect {
16544                    Some(DialectType::BigQuery) => self.write_keyword("EXCEPT"),
16545                    Some(DialectType::DuckDB) | Some(DialectType::Snowflake) => {
16546                        self.write_keyword("EXCLUDE")
16547                    }
16548                    _ => self.write_keyword("EXCEPT"), // Default to EXCEPT
16549                }
16550                self.write(" (");
16551                for (i, col) in except.iter().enumerate() {
16552                    if i > 0 {
16553                        self.write(", ");
16554                    }
16555                    self.generate_identifier(col)?;
16556                }
16557                self.write(")");
16558            }
16559        }
16560
16561        // Generate REPLACE clause
16562        if let Some(replace) = &star.replace {
16563            if !replace.is_empty() {
16564                self.write_space();
16565                self.write_keyword("REPLACE");
16566                self.write(" (");
16567                for (i, alias) in replace.iter().enumerate() {
16568                    if i > 0 {
16569                        self.write(", ");
16570                    }
16571                    self.generate_expression(&alias.this)?;
16572                    self.write_space();
16573                    self.write_keyword("AS");
16574                    self.write_space();
16575                    self.generate_identifier(&alias.alias)?;
16576                }
16577                self.write(")");
16578            }
16579        }
16580
16581        // Generate RENAME clause (Snowflake specific)
16582        if let Some(rename) = &star.rename {
16583            if !rename.is_empty() {
16584                self.write_space();
16585                self.write_keyword("RENAME");
16586                self.write(" (");
16587                for (i, (old_name, new_name)) in rename.iter().enumerate() {
16588                    if i > 0 {
16589                        self.write(", ");
16590                    }
16591                    self.generate_identifier(old_name)?;
16592                    self.write_space();
16593                    self.write_keyword("AS");
16594                    self.write_space();
16595                    self.generate_identifier(new_name)?;
16596                }
16597                self.write(")");
16598            }
16599        }
16600
16601        // Output trailing comments
16602        for comment in &star.trailing_comments {
16603            self.write_space();
16604            self.write_formatted_comment(comment);
16605        }
16606
16607        Ok(())
16608    }
16609
16610    /// Generate Snowflake braced wildcard syntax: {*}, {tbl.*}, {* EXCLUDE (...)}, {* ILIKE '...'}
16611    fn generate_braced_wildcard(&mut self, expr: &Expression) -> Result<()> {
16612        self.write("{");
16613        match expr {
16614            Expression::Star(star) => {
16615                // Generate the star (table.* or just * with optional EXCLUDE)
16616                self.generate_star(star)?;
16617            }
16618            Expression::ILike(ilike) => {
16619                // {* ILIKE 'pattern'} syntax
16620                self.generate_expression(&ilike.left)?;
16621                self.write_space();
16622                self.write_keyword("ILIKE");
16623                self.write_space();
16624                self.generate_expression(&ilike.right)?;
16625            }
16626            _ => {
16627                self.generate_expression(expr)?;
16628            }
16629        }
16630        self.write("}");
16631        Ok(())
16632    }
16633
16634    fn generate_alias(&mut self, alias: &Alias) -> Result<()> {
16635        // Generate inner expression, but skip trailing comments if they're in pre_alias_comments
16636        // to avoid duplication (comments are captured as both Column.trailing_comments
16637        // and Alias.pre_alias_comments during parsing)
16638        match &alias.this {
16639            Expression::Column(col) => {
16640                // Generate column without trailing comments - they're in pre_alias_comments
16641                if let Some(table) = &col.table {
16642                    self.generate_identifier(table)?;
16643                    self.write(".");
16644                }
16645                self.generate_identifier(&col.name)?;
16646            }
16647            _ => {
16648                self.generate_expression(&alias.this)?;
16649            }
16650        }
16651
16652        // Handle pre-alias comments: when there are no trailing_comments, sqlglot
16653        // moves pre-alias comments to after the alias. When there are also trailing_comments,
16654        // keep pre-alias comments in their original position (between expression and AS).
16655        if !alias.pre_alias_comments.is_empty() && !alias.trailing_comments.is_empty() {
16656            for comment in &alias.pre_alias_comments {
16657                self.write_space();
16658                self.write_formatted_comment(comment);
16659            }
16660        }
16661
16662        use crate::dialects::DialectType;
16663
16664        // Determine if we should skip AS keyword for table-valued function aliases
16665        // Oracle and some other dialects don't use AS for table aliases
16666        // Note: We specifically use TableFromRows here, NOT Function, because Function
16667        // matches regular functions like MATCH_NUMBER() which should include the AS keyword.
16668        // TableFromRows represents TABLE(expr) constructs which are actual table-valued functions.
16669        let is_table_source = matches!(
16670            &alias.this,
16671            Expression::JSONTable(_)
16672                | Expression::XMLTable(_)
16673                | Expression::TableFromRows(_)
16674                | Expression::Unnest(_)
16675                | Expression::MatchRecognize(_)
16676                | Expression::Select(_)
16677                | Expression::Subquery(_)
16678                | Expression::Paren(_)
16679        );
16680        let dialect_skips_table_alias_as = matches!(self.config.dialect, Some(DialectType::Oracle));
16681        let skip_as = is_table_source && dialect_skips_table_alias_as;
16682
16683        self.write_space();
16684        if !skip_as {
16685            if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
16686                if let Some(ref alias_keyword) = alias.alias_keyword {
16687                    self.write(alias_keyword);
16688                } else {
16689                    self.write_keyword("AS");
16690                }
16691            } else {
16692                self.write_keyword("AS");
16693            }
16694            self.write_space();
16695        }
16696
16697        // BigQuery doesn't support column aliases in table aliases: AS t(c1, c2)
16698        let skip_column_aliases = matches!(self.config.dialect, Some(DialectType::BigQuery));
16699
16700        // Check if we have column aliases only (no table alias name)
16701        if alias.alias.is_empty() && !alias.column_aliases.is_empty() && !skip_column_aliases {
16702            // Generate AS (col1, col2, ...)
16703            self.write("(");
16704            for (i, col_alias) in alias.column_aliases.iter().enumerate() {
16705                if i > 0 {
16706                    self.write(", ");
16707                }
16708                self.generate_alias_identifier(col_alias)?;
16709            }
16710            self.write(")");
16711        } else if !alias.column_aliases.is_empty() && !skip_column_aliases {
16712            // Generate AS alias(col1, col2, ...)
16713            self.generate_alias_identifier(&alias.alias)?;
16714            self.write("(");
16715            for (i, col_alias) in alias.column_aliases.iter().enumerate() {
16716                if i > 0 {
16717                    self.write(", ");
16718                }
16719                self.generate_alias_identifier(col_alias)?;
16720            }
16721            self.write(")");
16722        } else {
16723            // Simple alias (or BigQuery without column aliases)
16724            self.generate_alias_identifier(&alias.alias)?;
16725        }
16726
16727        // Output trailing comments (comments after the alias)
16728        for comment in &alias.trailing_comments {
16729            self.write_space();
16730            self.write_formatted_comment(comment);
16731        }
16732
16733        // Output pre-alias comments: when there are no trailing_comments, sqlglot
16734        // moves pre-alias comments to after the alias. When there are trailing_comments,
16735        // the pre-alias comments were already lost (consumed as column trailing comments
16736        // that were then used as pre_alias_comments). We always emit them after alias.
16737        if alias.trailing_comments.is_empty() {
16738            for comment in &alias.pre_alias_comments {
16739                self.write_space();
16740                self.write_formatted_comment(comment);
16741            }
16742        }
16743
16744        Ok(())
16745    }
16746
16747    fn generate_cast(&mut self, cast: &Cast) -> Result<()> {
16748        use crate::dialects::DialectType;
16749
16750        // SingleStore uses :> syntax
16751        if matches!(self.config.dialect, Some(DialectType::SingleStore)) {
16752            self.generate_expression(&cast.this)?;
16753            self.write(" :> ");
16754            self.generate_data_type(&cast.to)?;
16755            return Ok(());
16756        }
16757
16758        // Teradata: CAST(x AS FORMAT 'fmt') (no data type)
16759        if matches!(self.config.dialect, Some(DialectType::Teradata)) {
16760            let is_unknown_type = matches!(cast.to, DataType::Unknown)
16761                || matches!(cast.to, DataType::Custom { ref name } if name.is_empty());
16762            if is_unknown_type {
16763                if let Some(format) = &cast.format {
16764                    self.write_keyword("CAST");
16765                    self.write("(");
16766                    self.generate_expression(&cast.this)?;
16767                    self.write_space();
16768                    self.write_keyword("AS");
16769                    self.write_space();
16770                    self.write_keyword("FORMAT");
16771                    self.write_space();
16772                    self.generate_expression(format)?;
16773                    self.write(")");
16774                    return Ok(());
16775                }
16776            }
16777        }
16778
16779        // Oracle: CAST(x AS DATE/TIMESTAMP ..., 'format') -> TO_DATE/TO_TIMESTAMP(x, 'format')
16780        // This follows Python sqlglot's behavior of transforming CAST with format to native functions
16781        if matches!(self.config.dialect, Some(DialectType::Oracle)) {
16782            if let Some(format) = &cast.format {
16783                // Check if target type is DATE or TIMESTAMP
16784                let is_date = matches!(cast.to, DataType::Date);
16785                let is_timestamp = matches!(cast.to, DataType::Timestamp { .. });
16786
16787                if is_date || is_timestamp {
16788                    let func_name = if is_date { "TO_DATE" } else { "TO_TIMESTAMP" };
16789                    self.write_keyword(func_name);
16790                    self.write("(");
16791                    self.generate_expression(&cast.this)?;
16792                    self.write(", ");
16793
16794                    // Normalize format string for Oracle (HH -> HH12)
16795                    // Oracle HH is 12-hour format, same as HH12. For clarity, Python sqlglot uses HH12.
16796                    if let Expression::Literal(lit) = format.as_ref() {
16797                        if let Literal::String(fmt_str) = lit.as_ref() {
16798                            let normalized = self.normalize_oracle_format(fmt_str);
16799                            self.write("'");
16800                            self.write(&normalized);
16801                            self.write("'");
16802                        }
16803                    } else {
16804                        self.generate_expression(format)?;
16805                    }
16806
16807                    self.write(")");
16808                    return Ok(());
16809                }
16810            }
16811        }
16812
16813        // BigQuery: CAST(ARRAY[...] AS ARRAY<T>) -> ARRAY<T>[...]
16814        // This preserves sqlglot's typed inline array literal output.
16815        if matches!(self.config.dialect, Some(DialectType::BigQuery)) {
16816            if let Expression::Array(arr) = &cast.this {
16817                self.generate_data_type(&cast.to)?;
16818                // Output just the bracket content [values] without the ARRAY prefix
16819                self.write("[");
16820                for (i, expr) in arr.expressions.iter().enumerate() {
16821                    if i > 0 {
16822                        self.write(", ");
16823                    }
16824                    self.generate_expression(expr)?;
16825                }
16826                self.write("]");
16827                return Ok(());
16828            }
16829            if matches!(&cast.this, Expression::ArrayFunc(_)) {
16830                self.generate_data_type(&cast.to)?;
16831                self.generate_expression(&cast.this)?;
16832                return Ok(());
16833            }
16834        }
16835
16836        // DuckDB/Presto/Trino: When CAST(Struct([unnamed]) AS STRUCT(...)),
16837        // convert the inner Struct to ROW(values...) format
16838        if matches!(
16839            self.config.dialect,
16840            Some(DialectType::DuckDB) | Some(DialectType::Presto) | Some(DialectType::Trino)
16841        ) {
16842            if let Expression::Struct(ref s) = cast.this {
16843                let all_unnamed = s.fields.iter().all(|(name, _)| name.is_none());
16844                if all_unnamed && matches!(cast.to, DataType::Struct { .. }) {
16845                    self.write_keyword("CAST");
16846                    self.write("(");
16847                    self.generate_struct_as_row(s)?;
16848                    self.write_space();
16849                    self.write_keyword("AS");
16850                    self.write_space();
16851                    self.generate_data_type(&cast.to)?;
16852                    self.write(")");
16853                    return Ok(());
16854                }
16855            }
16856        }
16857
16858        // Determine if we should use :: syntax based on dialect
16859        // PostgreSQL prefers :: for identity, most others prefer CAST()
16860        let use_double_colon = cast.double_colon_syntax && self.dialect_prefers_double_colon();
16861
16862        if use_double_colon {
16863            // PostgreSQL :: syntax: expr::type
16864            self.generate_expression(&cast.this)?;
16865            self.write("::");
16866            self.generate_data_type(&cast.to)?;
16867        } else {
16868            // Standard CAST() syntax
16869            self.write_keyword("CAST");
16870            self.write("(");
16871            self.generate_expression(&cast.this)?;
16872            self.write_space();
16873            self.write_keyword("AS");
16874            self.write_space();
16875            // For MySQL/SingleStore/TiDB, map text/blob variant types to CHAR in CAST
16876            // This matches Python sqlglot's CAST_MAPPING behavior
16877            if matches!(
16878                self.config.dialect,
16879                Some(DialectType::MySQL) | Some(DialectType::SingleStore) | Some(DialectType::TiDB)
16880            ) {
16881                match &cast.to {
16882                    DataType::Custom { ref name } => {
16883                        if name.eq_ignore_ascii_case("LONGTEXT")
16884                            || name.eq_ignore_ascii_case("MEDIUMTEXT")
16885                            || name.eq_ignore_ascii_case("TINYTEXT")
16886                            || name.eq_ignore_ascii_case("LONGBLOB")
16887                            || name.eq_ignore_ascii_case("MEDIUMBLOB")
16888                            || name.eq_ignore_ascii_case("TINYBLOB")
16889                        {
16890                            self.write_keyword("CHAR");
16891                        } else {
16892                            self.generate_data_type(&cast.to)?;
16893                        }
16894                    }
16895                    DataType::VarChar { length, .. } => {
16896                        // MySQL CAST: VARCHAR -> CHAR
16897                        self.write_keyword("CHAR");
16898                        if let Some(n) = length {
16899                            self.write(&format!("({})", n));
16900                        }
16901                    }
16902                    DataType::Text => {
16903                        // MySQL CAST: TEXT -> CHAR
16904                        self.write_keyword("CHAR");
16905                    }
16906                    DataType::Timestamp {
16907                        precision,
16908                        timezone: false,
16909                    } => {
16910                        // MySQL CAST: TIMESTAMP -> DATETIME
16911                        self.write_keyword("DATETIME");
16912                        if let Some(p) = precision {
16913                            self.write(&format!("({})", p));
16914                        }
16915                    }
16916                    _ => {
16917                        self.generate_data_type(&cast.to)?;
16918                    }
16919                }
16920            } else if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
16921                // Snowflake CAST: STRING -> VARCHAR
16922                match &cast.to {
16923                    DataType::String { length } => {
16924                        self.write_keyword("VARCHAR");
16925                        if let Some(n) = length {
16926                            self.write(&format!("({})", n));
16927                        }
16928                    }
16929                    _ => {
16930                        self.generate_data_type(&cast.to)?;
16931                    }
16932                }
16933            } else {
16934                self.generate_data_type(&cast.to)?;
16935            }
16936
16937            // Output DEFAULT ... ON CONVERSION ERROR clause if present (Oracle)
16938            if let Some(default) = &cast.default {
16939                self.write_space();
16940                self.write_keyword("DEFAULT");
16941                self.write_space();
16942                self.generate_expression(default)?;
16943                self.write_space();
16944                self.write_keyword("ON");
16945                self.write_space();
16946                self.write_keyword("CONVERSION");
16947                self.write_space();
16948                self.write_keyword("ERROR");
16949            }
16950
16951            // Output FORMAT clause if present (BigQuery: CAST(x AS STRING FORMAT 'format'))
16952            // For Oracle with comma-separated format: CAST(x AS DATE DEFAULT NULL ON CONVERSION ERROR, 'format')
16953            if let Some(format) = &cast.format {
16954                // Check if Oracle dialect - use comma syntax
16955                if matches!(
16956                    self.config.dialect,
16957                    Some(crate::dialects::DialectType::Oracle)
16958                ) {
16959                    self.write(", ");
16960                } else {
16961                    self.write_space();
16962                    self.write_keyword("FORMAT");
16963                    self.write_space();
16964                }
16965                self.generate_expression(format)?;
16966            }
16967
16968            self.write(")");
16969            // Output trailing comments
16970            for comment in &cast.trailing_comments {
16971                self.write_space();
16972                self.write_formatted_comment(comment);
16973            }
16974        }
16975        Ok(())
16976    }
16977
16978    /// Generate a Struct as ROW(values...) format, recursively converting inner Struct to ROW too.
16979    /// Used for DuckDB/Presto/Trino CAST(Struct AS STRUCT(...)) context.
16980    fn generate_struct_as_row(&mut self, s: &crate::expressions::Struct) -> Result<()> {
16981        self.write_keyword("ROW");
16982        self.write("(");
16983        for (i, (_, expr)) in s.fields.iter().enumerate() {
16984            if i > 0 {
16985                self.write(", ");
16986            }
16987            // Recursively convert inner Struct to ROW format
16988            if let Expression::Struct(ref inner_s) = expr {
16989                self.generate_struct_as_row(inner_s)?;
16990            } else {
16991                self.generate_expression(expr)?;
16992            }
16993        }
16994        self.write(")");
16995        Ok(())
16996    }
16997
16998    /// Normalize Oracle date/time format strings
16999    /// HH -> HH12 (both are 12-hour format, but Python sqlglot prefers explicit HH12)
17000    fn normalize_oracle_format(&self, format: &str) -> String {
17001        // Replace standalone HH with HH12 (but not HH12 or HH24)
17002        // We need to be careful not to replace HH12 -> HH1212 or HH24 -> HH1224
17003        let mut result = String::new();
17004        let chars: Vec<char> = format.chars().collect();
17005        let mut i = 0;
17006
17007        while i < chars.len() {
17008            if i + 1 < chars.len() && chars[i] == 'H' && chars[i + 1] == 'H' {
17009                // Check what follows HH
17010                if i + 2 < chars.len() {
17011                    let next = chars[i + 2];
17012                    if next == '1' || next == '2' {
17013                        // This is HH12 or HH24, keep as is
17014                        result.push('H');
17015                        result.push('H');
17016                        i += 2;
17017                        continue;
17018                    }
17019                }
17020                // Standalone HH -> HH12
17021                result.push_str("HH12");
17022                i += 2;
17023            } else {
17024                result.push(chars[i]);
17025                i += 1;
17026            }
17027        }
17028
17029        result
17030    }
17031
17032    /// Check if the current dialect prefers :: cast syntax
17033    /// Preserve ClickHouse's native `::` shorthand when the parser saw it.
17034    fn dialect_prefers_double_colon(&self) -> bool {
17035        matches!(self.config.dialect, Some(DialectType::ClickHouse))
17036    }
17037
17038    /// Generate MOD function - uses % operator for Snowflake/MySQL/Presto/Trino, MOD() for others
17039    fn generate_mod_func(&mut self, f: &crate::expressions::BinaryFunc) -> Result<()> {
17040        use crate::dialects::DialectType;
17041
17042        // Snowflake, MySQL, Presto, Trino, PostgreSQL, and DuckDB prefer x % y instead of MOD(x, y)
17043        let use_percent_operator = matches!(
17044            self.config.dialect,
17045            Some(DialectType::Snowflake)
17046                | Some(DialectType::MySQL)
17047                | Some(DialectType::Presto)
17048                | Some(DialectType::Trino)
17049                | Some(DialectType::PostgreSQL)
17050                | Some(DialectType::DuckDB)
17051                | Some(DialectType::Hive)
17052                | Some(DialectType::Spark)
17053                | Some(DialectType::Databricks)
17054                | Some(DialectType::Athena)
17055        );
17056
17057        if use_percent_operator {
17058            // Wrap complex expressions in parens to preserve precedence
17059            // Since % has higher precedence than +/-, we need parens for Add/Sub on either side
17060            let needs_paren = |e: &Expression| matches!(e, Expression::Add(_) | Expression::Sub(_));
17061            if needs_paren(&f.this) {
17062                self.write("(");
17063                self.generate_expression(&f.this)?;
17064                self.write(")");
17065            } else {
17066                self.generate_expression(&f.this)?;
17067            }
17068            self.write(" % ");
17069            if needs_paren(&f.expression) {
17070                self.write("(");
17071                self.generate_expression(&f.expression)?;
17072                self.write(")");
17073            } else {
17074                self.generate_expression(&f.expression)?;
17075            }
17076            Ok(())
17077        } else {
17078            self.generate_binary_func("MOD", &f.this, &f.expression)
17079        }
17080    }
17081
17082    /// Generate IFNULL - uses COALESCE for Snowflake, IFNULL for others
17083    fn generate_ifnull(&mut self, f: &crate::expressions::BinaryFunc) -> Result<()> {
17084        use crate::dialects::DialectType;
17085
17086        // Snowflake normalizes IFNULL to COALESCE
17087        let func_name = match self.config.dialect {
17088            Some(DialectType::Snowflake) => "COALESCE",
17089            _ => "IFNULL",
17090        };
17091
17092        self.generate_binary_func(func_name, &f.this, &f.expression)
17093    }
17094
17095    /// Generate NVL - preserves original name if available, otherwise uses dialect-specific output
17096    fn generate_nvl(&mut self, f: &crate::expressions::BinaryFunc) -> Result<()> {
17097        // Use original function name if preserved (for identity tests)
17098        if let Some(ref original_name) = f.original_name {
17099            return self.generate_binary_func(original_name, &f.this, &f.expression);
17100        }
17101
17102        // Otherwise, use dialect-specific function names
17103        use crate::dialects::DialectType;
17104        let func_name = match self.config.dialect {
17105            Some(DialectType::Snowflake)
17106            | Some(DialectType::ClickHouse)
17107            | Some(DialectType::PostgreSQL)
17108            | Some(DialectType::Presto)
17109            | Some(DialectType::Trino)
17110            | Some(DialectType::Athena)
17111            | Some(DialectType::DuckDB)
17112            | Some(DialectType::BigQuery)
17113            | Some(DialectType::Spark)
17114            | Some(DialectType::Databricks)
17115            | Some(DialectType::Hive) => "COALESCE",
17116            Some(DialectType::MySQL)
17117            | Some(DialectType::Doris)
17118            | Some(DialectType::StarRocks)
17119            | Some(DialectType::SingleStore)
17120            | Some(DialectType::TiDB) => "IFNULL",
17121            _ => "NVL",
17122        };
17123
17124        self.generate_binary_func(func_name, &f.this, &f.expression)
17125    }
17126
17127    /// Generate STDDEV_SAMP - uses STDDEV for Snowflake, STDDEV_SAMP for others
17128    fn generate_stddev_samp(&mut self, f: &crate::expressions::AggFunc) -> Result<()> {
17129        use crate::dialects::DialectType;
17130
17131        // Snowflake normalizes STDDEV_SAMP to STDDEV
17132        let func_name = match self.config.dialect {
17133            Some(DialectType::Snowflake) => "STDDEV",
17134            _ => "STDDEV_SAMP",
17135        };
17136
17137        self.generate_agg_func(func_name, f)
17138    }
17139
17140    fn generate_collation(&mut self, coll: &CollationExpr) -> Result<()> {
17141        self.generate_expression(&coll.this)?;
17142        self.write_space();
17143        self.write_keyword("COLLATE");
17144        self.write_space();
17145        if coll.quoted {
17146            // Single-quoted string: COLLATE 'de_DE'
17147            self.write("'");
17148            self.write(&coll.collation);
17149            self.write("'");
17150        } else if coll.double_quoted {
17151            // Double-quoted identifier: COLLATE "de_DE"
17152            self.write("\"");
17153            self.write(&coll.collation);
17154            self.write("\"");
17155        } else {
17156            // Unquoted identifier: COLLATE de_DE
17157            self.write(&coll.collation);
17158        }
17159        Ok(())
17160    }
17161
17162    fn generate_case(&mut self, case: &Case) -> Result<()> {
17163        // In pretty mode, decide whether to expand based on total text width
17164        let multiline_case = if self.config.pretty {
17165            // Build the flat representation to check width
17166            let mut statements: Vec<String> = Vec::new();
17167            let operand_str = if let Some(operand) = &case.operand {
17168                let s = self.generate_to_string(operand)?;
17169                statements.push(format!("CASE {}", s));
17170                s
17171            } else {
17172                statements.push("CASE".to_string());
17173                String::new()
17174            };
17175            let _ = operand_str;
17176            for (condition, result) in &case.whens {
17177                statements.push(format!("WHEN {}", self.generate_to_string(condition)?));
17178                statements.push(format!("THEN {}", self.generate_to_string(result)?));
17179            }
17180            if let Some(else_) = &case.else_ {
17181                statements.push(format!("ELSE {}", self.generate_to_string(else_)?));
17182            }
17183            statements.push("END".to_string());
17184            self.too_wide(&statements)
17185        } else {
17186            false
17187        };
17188
17189        self.write_keyword("CASE");
17190        if let Some(operand) = &case.operand {
17191            self.write_space();
17192            self.generate_expression(operand)?;
17193        }
17194        if multiline_case {
17195            self.indent_level += 1;
17196        }
17197        for (condition, result) in &case.whens {
17198            if multiline_case {
17199                self.write_newline();
17200                self.write_indent();
17201            } else {
17202                self.write_space();
17203            }
17204            self.write_keyword("WHEN");
17205            self.write_space();
17206            self.generate_expression(condition)?;
17207            if multiline_case {
17208                self.write_newline();
17209                self.write_indent();
17210            } else {
17211                self.write_space();
17212            }
17213            self.write_keyword("THEN");
17214            self.write_space();
17215            self.generate_expression(result)?;
17216        }
17217        if let Some(else_) = &case.else_ {
17218            if multiline_case {
17219                self.write_newline();
17220                self.write_indent();
17221            } else {
17222                self.write_space();
17223            }
17224            self.write_keyword("ELSE");
17225            self.write_space();
17226            self.generate_expression(else_)?;
17227        }
17228        if multiline_case {
17229            self.indent_level -= 1;
17230            self.write_newline();
17231            self.write_indent();
17232        } else {
17233            self.write_space();
17234        }
17235        self.write_keyword("END");
17236        // Emit any comments that were attached to the CASE keyword
17237        for comment in &case.comments {
17238            self.write(" ");
17239            self.write_formatted_comment(comment);
17240        }
17241        Ok(())
17242    }
17243
17244    fn generate_function(&mut self, func: &Function) -> Result<()> {
17245        // Normalize function name based on dialect settings
17246        let normalized_name = self.normalize_func_name(&func.name);
17247
17248        // DuckDB: ARRAY_CONSTRUCT_COMPACT(a, b, c) -> LIST_FILTER([a, b, c], _u -> NOT _u IS NULL)
17249        if matches!(self.config.dialect, Some(DialectType::DuckDB))
17250            && func.name.eq_ignore_ascii_case("ARRAY_CONSTRUCT_COMPACT")
17251        {
17252            self.write("LIST_FILTER(");
17253            self.write("[");
17254            for (i, arg) in func.args.iter().enumerate() {
17255                if i > 0 {
17256                    self.write(", ");
17257                }
17258                self.generate_expression(arg)?;
17259            }
17260            self.write("], _u -> NOT _u IS NULL)");
17261            return Ok(());
17262        }
17263
17264        // Snowflake fixtures expect TO_VARIANT applied to arrays to keep ARRAY_CONSTRUCT(...)
17265        // rather than bracket-array syntax.
17266        if matches!(self.config.dialect, Some(DialectType::Snowflake))
17267            && func.name.eq_ignore_ascii_case("TO_VARIANT")
17268            && func.args.len() == 1
17269        {
17270            let array_expressions = match &func.args[0] {
17271                Expression::ArrayFunc(arr) => Some(&arr.expressions),
17272                Expression::Array(arr) => Some(&arr.expressions),
17273                _ => None,
17274            };
17275            if let Some(expressions) = array_expressions {
17276                self.write_keyword("TO_VARIANT");
17277                self.write("(");
17278                self.write_keyword("ARRAY_CONSTRUCT");
17279                self.write("(");
17280                for (i, arg) in expressions.iter().enumerate() {
17281                    if i > 0 {
17282                        self.write(", ");
17283                    }
17284                    self.generate_expression(arg)?;
17285                }
17286                self.write(")");
17287                self.write(")");
17288                return Ok(());
17289            }
17290        }
17291
17292        // STRUCT function: BigQuery STRUCT('Alice' AS name, 85 AS score) -> dialect-specific
17293        if func.name.eq_ignore_ascii_case("STRUCT")
17294            && !matches!(
17295                self.config.dialect,
17296                Some(DialectType::BigQuery)
17297                    | Some(DialectType::Spark)
17298                    | Some(DialectType::Databricks)
17299                    | Some(DialectType::Hive)
17300                    | None
17301            )
17302        {
17303            return self.generate_struct_function_cross_dialect(func);
17304        }
17305
17306        // SingleStore: __SS_JSON_PATH_QMARK__(expr, key) -> expr::?key
17307        // This is an internal marker function for ::? JSON path syntax
17308        if func.name.eq_ignore_ascii_case("__SS_JSON_PATH_QMARK__") && func.args.len() == 2 {
17309            self.generate_expression(&func.args[0])?;
17310            self.write("::?");
17311            // Extract the key from the string literal
17312            if let Expression::Literal(lit) = &func.args[1] {
17313                if let crate::expressions::Literal::String(key) = lit.as_ref() {
17314                    self.write(key);
17315                }
17316            } else {
17317                self.generate_expression(&func.args[1])?;
17318            }
17319            return Ok(());
17320        }
17321
17322        // PostgreSQL: __PG_BITWISE_XOR__(a, b) -> a # b
17323        if func.name.eq_ignore_ascii_case("__PG_BITWISE_XOR__") && func.args.len() == 2 {
17324            self.generate_expression(&func.args[0])?;
17325            self.write(" # ");
17326            self.generate_expression(&func.args[1])?;
17327            return Ok(());
17328        }
17329
17330        // Spark/Hive family: unwrap TRY(expr) since these dialects don't emit TRY as a scalar wrapper.
17331        if matches!(
17332            self.config.dialect,
17333            Some(DialectType::Spark | DialectType::Databricks | DialectType::Hive)
17334        ) && func.name.eq_ignore_ascii_case("TRY")
17335            && func.args.len() == 1
17336        {
17337            self.generate_expression(&func.args[0])?;
17338            return Ok(());
17339        }
17340
17341        // ClickHouse normalization: toStartOfDay(x) -> dateTrunc('DAY', x)
17342        if self.config.dialect == Some(DialectType::ClickHouse)
17343            && func.name.eq_ignore_ascii_case("TOSTARTOFDAY")
17344            && func.args.len() == 1
17345        {
17346            self.write("dateTrunc('DAY', ");
17347            self.generate_expression(&func.args[0])?;
17348            self.write(")");
17349            return Ok(());
17350        }
17351
17352        // ClickHouse uses dateTrunc casing.
17353        if self.config.dialect == Some(DialectType::ClickHouse)
17354            && func.name.eq_ignore_ascii_case("DATE_TRUNC")
17355            && func.args.len() == 2
17356        {
17357            self.write("dateTrunc(");
17358            self.generate_expression(&func.args[0])?;
17359            self.write(", ");
17360            self.generate_expression(&func.args[1])?;
17361            self.write(")");
17362            return Ok(());
17363        }
17364
17365        // Presto-family dialects spell SUBSTRING as SUBSTR in SQLGlot outputs.
17366        if matches!(
17367            self.config.dialect,
17368            Some(DialectType::Presto | DialectType::Trino | DialectType::Athena)
17369        ) && func.name.eq_ignore_ascii_case("SUBSTRING")
17370        {
17371            self.write_keyword("SUBSTR");
17372            self.write("(");
17373            for (i, arg) in func.args.iter().enumerate() {
17374                if i > 0 {
17375                    self.write(", ");
17376                }
17377                self.generate_expression(arg)?;
17378            }
17379            self.write(")");
17380            return Ok(());
17381        }
17382
17383        if self.config.dialect == Some(DialectType::Snowflake)
17384            && func.name.eq_ignore_ascii_case("LIST_DISTINCT")
17385            && func.args.len() == 1
17386        {
17387            self.write_keyword("ARRAY_DISTINCT");
17388            self.write("(");
17389            self.write_keyword("ARRAY_COMPACT");
17390            self.write("(");
17391            self.generate_expression(&func.args[0])?;
17392            self.write("))");
17393            return Ok(());
17394        }
17395
17396        if self.config.dialect == Some(DialectType::Snowflake)
17397            && func.name.eq_ignore_ascii_case("LIST")
17398            && func.args.len() == 1
17399            && !matches!(func.args.first(), Some(Expression::Select(_)))
17400        {
17401            self.write_keyword("ARRAY_AGG");
17402            self.write("(");
17403            self.generate_expression(&func.args[0])?;
17404            self.write(")");
17405            return Ok(());
17406        }
17407
17408        // Redshift: CONCAT(a, b, ...) -> a || b || ...
17409        if self.config.dialect == Some(DialectType::Redshift)
17410            && func.name.eq_ignore_ascii_case("CONCAT")
17411            && func.args.len() >= 2
17412        {
17413            for (i, arg) in func.args.iter().enumerate() {
17414                if i > 0 {
17415                    self.write(" || ");
17416                }
17417                self.generate_expression(arg)?;
17418            }
17419            return Ok(());
17420        }
17421
17422        // Redshift: CONCAT_WS(delim, a, b, c) -> a || delim || b || delim || c
17423        if self.config.dialect == Some(DialectType::Redshift)
17424            && func.name.eq_ignore_ascii_case("CONCAT_WS")
17425            && func.args.len() >= 2
17426        {
17427            let sep = &func.args[0];
17428            for (i, arg) in func.args.iter().skip(1).enumerate() {
17429                if i > 0 {
17430                    self.write(" || ");
17431                    self.generate_expression(sep)?;
17432                    self.write(" || ");
17433                }
17434                self.generate_expression(arg)?;
17435            }
17436            return Ok(());
17437        }
17438
17439        // Redshift: DATEDIFF/DATE_DIFF(unit, start, end) -> DATEDIFF(UNIT, start, end)
17440        // Unit should be unquoted uppercase identifier
17441        if self.config.dialect == Some(DialectType::Redshift)
17442            && (func.name.eq_ignore_ascii_case("DATEDIFF")
17443                || func.name.eq_ignore_ascii_case("DATE_DIFF"))
17444            && func.args.len() == 3
17445        {
17446            self.write_keyword("DATEDIFF");
17447            self.write("(");
17448            // First arg is unit - normalize to unquoted uppercase
17449            self.write_redshift_date_part(&func.args[0]);
17450            self.write(", ");
17451            self.generate_expression(&func.args[1])?;
17452            self.write(", ");
17453            self.generate_expression(&func.args[2])?;
17454            self.write(")");
17455            return Ok(());
17456        }
17457
17458        // Redshift: DATEADD/DATE_ADD(unit, interval, date) -> DATEADD(UNIT, interval, date)
17459        // Unit should be unquoted uppercase identifier
17460        if self.config.dialect == Some(DialectType::Redshift)
17461            && (func.name.eq_ignore_ascii_case("DATEADD")
17462                || func.name.eq_ignore_ascii_case("DATE_ADD"))
17463            && func.args.len() == 3
17464        {
17465            self.write_keyword("DATEADD");
17466            self.write("(");
17467            // First arg is unit - normalize to unquoted uppercase
17468            self.write_redshift_date_part(&func.args[0]);
17469            self.write(", ");
17470            self.generate_expression(&func.args[1])?;
17471            self.write(", ");
17472            self.generate_expression(&func.args[2])?;
17473            self.write(")");
17474            return Ok(());
17475        }
17476
17477        // UUID_STRING(args) from Snowflake -> dialect-specific UUID function.
17478        if func.name.eq_ignore_ascii_case("UUID_STRING")
17479            && !matches!(self.config.dialect, Some(DialectType::Snowflake) | None)
17480        {
17481            if matches!(
17482                self.config.dialect,
17483                Some(DialectType::Hive | DialectType::Spark | DialectType::Databricks)
17484            ) {
17485                self.write_keyword("CAST");
17486                self.write("(");
17487                self.write_keyword("UUID");
17488                self.write("() ");
17489                self.write_keyword("AS");
17490                self.write(" ");
17491                self.write_keyword("STRING");
17492                self.write(")");
17493                return Ok(());
17494            }
17495
17496            if matches!(
17497                self.config.dialect,
17498                Some(DialectType::Presto | DialectType::Trino)
17499            ) {
17500                self.write_keyword("CAST");
17501                self.write("(");
17502                self.write_keyword("UUID");
17503                self.write("() ");
17504                self.write_keyword("AS");
17505                self.write(" ");
17506                self.write_keyword("VARCHAR");
17507                self.write(")");
17508                return Ok(());
17509            }
17510
17511            if self.config.dialect == Some(DialectType::DuckDB) && func.args.len() == 2 {
17512                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(");
17513                self.generate_expression(&func.args[0])?;
17514                self.write(", '-', '')) || ENCODE(");
17515                self.generate_expression(&func.args[1])?;
17516                self.write(")), 1, 32) AS h))");
17517                return Ok(());
17518            }
17519
17520            let func_name = match self.config.dialect {
17521                Some(DialectType::PostgreSQL) | Some(DialectType::Redshift) => "GEN_RANDOM_UUID",
17522                Some(DialectType::BigQuery) => "GENERATE_UUID",
17523                _ => "UUID",
17524            };
17525            self.write_keyword(func_name);
17526            self.write("()");
17527            return Ok(());
17528        }
17529
17530        // Snowflake: GENERATOR(val) -> GENERATOR(ROWCOUNT => val)
17531        // GENERATOR(val1, val2) -> GENERATOR(ROWCOUNT => val1, TIMELIMIT => val2)
17532        // Positional args are mapped to named parameters.
17533        if matches!(self.config.dialect, Some(DialectType::Snowflake))
17534            && func.name.eq_ignore_ascii_case("GENERATOR")
17535        {
17536            let has_positional_args =
17537                !func.args.is_empty() && !matches!(&func.args[0], Expression::NamedArgument(_));
17538            if has_positional_args {
17539                let param_names = ["ROWCOUNT", "TIMELIMIT"];
17540                self.write_keyword("GENERATOR");
17541                self.write("(");
17542                for (i, arg) in func.args.iter().enumerate() {
17543                    if i > 0 {
17544                        self.write(", ");
17545                    }
17546                    if i < param_names.len() {
17547                        self.write_keyword(param_names[i]);
17548                        self.write(" => ");
17549                        self.generate_expression(arg)?;
17550                    } else {
17551                        self.generate_expression(arg)?;
17552                    }
17553                }
17554                self.write(")");
17555                return Ok(());
17556            }
17557        }
17558
17559        // Redshift: DATE_TRUNC('unit', date) -> DATE_TRUNC('UNIT', date)
17560        // Unit should be quoted uppercase string
17561        if self.config.dialect == Some(DialectType::Redshift)
17562            && func.name.eq_ignore_ascii_case("DATE_TRUNC")
17563            && func.args.len() == 2
17564        {
17565            self.write_keyword("DATE_TRUNC");
17566            self.write("(");
17567            // First arg is unit - normalize to quoted uppercase
17568            self.write_redshift_date_part_quoted(&func.args[0]);
17569            self.write(", ");
17570            self.generate_expression(&func.args[1])?;
17571            self.write(")");
17572            return Ok(());
17573        }
17574
17575        // TSQL/Fabric: DATE_PART -> DATEPART (no underscore)
17576        if matches!(
17577            self.config.dialect,
17578            Some(DialectType::TSQL) | Some(DialectType::Fabric)
17579        ) && (func.name.eq_ignore_ascii_case("DATE_PART")
17580            || func.name.eq_ignore_ascii_case("DATEPART"))
17581            && func.args.len() == 2
17582        {
17583            self.write_keyword("DATEPART");
17584            self.write("(");
17585            self.generate_expression(&func.args[0])?;
17586            self.write(", ");
17587            self.generate_expression(&func.args[1])?;
17588            self.write(")");
17589            return Ok(());
17590        }
17591
17592        // PostgreSQL/Redshift: DATE_PART(part, value) -> EXTRACT(part FROM value)
17593        if matches!(
17594            self.config.dialect,
17595            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
17596        ) && (func.name.eq_ignore_ascii_case("DATE_PART")
17597            || func.name.eq_ignore_ascii_case("DATEPART"))
17598            && func.args.len() == 2
17599        {
17600            self.write_keyword("EXTRACT");
17601            self.write("(");
17602            // Extract the datetime field - if it's a string literal, strip quotes to make it a keyword
17603            match &func.args[0] {
17604                Expression::Literal(lit)
17605                    if matches!(lit.as_ref(), crate::expressions::Literal::String(_)) =>
17606                {
17607                    let crate::expressions::Literal::String(s) = lit.as_ref() else {
17608                        unreachable!()
17609                    };
17610                    self.write(&s.to_ascii_lowercase());
17611                }
17612                _ => self.generate_expression(&func.args[0])?,
17613            }
17614            self.write_space();
17615            self.write_keyword("FROM");
17616            self.write_space();
17617            self.generate_expression(&func.args[1])?;
17618            self.write(")");
17619            return Ok(());
17620        }
17621
17622        // PostgreSQL: DATE_ADD(date, INTERVAL '...') / DATE_SUB(...) -> infix interval arithmetic.
17623        if self.config.dialect == Some(DialectType::PostgreSQL)
17624            && matches!(
17625                func.name.to_ascii_uppercase().as_str(),
17626                "DATE_ADD" | "DATE_SUB"
17627            )
17628            && func.args.len() == 2
17629            && matches!(func.args[1], Expression::Interval(_))
17630        {
17631            self.generate_expression(&func.args[0])?;
17632            self.write_space();
17633            if func.name.eq_ignore_ascii_case("DATE_SUB") {
17634                self.write("-");
17635            } else {
17636                self.write("+");
17637            }
17638            self.write_space();
17639            self.generate_expression(&func.args[1])?;
17640            return Ok(());
17641        }
17642
17643        // Dremio: DATE_PART(part, value) -> EXTRACT(part FROM value)
17644        // Also DATE literals in Dremio should be CAST(...AS DATE)
17645        if self.config.dialect == Some(DialectType::Dremio)
17646            && (func.name.eq_ignore_ascii_case("DATE_PART")
17647                || func.name.eq_ignore_ascii_case("DATEPART"))
17648            && func.args.len() == 2
17649        {
17650            self.write_keyword("EXTRACT");
17651            self.write("(");
17652            self.generate_expression(&func.args[0])?;
17653            self.write_space();
17654            self.write_keyword("FROM");
17655            self.write_space();
17656            // For Dremio, DATE literals should become CAST('value' AS DATE)
17657            self.generate_dremio_date_expression(&func.args[1])?;
17658            self.write(")");
17659            return Ok(());
17660        }
17661
17662        // Dremio: CURRENT_DATE_UTC() -> CURRENT_DATE_UTC (no parentheses)
17663        if self.config.dialect == Some(DialectType::Dremio)
17664            && func.name.eq_ignore_ascii_case("CURRENT_DATE_UTC")
17665            && func.args.is_empty()
17666        {
17667            self.write_keyword("CURRENT_DATE_UTC");
17668            return Ok(());
17669        }
17670
17671        // Dremio: DATETYPE(year, month, day) transformation
17672        // - If all args are integer literals: DATE('YYYY-MM-DD')
17673        // - If args are expressions: CAST(CONCAT(x, '-', y, '-', z) AS DATE)
17674        if self.config.dialect == Some(DialectType::Dremio)
17675            && func.name.eq_ignore_ascii_case("DATETYPE")
17676            && func.args.len() == 3
17677        {
17678            // Helper function to extract integer from number literal
17679            fn get_int_literal(expr: &Expression) -> Option<i64> {
17680                if let Expression::Literal(lit) = expr {
17681                    if let crate::expressions::Literal::Number(s) = lit.as_ref() {
17682                        s.parse::<i64>().ok()
17683                    } else {
17684                        None
17685                    }
17686                } else {
17687                    None
17688                }
17689            }
17690
17691            // Check if all arguments are integer literals
17692            if let (Some(year), Some(month), Some(day)) = (
17693                get_int_literal(&func.args[0]),
17694                get_int_literal(&func.args[1]),
17695                get_int_literal(&func.args[2]),
17696            ) {
17697                // All are integer literals: DATE('YYYY-MM-DD')
17698                self.write_keyword("DATE");
17699                self.write(&format!("('{:04}-{:02}-{:02}')", year, month, day));
17700                return Ok(());
17701            }
17702
17703            // For expressions: CAST(CONCAT(x, '-', y, '-', z) AS DATE)
17704            self.write_keyword("CAST");
17705            self.write("(");
17706            self.write_keyword("CONCAT");
17707            self.write("(");
17708            self.generate_expression(&func.args[0])?;
17709            self.write(", '-', ");
17710            self.generate_expression(&func.args[1])?;
17711            self.write(", '-', ");
17712            self.generate_expression(&func.args[2])?;
17713            self.write(")");
17714            self.write_space();
17715            self.write_keyword("AS");
17716            self.write_space();
17717            self.write_keyword("DATE");
17718            self.write(")");
17719            return Ok(());
17720        }
17721
17722        // Presto/Trino: DATE_ADD('unit', interval, date) - wrap interval in CAST(...AS BIGINT)
17723        // when it's not an integer literal
17724        let is_presto_like = matches!(
17725            self.config.dialect,
17726            Some(DialectType::Presto) | Some(DialectType::Trino)
17727        );
17728        if is_presto_like && func.name.eq_ignore_ascii_case("DATE_ADD") && func.args.len() == 3 {
17729            self.write_keyword("DATE_ADD");
17730            self.write("(");
17731            // First arg: unit (pass through as-is, e.g., 'DAY')
17732            self.generate_expression(&func.args[0])?;
17733            self.write(", ");
17734            // Second arg: interval - wrap in CAST(...AS BIGINT) if it doesn't return integer type
17735            let interval = &func.args[1];
17736            let needs_cast = !self.returns_integer_type(interval);
17737            if needs_cast {
17738                self.write_keyword("CAST");
17739                self.write("(");
17740            }
17741            self.generate_expression(interval)?;
17742            if needs_cast {
17743                self.write_space();
17744                self.write_keyword("AS");
17745                self.write_space();
17746                self.write_keyword("BIGINT");
17747                self.write(")");
17748            }
17749            self.write(", ");
17750            // Third arg: date
17751            self.generate_expression(&func.args[2])?;
17752            self.write(")");
17753            return Ok(());
17754        }
17755
17756        // Use bracket syntax if the function was parsed with brackets (e.g., MAP[keys, values])
17757        let use_brackets = func.use_bracket_syntax;
17758
17759        // Special case: functions WITH ORDINALITY need special output order
17760        // Input: FUNC(args) WITH ORDINALITY
17761        // Stored as: name="FUNC WITH ORDINALITY", args=[...]
17762        // Output must be: FUNC(args) WITH ORDINALITY
17763        let has_ordinality = func.name.len() >= 16
17764            && func.name[func.name.len() - 16..].eq_ignore_ascii_case(" WITH ORDINALITY");
17765        let output_name = if has_ordinality {
17766            let base_name = &func.name[..func.name.len() - " WITH ORDINALITY".len()];
17767            self.normalize_func_name(base_name)
17768        } else {
17769            normalized_name.clone()
17770        };
17771
17772        // For qualified names (schema.function or object.method), preserve original case
17773        // because they can be case-sensitive (e.g., TSQL XML methods like .nodes(), .value())
17774        let quote_source_clickhouse_function =
17775            matches!(self.config.dialect, Some(DialectType::ClickHouse))
17776                && matches!(self.config.source_dialect, Some(DialectType::ClickHouse))
17777                && func.quoted;
17778
17779        if quote_source_clickhouse_function {
17780            self.generate_identifier(&Identifier {
17781                name: func.name.clone(),
17782                quoted: true,
17783                trailing_comments: Vec::new(),
17784                span: None,
17785            })?;
17786        } else if func.name.contains('.') && !has_ordinality {
17787            // Don't normalize qualified functions - preserve original case
17788            // If the function was quoted (e.g., BigQuery `p.d.UdF`), wrap it in backticks
17789            if func.quoted {
17790                self.write("`");
17791                self.write(&func.name);
17792                self.write("`");
17793            } else {
17794                self.write(&func.name);
17795            }
17796        } else {
17797            self.write(&output_name);
17798        }
17799
17800        // If no_parens is true and there are no args, output just the function name
17801        // Unless the target dialect requires parens for this function
17802        let force_parens = func.no_parens && func.args.is_empty() && !func.distinct && {
17803            let needs_parens = if func.name.eq_ignore_ascii_case("CURRENT_USER")
17804                || func.name.eq_ignore_ascii_case("SESSION_USER")
17805                || func.name.eq_ignore_ascii_case("SYSTEM_USER")
17806            {
17807                matches!(
17808                    self.config.dialect,
17809                    Some(DialectType::Snowflake)
17810                        | Some(DialectType::Spark)
17811                        | Some(DialectType::Databricks)
17812                        | Some(DialectType::Hive)
17813                )
17814            } else {
17815                false
17816            };
17817            !needs_parens
17818        };
17819        if force_parens {
17820            // Output trailing comments
17821            for comment in &func.trailing_comments {
17822                self.write_space();
17823                self.write_formatted_comment(comment);
17824            }
17825            return Ok(());
17826        }
17827
17828        // CUBE, ROLLUP, GROUPING SETS need a space before the parenthesis
17829        if func.name.eq_ignore_ascii_case("CUBE")
17830            || func.name.eq_ignore_ascii_case("ROLLUP")
17831            || func.name.eq_ignore_ascii_case("GROUPING SETS")
17832        {
17833            self.write(" (");
17834        } else if use_brackets {
17835            self.write("[");
17836        } else {
17837            self.write("(");
17838        }
17839        if func.distinct {
17840            self.write_keyword("DISTINCT");
17841            self.write_space();
17842        }
17843
17844        // Check if arguments should be split onto multiple lines (pretty + too wide)
17845        let compact_pretty_func = matches!(self.config.dialect, Some(DialectType::Snowflake))
17846            && (func.name.eq_ignore_ascii_case("TABLE")
17847                || func.name.eq_ignore_ascii_case("FLATTEN"));
17848        // GROUPING SETS, CUBE, ROLLUP always expand in pretty mode
17849        let is_grouping_func = func.name.eq_ignore_ascii_case("GROUPING SETS")
17850            || func.name.eq_ignore_ascii_case("CUBE")
17851            || func.name.eq_ignore_ascii_case("ROLLUP");
17852        let should_split = if self.config.pretty && !func.args.is_empty() && !compact_pretty_func {
17853            if is_grouping_func {
17854                true
17855            } else {
17856                // Pre-render arguments to check total width
17857                let mut expr_strings: Vec<String> = Vec::with_capacity(func.args.len());
17858                for arg in &func.args {
17859                    let mut temp_gen = Generator::with_arc_config(self.config.clone());
17860                    Arc::make_mut(&mut temp_gen.config).pretty = false; // Don't recurse into pretty
17861                    temp_gen.generate_expression(arg)?;
17862                    expr_strings.push(temp_gen.output);
17863                }
17864                self.too_wide(&expr_strings)
17865            }
17866        } else {
17867            false
17868        };
17869
17870        if should_split {
17871            // Split onto multiple lines
17872            self.write_newline();
17873            self.indent_level += 1;
17874            for (i, arg) in func.args.iter().enumerate() {
17875                self.write_indent();
17876                self.generate_expression(arg)?;
17877                if i + 1 < func.args.len() {
17878                    self.write(",");
17879                }
17880                self.write_newline();
17881            }
17882            self.indent_level -= 1;
17883            self.write_indent();
17884        } else {
17885            // All on one line
17886            for (i, arg) in func.args.iter().enumerate() {
17887                if i > 0 {
17888                    self.write(", ");
17889                }
17890                self.generate_expression(arg)?;
17891            }
17892        }
17893
17894        if use_brackets {
17895            self.write("]");
17896        } else {
17897            self.write(")");
17898        }
17899        // Append WITH ORDINALITY after closing paren for table-valued functions
17900        if has_ordinality {
17901            self.write_space();
17902            self.write_keyword("WITH ORDINALITY");
17903        }
17904        // Output trailing comments
17905        for comment in &func.trailing_comments {
17906            self.write_space();
17907            self.write_formatted_comment(comment);
17908        }
17909        Ok(())
17910    }
17911
17912    fn generate_function_emits(&mut self, fe: &FunctionEmits) -> Result<()> {
17913        self.generate_expression(&fe.this)?;
17914        self.write_keyword(" EMITS ");
17915        self.generate_expression(&fe.emits)?;
17916        Ok(())
17917    }
17918
17919    fn generate_aggregate_function(&mut self, func: &AggregateFunction) -> Result<()> {
17920        // Normalize function name based on dialect settings
17921        let mut normalized_name = self.normalize_func_name(&func.name);
17922
17923        // Dialect-specific name mappings for aggregate functions
17924        if func.name.eq_ignore_ascii_case("MAX_BY") || func.name.eq_ignore_ascii_case("MIN_BY") {
17925            let is_max = func.name.eq_ignore_ascii_case("MAX_BY");
17926            match self.config.dialect {
17927                Some(DialectType::ClickHouse) => {
17928                    normalized_name = if is_max {
17929                        Cow::Borrowed("argMax")
17930                    } else {
17931                        Cow::Borrowed("argMin")
17932                    };
17933                }
17934                Some(DialectType::DuckDB) => {
17935                    normalized_name = if is_max {
17936                        Cow::Borrowed("ARG_MAX")
17937                    } else {
17938                        Cow::Borrowed("ARG_MIN")
17939                    };
17940                }
17941                _ => {}
17942            }
17943        }
17944        self.write(normalized_name.as_ref());
17945        self.write("(");
17946        if func.distinct {
17947            self.write_keyword("DISTINCT");
17948            self.write_space();
17949        }
17950
17951        // Check if we need to transform multi-arg COUNT DISTINCT
17952        // When dialect doesn't support multi_arg_distinct, transform:
17953        // COUNT(DISTINCT a, b) -> COUNT(DISTINCT CASE WHEN a IS NULL THEN NULL WHEN b IS NULL THEN NULL ELSE (a, b) END)
17954        let is_count = normalized_name.eq_ignore_ascii_case("COUNT");
17955        let needs_multi_arg_transform =
17956            func.distinct && is_count && func.args.len() > 1 && !self.config.multi_arg_distinct;
17957
17958        if needs_multi_arg_transform {
17959            // Generate: CASE WHEN a IS NULL THEN NULL WHEN b IS NULL THEN NULL ELSE (a, b) END
17960            self.write_keyword("CASE");
17961            for arg in &func.args {
17962                self.write_space();
17963                self.write_keyword("WHEN");
17964                self.write_space();
17965                self.generate_expression(arg)?;
17966                self.write_space();
17967                self.write_keyword("IS NULL THEN NULL");
17968            }
17969            self.write_space();
17970            self.write_keyword("ELSE");
17971            self.write(" (");
17972            for (i, arg) in func.args.iter().enumerate() {
17973                if i > 0 {
17974                    self.write(", ");
17975                }
17976                self.generate_expression(arg)?;
17977            }
17978            self.write(")");
17979            self.write_space();
17980            self.write_keyword("END");
17981        } else {
17982            for (i, arg) in func.args.iter().enumerate() {
17983                if i > 0 {
17984                    self.write(", ");
17985                }
17986                self.generate_expression(arg)?;
17987            }
17988        }
17989
17990        // IGNORE NULLS / RESPECT NULLS inside parens (for BigQuery style or when config says in_func)
17991        let clickhouse_ignore_nulls_outside =
17992            matches!(self.config.dialect, Some(DialectType::ClickHouse));
17993        if self.config.ignore_nulls_in_func
17994            && !matches!(
17995                self.config.dialect,
17996                Some(DialectType::DuckDB) | Some(DialectType::ClickHouse)
17997            )
17998        {
17999            if let Some(ignore) = func.ignore_nulls {
18000                self.write_space();
18001                if ignore {
18002                    self.write_keyword("IGNORE NULLS");
18003                } else {
18004                    self.write_keyword("RESPECT NULLS");
18005                }
18006            }
18007        }
18008
18009        // ORDER BY inside aggregate
18010        if !func.order_by.is_empty() {
18011            self.write_space();
18012            self.write_keyword("ORDER BY");
18013            self.write_space();
18014            for (i, ord) in func.order_by.iter().enumerate() {
18015                if i > 0 {
18016                    self.write(", ");
18017                }
18018                self.generate_ordered(ord)?;
18019            }
18020        }
18021
18022        // LIMIT inside aggregate
18023        if let Some(limit) = &func.limit {
18024            self.write_space();
18025            self.write_keyword("LIMIT");
18026            self.write_space();
18027            // Check if this is a Tuple representing LIMIT offset, count
18028            if let Expression::Tuple(t) = limit.as_ref() {
18029                if t.expressions.len() == 2 {
18030                    self.generate_expression(&t.expressions[0])?;
18031                    self.write(", ");
18032                    self.generate_expression(&t.expressions[1])?;
18033                } else {
18034                    self.generate_expression(limit)?;
18035                }
18036            } else {
18037                self.generate_expression(limit)?;
18038            }
18039        }
18040
18041        self.write(")");
18042
18043        // IGNORE NULLS / RESPECT NULLS outside parens (standard style)
18044        if (!self.config.ignore_nulls_in_func || clickhouse_ignore_nulls_outside)
18045            && !matches!(self.config.dialect, Some(DialectType::DuckDB))
18046        {
18047            if let Some(ignore) = func.ignore_nulls {
18048                self.write_space();
18049                if ignore {
18050                    self.write_keyword("IGNORE NULLS");
18051                } else {
18052                    self.write_keyword("RESPECT NULLS");
18053                }
18054            }
18055        }
18056
18057        if let Some(filter) = &func.filter {
18058            self.write_space();
18059            self.write_keyword("FILTER");
18060            self.write("(");
18061            self.write_keyword("WHERE");
18062            self.write_space();
18063            self.generate_expression(filter)?;
18064            self.write(")");
18065        }
18066
18067        Ok(())
18068    }
18069
18070    fn generate_window_function(&mut self, wf: &WindowFunction) -> Result<()> {
18071        self.generate_expression(&wf.this)?;
18072
18073        // Generate KEEP clause if present (Oracle KEEP (DENSE_RANK FIRST|LAST ORDER BY ...))
18074        if let Some(keep) = &wf.keep {
18075            self.write_space();
18076            self.write_keyword("KEEP");
18077            self.write(" (");
18078            self.write_keyword("DENSE_RANK");
18079            self.write_space();
18080            if keep.first {
18081                self.write_keyword("FIRST");
18082            } else {
18083                self.write_keyword("LAST");
18084            }
18085            self.write_space();
18086            self.write_keyword("ORDER BY");
18087            self.write_space();
18088            for (i, ord) in keep.order_by.iter().enumerate() {
18089                if i > 0 {
18090                    self.write(", ");
18091                }
18092                self.generate_ordered(ord)?;
18093            }
18094            self.write(")");
18095        }
18096
18097        // Check if there's any OVER clause content
18098        let has_over = !wf.over.partition_by.is_empty()
18099            || !wf.over.order_by.is_empty()
18100            || wf.over.frame.is_some()
18101            || wf.over.window_name.is_some();
18102
18103        // Only output OVER if there's actual window specification (not just KEEP alone)
18104        if has_over {
18105            self.write_space();
18106            self.write_keyword("OVER");
18107
18108            // Check if this is just a bare named window reference (no parens needed)
18109            let has_specs = !wf.over.partition_by.is_empty()
18110                || !wf.over.order_by.is_empty()
18111                || wf.over.frame.is_some();
18112
18113            if wf.over.window_name.is_some() && !has_specs {
18114                // OVER window_name (without parentheses)
18115                self.write_space();
18116                self.write(&wf.over.window_name.as_ref().unwrap().name);
18117            } else {
18118                // OVER (...) or OVER (window_name ...)
18119                self.write(" (");
18120                self.generate_over(&wf.over)?;
18121                self.write(")");
18122            }
18123        } else if wf.keep.is_none() {
18124            // No KEEP and no OVER content, but still a WindowFunction - output empty OVER ()
18125            self.write_space();
18126            self.write_keyword("OVER");
18127            self.write(" ()");
18128        }
18129
18130        Ok(())
18131    }
18132
18133    /// Generate WITHIN GROUP clause (for ordered-set aggregate functions)
18134    fn generate_within_group(&mut self, wg: &WithinGroup) -> Result<()> {
18135        self.generate_expression(&wg.this)?;
18136        self.write_space();
18137        self.write_keyword("WITHIN GROUP");
18138        self.write(" (");
18139        self.write_keyword("ORDER BY");
18140        self.write_space();
18141        for (i, ord) in wg.order_by.iter().enumerate() {
18142            if i > 0 {
18143                self.write(", ");
18144            }
18145            self.generate_ordered(ord)?;
18146        }
18147        self.write(")");
18148        Ok(())
18149    }
18150
18151    /// Generate the contents of an OVER clause (without parentheses)
18152    fn generate_over(&mut self, over: &Over) -> Result<()> {
18153        let mut has_content = false;
18154
18155        // Named window reference
18156        if let Some(name) = &over.window_name {
18157            self.write(&name.name);
18158            has_content = true;
18159        }
18160
18161        // PARTITION BY
18162        if !over.partition_by.is_empty() {
18163            if has_content {
18164                self.write_space();
18165            }
18166            self.write_keyword("PARTITION BY");
18167            self.write_space();
18168            for (i, expr) in over.partition_by.iter().enumerate() {
18169                if i > 0 {
18170                    self.write(", ");
18171                }
18172                self.generate_expression(expr)?;
18173            }
18174            has_content = true;
18175        }
18176
18177        // ORDER BY
18178        if !over.order_by.is_empty() {
18179            if has_content {
18180                self.write_space();
18181            }
18182            self.write_keyword("ORDER BY");
18183            self.write_space();
18184            for (i, ordered) in over.order_by.iter().enumerate() {
18185                if i > 0 {
18186                    self.write(", ");
18187                }
18188                self.generate_ordered(ordered)?;
18189            }
18190            has_content = true;
18191        }
18192
18193        // Window frame
18194        if let Some(frame) = &over.frame {
18195            if has_content {
18196                self.write_space();
18197            }
18198            self.generate_window_frame(frame)?;
18199        }
18200
18201        Ok(())
18202    }
18203
18204    fn generate_window_frame(&mut self, frame: &WindowFrame) -> Result<()> {
18205        // Exasol uses lowercase for frame kind (rows/range/groups)
18206        let lowercase_frame = self.config.lowercase_window_frame_keywords;
18207
18208        // Use preserved kind_text if available (for case preservation), unless lowercase override is active
18209        if !lowercase_frame {
18210            if let Some(kind_text) = &frame.kind_text {
18211                self.write(kind_text);
18212            } else {
18213                match frame.kind {
18214                    WindowFrameKind::Rows => self.write_keyword("ROWS"),
18215                    WindowFrameKind::Range => self.write_keyword("RANGE"),
18216                    WindowFrameKind::Groups => self.write_keyword("GROUPS"),
18217                }
18218            }
18219        } else {
18220            match frame.kind {
18221                WindowFrameKind::Rows => self.write("rows"),
18222                WindowFrameKind::Range => self.write("range"),
18223                WindowFrameKind::Groups => self.write("groups"),
18224            }
18225        }
18226
18227        // Use BETWEEN format only when there's an explicit end bound,
18228        // or when normalize_window_frame_between is enabled and the start is a directional bound
18229        self.write_space();
18230        let should_normalize = self.config.normalize_window_frame_between
18231            && frame.end.is_none()
18232            && matches!(
18233                frame.start,
18234                WindowFrameBound::Preceding(_)
18235                    | WindowFrameBound::Following(_)
18236                    | WindowFrameBound::UnboundedPreceding
18237                    | WindowFrameBound::UnboundedFollowing
18238            );
18239
18240        if let Some(end) = &frame.end {
18241            // BETWEEN format: RANGE BETWEEN start AND end
18242            self.write_keyword("BETWEEN");
18243            self.write_space();
18244            self.generate_window_frame_bound(&frame.start, frame.start_side_text.as_deref())?;
18245            self.write_space();
18246            self.write_keyword("AND");
18247            self.write_space();
18248            self.generate_window_frame_bound(end, frame.end_side_text.as_deref())?;
18249        } else if should_normalize {
18250            // Normalize single-bound to BETWEEN form: ROWS 1 PRECEDING → ROWS BETWEEN 1 PRECEDING AND CURRENT ROW
18251            self.write_keyword("BETWEEN");
18252            self.write_space();
18253            self.generate_window_frame_bound(&frame.start, frame.start_side_text.as_deref())?;
18254            self.write_space();
18255            self.write_keyword("AND");
18256            self.write_space();
18257            self.write_keyword("CURRENT ROW");
18258        } else {
18259            // Single bound format: RANGE CURRENT ROW
18260            self.generate_window_frame_bound(&frame.start, frame.start_side_text.as_deref())?;
18261        }
18262
18263        // EXCLUDE clause
18264        if let Some(exclude) = &frame.exclude {
18265            self.write_space();
18266            self.write_keyword("EXCLUDE");
18267            self.write_space();
18268            match exclude {
18269                WindowFrameExclude::CurrentRow => self.write_keyword("CURRENT ROW"),
18270                WindowFrameExclude::Group => self.write_keyword("GROUP"),
18271                WindowFrameExclude::Ties => self.write_keyword("TIES"),
18272                WindowFrameExclude::NoOthers => self.write_keyword("NO OTHERS"),
18273            }
18274        }
18275
18276        Ok(())
18277    }
18278
18279    fn generate_window_frame_bound(
18280        &mut self,
18281        bound: &WindowFrameBound,
18282        side_text: Option<&str>,
18283    ) -> Result<()> {
18284        // Exasol uses lowercase for preceding/following
18285        let lowercase_frame = self.config.lowercase_window_frame_keywords;
18286
18287        match bound {
18288            WindowFrameBound::CurrentRow => {
18289                self.write_keyword("CURRENT ROW");
18290            }
18291            WindowFrameBound::UnboundedPreceding => {
18292                self.write_keyword("UNBOUNDED");
18293                self.write_space();
18294                if lowercase_frame {
18295                    self.write("preceding");
18296                } else if let Some(text) = side_text {
18297                    self.write(text);
18298                } else {
18299                    self.write_keyword("PRECEDING");
18300                }
18301            }
18302            WindowFrameBound::UnboundedFollowing => {
18303                self.write_keyword("UNBOUNDED");
18304                self.write_space();
18305                if lowercase_frame {
18306                    self.write("following");
18307                } else if let Some(text) = side_text {
18308                    self.write(text);
18309                } else {
18310                    self.write_keyword("FOLLOWING");
18311                }
18312            }
18313            WindowFrameBound::Preceding(expr) => {
18314                self.generate_expression(expr)?;
18315                self.write_space();
18316                if lowercase_frame {
18317                    self.write("preceding");
18318                } else if let Some(text) = side_text {
18319                    self.write(text);
18320                } else {
18321                    self.write_keyword("PRECEDING");
18322                }
18323            }
18324            WindowFrameBound::Following(expr) => {
18325                self.generate_expression(expr)?;
18326                self.write_space();
18327                if lowercase_frame {
18328                    self.write("following");
18329                } else if let Some(text) = side_text {
18330                    self.write(text);
18331                } else {
18332                    self.write_keyword("FOLLOWING");
18333                }
18334            }
18335            WindowFrameBound::BarePreceding => {
18336                if lowercase_frame {
18337                    self.write("preceding");
18338                } else if let Some(text) = side_text {
18339                    self.write(text);
18340                } else {
18341                    self.write_keyword("PRECEDING");
18342                }
18343            }
18344            WindowFrameBound::BareFollowing => {
18345                if lowercase_frame {
18346                    self.write("following");
18347                } else if let Some(text) = side_text {
18348                    self.write(text);
18349                } else {
18350                    self.write_keyword("FOLLOWING");
18351                }
18352            }
18353            WindowFrameBound::Value(expr) => {
18354                // Bare numeric bound without PRECEDING/FOLLOWING
18355                self.generate_expression(expr)?;
18356            }
18357        }
18358        Ok(())
18359    }
18360
18361    fn generate_interval(&mut self, interval: &Interval) -> Result<()> {
18362        // For Oracle with ExprSpan: only output INTERVAL if `this` is a literal
18363        // (e.g., `(expr) DAY(9) TO SECOND(3)` should NOT have INTERVAL prefix)
18364        let skip_interval_keyword = matches!(self.config.dialect, Some(DialectType::Oracle))
18365            && matches!(&interval.unit, Some(IntervalUnitSpec::ExprSpan(_)))
18366            && !matches!(&interval.this, Some(Expression::Literal(_)));
18367
18368        // SINGLE_STRING_INTERVAL: combine value and unit into a single quoted string
18369        // e.g., INTERVAL '1' DAY -> INTERVAL '1 DAY'
18370        if self.config.single_string_interval {
18371            if let (
18372                Some(Expression::Literal(lit)),
18373                Some(IntervalUnitSpec::Simple {
18374                    ref unit,
18375                    ref use_plural,
18376                }),
18377            ) = (&interval.this, &interval.unit)
18378            {
18379                if let Literal::String(ref val) = lit.as_ref() {
18380                    self.write_keyword("INTERVAL");
18381                    self.write_space();
18382                    let effective_plural = *use_plural && self.config.interval_allows_plural_form;
18383                    let unit_str = self.interval_unit_str(unit, effective_plural);
18384                    self.write("'");
18385                    self.write(val);
18386                    self.write(" ");
18387                    self.write(&unit_str);
18388                    self.write("'");
18389                    return Ok(());
18390                }
18391            }
18392        }
18393
18394        if !skip_interval_keyword {
18395            self.write_keyword("INTERVAL");
18396        }
18397
18398        // Generate value if present
18399        if let Some(ref value) = interval.this {
18400            if !skip_interval_keyword {
18401                self.write_space();
18402            }
18403            // If the value is a complex expression (not a literal/column/function call)
18404            // and there's a unit, wrap it in parentheses
18405            // e.g., INTERVAL (2 * 2) MONTH, INTERVAL (DAYOFMONTH(dt) - 1) DAY
18406            let needs_parens = interval.unit.is_some()
18407                && matches!(
18408                    value,
18409                    Expression::Add(_)
18410                        | Expression::Sub(_)
18411                        | Expression::Mul(_)
18412                        | Expression::Div(_)
18413                        | Expression::Mod(_)
18414                        | Expression::BitwiseAnd(_)
18415                        | Expression::BitwiseOr(_)
18416                        | Expression::BitwiseXor(_)
18417                );
18418            if needs_parens {
18419                self.write("(");
18420            }
18421            self.generate_expression(value)?;
18422            if needs_parens {
18423                self.write(")");
18424            }
18425        }
18426
18427        // Generate unit if present
18428        if let Some(ref unit_spec) = interval.unit {
18429            self.write_space();
18430            self.write_interval_unit_spec(unit_spec)?;
18431        }
18432
18433        Ok(())
18434    }
18435
18436    /// Return the string representation of an interval unit
18437    fn interval_unit_str(&self, unit: &IntervalUnit, use_plural: bool) -> &'static str {
18438        match (unit, use_plural) {
18439            (IntervalUnit::Year, false) => "YEAR",
18440            (IntervalUnit::Year, true) => "YEARS",
18441            (IntervalUnit::Quarter, false) => "QUARTER",
18442            (IntervalUnit::Quarter, true) => "QUARTERS",
18443            (IntervalUnit::Month, false) => "MONTH",
18444            (IntervalUnit::Month, true) => "MONTHS",
18445            (IntervalUnit::Week, false) => "WEEK",
18446            (IntervalUnit::Week, true) => "WEEKS",
18447            (IntervalUnit::Day, false) => "DAY",
18448            (IntervalUnit::Day, true) => "DAYS",
18449            (IntervalUnit::Hour, false) => "HOUR",
18450            (IntervalUnit::Hour, true) => "HOURS",
18451            (IntervalUnit::Minute, false) => "MINUTE",
18452            (IntervalUnit::Minute, true) => "MINUTES",
18453            (IntervalUnit::Second, false) => "SECOND",
18454            (IntervalUnit::Second, true) => "SECONDS",
18455            (IntervalUnit::Millisecond, false) => "MILLISECOND",
18456            (IntervalUnit::Millisecond, true) => "MILLISECONDS",
18457            (IntervalUnit::Microsecond, false) => "MICROSECOND",
18458            (IntervalUnit::Microsecond, true) => "MICROSECONDS",
18459            (IntervalUnit::Nanosecond, false) => "NANOSECOND",
18460            (IntervalUnit::Nanosecond, true) => "NANOSECONDS",
18461        }
18462    }
18463
18464    fn write_interval_unit_spec(&mut self, unit_spec: &IntervalUnitSpec) -> Result<()> {
18465        match unit_spec {
18466            IntervalUnitSpec::Simple { unit, use_plural } => {
18467                // If dialect doesn't allow plural forms, force singular
18468                let effective_plural = *use_plural && self.config.interval_allows_plural_form;
18469                self.write_simple_interval_unit(unit, effective_plural);
18470            }
18471            IntervalUnitSpec::Span(span) => {
18472                self.write_simple_interval_unit(&span.this, false);
18473                self.write_space();
18474                self.write_keyword("TO");
18475                self.write_space();
18476                self.write_simple_interval_unit(&span.expression, false);
18477            }
18478            IntervalUnitSpec::ExprSpan(span) => {
18479                // Expression-based interval span (e.g., DAY(9) TO SECOND(3))
18480                self.generate_expression(&span.this)?;
18481                self.write_space();
18482                self.write_keyword("TO");
18483                self.write_space();
18484                self.generate_expression(&span.expression)?;
18485            }
18486            IntervalUnitSpec::Expr(expr) => {
18487                self.generate_expression(expr)?;
18488            }
18489        }
18490        Ok(())
18491    }
18492
18493    fn write_simple_interval_unit(&mut self, unit: &IntervalUnit, use_plural: bool) {
18494        // Output interval unit, respecting plural preference
18495        match (unit, use_plural) {
18496            (IntervalUnit::Year, false) => self.write_keyword("YEAR"),
18497            (IntervalUnit::Year, true) => self.write_keyword("YEARS"),
18498            (IntervalUnit::Quarter, false) => self.write_keyword("QUARTER"),
18499            (IntervalUnit::Quarter, true) => self.write_keyword("QUARTERS"),
18500            (IntervalUnit::Month, false) => self.write_keyword("MONTH"),
18501            (IntervalUnit::Month, true) => self.write_keyword("MONTHS"),
18502            (IntervalUnit::Week, false) => self.write_keyword("WEEK"),
18503            (IntervalUnit::Week, true) => self.write_keyword("WEEKS"),
18504            (IntervalUnit::Day, false) => self.write_keyword("DAY"),
18505            (IntervalUnit::Day, true) => self.write_keyword("DAYS"),
18506            (IntervalUnit::Hour, false) => self.write_keyword("HOUR"),
18507            (IntervalUnit::Hour, true) => self.write_keyword("HOURS"),
18508            (IntervalUnit::Minute, false) => self.write_keyword("MINUTE"),
18509            (IntervalUnit::Minute, true) => self.write_keyword("MINUTES"),
18510            (IntervalUnit::Second, false) => self.write_keyword("SECOND"),
18511            (IntervalUnit::Second, true) => self.write_keyword("SECONDS"),
18512            (IntervalUnit::Millisecond, false) => self.write_keyword("MILLISECOND"),
18513            (IntervalUnit::Millisecond, true) => self.write_keyword("MILLISECONDS"),
18514            (IntervalUnit::Microsecond, false) => self.write_keyword("MICROSECOND"),
18515            (IntervalUnit::Microsecond, true) => self.write_keyword("MICROSECONDS"),
18516            (IntervalUnit::Nanosecond, false) => self.write_keyword("NANOSECOND"),
18517            (IntervalUnit::Nanosecond, true) => self.write_keyword("NANOSECONDS"),
18518        }
18519    }
18520
18521    /// Normalize a date part expression to unquoted uppercase for Redshift DATEDIFF/DATEADD
18522    /// Converts: 'day', 'days', day, days, DAY -> DAY (unquoted)
18523    fn write_redshift_date_part(&mut self, expr: &Expression) {
18524        let part_str = self.extract_date_part_string(expr);
18525        if let Some(part) = part_str {
18526            let normalized = self.normalize_date_part(&part);
18527            self.write_keyword(&normalized);
18528        } else {
18529            // If we can't extract a date part string, fall back to generating the expression
18530            let _ = self.generate_expression(expr);
18531        }
18532    }
18533
18534    /// Normalize a date part expression to quoted uppercase for Redshift DATE_TRUNC
18535    /// Converts: 'day', day, DAY -> 'DAY' (quoted)
18536    fn write_redshift_date_part_quoted(&mut self, expr: &Expression) {
18537        let part_str = self.extract_date_part_string(expr);
18538        if let Some(part) = part_str {
18539            let normalized = self.normalize_date_part(&part);
18540            self.write("'");
18541            self.write(&normalized);
18542            self.write("'");
18543        } else {
18544            // If we can't extract a date part string, fall back to generating the expression
18545            let _ = self.generate_expression(expr);
18546        }
18547    }
18548
18549    /// Extract date part string from expression (handles string literals and identifiers)
18550    fn extract_date_part_string(&self, expr: &Expression) -> Option<String> {
18551        match expr {
18552            Expression::Literal(lit)
18553                if matches!(lit.as_ref(), crate::expressions::Literal::String(_)) =>
18554            {
18555                let crate::expressions::Literal::String(s) = lit.as_ref() else {
18556                    unreachable!()
18557                };
18558                Some(s.clone())
18559            }
18560            Expression::Identifier(id) => Some(id.name.clone()),
18561            Expression::Var(v) => Some(v.this.clone()),
18562            Expression::Column(col) if col.table.is_none() => {
18563                // Simple column reference without table prefix, treat as identifier
18564                Some(col.name.name.clone())
18565            }
18566            _ => None,
18567        }
18568    }
18569
18570    /// Normalize date part to uppercase singular form
18571    /// days -> DAY, months -> MONTH, etc.
18572    fn normalize_date_part(&self, part: &str) -> String {
18573        let mut buf = [0u8; 64];
18574        let lower: &str = if part.len() <= 64 {
18575            for (i, b) in part.bytes().enumerate() {
18576                buf[i] = b.to_ascii_lowercase();
18577            }
18578            std::str::from_utf8(&buf[..part.len()]).unwrap_or(part)
18579        } else {
18580            return part.to_ascii_uppercase();
18581        };
18582        match lower {
18583            "day" | "days" | "d" => "DAY".to_string(),
18584            "month" | "months" | "mon" | "mons" | "mm" => "MONTH".to_string(),
18585            "year" | "years" | "y" | "yy" | "yyyy" => "YEAR".to_string(),
18586            "week" | "weeks" | "w" | "wk" => "WEEK".to_string(),
18587            "hour" | "hours" | "h" | "hh" => "HOUR".to_string(),
18588            "minute" | "minutes" | "m" | "mi" | "n" => "MINUTE".to_string(),
18589            "second" | "seconds" | "s" | "ss" => "SECOND".to_string(),
18590            "millisecond" | "milliseconds" | "ms" => "MILLISECOND".to_string(),
18591            "microsecond" | "microseconds" | "us" => "MICROSECOND".to_string(),
18592            "quarter" | "quarters" | "q" | "qq" => "QUARTER".to_string(),
18593            _ => part.to_ascii_uppercase(),
18594        }
18595    }
18596
18597    fn write_datetime_field(&mut self, field: &DateTimeField) {
18598        match field {
18599            DateTimeField::Year => self.write_keyword("YEAR"),
18600            DateTimeField::Month => self.write_keyword("MONTH"),
18601            DateTimeField::Day => self.write_keyword("DAY"),
18602            DateTimeField::Hour => self.write_keyword("HOUR"),
18603            DateTimeField::Minute => self.write_keyword("MINUTE"),
18604            DateTimeField::Second => self.write_keyword("SECOND"),
18605            DateTimeField::Millisecond => self.write_keyword("MILLISECOND"),
18606            DateTimeField::Microsecond => self.write_keyword("MICROSECOND"),
18607            DateTimeField::DayOfWeek => {
18608                let name = match self.config.dialect {
18609                    Some(DialectType::DuckDB) | Some(DialectType::Snowflake) => "DAYOFWEEK",
18610                    Some(DialectType::TSQL) | Some(DialectType::Fabric) => "WEEKDAY",
18611                    _ => "DOW",
18612                };
18613                self.write_keyword(name);
18614            }
18615            DateTimeField::DayOfYear => {
18616                let name = match self.config.dialect {
18617                    Some(DialectType::DuckDB) | Some(DialectType::Snowflake) => "DAYOFYEAR",
18618                    _ => "DOY",
18619                };
18620                self.write_keyword(name);
18621            }
18622            DateTimeField::Week => self.write_keyword("WEEK"),
18623            DateTimeField::WeekWithModifier(modifier) => {
18624                self.write_keyword("WEEK");
18625                self.write("(");
18626                self.write(modifier);
18627                self.write(")");
18628            }
18629            DateTimeField::Quarter => self.write_keyword("QUARTER"),
18630            DateTimeField::Epoch => self.write_keyword("EPOCH"),
18631            DateTimeField::Timezone => self.write_keyword("TIMEZONE"),
18632            DateTimeField::TimezoneHour => self.write_keyword("TIMEZONE_HOUR"),
18633            DateTimeField::TimezoneMinute => self.write_keyword("TIMEZONE_MINUTE"),
18634            DateTimeField::Date => self.write_keyword("DATE"),
18635            DateTimeField::Time => self.write_keyword("TIME"),
18636            DateTimeField::Custom(name) => self.write(name),
18637        }
18638    }
18639
18640    /// Write datetime field in lowercase (for Spark/Hive/Databricks)
18641    fn write_datetime_field_lower(&mut self, field: &DateTimeField) {
18642        match field {
18643            DateTimeField::Year => self.write("year"),
18644            DateTimeField::Month => self.write("month"),
18645            DateTimeField::Day => self.write("day"),
18646            DateTimeField::Hour => self.write("hour"),
18647            DateTimeField::Minute => self.write("minute"),
18648            DateTimeField::Second => self.write("second"),
18649            DateTimeField::Millisecond => self.write("millisecond"),
18650            DateTimeField::Microsecond => self.write("microsecond"),
18651            DateTimeField::DayOfWeek => self.write("dow"),
18652            DateTimeField::DayOfYear => self.write("doy"),
18653            DateTimeField::Week => self.write("week"),
18654            DateTimeField::WeekWithModifier(modifier) => {
18655                self.write("week(");
18656                self.write(modifier);
18657                self.write(")");
18658            }
18659            DateTimeField::Quarter => self.write("quarter"),
18660            DateTimeField::Epoch => self.write("epoch"),
18661            DateTimeField::Timezone => self.write("timezone"),
18662            DateTimeField::TimezoneHour => self.write("timezone_hour"),
18663            DateTimeField::TimezoneMinute => self.write("timezone_minute"),
18664            DateTimeField::Date => self.write("date"),
18665            DateTimeField::Time => self.write("time"),
18666            DateTimeField::Custom(name) => self.write(name),
18667        }
18668    }
18669
18670    // Helper function generators
18671
18672    fn generate_simple_func(&mut self, name: &str, arg: &Expression) -> Result<()> {
18673        self.write_keyword(name);
18674        self.write("(");
18675        self.generate_expression(arg)?;
18676        self.write(")");
18677        Ok(())
18678    }
18679
18680    /// Generate a unary function, using the original name if available for round-trip preservation
18681    fn generate_unary_func(
18682        &mut self,
18683        default_name: &str,
18684        f: &crate::expressions::UnaryFunc,
18685    ) -> Result<()> {
18686        let name = f.original_name.as_deref().unwrap_or(default_name);
18687        self.write_keyword(name);
18688        self.write("(");
18689        self.generate_expression(&f.this)?;
18690        self.write(")");
18691        Ok(())
18692    }
18693
18694    /// Generate SQRT/CBRT - always use function form (matches Python SQLGlot normalization)
18695    fn generate_sqrt_cbrt(
18696        &mut self,
18697        f: &crate::expressions::UnaryFunc,
18698        func_name: &str,
18699        _op: &str,
18700    ) -> Result<()> {
18701        // Python SQLGlot normalizes |/ and ||/ to SQRT() and CBRT()
18702        // Always use function syntax for consistency
18703        self.write_keyword(func_name);
18704        self.write("(");
18705        self.generate_expression(&f.this)?;
18706        self.write(")");
18707        Ok(())
18708    }
18709
18710    fn generate_binary_func(
18711        &mut self,
18712        name: &str,
18713        arg1: &Expression,
18714        arg2: &Expression,
18715    ) -> Result<()> {
18716        self.write_keyword(name);
18717        self.write("(");
18718        self.generate_expression(arg1)?;
18719        self.write(", ");
18720        self.generate_expression(arg2)?;
18721        self.write(")");
18722        Ok(())
18723    }
18724
18725    /// Generate CHAR/CHR function with optional USING charset
18726    /// e.g., CHAR(77, 77.3, '77.3' USING utf8mb4)
18727    /// e.g., CHR(187 USING NCHAR_CS) -- Oracle
18728    fn generate_char_func(&mut self, f: &crate::expressions::CharFunc) -> Result<()> {
18729        // Use stored name if available, otherwise default to CHAR
18730        let func_name = f.name.as_deref().unwrap_or("CHAR");
18731        self.write_keyword(func_name);
18732        self.write("(");
18733        for (i, arg) in f.args.iter().enumerate() {
18734            if i > 0 {
18735                self.write(", ");
18736            }
18737            self.generate_expression(arg)?;
18738        }
18739        if let Some(ref charset) = f.charset {
18740            self.write(" ");
18741            self.write_keyword("USING");
18742            self.write(" ");
18743            self.write(charset);
18744        }
18745        self.write(")");
18746        Ok(())
18747    }
18748
18749    fn generate_power(&mut self, f: &BinaryFunc) -> Result<()> {
18750        use crate::dialects::DialectType;
18751
18752        match self.config.dialect {
18753            Some(DialectType::Teradata) => {
18754                // Teradata uses ** operator for exponentiation
18755                self.generate_expression(&f.this)?;
18756                self.write(" ** ");
18757                self.generate_expression(&f.expression)?;
18758                Ok(())
18759            }
18760            _ => {
18761                // Other dialects use POWER function
18762                self.generate_binary_func("POWER", &f.this, &f.expression)
18763            }
18764        }
18765    }
18766
18767    fn generate_vararg_func(&mut self, name: &str, args: &[Expression]) -> Result<()> {
18768        self.write_func_name(name);
18769        self.write("(");
18770        for (i, arg) in args.iter().enumerate() {
18771            if i > 0 {
18772                self.write(", ");
18773            }
18774            self.generate_expression(arg)?;
18775        }
18776        self.write(")");
18777        Ok(())
18778    }
18779
18780    // String function generators
18781
18782    fn generate_concat_ws(&mut self, f: &ConcatWs) -> Result<()> {
18783        self.write_keyword("CONCAT_WS");
18784        self.write("(");
18785        self.generate_expression(&f.separator)?;
18786        for expr in &f.expressions {
18787            self.write(", ");
18788            self.generate_expression(expr)?;
18789        }
18790        self.write(")");
18791        Ok(())
18792    }
18793
18794    fn collect_concat_operands<'a>(expr: &'a Expression, out: &mut Vec<&'a Expression>) {
18795        if let Expression::Concat(op) = expr {
18796            Self::collect_concat_operands(&op.left, out);
18797            Self::collect_concat_operands(&op.right, out);
18798        } else {
18799            out.push(expr);
18800        }
18801    }
18802
18803    fn generate_mysql_concat_from_concat(&mut self, op: &BinaryOp) -> Result<()> {
18804        let mut operands = Vec::new();
18805        Self::collect_concat_operands(&op.left, &mut operands);
18806        Self::collect_concat_operands(&op.right, &mut operands);
18807
18808        self.write_keyword("CONCAT");
18809        self.write("(");
18810        for (i, operand) in operands.iter().enumerate() {
18811            if i > 0 {
18812                self.write(", ");
18813            }
18814            self.generate_expression(operand)?;
18815        }
18816        self.write(")");
18817        Ok(())
18818    }
18819
18820    fn collect_dpipe_operands<'a>(expr: &'a Expression, out: &mut Vec<&'a Expression>) {
18821        if let Expression::DPipe(dpipe) = expr {
18822            Self::collect_dpipe_operands(&dpipe.this, out);
18823            Self::collect_dpipe_operands(&dpipe.expression, out);
18824        } else {
18825            out.push(expr);
18826        }
18827    }
18828
18829    fn generate_mysql_concat_from_dpipe(&mut self, e: &DPipe) -> Result<()> {
18830        let mut operands = Vec::new();
18831        Self::collect_dpipe_operands(&e.this, &mut operands);
18832        Self::collect_dpipe_operands(&e.expression, &mut operands);
18833
18834        self.write_keyword("CONCAT");
18835        self.write("(");
18836        for (i, operand) in operands.iter().enumerate() {
18837            if i > 0 {
18838                self.write(", ");
18839            }
18840            self.generate_expression(operand)?;
18841        }
18842        self.write(")");
18843        Ok(())
18844    }
18845
18846    fn generate_substring(&mut self, f: &SubstringFunc) -> Result<()> {
18847        // Oracle and Presto-family dialects use SUBSTR; most others use SUBSTRING
18848        let use_substr = matches!(
18849            self.config.dialect,
18850            Some(
18851                DialectType::Oracle
18852                    | DialectType::Presto
18853                    | DialectType::Trino
18854                    | DialectType::Athena
18855            )
18856        );
18857        if use_substr {
18858            self.write_keyword("SUBSTR");
18859        } else {
18860            self.write_keyword("SUBSTRING");
18861        }
18862        self.write("(");
18863        self.generate_expression(&f.this)?;
18864        // PostgreSQL always uses FROM/FOR syntax
18865        let force_from_for = matches!(self.config.dialect, Some(DialectType::PostgreSQL));
18866        // Spark/Hive/TSQL/Fabric use comma syntax, not FROM/FOR syntax
18867        let use_comma_syntax = matches!(
18868            self.config.dialect,
18869            Some(DialectType::Spark)
18870                | Some(DialectType::Hive)
18871                | Some(DialectType::Databricks)
18872                | Some(DialectType::TSQL)
18873                | Some(DialectType::Fabric)
18874        );
18875        if (f.from_for_syntax || force_from_for) && !use_comma_syntax {
18876            // SQL standard syntax: SUBSTRING(str FROM pos FOR len)
18877            self.write_space();
18878            self.write_keyword("FROM");
18879            self.write_space();
18880            self.generate_expression(&f.start)?;
18881            if let Some(length) = &f.length {
18882                self.write_space();
18883                self.write_keyword("FOR");
18884                self.write_space();
18885                self.generate_expression(length)?;
18886            }
18887        } else {
18888            // Comma-separated syntax: SUBSTRING(str, pos, len) or SUBSTR(str, pos, len)
18889            self.write(", ");
18890            self.generate_expression(&f.start)?;
18891            if let Some(length) = &f.length {
18892                self.write(", ");
18893                self.generate_expression(length)?;
18894            }
18895        }
18896        self.write(")");
18897        Ok(())
18898    }
18899
18900    fn generate_overlay(&mut self, f: &OverlayFunc) -> Result<()> {
18901        self.write_keyword("OVERLAY");
18902        self.write("(");
18903        self.generate_expression(&f.this)?;
18904        self.write_space();
18905        self.write_keyword("PLACING");
18906        self.write_space();
18907        self.generate_expression(&f.replacement)?;
18908        self.write_space();
18909        self.write_keyword("FROM");
18910        self.write_space();
18911        self.generate_expression(&f.from)?;
18912        if let Some(length) = &f.length {
18913            self.write_space();
18914            self.write_keyword("FOR");
18915            self.write_space();
18916            self.generate_expression(length)?;
18917        }
18918        self.write(")");
18919        Ok(())
18920    }
18921
18922    fn generate_trim(&mut self, f: &TrimFunc) -> Result<()> {
18923        // Special case: TRIM(LEADING str) -> LTRIM(str), TRIM(TRAILING str) -> RTRIM(str)
18924        // when no characters are specified (PostgreSQL style)
18925        if f.position_explicit && f.characters.is_none() {
18926            match f.position {
18927                TrimPosition::Leading => {
18928                    self.write_keyword("LTRIM");
18929                    self.write("(");
18930                    self.generate_expression(&f.this)?;
18931                    self.write(")");
18932                    return Ok(());
18933                }
18934                TrimPosition::Trailing => {
18935                    self.write_keyword("RTRIM");
18936                    self.write("(");
18937                    self.generate_expression(&f.this)?;
18938                    self.write(")");
18939                    return Ok(());
18940                }
18941                TrimPosition::Both => {
18942                    // TRIM(BOTH str) -> BTRIM(str) in PostgreSQL, but TRIM(str) is more standard
18943                    // Fall through to standard TRIM handling
18944                }
18945            }
18946        }
18947
18948        self.write_keyword("TRIM");
18949        self.write("(");
18950        // When BOTH is specified without trim characters, simplify to just TRIM(str)
18951        // Force standard syntax for dialects that require it (Hive, Spark, Databricks, ClickHouse)
18952        let force_standard = f.characters.is_some()
18953            && !f.sql_standard_syntax
18954            && matches!(
18955                self.config.dialect,
18956                Some(DialectType::Hive)
18957                    | Some(DialectType::Spark)
18958                    | Some(DialectType::Databricks)
18959                    | Some(DialectType::ClickHouse)
18960            );
18961        let use_standard = (f.sql_standard_syntax || force_standard)
18962            && !(f.position_explicit
18963                && f.characters.is_none()
18964                && matches!(f.position, TrimPosition::Both));
18965        if use_standard {
18966            // SQL standard syntax: TRIM(BOTH chars FROM str)
18967            // Only output position if it was explicitly specified
18968            if f.position_explicit {
18969                match f.position {
18970                    TrimPosition::Both => self.write_keyword("BOTH"),
18971                    TrimPosition::Leading => self.write_keyword("LEADING"),
18972                    TrimPosition::Trailing => self.write_keyword("TRAILING"),
18973                }
18974                self.write_space();
18975            }
18976            if let Some(chars) = &f.characters {
18977                self.generate_expression(chars)?;
18978                self.write_space();
18979            }
18980            self.write_keyword("FROM");
18981            self.write_space();
18982            self.generate_expression(&f.this)?;
18983        } else {
18984            // Simple function syntax: TRIM(str) or TRIM(str, chars)
18985            self.generate_expression(&f.this)?;
18986            if let Some(chars) = &f.characters {
18987                self.write(", ");
18988                self.generate_expression(chars)?;
18989            }
18990        }
18991        self.write(")");
18992        Ok(())
18993    }
18994
18995    fn generate_replace(&mut self, f: &ReplaceFunc) -> Result<()> {
18996        self.write_keyword("REPLACE");
18997        self.write("(");
18998        self.generate_expression(&f.this)?;
18999        self.write(", ");
19000        self.generate_expression(&f.old)?;
19001        self.write(", ");
19002        self.generate_expression(&f.new)?;
19003        self.write(")");
19004        Ok(())
19005    }
19006
19007    fn generate_left_right(&mut self, name: &str, f: &LeftRightFunc) -> Result<()> {
19008        self.write_keyword(name);
19009        self.write("(");
19010        self.generate_expression(&f.this)?;
19011        self.write(", ");
19012        self.generate_expression(&f.length)?;
19013        self.write(")");
19014        Ok(())
19015    }
19016
19017    fn generate_repeat(&mut self, f: &RepeatFunc) -> Result<()> {
19018        self.write_keyword("REPEAT");
19019        self.write("(");
19020        self.generate_expression(&f.this)?;
19021        self.write(", ");
19022        self.generate_expression(&f.times)?;
19023        self.write(")");
19024        Ok(())
19025    }
19026
19027    fn generate_pad(&mut self, name: &str, f: &PadFunc) -> Result<()> {
19028        self.write_keyword(name);
19029        self.write("(");
19030        self.generate_expression(&f.this)?;
19031        self.write(", ");
19032        self.generate_expression(&f.length)?;
19033        if let Some(fill) = &f.fill {
19034            self.write(", ");
19035            self.generate_expression(fill)?;
19036        }
19037        self.write(")");
19038        Ok(())
19039    }
19040
19041    fn generate_split(&mut self, f: &SplitFunc) -> Result<()> {
19042        self.write_keyword("SPLIT");
19043        self.write("(");
19044        self.generate_expression(&f.this)?;
19045        self.write(", ");
19046        self.generate_expression(&f.delimiter)?;
19047        self.write(")");
19048        Ok(())
19049    }
19050
19051    fn generate_regexp_like(&mut self, f: &RegexpFunc) -> Result<()> {
19052        use crate::dialects::DialectType;
19053        // PostgreSQL uses ~ operator for regex matching
19054        if matches!(self.config.dialect, Some(DialectType::PostgreSQL)) && f.flags.is_none() {
19055            self.generate_expression(&f.this)?;
19056            self.write(" ~ ");
19057            self.generate_expression(&f.pattern)?;
19058        } else if matches!(self.config.dialect, Some(DialectType::Exasol)) && f.flags.is_none() {
19059            // Exasol uses REGEXP_LIKE as infix binary operator
19060            self.generate_expression(&f.this)?;
19061            self.write_keyword(" REGEXP_LIKE ");
19062            self.generate_expression(&f.pattern)?;
19063        } else if matches!(
19064            self.config.dialect,
19065            Some(DialectType::SingleStore)
19066                | Some(DialectType::Spark)
19067                | Some(DialectType::Hive)
19068                | Some(DialectType::Databricks)
19069        ) && f.flags.is_none()
19070        {
19071            // SingleStore/Spark/Hive/Databricks use RLIKE infix operator
19072            self.generate_expression(&f.this)?;
19073            self.write_keyword(" RLIKE ");
19074            self.generate_expression(&f.pattern)?;
19075        } else if matches!(self.config.dialect, Some(DialectType::StarRocks)) {
19076            // StarRocks uses REGEXP function syntax
19077            self.write_keyword("REGEXP");
19078            self.write("(");
19079            self.generate_expression(&f.this)?;
19080            self.write(", ");
19081            self.generate_expression(&f.pattern)?;
19082            if let Some(flags) = &f.flags {
19083                self.write(", ");
19084                self.generate_expression(flags)?;
19085            }
19086            self.write(")");
19087        } else {
19088            self.write_keyword("REGEXP_LIKE");
19089            self.write("(");
19090            self.generate_expression(&f.this)?;
19091            self.write(", ");
19092            self.generate_expression(&f.pattern)?;
19093            if let Some(flags) = &f.flags {
19094                self.write(", ");
19095                self.generate_expression(flags)?;
19096            }
19097            self.write(")");
19098        }
19099        Ok(())
19100    }
19101
19102    fn generate_regexp_replace(&mut self, f: &RegexpReplaceFunc) -> Result<()> {
19103        self.write_keyword("REGEXP_REPLACE");
19104        self.write("(");
19105        self.generate_expression(&f.this)?;
19106        self.write(", ");
19107        self.generate_expression(&f.pattern)?;
19108        self.write(", ");
19109        self.generate_expression(&f.replacement)?;
19110        if let Some(flags) = &f.flags {
19111            self.write(", ");
19112            self.generate_expression(flags)?;
19113        }
19114        self.write(")");
19115        Ok(())
19116    }
19117
19118    fn generate_regexp_extract(&mut self, f: &RegexpExtractFunc) -> Result<()> {
19119        self.write_keyword("REGEXP_EXTRACT");
19120        self.write("(");
19121        self.generate_expression(&f.this)?;
19122        self.write(", ");
19123        self.generate_expression(&f.pattern)?;
19124        if let Some(group) = &f.group {
19125            self.write(", ");
19126            self.generate_expression(group)?;
19127        }
19128        self.write(")");
19129        Ok(())
19130    }
19131
19132    // Math function generators
19133
19134    fn generate_round(&mut self, f: &RoundFunc) -> Result<()> {
19135        self.write_keyword("ROUND");
19136        self.write("(");
19137        self.generate_expression(&f.this)?;
19138        if let Some(decimals) = &f.decimals {
19139            self.write(", ");
19140            self.generate_expression(decimals)?;
19141        }
19142        self.write(")");
19143        Ok(())
19144    }
19145
19146    fn generate_floor(&mut self, f: &FloorFunc) -> Result<()> {
19147        self.write_keyword("FLOOR");
19148        self.write("(");
19149        self.generate_expression(&f.this)?;
19150        // Handle Druid-style FLOOR(time TO unit) syntax
19151        if let Some(to) = &f.to {
19152            self.write(" ");
19153            self.write_keyword("TO");
19154            self.write(" ");
19155            self.generate_expression(to)?;
19156        } else if let Some(scale) = &f.scale {
19157            self.write(", ");
19158            self.generate_expression(scale)?;
19159        }
19160        self.write(")");
19161        Ok(())
19162    }
19163
19164    fn generate_ceil(&mut self, f: &CeilFunc) -> Result<()> {
19165        self.write_keyword("CEIL");
19166        self.write("(");
19167        self.generate_expression(&f.this)?;
19168        // Handle Druid-style CEIL(time TO unit) syntax
19169        if let Some(to) = &f.to {
19170            self.write(" ");
19171            self.write_keyword("TO");
19172            self.write(" ");
19173            self.generate_expression(to)?;
19174        } else if let Some(decimals) = &f.decimals {
19175            self.write(", ");
19176            self.generate_expression(decimals)?;
19177        }
19178        self.write(")");
19179        Ok(())
19180    }
19181
19182    fn generate_log(&mut self, f: &LogFunc) -> Result<()> {
19183        use crate::expressions::Literal;
19184
19185        if let Some(base) = &f.base {
19186            // Check for LOG_BASE_FIRST = None dialects (Presto, Trino, ClickHouse, Athena)
19187            // These dialects use LOG2()/LOG10() instead of LOG(base, value)
19188            if self.is_log_base_none() {
19189                if matches!(base, Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(s) if s == "2"))
19190                {
19191                    self.write_func_name("LOG2");
19192                    self.write("(");
19193                    self.generate_expression(&f.this)?;
19194                    self.write(")");
19195                    return Ok(());
19196                } else if matches!(base, Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(s) if s == "10"))
19197                {
19198                    self.write_func_name("LOG10");
19199                    self.write("(");
19200                    self.generate_expression(&f.this)?;
19201                    self.write(")");
19202                    return Ok(());
19203                }
19204                // Other bases: fall through to LOG(base, value) — best effort
19205            }
19206
19207            self.write_func_name("LOG");
19208            self.write("(");
19209            if self.is_log_value_first() {
19210                // BigQuery, TSQL, Tableau, Fabric: LOG(value, base)
19211                self.generate_expression(&f.this)?;
19212                self.write(", ");
19213                self.generate_expression(base)?;
19214            } else {
19215                // Default (PostgreSQL, etc.): LOG(base, value)
19216                self.generate_expression(base)?;
19217                self.write(", ");
19218                self.generate_expression(&f.this)?;
19219            }
19220            self.write(")");
19221        } else {
19222            // Single arg: LOG(x) — unspecified base (log base 10 in default dialect)
19223            self.write_func_name("LOG");
19224            self.write("(");
19225            self.generate_expression(&f.this)?;
19226            self.write(")");
19227        }
19228        Ok(())
19229    }
19230
19231    /// Whether the target dialect uses LOG(value, base) order (value first).
19232    /// BigQuery, TSQL, Tableau, Fabric use LOG(value, base).
19233    fn is_log_value_first(&self) -> bool {
19234        use crate::dialects::DialectType;
19235        matches!(
19236            self.config.dialect,
19237            Some(DialectType::BigQuery)
19238                | Some(DialectType::TSQL)
19239                | Some(DialectType::Tableau)
19240                | Some(DialectType::Fabric)
19241        )
19242    }
19243
19244    /// Whether the target dialect has LOG_BASE_FIRST = None (uses LOG2/LOG10 instead).
19245    /// Presto, Trino, ClickHouse, Athena.
19246    fn is_log_base_none(&self) -> bool {
19247        use crate::dialects::DialectType;
19248        matches!(
19249            self.config.dialect,
19250            Some(DialectType::Presto)
19251                | Some(DialectType::Trino)
19252                | Some(DialectType::ClickHouse)
19253                | Some(DialectType::Athena)
19254        )
19255    }
19256
19257    // Date/time function generators
19258
19259    fn generate_current_time(&mut self, f: &CurrentTime) -> Result<()> {
19260        self.write_keyword("CURRENT_TIME");
19261        if let Some(precision) = f.precision {
19262            self.write(&format!("({})", precision));
19263        } else if matches!(
19264            self.config.dialect,
19265            Some(crate::dialects::DialectType::MySQL)
19266                | Some(crate::dialects::DialectType::SingleStore)
19267                | Some(crate::dialects::DialectType::TiDB)
19268        ) {
19269            self.write("()");
19270        }
19271        Ok(())
19272    }
19273
19274    fn generate_current_timestamp(&mut self, f: &CurrentTimestamp) -> Result<()> {
19275        use crate::dialects::DialectType;
19276
19277        // Oracle/Redshift SYSDATE handling
19278        if f.sysdate {
19279            match self.config.dialect {
19280                Some(DialectType::Oracle) | Some(DialectType::Redshift) => {
19281                    self.write_keyword("SYSDATE");
19282                    return Ok(());
19283                }
19284                Some(DialectType::Snowflake) => {
19285                    // Snowflake uses SYSDATE() function
19286                    self.write_keyword("SYSDATE");
19287                    self.write("()");
19288                    return Ok(());
19289                }
19290                _ => {
19291                    // Other dialects use CURRENT_TIMESTAMP for SYSDATE
19292                }
19293            }
19294        }
19295
19296        self.write_keyword("CURRENT_TIMESTAMP");
19297        // MySQL, Spark, Hive always use CURRENT_TIMESTAMP() with parentheses
19298        if let Some(precision) = f.precision {
19299            self.write(&format!("({})", precision));
19300        } else if matches!(
19301            self.config.dialect,
19302            Some(crate::dialects::DialectType::MySQL)
19303                | Some(crate::dialects::DialectType::SingleStore)
19304                | Some(crate::dialects::DialectType::TiDB)
19305                | Some(crate::dialects::DialectType::Spark)
19306                | Some(crate::dialects::DialectType::Hive)
19307                | Some(crate::dialects::DialectType::Databricks)
19308                | Some(crate::dialects::DialectType::ClickHouse)
19309                | Some(crate::dialects::DialectType::BigQuery)
19310                | Some(crate::dialects::DialectType::Snowflake)
19311                | Some(crate::dialects::DialectType::Exasol)
19312        ) {
19313            self.write("()");
19314        }
19315        Ok(())
19316    }
19317
19318    fn generate_at_time_zone(&mut self, f: &AtTimeZone) -> Result<()> {
19319        // Exasol uses CONVERT_TZ(timestamp, 'UTC', zone) instead of AT TIME ZONE
19320        if self.config.dialect == Some(DialectType::Exasol) {
19321            self.write_keyword("CONVERT_TZ");
19322            self.write("(");
19323            self.generate_expression(&f.this)?;
19324            self.write(", 'UTC', ");
19325            self.generate_expression(&f.zone)?;
19326            self.write(")");
19327            return Ok(());
19328        }
19329
19330        self.generate_expression(&f.this)?;
19331        self.write_space();
19332        self.write_keyword("AT TIME ZONE");
19333        self.write_space();
19334        self.generate_expression(&f.zone)?;
19335        Ok(())
19336    }
19337
19338    fn generate_date_add(&mut self, f: &DateAddFunc, name: &str) -> Result<()> {
19339        use crate::dialects::DialectType;
19340
19341        // Presto/Trino use DATE_ADD('unit', interval, date) format
19342        // with the interval cast to BIGINT when needed
19343        let is_presto_like = matches!(
19344            self.config.dialect,
19345            Some(DialectType::Presto) | Some(DialectType::Trino)
19346        );
19347
19348        if is_presto_like {
19349            self.write_keyword(name);
19350            self.write("(");
19351            // Unit as string literal
19352            self.write("'");
19353            self.write_simple_interval_unit(&f.unit, false);
19354            self.write("'");
19355            self.write(", ");
19356            // Interval - wrap in CAST(...AS BIGINT) if it doesn't return integer type
19357            let needs_cast = !self.returns_integer_type(&f.interval);
19358            if needs_cast {
19359                self.write_keyword("CAST");
19360                self.write("(");
19361            }
19362            self.generate_expression(&f.interval)?;
19363            if needs_cast {
19364                self.write_space();
19365                self.write_keyword("AS");
19366                self.write_space();
19367                self.write_keyword("BIGINT");
19368                self.write(")");
19369            }
19370            self.write(", ");
19371            self.generate_expression(&f.this)?;
19372            self.write(")");
19373        } else if matches!(self.config.dialect, Some(DialectType::PostgreSQL)) {
19374            self.generate_expression(&f.this)?;
19375            self.write_space();
19376            if name.eq_ignore_ascii_case("DATE_SUB") {
19377                self.write("-");
19378            } else {
19379                self.write("+");
19380            }
19381            self.write_space();
19382            self.write_keyword("INTERVAL");
19383            self.write_space();
19384            self.write("'");
19385            let mut interval_gen = Generator::with_arc_config(self.config.clone());
19386            let interval_sql = interval_gen.generate(&f.interval)?;
19387            self.write(&interval_sql);
19388            self.write(" ");
19389            self.write_simple_interval_unit(&f.unit, false);
19390            self.write("'");
19391        } else {
19392            self.write_keyword(name);
19393            self.write("(");
19394            self.generate_expression(&f.this)?;
19395            self.write(", ");
19396            self.write_keyword("INTERVAL");
19397            self.write_space();
19398            self.generate_expression(&f.interval)?;
19399            self.write_space();
19400            self.write_simple_interval_unit(&f.unit, false); // Use singular form for DATEADD
19401            self.write(")");
19402        }
19403        Ok(())
19404    }
19405
19406    /// Check if an expression returns an integer type (doesn't need cast to BIGINT in Presto DATE_ADD)
19407    /// This is a heuristic to avoid full type inference
19408    fn returns_integer_type(&self, expr: &Expression) -> bool {
19409        use crate::expressions::{DataType, Literal};
19410        match expr {
19411            // Integer literals (no decimal point)
19412            Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(_)) => {
19413                let Literal::Number(n) = lit.as_ref() else {
19414                    unreachable!()
19415                };
19416                !n.contains('.')
19417            }
19418
19419            // FLOOR(x) returns integer if x is integer
19420            Expression::Floor(f) => self.returns_integer_type(&f.this),
19421
19422            // ROUND(x) returns integer if x is integer
19423            Expression::Round(f) => {
19424                // Only if no decimals arg or it's returning an integer
19425                f.decimals.is_none() && self.returns_integer_type(&f.this)
19426            }
19427
19428            // SIGN returns integer if input is integer
19429            Expression::Sign(f) => self.returns_integer_type(&f.this),
19430
19431            // ABS returns the same type as input
19432            Expression::Abs(f) => self.returns_integer_type(&f.this),
19433
19434            // Arithmetic operations on integers return integers
19435            Expression::Mul(op) => {
19436                self.returns_integer_type(&op.left) && self.returns_integer_type(&op.right)
19437            }
19438            Expression::Add(op) => {
19439                self.returns_integer_type(&op.left) && self.returns_integer_type(&op.right)
19440            }
19441            Expression::Sub(op) => {
19442                self.returns_integer_type(&op.left) && self.returns_integer_type(&op.right)
19443            }
19444            Expression::Mod(op) => self.returns_integer_type(&op.left),
19445
19446            // CAST(x AS BIGINT/INT/INTEGER/SMALLINT/TINYINT) returns integer
19447            Expression::Cast(c) => matches!(
19448                &c.to,
19449                DataType::BigInt { .. }
19450                    | DataType::Int { .. }
19451                    | DataType::SmallInt { .. }
19452                    | DataType::TinyInt { .. }
19453            ),
19454
19455            // Negation: -x returns integer if x is integer
19456            Expression::Neg(op) => self.returns_integer_type(&op.this),
19457
19458            // Parenthesized expression
19459            Expression::Paren(p) => self.returns_integer_type(&p.this),
19460
19461            // Column references and most expressions are assumed to need casting
19462            // since we don't have full type information
19463            _ => false,
19464        }
19465    }
19466
19467    fn generate_datediff(&mut self, f: &DateDiffFunc) -> Result<()> {
19468        self.write_keyword("DATEDIFF");
19469        self.write("(");
19470        if let Some(unit) = &f.unit {
19471            self.write_simple_interval_unit(unit, false); // Use singular form for DATEDIFF
19472            self.write(", ");
19473        }
19474        self.generate_expression(&f.this)?;
19475        self.write(", ");
19476        self.generate_expression(&f.expression)?;
19477        self.write(")");
19478        Ok(())
19479    }
19480
19481    fn generate_date_trunc(&mut self, f: &DateTruncFunc) -> Result<()> {
19482        if self.config.dialect == Some(DialectType::ClickHouse) {
19483            self.write("dateTrunc");
19484        } else {
19485            self.write_keyword("DATE_TRUNC");
19486        }
19487        self.write("('");
19488        self.write_datetime_field(&f.unit);
19489        self.write("', ");
19490        self.generate_expression(&f.this)?;
19491        self.write(")");
19492        Ok(())
19493    }
19494
19495    fn generate_last_day(&mut self, f: &LastDayFunc) -> Result<()> {
19496        use crate::dialects::DialectType;
19497        use crate::expressions::DateTimeField;
19498
19499        self.write_keyword("LAST_DAY");
19500        self.write("(");
19501        self.generate_expression(&f.this)?;
19502        if let Some(unit) = &f.unit {
19503            self.write(", ");
19504            // BigQuery: strip week-start modifier from WEEK(SUNDAY), WEEK(MONDAY), etc.
19505            // WEEK(SUNDAY) -> WEEK
19506            if matches!(self.config.dialect, Some(DialectType::BigQuery)) {
19507                if let DateTimeField::WeekWithModifier(_) = unit {
19508                    self.write_keyword("WEEK");
19509                } else {
19510                    self.write_datetime_field(unit);
19511                }
19512            } else {
19513                self.write_datetime_field(unit);
19514            }
19515        }
19516        self.write(")");
19517        Ok(())
19518    }
19519
19520    fn generate_extract(&mut self, f: &ExtractFunc) -> Result<()> {
19521        // TSQL/Fabric use DATEPART(part, expr) instead of EXTRACT(part FROM expr)
19522        if matches!(
19523            self.config.dialect,
19524            Some(DialectType::TSQL) | Some(DialectType::Fabric)
19525        ) {
19526            self.write_keyword("DATEPART");
19527            self.write("(");
19528            self.write_datetime_field(&f.field);
19529            self.write(", ");
19530            self.generate_expression(&f.this)?;
19531            self.write(")");
19532            return Ok(());
19533        }
19534        self.write_keyword("EXTRACT");
19535        self.write("(");
19536        // Hive/Spark use lowercase datetime fields in EXTRACT
19537        if matches!(
19538            self.config.dialect,
19539            Some(DialectType::Hive) | Some(DialectType::Spark) | Some(DialectType::Databricks)
19540        ) {
19541            self.write_datetime_field_lower(&f.field);
19542        } else {
19543            self.write_datetime_field(&f.field);
19544        }
19545        self.write_space();
19546        self.write_keyword("FROM");
19547        self.write_space();
19548        self.generate_expression(&f.this)?;
19549        self.write(")");
19550        Ok(())
19551    }
19552
19553    fn generate_to_date(&mut self, f: &ToDateFunc) -> Result<()> {
19554        self.write_keyword("TO_DATE");
19555        self.write("(");
19556        self.generate_expression(&f.this)?;
19557        if let Some(format) = &f.format {
19558            self.write(", ");
19559            self.generate_expression(format)?;
19560        }
19561        self.write(")");
19562        Ok(())
19563    }
19564
19565    fn generate_to_timestamp(&mut self, f: &ToTimestampFunc) -> Result<()> {
19566        self.write_keyword("TO_TIMESTAMP");
19567        self.write("(");
19568        self.generate_expression(&f.this)?;
19569        if let Some(format) = &f.format {
19570            self.write(", ");
19571            self.generate_expression(format)?;
19572        }
19573        self.write(")");
19574        Ok(())
19575    }
19576
19577    // Control flow function generators
19578
19579    fn generate_if_func(&mut self, f: &IfFunc) -> Result<()> {
19580        use crate::dialects::DialectType;
19581
19582        // Generic mode: normalize IF to CASE WHEN
19583        if self.config.dialect.is_none() || self.config.dialect == Some(DialectType::Generic) {
19584            self.write_keyword("CASE WHEN");
19585            self.write_space();
19586            self.generate_expression(&f.condition)?;
19587            self.write_space();
19588            self.write_keyword("THEN");
19589            self.write_space();
19590            self.generate_expression(&f.true_value)?;
19591            if let Some(false_val) = &f.false_value {
19592                self.write_space();
19593                self.write_keyword("ELSE");
19594                self.write_space();
19595                self.generate_expression(false_val)?;
19596            }
19597            self.write_space();
19598            self.write_keyword("END");
19599            return Ok(());
19600        }
19601
19602        // Exasol uses IF condition THEN true_value ELSE false_value ENDIF syntax
19603        if self.config.dialect == Some(DialectType::Exasol) {
19604            self.write_keyword("IF");
19605            self.write_space();
19606            self.generate_expression(&f.condition)?;
19607            self.write_space();
19608            self.write_keyword("THEN");
19609            self.write_space();
19610            self.generate_expression(&f.true_value)?;
19611            if let Some(false_val) = &f.false_value {
19612                self.write_space();
19613                self.write_keyword("ELSE");
19614                self.write_space();
19615                self.generate_expression(false_val)?;
19616            }
19617            self.write_space();
19618            self.write_keyword("ENDIF");
19619            return Ok(());
19620        }
19621
19622        // Choose function name based on target dialect
19623        let func_name = match self.config.dialect {
19624            Some(DialectType::ClickHouse) => f.original_name.as_deref().unwrap_or("IF"),
19625            Some(DialectType::Snowflake) => "IFF",
19626            Some(DialectType::SQLite) | Some(DialectType::TSQL) => "IIF",
19627            Some(DialectType::Drill) => "`IF`",
19628            _ => "IF",
19629        };
19630        self.write(func_name);
19631        self.write("(");
19632        self.generate_expression(&f.condition)?;
19633        self.write(", ");
19634        self.generate_expression(&f.true_value)?;
19635        if let Some(false_val) = &f.false_value {
19636            self.write(", ");
19637            self.generate_expression(false_val)?;
19638        }
19639        self.write(")");
19640        Ok(())
19641    }
19642
19643    fn generate_nvl2(&mut self, f: &Nvl2Func) -> Result<()> {
19644        self.write_keyword("NVL2");
19645        self.write("(");
19646        self.generate_expression(&f.this)?;
19647        self.write(", ");
19648        self.generate_expression(&f.true_value)?;
19649        self.write(", ");
19650        self.generate_expression(&f.false_value)?;
19651        self.write(")");
19652        Ok(())
19653    }
19654
19655    // Typed aggregate function generators
19656
19657    fn generate_count(&mut self, f: &CountFunc) -> Result<()> {
19658        // Use normalize_functions for COUNT to respect ClickHouse case preservation
19659        let count_name = match self.config.normalize_functions {
19660            NormalizeFunctions::Upper => "COUNT".to_string(),
19661            NormalizeFunctions::Lower => "count".to_string(),
19662            NormalizeFunctions::None => f
19663                .original_name
19664                .clone()
19665                .unwrap_or_else(|| "COUNT".to_string()),
19666        };
19667        self.write(&count_name);
19668        self.write("(");
19669        if f.distinct {
19670            self.write_keyword("DISTINCT");
19671            self.write_space();
19672        }
19673        if f.star {
19674            self.write("*");
19675        } else if let Some(ref expr) = f.this {
19676            // For COUNT(DISTINCT a, b), unwrap the Tuple to avoid extra parentheses
19677            if let Expression::Tuple(tuple) = expr {
19678                // Check if we need to transform multi-arg COUNT DISTINCT
19679                // When dialect doesn't support multi_arg_distinct, transform:
19680                // COUNT(DISTINCT a, b) -> COUNT(DISTINCT CASE WHEN a IS NULL THEN NULL WHEN b IS NULL THEN NULL ELSE (a, b) END)
19681                let needs_transform =
19682                    f.distinct && tuple.expressions.len() > 1 && !self.config.multi_arg_distinct;
19683
19684                if needs_transform {
19685                    // Generate: CASE WHEN a IS NULL THEN NULL WHEN b IS NULL THEN NULL ELSE (a, b) END
19686                    self.write_keyword("CASE");
19687                    for e in &tuple.expressions {
19688                        self.write_space();
19689                        self.write_keyword("WHEN");
19690                        self.write_space();
19691                        self.generate_expression(e)?;
19692                        self.write_space();
19693                        self.write_keyword("IS NULL THEN NULL");
19694                    }
19695                    self.write_space();
19696                    self.write_keyword("ELSE");
19697                    self.write(" (");
19698                    for (i, e) in tuple.expressions.iter().enumerate() {
19699                        if i > 0 {
19700                            self.write(", ");
19701                        }
19702                        self.generate_expression(e)?;
19703                    }
19704                    self.write(")");
19705                    self.write_space();
19706                    self.write_keyword("END");
19707                } else {
19708                    for (i, e) in tuple.expressions.iter().enumerate() {
19709                        if i > 0 {
19710                            self.write(", ");
19711                        }
19712                        self.generate_expression(e)?;
19713                    }
19714                }
19715            } else {
19716                self.generate_expression(expr)?;
19717            }
19718        }
19719        let clickhouse_ignore_nulls_outside =
19720            matches!(self.config.dialect, Some(DialectType::ClickHouse));
19721        if let Some(ignore) = f.ignore_nulls.filter(|_| !clickhouse_ignore_nulls_outside) {
19722            self.write_space();
19723            if ignore {
19724                self.write_keyword("IGNORE NULLS");
19725            } else {
19726                self.write_keyword("RESPECT NULLS");
19727            }
19728        }
19729        self.write(")");
19730        if let Some(ignore) = f.ignore_nulls.filter(|_| clickhouse_ignore_nulls_outside) {
19731            self.write_space();
19732            if ignore {
19733                self.write_keyword("IGNORE NULLS");
19734            } else {
19735                self.write_keyword("RESPECT NULLS");
19736            }
19737        }
19738        if let Some(ref filter) = f.filter {
19739            self.write_space();
19740            self.write_keyword("FILTER");
19741            self.write("(");
19742            self.write_keyword("WHERE");
19743            self.write_space();
19744            self.generate_expression(filter)?;
19745            self.write(")");
19746        }
19747        Ok(())
19748    }
19749
19750    fn generate_agg_func(&mut self, name: &str, f: &AggFunc) -> Result<()> {
19751        // Apply function name normalization based on config
19752        let func_name: Cow<'_, str> = match self.config.normalize_functions {
19753            NormalizeFunctions::Upper => Cow::Owned(name.to_ascii_uppercase()),
19754            NormalizeFunctions::Lower => Cow::Owned(name.to_ascii_lowercase()),
19755            NormalizeFunctions::None => {
19756                // Use the original function name from parsing if available,
19757                // otherwise fall back to lowercase of the hardcoded constant
19758                if let Some(ref original) = f.name {
19759                    Cow::Owned(original.clone())
19760                } else {
19761                    Cow::Owned(name.to_ascii_lowercase())
19762                }
19763            }
19764        };
19765        self.write(func_name.as_ref());
19766        self.write("(");
19767        if f.distinct {
19768            self.write_keyword("DISTINCT");
19769            self.write_space();
19770        }
19771        // MODE() uses a NULL placeholder internally for its zero-arg ordered-set form.
19772        // Other aggregates may legitimately receive NULL as an explicit argument.
19773        let is_zero_arg_mode =
19774            name.eq_ignore_ascii_case("MODE") && matches!(f.this, Expression::Null(_));
19775        if !is_zero_arg_mode {
19776            self.generate_expression(&f.this)?;
19777        }
19778        // Generate IGNORE NULLS / RESPECT NULLS inside parens if config says so (BigQuery style)
19779        // DuckDB doesn't support IGNORE NULLS / RESPECT NULLS in aggregate functions - skip it
19780        if self.config.ignore_nulls_in_func
19781            && !matches!(self.config.dialect, Some(DialectType::DuckDB))
19782        {
19783            match f.ignore_nulls {
19784                Some(true) => {
19785                    self.write_space();
19786                    self.write_keyword("IGNORE NULLS");
19787                }
19788                Some(false) => {
19789                    self.write_space();
19790                    self.write_keyword("RESPECT NULLS");
19791                }
19792                None => {}
19793            }
19794        }
19795        // Generate HAVING MAX/MIN if present (BigQuery syntax)
19796        // e.g., ANY_VALUE(fruit HAVING MAX sold)
19797        if let Some((ref expr, is_max)) = f.having_max {
19798            self.write_space();
19799            self.write_keyword("HAVING");
19800            self.write_space();
19801            if is_max {
19802                self.write_keyword("MAX");
19803            } else {
19804                self.write_keyword("MIN");
19805            }
19806            self.write_space();
19807            self.generate_expression(expr)?;
19808        }
19809        // Generate ORDER BY if present (for aggregates like ARRAY_AGG(x ORDER BY y))
19810        if !f.order_by.is_empty() {
19811            self.write_space();
19812            self.write_keyword("ORDER BY");
19813            self.write_space();
19814            for (i, ord) in f.order_by.iter().enumerate() {
19815                if i > 0 {
19816                    self.write(", ");
19817                }
19818                self.generate_ordered(ord)?;
19819            }
19820        }
19821        // Generate LIMIT if present (for aggregates like ARRAY_AGG(x ORDER BY y LIMIT 2))
19822        if let Some(ref limit) = f.limit {
19823            self.write_space();
19824            self.write_keyword("LIMIT");
19825            self.write_space();
19826            // Check if this is a Tuple representing LIMIT offset, count
19827            if let Expression::Tuple(t) = limit.as_ref() {
19828                if t.expressions.len() == 2 {
19829                    self.generate_expression(&t.expressions[0])?;
19830                    self.write(", ");
19831                    self.generate_expression(&t.expressions[1])?;
19832                } else {
19833                    self.generate_expression(limit)?;
19834                }
19835            } else {
19836                self.generate_expression(limit)?;
19837            }
19838        }
19839        self.write(")");
19840        // Generate IGNORE NULLS / RESPECT NULLS outside parens if config says so (standard style)
19841        // DuckDB doesn't support IGNORE NULLS / RESPECT NULLS in aggregate functions - skip it
19842        if !self.config.ignore_nulls_in_func
19843            && !matches!(self.config.dialect, Some(DialectType::DuckDB))
19844        {
19845            match f.ignore_nulls {
19846                Some(true) => {
19847                    self.write_space();
19848                    self.write_keyword("IGNORE NULLS");
19849                }
19850                Some(false) => {
19851                    self.write_space();
19852                    self.write_keyword("RESPECT NULLS");
19853                }
19854                None => {}
19855            }
19856        }
19857        if let Some(ref filter) = f.filter {
19858            self.write_space();
19859            self.write_keyword("FILTER");
19860            self.write("(");
19861            self.write_keyword("WHERE");
19862            self.write_space();
19863            self.generate_expression(filter)?;
19864            self.write(")");
19865        }
19866        Ok(())
19867    }
19868
19869    /// Generate FIRST/LAST aggregate functions with Hive/Spark2-style boolean argument
19870    /// for IGNORE NULLS. In Hive/Spark2, `FIRST(col) IGNORE NULLS` is written as `FIRST(col, TRUE)`.
19871    fn generate_agg_func_with_ignore_nulls_bool(&mut self, name: &str, f: &AggFunc) -> Result<()> {
19872        // For Hive/Spark2 dialects, convert IGNORE NULLS to boolean TRUE argument
19873        if matches!(self.config.dialect, Some(DialectType::Hive)) && f.ignore_nulls == Some(true) {
19874            // Create a modified copy without ignore_nulls, add TRUE as part of the output
19875            let func_name: Cow<'_, str> = match self.config.normalize_functions {
19876                NormalizeFunctions::Upper => Cow::Owned(name.to_ascii_uppercase()),
19877                NormalizeFunctions::Lower => Cow::Owned(name.to_ascii_lowercase()),
19878                NormalizeFunctions::None => {
19879                    if let Some(ref original) = f.name {
19880                        Cow::Owned(original.clone())
19881                    } else {
19882                        Cow::Owned(name.to_ascii_lowercase())
19883                    }
19884                }
19885            };
19886            self.write(func_name.as_ref());
19887            self.write("(");
19888            if f.distinct {
19889                self.write_keyword("DISTINCT");
19890                self.write_space();
19891            }
19892            if !matches!(f.this, Expression::Null(_)) {
19893                self.generate_expression(&f.this)?;
19894            }
19895            self.write(", ");
19896            self.write_keyword("TRUE");
19897            self.write(")");
19898            return Ok(());
19899        }
19900        self.generate_agg_func(name, f)
19901    }
19902
19903    fn generate_group_concat(&mut self, f: &GroupConcatFunc) -> Result<()> {
19904        self.write_keyword("GROUP_CONCAT");
19905        self.write("(");
19906        if f.distinct {
19907            self.write_keyword("DISTINCT");
19908            self.write_space();
19909        }
19910        self.generate_expression(&f.this)?;
19911        if let Some(ref order_by) = f.order_by {
19912            self.write_space();
19913            self.write_keyword("ORDER BY");
19914            self.write_space();
19915            for (i, ord) in order_by.iter().enumerate() {
19916                if i > 0 {
19917                    self.write(", ");
19918                }
19919                self.generate_ordered(ord)?;
19920            }
19921        }
19922        if let Some(ref sep) = f.separator {
19923            // SQLite uses GROUP_CONCAT(x, sep) syntax (comma-separated)
19924            // MySQL and others use GROUP_CONCAT(x SEPARATOR sep) syntax
19925            if matches!(
19926                self.config.dialect,
19927                Some(crate::dialects::DialectType::SQLite)
19928            ) {
19929                self.write(", ");
19930                self.generate_expression(sep)?;
19931            } else {
19932                self.write_space();
19933                self.write_keyword("SEPARATOR");
19934                self.write_space();
19935                self.generate_expression(sep)?;
19936            }
19937        }
19938        if let Some(ref limit) = f.limit {
19939            self.write_space();
19940            self.write_keyword("LIMIT");
19941            self.write_space();
19942            self.generate_expression(limit)?;
19943        }
19944        self.write(")");
19945        if let Some(ref filter) = f.filter {
19946            self.write_space();
19947            self.write_keyword("FILTER");
19948            self.write("(");
19949            self.write_keyword("WHERE");
19950            self.write_space();
19951            self.generate_expression(filter)?;
19952            self.write(")");
19953        }
19954        Ok(())
19955    }
19956
19957    fn generate_string_agg(&mut self, f: &StringAggFunc) -> Result<()> {
19958        let is_tsql = matches!(
19959            self.config.dialect,
19960            Some(crate::dialects::DialectType::TSQL)
19961        );
19962        self.write_keyword("STRING_AGG");
19963        self.write("(");
19964        if f.distinct {
19965            self.write_keyword("DISTINCT");
19966            self.write_space();
19967        }
19968        self.generate_expression(&f.this)?;
19969        if let Some(ref separator) = f.separator {
19970            self.write(", ");
19971            self.generate_expression(separator)?;
19972        }
19973        // For TSQL, ORDER BY goes in WITHIN GROUP clause after the closing paren
19974        if !is_tsql {
19975            if let Some(ref order_by) = f.order_by {
19976                self.write_space();
19977                self.write_keyword("ORDER BY");
19978                self.write_space();
19979                for (i, ord) in order_by.iter().enumerate() {
19980                    if i > 0 {
19981                        self.write(", ");
19982                    }
19983                    self.generate_ordered(ord)?;
19984                }
19985            }
19986        }
19987        if let Some(ref limit) = f.limit {
19988            self.write_space();
19989            self.write_keyword("LIMIT");
19990            self.write_space();
19991            self.generate_expression(limit)?;
19992        }
19993        self.write(")");
19994        // TSQL uses WITHIN GROUP (ORDER BY ...) after the function call
19995        if is_tsql {
19996            if let Some(ref order_by) = f.order_by {
19997                self.write_space();
19998                self.write_keyword("WITHIN GROUP");
19999                self.write(" (");
20000                self.write_keyword("ORDER BY");
20001                self.write_space();
20002                for (i, ord) in order_by.iter().enumerate() {
20003                    if i > 0 {
20004                        self.write(", ");
20005                    }
20006                    self.generate_ordered(ord)?;
20007                }
20008                self.write(")");
20009            }
20010        }
20011        if let Some(ref filter) = f.filter {
20012            self.write_space();
20013            self.write_keyword("FILTER");
20014            self.write("(");
20015            self.write_keyword("WHERE");
20016            self.write_space();
20017            self.generate_expression(filter)?;
20018            self.write(")");
20019        }
20020        Ok(())
20021    }
20022
20023    fn generate_listagg(&mut self, f: &ListAggFunc) -> Result<()> {
20024        use crate::dialects::DialectType;
20025        self.write_keyword("LISTAGG");
20026        self.write("(");
20027        if f.distinct {
20028            self.write_keyword("DISTINCT");
20029            self.write_space();
20030        }
20031        self.generate_expression(&f.this)?;
20032        if let Some(ref sep) = f.separator {
20033            self.write(", ");
20034            self.generate_expression(sep)?;
20035        } else if matches!(
20036            self.config.dialect,
20037            Some(DialectType::Trino) | Some(DialectType::Presto)
20038        ) {
20039            // Trino/Presto require explicit separator; default to ','
20040            self.write(", ','");
20041        }
20042        if let Some(ref overflow) = f.on_overflow {
20043            self.write_space();
20044            self.write_keyword("ON OVERFLOW");
20045            self.write_space();
20046            match overflow {
20047                ListAggOverflow::Error => self.write_keyword("ERROR"),
20048                ListAggOverflow::Truncate { filler, with_count } => {
20049                    self.write_keyword("TRUNCATE");
20050                    if let Some(ref fill) = filler {
20051                        self.write_space();
20052                        self.generate_expression(fill)?;
20053                    }
20054                    if *with_count {
20055                        self.write_space();
20056                        self.write_keyword("WITH COUNT");
20057                    } else {
20058                        self.write_space();
20059                        self.write_keyword("WITHOUT COUNT");
20060                    }
20061                }
20062            }
20063        }
20064        self.write(")");
20065        if let Some(ref order_by) = f.order_by {
20066            self.write_space();
20067            self.write_keyword("WITHIN GROUP");
20068            self.write(" (");
20069            self.write_keyword("ORDER BY");
20070            self.write_space();
20071            for (i, ord) in order_by.iter().enumerate() {
20072                if i > 0 {
20073                    self.write(", ");
20074                }
20075                self.generate_ordered(ord)?;
20076            }
20077            self.write(")");
20078        }
20079        if let Some(ref filter) = f.filter {
20080            self.write_space();
20081            self.write_keyword("FILTER");
20082            self.write("(");
20083            self.write_keyword("WHERE");
20084            self.write_space();
20085            self.generate_expression(filter)?;
20086            self.write(")");
20087        }
20088        Ok(())
20089    }
20090
20091    fn generate_sum_if(&mut self, f: &SumIfFunc) -> Result<()> {
20092        self.write_keyword("SUM_IF");
20093        self.write("(");
20094        self.generate_expression(&f.this)?;
20095        self.write(", ");
20096        self.generate_expression(&f.condition)?;
20097        self.write(")");
20098        if let Some(ref filter) = f.filter {
20099            self.write_space();
20100            self.write_keyword("FILTER");
20101            self.write("(");
20102            self.write_keyword("WHERE");
20103            self.write_space();
20104            self.generate_expression(filter)?;
20105            self.write(")");
20106        }
20107        Ok(())
20108    }
20109
20110    fn generate_approx_percentile(&mut self, f: &ApproxPercentileFunc) -> Result<()> {
20111        self.write_keyword("APPROX_PERCENTILE");
20112        self.write("(");
20113        self.generate_expression(&f.this)?;
20114        self.write(", ");
20115        self.generate_expression(&f.percentile)?;
20116        if let Some(ref acc) = f.accuracy {
20117            self.write(", ");
20118            self.generate_expression(acc)?;
20119        }
20120        self.write(")");
20121        if let Some(ref filter) = f.filter {
20122            self.write_space();
20123            self.write_keyword("FILTER");
20124            self.write("(");
20125            self.write_keyword("WHERE");
20126            self.write_space();
20127            self.generate_expression(filter)?;
20128            self.write(")");
20129        }
20130        Ok(())
20131    }
20132
20133    fn generate_percentile(&mut self, name: &str, f: &PercentileFunc) -> Result<()> {
20134        self.write_keyword(name);
20135        self.write("(");
20136        self.generate_expression(&f.percentile)?;
20137        self.write(")");
20138        if let Some(ref order_by) = f.order_by {
20139            self.write_space();
20140            self.write_keyword("WITHIN GROUP");
20141            self.write(" (");
20142            self.write_keyword("ORDER BY");
20143            self.write_space();
20144            self.generate_expression(&f.this)?;
20145            for ord in order_by.iter() {
20146                if ord.desc {
20147                    self.write_space();
20148                    self.write_keyword("DESC");
20149                }
20150            }
20151            self.write(")");
20152        }
20153        if let Some(ref filter) = f.filter {
20154            self.write_space();
20155            self.write_keyword("FILTER");
20156            self.write("(");
20157            self.write_keyword("WHERE");
20158            self.write_space();
20159            self.generate_expression(filter)?;
20160            self.write(")");
20161        }
20162        Ok(())
20163    }
20164
20165    // Window function generators
20166
20167    fn generate_ntile(&mut self, f: &NTileFunc) -> Result<()> {
20168        self.write_keyword("NTILE");
20169        self.write("(");
20170        if let Some(num_buckets) = &f.num_buckets {
20171            self.generate_expression(num_buckets)?;
20172        }
20173        if let Some(order_by) = &f.order_by {
20174            self.write_keyword(" ORDER BY ");
20175            for (i, ob) in order_by.iter().enumerate() {
20176                if i > 0 {
20177                    self.write(", ");
20178                }
20179                self.generate_ordered(ob)?;
20180            }
20181        }
20182        self.write(")");
20183        Ok(())
20184    }
20185
20186    fn generate_lead_lag(&mut self, name: &str, f: &LeadLagFunc) -> Result<()> {
20187        self.write_keyword(name);
20188        self.write("(");
20189        self.generate_expression(&f.this)?;
20190        if let Some(ref offset) = f.offset {
20191            self.write(", ");
20192            self.generate_expression(offset)?;
20193            if let Some(ref default) = f.default {
20194                self.write(", ");
20195                self.generate_expression(default)?;
20196            }
20197        }
20198        // IGNORE NULLS / RESPECT NULLS inside parens for dialects like BigQuery
20199        if self.config.ignore_nulls_in_func {
20200            match f.ignore_nulls {
20201                Some(true) => {
20202                    self.write_space();
20203                    self.write_keyword("IGNORE NULLS");
20204                }
20205                Some(false) => {
20206                    self.write_space();
20207                    self.write_keyword("RESPECT NULLS");
20208                }
20209                None => {}
20210            }
20211        }
20212        self.write(")");
20213        // IGNORE NULLS / RESPECT NULLS outside parens for other dialects
20214        if !self.config.ignore_nulls_in_func {
20215            match f.ignore_nulls {
20216                Some(true) => {
20217                    self.write_space();
20218                    self.write_keyword("IGNORE NULLS");
20219                }
20220                Some(false) => {
20221                    self.write_space();
20222                    self.write_keyword("RESPECT NULLS");
20223                }
20224                None => {}
20225            }
20226        }
20227        Ok(())
20228    }
20229
20230    fn generate_value_func(&mut self, name: &str, f: &ValueFunc) -> Result<()> {
20231        self.write_keyword(name);
20232        self.write("(");
20233        self.generate_expression(&f.this)?;
20234        // ORDER BY inside parens (e.g., DuckDB: LAST_VALUE(x ORDER BY x))
20235        if !f.order_by.is_empty() {
20236            self.write_space();
20237            self.write_keyword("ORDER BY");
20238            self.write_space();
20239            for (i, ordered) in f.order_by.iter().enumerate() {
20240                if i > 0 {
20241                    self.write(", ");
20242                }
20243                self.generate_ordered(ordered)?;
20244            }
20245        }
20246        // IGNORE NULLS / RESPECT NULLS inside parens for dialects like BigQuery, DuckDB
20247        if self.config.ignore_nulls_in_func {
20248            match f.ignore_nulls {
20249                Some(true) => {
20250                    self.write_space();
20251                    self.write_keyword("IGNORE NULLS");
20252                }
20253                Some(false) => {
20254                    self.write_space();
20255                    self.write_keyword("RESPECT NULLS");
20256                }
20257                None => {}
20258            }
20259        }
20260        self.write(")");
20261        // IGNORE NULLS / RESPECT NULLS outside parens for other dialects
20262        if !self.config.ignore_nulls_in_func {
20263            match f.ignore_nulls {
20264                Some(true) => {
20265                    self.write_space();
20266                    self.write_keyword("IGNORE NULLS");
20267                }
20268                Some(false) => {
20269                    self.write_space();
20270                    self.write_keyword("RESPECT NULLS");
20271                }
20272                None => {}
20273            }
20274        }
20275        Ok(())
20276    }
20277
20278    /// Generate FIRST_VALUE/LAST_VALUE with Hive/Spark2-style boolean argument for IGNORE NULLS.
20279    /// In Hive/Spark2, `FIRST_VALUE(col) IGNORE NULLS` is written as `FIRST_VALUE(col, TRUE)`.
20280    fn generate_value_func_with_ignore_nulls_bool(
20281        &mut self,
20282        name: &str,
20283        f: &ValueFunc,
20284    ) -> Result<()> {
20285        if matches!(self.config.dialect, Some(DialectType::Hive)) && f.ignore_nulls == Some(true) {
20286            self.write_keyword(name);
20287            self.write("(");
20288            self.generate_expression(&f.this)?;
20289            self.write(", ");
20290            self.write_keyword("TRUE");
20291            self.write(")");
20292            return Ok(());
20293        }
20294        self.generate_value_func(name, f)
20295    }
20296
20297    fn generate_nth_value(&mut self, f: &NthValueFunc) -> Result<()> {
20298        self.write_keyword("NTH_VALUE");
20299        self.write("(");
20300        self.generate_expression(&f.this)?;
20301        self.write(", ");
20302        self.generate_expression(&f.offset)?;
20303        // IGNORE NULLS / RESPECT NULLS inside parens for dialects like BigQuery, DuckDB
20304        if self.config.ignore_nulls_in_func {
20305            match f.ignore_nulls {
20306                Some(true) => {
20307                    self.write_space();
20308                    self.write_keyword("IGNORE NULLS");
20309                }
20310                Some(false) => {
20311                    self.write_space();
20312                    self.write_keyword("RESPECT NULLS");
20313                }
20314                None => {}
20315            }
20316        }
20317        self.write(")");
20318        // FROM FIRST / FROM LAST (Snowflake-specific, before IGNORE/RESPECT NULLS)
20319        if matches!(
20320            self.config.dialect,
20321            Some(crate::dialects::DialectType::Snowflake)
20322        ) {
20323            match f.from_first {
20324                Some(true) => {
20325                    self.write_space();
20326                    self.write_keyword("FROM FIRST");
20327                }
20328                Some(false) => {
20329                    self.write_space();
20330                    self.write_keyword("FROM LAST");
20331                }
20332                None => {}
20333            }
20334        }
20335        // IGNORE NULLS / RESPECT NULLS outside parens for other dialects
20336        if !self.config.ignore_nulls_in_func {
20337            match f.ignore_nulls {
20338                Some(true) => {
20339                    self.write_space();
20340                    self.write_keyword("IGNORE NULLS");
20341                }
20342                Some(false) => {
20343                    self.write_space();
20344                    self.write_keyword("RESPECT NULLS");
20345                }
20346                None => {}
20347            }
20348        }
20349        Ok(())
20350    }
20351
20352    // Additional string function generators
20353
20354    fn generate_position(&mut self, f: &PositionFunc) -> Result<()> {
20355        // Standard syntax: POSITION(substr IN str)
20356        // ClickHouse prefers comma syntax with reversed arg order: POSITION(str, substr[, start])
20357        if matches!(
20358            self.config.dialect,
20359            Some(crate::dialects::DialectType::ClickHouse)
20360        ) {
20361            self.write_keyword("POSITION");
20362            self.write("(");
20363            self.generate_expression(&f.string)?;
20364            self.write(", ");
20365            self.generate_expression(&f.substring)?;
20366            if let Some(ref start) = f.start {
20367                self.write(", ");
20368                self.generate_expression(start)?;
20369            }
20370            self.write(")");
20371            return Ok(());
20372        }
20373
20374        self.write_keyword("POSITION");
20375        self.write("(");
20376        self.generate_expression(&f.substring)?;
20377        self.write_space();
20378        self.write_keyword("IN");
20379        self.write_space();
20380        self.generate_expression(&f.string)?;
20381        if let Some(ref start) = f.start {
20382            self.write(", ");
20383            self.generate_expression(start)?;
20384        }
20385        self.write(")");
20386        Ok(())
20387    }
20388
20389    // Additional math function generators
20390
20391    fn generate_rand(&mut self, f: &Rand) -> Result<()> {
20392        // Teradata RANDOM(lower, upper)
20393        if f.lower.is_some() || f.upper.is_some() {
20394            self.write_keyword("RANDOM");
20395            self.write("(");
20396            if let Some(ref lower) = f.lower {
20397                self.generate_expression(lower)?;
20398            }
20399            if let Some(ref upper) = f.upper {
20400                self.write(", ");
20401                self.generate_expression(upper)?;
20402            }
20403            self.write(")");
20404            return Ok(());
20405        }
20406        // Snowflake uses RANDOM instead of RAND, DuckDB uses RANDOM without seed
20407        let func_name = match self.config.dialect {
20408            Some(crate::dialects::DialectType::Snowflake)
20409            | Some(crate::dialects::DialectType::DuckDB) => "RANDOM",
20410            _ => "RAND",
20411        };
20412        self.write_keyword(func_name);
20413        self.write("(");
20414        // DuckDB doesn't support seeded RANDOM, so skip the seed
20415        if !matches!(
20416            self.config.dialect,
20417            Some(crate::dialects::DialectType::DuckDB)
20418        ) {
20419            if let Some(ref seed) = f.seed {
20420                self.generate_expression(seed)?;
20421            }
20422        }
20423        self.write(")");
20424        Ok(())
20425    }
20426
20427    fn generate_truncate_func(&mut self, f: &TruncateFunc) -> Result<()> {
20428        self.write_keyword("TRUNCATE");
20429        self.write("(");
20430        self.generate_expression(&f.this)?;
20431        if let Some(ref decimals) = f.decimals {
20432            self.write(", ");
20433            self.generate_expression(decimals)?;
20434        }
20435        self.write(")");
20436        Ok(())
20437    }
20438
20439    // Control flow generators
20440
20441    fn generate_decode(&mut self, f: &DecodeFunc) -> Result<()> {
20442        self.write_keyword("DECODE");
20443        self.write("(");
20444        self.generate_expression(&f.this)?;
20445        for (search, result) in &f.search_results {
20446            self.write(", ");
20447            self.generate_expression(search)?;
20448            self.write(", ");
20449            self.generate_expression(result)?;
20450        }
20451        if let Some(ref default) = f.default {
20452            self.write(", ");
20453            self.generate_expression(default)?;
20454        }
20455        self.write(")");
20456        Ok(())
20457    }
20458
20459    // Date/time function generators
20460
20461    fn generate_date_format(&mut self, name: &str, f: &DateFormatFunc) -> Result<()> {
20462        self.write_keyword(name);
20463        self.write("(");
20464        self.generate_expression(&f.this)?;
20465        self.write(", ");
20466        self.generate_expression(&f.format)?;
20467        self.write(")");
20468        Ok(())
20469    }
20470
20471    fn generate_from_unixtime(&mut self, f: &FromUnixtimeFunc) -> Result<()> {
20472        self.write_keyword("FROM_UNIXTIME");
20473        self.write("(");
20474        self.generate_expression(&f.this)?;
20475        if let Some(ref format) = f.format {
20476            self.write(", ");
20477            self.generate_expression(format)?;
20478        }
20479        self.write(")");
20480        Ok(())
20481    }
20482
20483    fn generate_unix_timestamp(&mut self, f: &UnixTimestampFunc) -> Result<()> {
20484        self.write_keyword("UNIX_TIMESTAMP");
20485        self.write("(");
20486        if let Some(ref expr) = f.this {
20487            self.generate_expression(expr)?;
20488            if let Some(ref format) = f.format {
20489                self.write(", ");
20490                self.generate_expression(format)?;
20491            }
20492        } else if matches!(
20493            self.config.dialect,
20494            Some(DialectType::Spark) | Some(DialectType::Hive) | Some(DialectType::Databricks)
20495        ) {
20496            // Spark/Hive: UNIX_TIMESTAMP() -> UNIX_TIMESTAMP(CURRENT_TIMESTAMP())
20497            self.write_keyword("CURRENT_TIMESTAMP");
20498            self.write("()");
20499        }
20500        self.write(")");
20501        Ok(())
20502    }
20503
20504    fn generate_make_date(&mut self, f: &MakeDateFunc) -> Result<()> {
20505        self.write_keyword("MAKE_DATE");
20506        self.write("(");
20507        self.generate_expression(&f.year)?;
20508        self.write(", ");
20509        self.generate_expression(&f.month)?;
20510        self.write(", ");
20511        self.generate_expression(&f.day)?;
20512        self.write(")");
20513        Ok(())
20514    }
20515
20516    fn generate_make_timestamp(&mut self, f: &MakeTimestampFunc) -> Result<()> {
20517        self.write_keyword("MAKE_TIMESTAMP");
20518        self.write("(");
20519        self.generate_expression(&f.year)?;
20520        self.write(", ");
20521        self.generate_expression(&f.month)?;
20522        self.write(", ");
20523        self.generate_expression(&f.day)?;
20524        self.write(", ");
20525        self.generate_expression(&f.hour)?;
20526        self.write(", ");
20527        self.generate_expression(&f.minute)?;
20528        self.write(", ");
20529        self.generate_expression(&f.second)?;
20530        if let Some(ref tz) = f.timezone {
20531            self.write(", ");
20532            self.generate_expression(tz)?;
20533        }
20534        self.write(")");
20535        Ok(())
20536    }
20537
20538    /// Extract field names from a struct expression (either Struct or Function named STRUCT with Alias args)
20539    fn extract_struct_field_names(expr: &Expression) -> Option<Vec<String>> {
20540        match expr {
20541            Expression::Struct(s) => {
20542                if s.fields.iter().all(|(name, _)| name.is_some()) {
20543                    Some(
20544                        s.fields
20545                            .iter()
20546                            .map(|(name, _)| name.as_deref().unwrap_or("").to_string())
20547                            .collect(),
20548                    )
20549                } else {
20550                    None
20551                }
20552            }
20553            Expression::Function(f) if f.name.eq_ignore_ascii_case("STRUCT") => {
20554                // Check if all args are Alias (named fields)
20555                if f.args.iter().all(|a| matches!(a, Expression::Alias(_))) {
20556                    Some(
20557                        f.args
20558                            .iter()
20559                            .filter_map(|a| {
20560                                if let Expression::Alias(alias) = a {
20561                                    Some(alias.alias.name.clone())
20562                                } else {
20563                                    None
20564                                }
20565                            })
20566                            .collect(),
20567                    )
20568                } else {
20569                    None
20570                }
20571            }
20572            _ => None,
20573        }
20574    }
20575
20576    /// Check if a struct expression has any unnamed fields
20577    fn struct_has_unnamed_fields(expr: &Expression) -> bool {
20578        match expr {
20579            Expression::Struct(s) => s.fields.iter().any(|(name, _)| name.is_none()),
20580            Expression::Function(f) if f.name.eq_ignore_ascii_case("STRUCT") => {
20581                f.args.iter().any(|a| !matches!(a, Expression::Alias(_)))
20582            }
20583            _ => false,
20584        }
20585    }
20586
20587    /// Get the field count of a struct expression
20588    fn struct_field_count(expr: &Expression) -> usize {
20589        match expr {
20590            Expression::Struct(s) => s.fields.len(),
20591            Expression::Function(f) if f.name.eq_ignore_ascii_case("STRUCT") => f.args.len(),
20592            _ => 0,
20593        }
20594    }
20595
20596    /// Apply field names to an unnamed struct expression, producing a new expression with names
20597    fn apply_struct_field_names(expr: &Expression, field_names: &[String]) -> Expression {
20598        match expr {
20599            Expression::Struct(s) => {
20600                let mut new_fields = Vec::with_capacity(s.fields.len());
20601                for (i, (name, value)) in s.fields.iter().enumerate() {
20602                    if name.is_none() && i < field_names.len() {
20603                        new_fields.push((Some(field_names[i].clone()), value.clone()));
20604                    } else {
20605                        new_fields.push((name.clone(), value.clone()));
20606                    }
20607                }
20608                Expression::Struct(Box::new(crate::expressions::Struct { fields: new_fields }))
20609            }
20610            Expression::Function(f) if f.name.eq_ignore_ascii_case("STRUCT") => {
20611                let mut new_args = Vec::with_capacity(f.args.len());
20612                for (i, arg) in f.args.iter().enumerate() {
20613                    if !matches!(arg, Expression::Alias(_)) && i < field_names.len() {
20614                        // Wrap the value in an Alias with the inherited name
20615                        new_args.push(Expression::Alias(Box::new(crate::expressions::Alias {
20616                            this: arg.clone(),
20617                            alias: crate::expressions::Identifier::new(field_names[i].clone()),
20618                            column_aliases: Vec::new(),
20619                            alias_explicit_as: false,
20620                            alias_keyword: None,
20621                            pre_alias_comments: Vec::new(),
20622                            trailing_comments: Vec::new(),
20623                            inferred_type: None,
20624                        })));
20625                    } else {
20626                        new_args.push(arg.clone());
20627                    }
20628                }
20629                Expression::Function(Box::new(crate::expressions::Function {
20630                    name: f.name.clone(),
20631                    args: new_args,
20632                    distinct: f.distinct,
20633                    trailing_comments: f.trailing_comments.clone(),
20634                    use_bracket_syntax: f.use_bracket_syntax,
20635                    no_parens: f.no_parens,
20636                    quoted: f.quoted,
20637                    span: None,
20638                    inferred_type: None,
20639                }))
20640            }
20641            _ => expr.clone(),
20642        }
20643    }
20644
20645    /// Propagate struct field names from the first struct in an array to subsequent unnamed structs.
20646    /// This implements BigQuery's implicit field name inheritance for struct arrays.
20647    /// Handles both Expression::Struct and Expression::Function named "STRUCT".
20648    fn inherit_struct_field_names(expressions: &[Expression]) -> Vec<Expression> {
20649        let first = match expressions.first() {
20650            Some(e) => e,
20651            None => return expressions.to_vec(),
20652        };
20653
20654        let field_names = match Self::extract_struct_field_names(first) {
20655            Some(names) if !names.is_empty() => names,
20656            _ => return expressions.to_vec(),
20657        };
20658
20659        let mut result = Vec::with_capacity(expressions.len());
20660        for (idx, expr) in expressions.iter().enumerate() {
20661            if idx == 0 {
20662                result.push(expr.clone());
20663                continue;
20664            }
20665            // Check if this is a struct with unnamed fields that needs name propagation
20666            if Self::struct_field_count(expr) == field_names.len()
20667                && Self::struct_has_unnamed_fields(expr)
20668            {
20669                result.push(Self::apply_struct_field_names(expr, &field_names));
20670            } else {
20671                result.push(expr.clone());
20672            }
20673        }
20674        result
20675    }
20676
20677    // Array function generators
20678
20679    fn generate_array_constructor(&mut self, f: &ArrayConstructor) -> Result<()> {
20680        // Apply struct name inheritance for target dialects that need it
20681        // (DuckDB, Spark, Databricks, Hive, Snowflake, Presto, Trino)
20682        let needs_inheritance = matches!(
20683            self.config.dialect,
20684            Some(DialectType::DuckDB)
20685                | Some(DialectType::Spark)
20686                | Some(DialectType::Databricks)
20687                | Some(DialectType::Hive)
20688                | Some(DialectType::Snowflake)
20689                | Some(DialectType::Presto)
20690                | Some(DialectType::Trino)
20691        );
20692        let propagated: Vec<Expression>;
20693        let expressions = if needs_inheritance && f.expressions.len() > 1 {
20694            propagated = Self::inherit_struct_field_names(&f.expressions);
20695            &propagated
20696        } else {
20697            &f.expressions
20698        };
20699
20700        // Check if elements should be split onto multiple lines (pretty + too wide)
20701        let should_split = if self.config.pretty && !expressions.is_empty() {
20702            let mut expr_strings: Vec<String> = Vec::with_capacity(expressions.len());
20703            for expr in expressions {
20704                let mut temp_gen = Generator::with_arc_config(self.config.clone());
20705                Arc::make_mut(&mut temp_gen.config).pretty = false;
20706                temp_gen.generate_expression(expr)?;
20707                expr_strings.push(temp_gen.output);
20708            }
20709            self.too_wide(&expr_strings)
20710        } else {
20711            false
20712        };
20713
20714        if f.bracket_notation {
20715            // For Spark/Databricks, use ARRAY(...) with parens
20716            // For Presto/Trino/PostgreSQL, use ARRAY[...] with keyword prefix
20717            // For others (DuckDB, Snowflake), use bare [...]
20718            let (open, close) = match self.config.dialect {
20719                None
20720                | Some(DialectType::Generic)
20721                | Some(DialectType::Spark)
20722                | Some(DialectType::Databricks)
20723                | Some(DialectType::Hive) => {
20724                    self.write_keyword("ARRAY");
20725                    ("(", ")")
20726                }
20727                Some(DialectType::Presto)
20728                | Some(DialectType::Trino)
20729                | Some(DialectType::PostgreSQL)
20730                | Some(DialectType::Redshift)
20731                | Some(DialectType::Materialize)
20732                | Some(DialectType::RisingWave)
20733                | Some(DialectType::CockroachDB) => {
20734                    self.write_keyword("ARRAY");
20735                    ("[", "]")
20736                }
20737                _ => ("[", "]"),
20738            };
20739            self.write(open);
20740            if should_split {
20741                self.write_newline();
20742                self.indent_level += 1;
20743                for (i, expr) in expressions.iter().enumerate() {
20744                    self.write_indent();
20745                    self.generate_expression(expr)?;
20746                    if i + 1 < expressions.len() {
20747                        self.write(",");
20748                    }
20749                    self.write_newline();
20750                }
20751                self.indent_level -= 1;
20752                self.write_indent();
20753            } else {
20754                for (i, expr) in expressions.iter().enumerate() {
20755                    if i > 0 {
20756                        self.write(", ");
20757                    }
20758                    self.generate_expression(expr)?;
20759                }
20760            }
20761            self.write(close);
20762        } else {
20763            // Use LIST keyword if that was the original syntax (DuckDB)
20764            if f.use_list_keyword {
20765                self.write_keyword("LIST");
20766            } else {
20767                self.write_keyword("ARRAY");
20768            }
20769            // For Spark/Hive, always use ARRAY(...) with parens
20770            // Also use parens for BigQuery when the array contains a subquery (ARRAY(SELECT ...))
20771            let has_subquery = expressions
20772                .iter()
20773                .any(|e| matches!(e, Expression::Select(_)));
20774            let (open, close) = if matches!(
20775                self.config.dialect,
20776                Some(DialectType::Spark) | Some(DialectType::Databricks) | Some(DialectType::Hive)
20777            ) || (matches!(self.config.dialect, Some(DialectType::BigQuery))
20778                && has_subquery)
20779            {
20780                ("(", ")")
20781            } else {
20782                ("[", "]")
20783            };
20784            self.write(open);
20785            if should_split {
20786                self.write_newline();
20787                self.indent_level += 1;
20788                for (i, expr) in expressions.iter().enumerate() {
20789                    self.write_indent();
20790                    self.generate_expression(expr)?;
20791                    if i + 1 < expressions.len() {
20792                        self.write(",");
20793                    }
20794                    self.write_newline();
20795                }
20796                self.indent_level -= 1;
20797                self.write_indent();
20798            } else {
20799                for (i, expr) in expressions.iter().enumerate() {
20800                    if i > 0 {
20801                        self.write(", ");
20802                    }
20803                    self.generate_expression(expr)?;
20804                }
20805            }
20806            self.write(close);
20807        }
20808        Ok(())
20809    }
20810
20811    fn generate_array_sort(&mut self, f: &ArraySortFunc) -> Result<()> {
20812        self.write_keyword("ARRAY_SORT");
20813        self.write("(");
20814        self.generate_expression(&f.this)?;
20815        if let Some(ref comp) = f.comparator {
20816            self.write(", ");
20817            self.generate_expression(comp)?;
20818        }
20819        self.write(")");
20820        Ok(())
20821    }
20822
20823    fn generate_array_join(&mut self, name: &str, f: &ArrayJoinFunc) -> Result<()> {
20824        self.write_keyword(name);
20825        self.write("(");
20826        self.generate_expression(&f.this)?;
20827        self.write(", ");
20828        self.generate_expression(&f.separator)?;
20829        if let Some(ref null_rep) = f.null_replacement {
20830            self.write(", ");
20831            self.generate_expression(null_rep)?;
20832        }
20833        self.write(")");
20834        Ok(())
20835    }
20836
20837    fn generate_unnest(&mut self, f: &UnnestFunc) -> Result<()> {
20838        self.write_keyword("UNNEST");
20839        self.write("(");
20840        self.generate_expression(&f.this)?;
20841        for extra in &f.expressions {
20842            self.write(", ");
20843            self.generate_expression(extra)?;
20844        }
20845        self.write(")");
20846        if f.with_ordinality {
20847            self.write_space();
20848            if self.config.unnest_with_ordinality {
20849                // Presto/Trino: UNNEST(arr) WITH ORDINALITY [AS alias]
20850                self.write_keyword("WITH ORDINALITY");
20851            } else if f.offset_alias.is_some() {
20852                // BigQuery: UNNEST(arr) [AS col] WITH OFFSET AS pos
20853                // Alias (if any) comes BEFORE WITH OFFSET
20854                if let Some(ref alias) = f.alias {
20855                    self.write_keyword("AS");
20856                    self.write_space();
20857                    self.generate_identifier(alias)?;
20858                    self.write_space();
20859                }
20860                self.write_keyword("WITH OFFSET");
20861                if let Some(ref offset_alias) = f.offset_alias {
20862                    self.write_space();
20863                    self.write_keyword("AS");
20864                    self.write_space();
20865                    self.generate_identifier(offset_alias)?;
20866                }
20867            } else {
20868                // WITH OFFSET (BigQuery identity) - add default "AS offset" if no explicit alias
20869                self.write_keyword("WITH OFFSET");
20870                if f.alias.is_none() {
20871                    self.write(" AS offset");
20872                }
20873            }
20874        }
20875        if let Some(ref alias) = f.alias {
20876            // Add alias for: non-WITH-OFFSET cases, Presto/Trino WITH ORDINALITY, or BigQuery WITH OFFSET + alias (no offset_alias)
20877            let should_add_alias = if !f.with_ordinality {
20878                true
20879            } else if self.config.unnest_with_ordinality {
20880                // Presto/Trino: alias comes after WITH ORDINALITY
20881                true
20882            } else if f.offset_alias.is_some() {
20883                // BigQuery expansion: alias already handled above
20884                false
20885            } else {
20886                // BigQuery WITH OFFSET + alias but no offset_alias: alias comes after
20887                true
20888            };
20889            if should_add_alias {
20890                self.write_space();
20891                self.write_keyword("AS");
20892                self.write_space();
20893                self.generate_identifier(alias)?;
20894            }
20895        }
20896        Ok(())
20897    }
20898
20899    fn generate_array_filter(&mut self, f: &ArrayFilterFunc) -> Result<()> {
20900        self.write_keyword("FILTER");
20901        self.write("(");
20902        self.generate_expression(&f.this)?;
20903        self.write(", ");
20904        self.generate_expression(&f.filter)?;
20905        self.write(")");
20906        Ok(())
20907    }
20908
20909    fn generate_array_transform(&mut self, f: &ArrayTransformFunc) -> Result<()> {
20910        self.write_keyword("TRANSFORM");
20911        self.write("(");
20912        self.generate_expression(&f.this)?;
20913        self.write(", ");
20914        self.generate_expression(&f.transform)?;
20915        self.write(")");
20916        Ok(())
20917    }
20918
20919    fn generate_sequence(&mut self, name: &str, f: &SequenceFunc) -> Result<()> {
20920        self.write_keyword(name);
20921        self.write("(");
20922        self.generate_expression(&f.start)?;
20923        self.write(", ");
20924        self.generate_expression(&f.stop)?;
20925        if let Some(ref step) = f.step {
20926            self.write(", ");
20927            self.generate_expression(step)?;
20928        }
20929        self.write(")");
20930        Ok(())
20931    }
20932
20933    // Struct function generators
20934
20935    fn generate_struct_constructor(&mut self, f: &StructConstructor) -> Result<()> {
20936        self.write_keyword("STRUCT");
20937        self.write("(");
20938        for (i, (name, expr)) in f.fields.iter().enumerate() {
20939            if i > 0 {
20940                self.write(", ");
20941            }
20942            if let Some(ref id) = name {
20943                self.generate_identifier(id)?;
20944                self.write(" ");
20945                self.write_keyword("AS");
20946                self.write(" ");
20947            }
20948            self.generate_expression(expr)?;
20949        }
20950        self.write(")");
20951        Ok(())
20952    }
20953
20954    /// Convert BigQuery STRUCT function (parsed as Function with Alias args) to target dialect
20955    fn generate_struct_function_cross_dialect(&mut self, func: &Function) -> Result<()> {
20956        // Extract named/unnamed fields from function args
20957        // Args are either Alias(this=value, alias=name) for named or plain expressions for unnamed
20958        let mut names: Vec<Option<String>> = Vec::new();
20959        let mut values: Vec<&Expression> = Vec::new();
20960        let mut all_named = true;
20961
20962        for arg in &func.args {
20963            match arg {
20964                Expression::Alias(a) => {
20965                    names.push(Some(a.alias.name.clone()));
20966                    values.push(&a.this);
20967                }
20968                _ => {
20969                    names.push(None);
20970                    values.push(arg);
20971                    all_named = false;
20972                }
20973            }
20974        }
20975
20976        if matches!(self.config.dialect, Some(DialectType::DuckDB)) {
20977            // DuckDB: {'name': value, ...} for named, {'_0': value, ...} for unnamed
20978            self.write("{");
20979            for (i, (name, value)) in names.iter().zip(values.iter()).enumerate() {
20980                if i > 0 {
20981                    self.write(", ");
20982                }
20983                if let Some(n) = name {
20984                    self.write("'");
20985                    self.write(n);
20986                    self.write("'");
20987                } else {
20988                    self.write("'_");
20989                    self.write(&i.to_string());
20990                    self.write("'");
20991                }
20992                self.write(": ");
20993                self.generate_expression(value)?;
20994            }
20995            self.write("}");
20996            return Ok(());
20997        }
20998
20999        if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
21000            // Snowflake: OBJECT_CONSTRUCT('name', value, ...)
21001            self.write_keyword("OBJECT_CONSTRUCT");
21002            self.write("(");
21003            for (i, (name, value)) in names.iter().zip(values.iter()).enumerate() {
21004                if i > 0 {
21005                    self.write(", ");
21006                }
21007                if let Some(n) = name {
21008                    self.write("'");
21009                    self.write(n);
21010                    self.write("'");
21011                } else {
21012                    self.write("'_");
21013                    self.write(&i.to_string());
21014                    self.write("'");
21015                }
21016                self.write(", ");
21017                self.generate_expression(value)?;
21018            }
21019            self.write(")");
21020            return Ok(());
21021        }
21022
21023        if matches!(
21024            self.config.dialect,
21025            Some(DialectType::Presto) | Some(DialectType::Trino)
21026        ) {
21027            if all_named && !names.is_empty() {
21028                // Presto/Trino: CAST(ROW(values...) AS ROW(name TYPE, ...))
21029                // Need to infer types from values
21030                self.write_keyword("CAST");
21031                self.write("(");
21032                self.write_keyword("ROW");
21033                self.write("(");
21034                for (i, value) in values.iter().enumerate() {
21035                    if i > 0 {
21036                        self.write(", ");
21037                    }
21038                    self.generate_expression(value)?;
21039                }
21040                self.write(")");
21041                self.write(" ");
21042                self.write_keyword("AS");
21043                self.write(" ");
21044                self.write_keyword("ROW");
21045                self.write("(");
21046                for (i, (name, value)) in names.iter().zip(values.iter()).enumerate() {
21047                    if i > 0 {
21048                        self.write(", ");
21049                    }
21050                    if let Some(n) = name {
21051                        self.write(n);
21052                    }
21053                    self.write(" ");
21054                    let type_str = Self::infer_sql_type_for_presto(value);
21055                    self.write_keyword(&type_str);
21056                }
21057                self.write(")");
21058                self.write(")");
21059            } else {
21060                // Unnamed: ROW(values...)
21061                self.write_keyword("ROW");
21062                self.write("(");
21063                for (i, value) in values.iter().enumerate() {
21064                    if i > 0 {
21065                        self.write(", ");
21066                    }
21067                    self.generate_expression(value)?;
21068                }
21069                self.write(")");
21070            }
21071            return Ok(());
21072        }
21073
21074        // Default: ROW(values...) for other dialects
21075        self.write_keyword("ROW");
21076        self.write("(");
21077        for (i, value) in values.iter().enumerate() {
21078            if i > 0 {
21079                self.write(", ");
21080            }
21081            self.generate_expression(value)?;
21082        }
21083        self.write(")");
21084        Ok(())
21085    }
21086
21087    /// Infer SQL type name for a Presto/Trino ROW CAST from a literal expression
21088    fn infer_sql_type_for_presto(expr: &Expression) -> String {
21089        match expr {
21090            Expression::Literal(lit)
21091                if matches!(lit.as_ref(), crate::expressions::Literal::String(_)) =>
21092            {
21093                "VARCHAR".to_string()
21094            }
21095            Expression::Literal(lit)
21096                if matches!(lit.as_ref(), crate::expressions::Literal::Number(_)) =>
21097            {
21098                let crate::expressions::Literal::Number(n) = lit.as_ref() else {
21099                    unreachable!()
21100                };
21101                if n.contains('.') {
21102                    "DOUBLE".to_string()
21103                } else {
21104                    "INTEGER".to_string()
21105                }
21106            }
21107            Expression::Boolean(_) => "BOOLEAN".to_string(),
21108            Expression::Literal(lit)
21109                if matches!(lit.as_ref(), crate::expressions::Literal::Date(_)) =>
21110            {
21111                "DATE".to_string()
21112            }
21113            Expression::Literal(lit)
21114                if matches!(lit.as_ref(), crate::expressions::Literal::Timestamp(_)) =>
21115            {
21116                "TIMESTAMP".to_string()
21117            }
21118            Expression::Literal(lit)
21119                if matches!(lit.as_ref(), crate::expressions::Literal::Datetime(_)) =>
21120            {
21121                "TIMESTAMP".to_string()
21122            }
21123            Expression::Array(_) | Expression::ArrayFunc(_) => {
21124                // Try to infer element type from first element
21125                "ARRAY(VARCHAR)".to_string()
21126            }
21127            // For nested structs - generate a nested ROW type by inspecting fields
21128            Expression::Struct(_) | Expression::StructFunc(_) => "ROW".to_string(),
21129            Expression::Function(f) => {
21130                if f.name.eq_ignore_ascii_case("STRUCT") {
21131                    "ROW".to_string()
21132                } else if f.name.eq_ignore_ascii_case("CURRENT_DATE") {
21133                    "DATE".to_string()
21134                } else if f.name.eq_ignore_ascii_case("CURRENT_TIMESTAMP")
21135                    || f.name.eq_ignore_ascii_case("NOW")
21136                {
21137                    "TIMESTAMP".to_string()
21138                } else {
21139                    "VARCHAR".to_string()
21140                }
21141            }
21142            _ => "VARCHAR".to_string(),
21143        }
21144    }
21145
21146    fn generate_struct_extract(&mut self, f: &StructExtractFunc) -> Result<()> {
21147        // DuckDB uses STRUCT_EXTRACT function syntax
21148        if matches!(self.config.dialect, Some(DialectType::DuckDB)) {
21149            self.write_keyword("STRUCT_EXTRACT");
21150            self.write("(");
21151            self.generate_expression(&f.this)?;
21152            self.write(", ");
21153            // Output field name as string literal
21154            self.write("'");
21155            self.write(&f.field.name);
21156            self.write("'");
21157            self.write(")");
21158            return Ok(());
21159        }
21160        self.generate_expression(&f.this)?;
21161        self.write(".");
21162        self.generate_identifier(&f.field)
21163    }
21164
21165    fn generate_named_struct(&mut self, f: &NamedStructFunc) -> Result<()> {
21166        if matches!(
21167            self.config.dialect,
21168            Some(DialectType::Spark | DialectType::Databricks)
21169        ) {
21170            self.write_keyword("STRUCT");
21171            self.write("(");
21172            for (i, (name, value)) in f.pairs.iter().enumerate() {
21173                if i > 0 {
21174                    self.write(", ");
21175                }
21176                self.generate_expression(value)?;
21177                self.write(" ");
21178                self.write_keyword("AS");
21179                self.write(" ");
21180                if let Expression::Literal(lit) = name {
21181                    if let Literal::String(field_name) = lit.as_ref() {
21182                        self.generate_identifier(&Identifier::new(field_name))?;
21183                    } else {
21184                        self.generate_expression(name)?;
21185                    }
21186                } else {
21187                    self.generate_expression(name)?;
21188                }
21189            }
21190            self.write(")");
21191            return Ok(());
21192        }
21193
21194        self.write_keyword("NAMED_STRUCT");
21195        self.write("(");
21196        for (i, (name, value)) in f.pairs.iter().enumerate() {
21197            if i > 0 {
21198                self.write(", ");
21199            }
21200            self.generate_expression(name)?;
21201            self.write(", ");
21202            self.generate_expression(value)?;
21203        }
21204        self.write(")");
21205        Ok(())
21206    }
21207
21208    // Map function generators
21209
21210    fn generate_map_constructor(&mut self, f: &MapConstructor) -> Result<()> {
21211        if f.curly_brace_syntax {
21212            // Curly brace syntax: MAP {'a': 1, 'b': 2} or just {'a': 1, 'b': 2}
21213            if f.with_map_keyword {
21214                self.write_keyword("MAP");
21215                self.write(" ");
21216            }
21217            self.write("{");
21218            for (i, (key, val)) in f.keys.iter().zip(f.values.iter()).enumerate() {
21219                if i > 0 {
21220                    self.write(", ");
21221                }
21222                self.generate_expression(key)?;
21223                self.write(": ");
21224                self.generate_expression(val)?;
21225            }
21226            self.write("}");
21227        } else {
21228            // MAP function syntax: MAP(ARRAY[keys], ARRAY[values])
21229            self.write_keyword("MAP");
21230            self.write("(");
21231            self.write_keyword("ARRAY");
21232            self.write("[");
21233            for (i, key) in f.keys.iter().enumerate() {
21234                if i > 0 {
21235                    self.write(", ");
21236                }
21237                self.generate_expression(key)?;
21238            }
21239            self.write("], ");
21240            self.write_keyword("ARRAY");
21241            self.write("[");
21242            for (i, val) in f.values.iter().enumerate() {
21243                if i > 0 {
21244                    self.write(", ");
21245                }
21246                self.generate_expression(val)?;
21247            }
21248            self.write("])");
21249        }
21250        Ok(())
21251    }
21252
21253    fn generate_transform_func(&mut self, name: &str, f: &TransformFunc) -> Result<()> {
21254        self.write_keyword(name);
21255        self.write("(");
21256        self.generate_expression(&f.this)?;
21257        self.write(", ");
21258        self.generate_expression(&f.transform)?;
21259        self.write(")");
21260        Ok(())
21261    }
21262
21263    // JSON function generators
21264
21265    fn generate_json_extract(&mut self, name: &str, f: &JsonExtractFunc) -> Result<()> {
21266        use crate::dialects::DialectType;
21267
21268        // Check if we should use arrow syntax (-> or ->>)
21269        let use_arrow = f.arrow_syntax && self.dialect_supports_json_arrow();
21270
21271        if use_arrow {
21272            // Output arrow syntax: expr -> path or expr ->> path
21273            self.generate_expression(&f.this)?;
21274            if name == "JSON_EXTRACT_SCALAR" || name == "JSON_EXTRACT_PATH_TEXT" {
21275                self.write(" ->> ");
21276            } else {
21277                self.write(" -> ");
21278            }
21279            self.generate_expression(&f.path)?;
21280            return Ok(());
21281        }
21282
21283        // PostgreSQL uses #>> operator for JSONB path text extraction (only when hash_arrow_syntax is true)
21284        if f.hash_arrow_syntax
21285            && matches!(
21286                self.config.dialect,
21287                Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
21288            )
21289        {
21290            self.generate_expression(&f.this)?;
21291            self.write(" #>> ");
21292            self.generate_expression(&f.path)?;
21293            return Ok(());
21294        }
21295
21296        // For PostgreSQL/Redshift, use JSON_EXTRACT_PATH / JSON_EXTRACT_PATH_TEXT for extraction without arrow syntax
21297        // Redshift maps everything to JSON_EXTRACT_PATH_TEXT since it doesn't have JSON_EXTRACT_PATH
21298        let func_name = if matches!(self.config.dialect, Some(DialectType::Redshift)) {
21299            match name {
21300                "JSON_EXTRACT_SCALAR"
21301                | "JSON_EXTRACT_PATH_TEXT"
21302                | "JSON_EXTRACT"
21303                | "JSON_EXTRACT_PATH" => "JSON_EXTRACT_PATH_TEXT",
21304                _ => name,
21305            }
21306        } else if matches!(self.config.dialect, Some(DialectType::PostgreSQL)) {
21307            match name {
21308                "JSON_EXTRACT_SCALAR" | "JSON_EXTRACT_PATH_TEXT" => "JSON_EXTRACT_PATH_TEXT",
21309                "JSON_EXTRACT" | "JSON_EXTRACT_PATH" => "JSON_EXTRACT_PATH",
21310                _ => name,
21311            }
21312        } else {
21313            name
21314        };
21315
21316        self.write_keyword(func_name);
21317        self.write("(");
21318        // For Redshift, strip CAST(... AS JSON) wrapper from the expression
21319        if matches!(self.config.dialect, Some(DialectType::Redshift)) {
21320            if let Expression::Cast(ref cast) = f.this {
21321                if matches!(cast.to, crate::expressions::DataType::Json) {
21322                    self.generate_expression(&cast.this)?;
21323                } else {
21324                    self.generate_expression(&f.this)?;
21325                }
21326            } else {
21327                self.generate_expression(&f.this)?;
21328            }
21329        } else {
21330            self.generate_expression(&f.this)?;
21331        }
21332        // For PostgreSQL/Redshift JSON_EXTRACT_PATH/JSON_EXTRACT_PATH_TEXT,
21333        // decompose JSON path into separate string arguments
21334        if matches!(
21335            self.config.dialect,
21336            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
21337        ) && (func_name == "JSON_EXTRACT_PATH" || func_name == "JSON_EXTRACT_PATH_TEXT")
21338        {
21339            if let Expression::Literal(ref lit) = f.path {
21340                if let Literal::String(ref s) = lit.as_ref() {
21341                    let parts = Self::decompose_json_path(s);
21342                    for part in &parts {
21343                        self.write(", '");
21344                        self.write(part);
21345                        self.write("'");
21346                    }
21347                }
21348            } else {
21349                self.write(", ");
21350                self.generate_expression(&f.path)?;
21351            }
21352        } else {
21353            self.write(", ");
21354            self.generate_expression(&f.path)?;
21355        }
21356
21357        // Output JSON_QUERY/JSON_VALUE options (Trino/Presto style)
21358        // These go BEFORE the closing parenthesis
21359        if let Some(ref wrapper) = f.wrapper_option {
21360            self.write_space();
21361            self.write_keyword(wrapper);
21362        }
21363        if let Some(ref quotes) = f.quotes_option {
21364            self.write_space();
21365            self.write_keyword(quotes);
21366            if f.on_scalar_string {
21367                self.write_space();
21368                self.write_keyword("ON SCALAR STRING");
21369            }
21370        }
21371        if let Some(ref on_err) = f.on_error {
21372            self.write_space();
21373            self.write_keyword(on_err);
21374        }
21375        if let Some(ref ret_type) = f.returning {
21376            self.write_space();
21377            self.write_keyword("RETURNING");
21378            self.write_space();
21379            self.generate_data_type(ret_type)?;
21380        }
21381
21382        self.write(")");
21383        Ok(())
21384    }
21385
21386    /// Check if the current dialect supports JSON arrow operators (-> and ->>)
21387    fn dialect_supports_json_arrow(&self) -> bool {
21388        use crate::dialects::DialectType;
21389        match self.config.dialect {
21390            // PostgreSQL, MySQL, DuckDB support -> and ->> operators
21391            Some(DialectType::PostgreSQL) => true,
21392            Some(DialectType::MySQL) => true,
21393            Some(DialectType::DuckDB) => true,
21394            Some(DialectType::CockroachDB) => true,
21395            Some(DialectType::StarRocks) => true,
21396            Some(DialectType::SQLite) => true,
21397            // Other dialects use function syntax
21398            _ => false,
21399        }
21400    }
21401
21402    fn generate_json_path(&mut self, name: &str, f: &JsonPathFunc) -> Result<()> {
21403        use crate::dialects::DialectType;
21404
21405        // PostgreSQL uses #> operator for JSONB path extraction
21406        if matches!(
21407            self.config.dialect,
21408            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
21409        ) && name == "JSON_EXTRACT_PATH"
21410        {
21411            self.generate_expression(&f.this)?;
21412            self.write(" #> ");
21413            if f.paths.len() == 1 {
21414                self.generate_expression(&f.paths[0])?;
21415            } else {
21416                // Multiple paths: ARRAY[path1, path2, ...]
21417                self.write_keyword("ARRAY");
21418                self.write("[");
21419                for (i, path) in f.paths.iter().enumerate() {
21420                    if i > 0 {
21421                        self.write(", ");
21422                    }
21423                    self.generate_expression(path)?;
21424                }
21425                self.write("]");
21426            }
21427            return Ok(());
21428        }
21429
21430        self.write_keyword(name);
21431        self.write("(");
21432        self.generate_expression(&f.this)?;
21433        for path in &f.paths {
21434            self.write(", ");
21435            self.generate_expression(path)?;
21436        }
21437        self.write(")");
21438        Ok(())
21439    }
21440
21441    fn generate_json_object(&mut self, f: &JsonObjectFunc) -> Result<()> {
21442        use crate::dialects::DialectType;
21443
21444        self.write_keyword("JSON_OBJECT");
21445        self.write("(");
21446        if f.star {
21447            self.write("*");
21448        } else {
21449            // BigQuery, MySQL, and SQLite use comma syntax: JSON_OBJECT('key', value)
21450            // Standard SQL uses colon syntax: JSON_OBJECT('key': value)
21451            // Also respect the json_key_value_pair_sep config
21452            let use_comma_syntax = self.config.json_key_value_pair_sep == ","
21453                || matches!(
21454                    self.config.dialect,
21455                    Some(DialectType::BigQuery)
21456                        | Some(DialectType::MySQL)
21457                        | Some(DialectType::SQLite)
21458                );
21459
21460            for (i, (key, value)) in f.pairs.iter().enumerate() {
21461                if i > 0 {
21462                    self.write(", ");
21463                }
21464                self.generate_expression(key)?;
21465                if use_comma_syntax {
21466                    self.write(", ");
21467                } else {
21468                    self.write(": ");
21469                }
21470                self.generate_expression(value)?;
21471            }
21472        }
21473        if let Some(null_handling) = f.null_handling {
21474            self.write_space();
21475            match null_handling {
21476                JsonNullHandling::NullOnNull => self.write_keyword("NULL ON NULL"),
21477                JsonNullHandling::AbsentOnNull => self.write_keyword("ABSENT ON NULL"),
21478            }
21479        }
21480        if f.with_unique_keys {
21481            self.write_space();
21482            self.write_keyword("WITH UNIQUE KEYS");
21483        }
21484        if let Some(ref ret_type) = f.returning_type {
21485            self.write_space();
21486            self.write_keyword("RETURNING");
21487            self.write_space();
21488            self.generate_data_type(ret_type)?;
21489            if f.format_json {
21490                self.write_space();
21491                self.write_keyword("FORMAT JSON");
21492            }
21493            if let Some(ref enc) = f.encoding {
21494                self.write_space();
21495                self.write_keyword("ENCODING");
21496                self.write_space();
21497                self.write(enc);
21498            }
21499        }
21500        self.write(")");
21501        Ok(())
21502    }
21503
21504    fn generate_json_modify(&mut self, name: &str, f: &JsonModifyFunc) -> Result<()> {
21505        self.write_keyword(name);
21506        self.write("(");
21507        self.generate_expression(&f.this)?;
21508        for (path, value) in &f.path_values {
21509            self.write(", ");
21510            self.generate_expression(path)?;
21511            self.write(", ");
21512            self.generate_expression(value)?;
21513        }
21514        self.write(")");
21515        Ok(())
21516    }
21517
21518    fn generate_json_array_agg(&mut self, f: &JsonArrayAggFunc) -> Result<()> {
21519        self.write_keyword("JSON_ARRAYAGG");
21520        self.write("(");
21521        self.generate_expression(&f.this)?;
21522        if let Some(ref order_by) = f.order_by {
21523            self.write_space();
21524            self.write_keyword("ORDER BY");
21525            self.write_space();
21526            for (i, ord) in order_by.iter().enumerate() {
21527                if i > 0 {
21528                    self.write(", ");
21529                }
21530                self.generate_ordered(ord)?;
21531            }
21532        }
21533        if let Some(null_handling) = f.null_handling {
21534            self.write_space();
21535            match null_handling {
21536                JsonNullHandling::NullOnNull => self.write_keyword("NULL ON NULL"),
21537                JsonNullHandling::AbsentOnNull => self.write_keyword("ABSENT ON NULL"),
21538            }
21539        }
21540        self.write(")");
21541        if let Some(ref filter) = f.filter {
21542            self.write_space();
21543            self.write_keyword("FILTER");
21544            self.write("(");
21545            self.write_keyword("WHERE");
21546            self.write_space();
21547            self.generate_expression(filter)?;
21548            self.write(")");
21549        }
21550        Ok(())
21551    }
21552
21553    fn generate_json_object_agg(&mut self, f: &JsonObjectAggFunc) -> Result<()> {
21554        self.write_keyword("JSON_OBJECTAGG");
21555        self.write("(");
21556        self.generate_expression(&f.key)?;
21557        self.write(": ");
21558        self.generate_expression(&f.value)?;
21559        if let Some(null_handling) = f.null_handling {
21560            self.write_space();
21561            match null_handling {
21562                JsonNullHandling::NullOnNull => self.write_keyword("NULL ON NULL"),
21563                JsonNullHandling::AbsentOnNull => self.write_keyword("ABSENT ON NULL"),
21564            }
21565        }
21566        self.write(")");
21567        if let Some(ref filter) = f.filter {
21568            self.write_space();
21569            self.write_keyword("FILTER");
21570            self.write("(");
21571            self.write_keyword("WHERE");
21572            self.write_space();
21573            self.generate_expression(filter)?;
21574            self.write(")");
21575        }
21576        Ok(())
21577    }
21578
21579    // Type casting/conversion generators
21580
21581    fn generate_convert(&mut self, f: &ConvertFunc) -> Result<()> {
21582        use crate::dialects::DialectType;
21583
21584        // Redshift: CONVERT(type, expr) -> CAST(expr AS type)
21585        if self.config.dialect == Some(DialectType::Redshift) {
21586            self.write_keyword("CAST");
21587            self.write("(");
21588            self.generate_expression(&f.this)?;
21589            self.write_space();
21590            self.write_keyword("AS");
21591            self.write_space();
21592            self.generate_data_type(&f.to)?;
21593            self.write(")");
21594            return Ok(());
21595        }
21596
21597        self.write_keyword("CONVERT");
21598        self.write("(");
21599        self.generate_data_type(&f.to)?;
21600        self.write(", ");
21601        self.generate_expression(&f.this)?;
21602        if let Some(ref style) = f.style {
21603            self.write(", ");
21604            self.generate_expression(style)?;
21605        }
21606        self.write(")");
21607        Ok(())
21608    }
21609
21610    // Additional expression generators
21611
21612    fn generate_lambda(&mut self, f: &LambdaExpr) -> Result<()> {
21613        if f.colon {
21614            // DuckDB syntax: LAMBDA x : expr
21615            self.write_keyword("LAMBDA");
21616            self.write_space();
21617            for (i, param) in f.parameters.iter().enumerate() {
21618                if i > 0 {
21619                    self.write(", ");
21620                }
21621                self.generate_identifier(param)?;
21622            }
21623            self.write(" : ");
21624        } else {
21625            // Standard syntax: x -> expr or (x, y) -> expr
21626            if f.parameters.len() == 1 {
21627                self.generate_identifier(&f.parameters[0])?;
21628            } else {
21629                self.write("(");
21630                for (i, param) in f.parameters.iter().enumerate() {
21631                    if i > 0 {
21632                        self.write(", ");
21633                    }
21634                    self.generate_identifier(param)?;
21635                }
21636                self.write(")");
21637            }
21638            self.write(" -> ");
21639        }
21640        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
21641            if let Expression::Lambda(inner) = &f.body {
21642                self.generate_lambda_with_parenthesized_single_param(inner)?;
21643                return Ok(());
21644            }
21645        }
21646
21647        self.generate_expression(&f.body)
21648    }
21649
21650    fn generate_lambda_with_parenthesized_single_param(&mut self, f: &LambdaExpr) -> Result<()> {
21651        if f.colon {
21652            return self.generate_lambda(f);
21653        }
21654
21655        self.write("(");
21656        for (i, param) in f.parameters.iter().enumerate() {
21657            if i > 0 {
21658                self.write(", ");
21659            }
21660            self.generate_identifier(param)?;
21661        }
21662        self.write(") -> ");
21663        self.generate_expression(&f.body)
21664    }
21665
21666    fn generate_named_argument(&mut self, f: &NamedArgument) -> Result<()> {
21667        self.generate_identifier(&f.name)?;
21668        match f.separator {
21669            NamedArgSeparator::DArrow => self.write(" => "),
21670            NamedArgSeparator::ColonEq => self.write(" := "),
21671            NamedArgSeparator::Eq => self.write(" = "),
21672        }
21673        self.generate_expression(&f.value)
21674    }
21675
21676    fn generate_table_argument(&mut self, f: &TableArgument) -> Result<()> {
21677        self.write_keyword(&f.prefix);
21678        self.write(" ");
21679        self.generate_expression(&f.this)
21680    }
21681
21682    fn generate_parameter(&mut self, f: &Parameter) -> Result<()> {
21683        match f.style {
21684            ParameterStyle::Question => self.write("?"),
21685            ParameterStyle::Dollar => {
21686                self.write("$");
21687                if let Some(idx) = f.index {
21688                    self.write(&idx.to_string());
21689                } else if let Some(ref name) = f.name {
21690                    // Session variable like $x or $query_id
21691                    self.write(name);
21692                }
21693            }
21694            ParameterStyle::DollarBrace => {
21695                // Template variable like ${x} or ${hiveconf:name} (Databricks, Hive)
21696                self.write("${");
21697                if let Some(ref name) = f.name {
21698                    self.write(name);
21699                }
21700                if let Some(ref expr) = f.expression {
21701                    self.write(":");
21702                    self.write(expr);
21703                }
21704                self.write("}");
21705            }
21706            ParameterStyle::Colon => {
21707                self.write(":");
21708                if let Some(idx) = f.index {
21709                    self.write(&idx.to_string());
21710                } else if let Some(ref name) = f.name {
21711                    self.write(name);
21712                }
21713            }
21714            ParameterStyle::At => {
21715                self.write("@");
21716                if let Some(ref name) = f.name {
21717                    if f.string_quoted {
21718                        self.write("'");
21719                        self.write(name);
21720                        self.write("'");
21721                    } else if f.quoted {
21722                        self.write("\"");
21723                        self.write(name);
21724                        self.write("\"");
21725                    } else {
21726                        self.write(name);
21727                    }
21728                }
21729            }
21730            ParameterStyle::DoubleAt => {
21731                self.write("@@");
21732                if let Some(ref name) = f.name {
21733                    self.write(name);
21734                }
21735            }
21736            ParameterStyle::DoubleDollar => {
21737                self.write("$$");
21738                if let Some(ref name) = f.name {
21739                    self.write(name);
21740                }
21741            }
21742            ParameterStyle::Percent => {
21743                if let Some(ref name) = f.name {
21744                    // %(name)s format
21745                    self.write("%(");
21746                    self.write(name);
21747                    self.write(")s");
21748                } else {
21749                    // %s format
21750                    self.write("%s");
21751                }
21752            }
21753            ParameterStyle::Brace => {
21754                // Spark/Databricks widget template variable: {name}
21755                // ClickHouse query parameter may include kind: {name: Type}
21756                self.write("{");
21757                if let Some(ref name) = f.name {
21758                    self.write(name);
21759                }
21760                if let Some(ref expr) = f.expression {
21761                    self.write(": ");
21762                    self.write(expr);
21763                }
21764                self.write("}");
21765            }
21766        }
21767        Ok(())
21768    }
21769
21770    fn generate_placeholder(&mut self, f: &Placeholder) -> Result<()> {
21771        self.write("?");
21772        if let Some(idx) = f.index {
21773            self.write(&idx.to_string());
21774        }
21775        Ok(())
21776    }
21777
21778    fn generate_sql_comment(&mut self, f: &SqlComment) -> Result<()> {
21779        if f.is_block {
21780            self.write("/*");
21781            self.write(&f.text);
21782            self.write("*/");
21783        } else {
21784            self.write("--");
21785            self.write(&f.text);
21786        }
21787        Ok(())
21788    }
21789
21790    // Additional predicate generators
21791
21792    fn generate_similar_to(&mut self, f: &SimilarToExpr) -> Result<()> {
21793        self.generate_expression(&f.this)?;
21794        if f.not {
21795            self.write_space();
21796            self.write_keyword("NOT");
21797        }
21798        self.write_space();
21799        self.write_keyword("SIMILAR TO");
21800        self.write_space();
21801        self.generate_expression(&f.pattern)?;
21802        if let Some(ref escape) = f.escape {
21803            self.write_space();
21804            self.write_keyword("ESCAPE");
21805            self.write_space();
21806            self.generate_expression(escape)?;
21807        }
21808        Ok(())
21809    }
21810
21811    fn generate_quantified(&mut self, name: &str, f: &QuantifiedExpr) -> Result<()> {
21812        self.generate_expression(&f.this)?;
21813        self.write_space();
21814        // Output comparison operator if present
21815        if let Some(op) = &f.op {
21816            match op {
21817                QuantifiedOp::Eq => self.write("="),
21818                QuantifiedOp::Neq => self.write("<>"),
21819                QuantifiedOp::Lt => self.write("<"),
21820                QuantifiedOp::Lte => self.write("<="),
21821                QuantifiedOp::Gt => self.write(">"),
21822                QuantifiedOp::Gte => self.write(">="),
21823            }
21824            self.write_space();
21825        }
21826        self.write_keyword(name);
21827
21828        // If the child is a Subquery, it provides its own parens — output with space
21829        if matches!(&f.subquery, Expression::Subquery(_)) {
21830            self.write_space();
21831            self.generate_expression(&f.subquery)?;
21832        } else {
21833            self.write("(");
21834
21835            let is_statement = matches!(
21836                &f.subquery,
21837                Expression::Select(_)
21838                    | Expression::Union(_)
21839                    | Expression::Intersect(_)
21840                    | Expression::Except(_)
21841            );
21842
21843            if self.config.pretty && is_statement {
21844                self.write_newline();
21845                self.indent_level += 1;
21846                self.write_indent();
21847            }
21848            self.generate_expression(&f.subquery)?;
21849            if self.config.pretty && is_statement {
21850                self.write_newline();
21851                self.indent_level -= 1;
21852                self.write_indent();
21853            }
21854            self.write(")");
21855        }
21856        Ok(())
21857    }
21858
21859    fn generate_overlaps(&mut self, f: &OverlapsExpr) -> Result<()> {
21860        // Check if this is a simple binary form (this OVERLAPS expression)
21861        if let (Some(this), Some(expr)) = (&f.this, &f.expression) {
21862            self.generate_expression(this)?;
21863            self.write_space();
21864            self.write_keyword("OVERLAPS");
21865            self.write_space();
21866            self.generate_expression(expr)?;
21867        } else if let (Some(ls), Some(le), Some(rs), Some(re)) =
21868            (&f.left_start, &f.left_end, &f.right_start, &f.right_end)
21869        {
21870            // Full ANSI form: (a, b) OVERLAPS (c, d)
21871            self.write("(");
21872            self.generate_expression(ls)?;
21873            self.write(", ");
21874            self.generate_expression(le)?;
21875            self.write(")");
21876            self.write_space();
21877            self.write_keyword("OVERLAPS");
21878            self.write_space();
21879            self.write("(");
21880            self.generate_expression(rs)?;
21881            self.write(", ");
21882            self.generate_expression(re)?;
21883            self.write(")");
21884        }
21885        Ok(())
21886    }
21887
21888    // Type conversion generators
21889
21890    fn generate_try_cast(&mut self, cast: &Cast) -> Result<()> {
21891        use crate::dialects::DialectType;
21892
21893        // SingleStore uses !:> syntax for try cast
21894        if matches!(self.config.dialect, Some(DialectType::SingleStore)) {
21895            self.generate_expression(&cast.this)?;
21896            self.write(" !:> ");
21897            self.generate_data_type(&cast.to)?;
21898            return Ok(());
21899        }
21900
21901        // Teradata uses TRYCAST (no underscore)
21902        if matches!(self.config.dialect, Some(DialectType::Teradata)) {
21903            self.write_keyword("TRYCAST");
21904            self.write("(");
21905            self.generate_expression(&cast.this)?;
21906            self.write_space();
21907            self.write_keyword("AS");
21908            self.write_space();
21909            self.generate_data_type(&cast.to)?;
21910            self.write(")");
21911            return Ok(());
21912        }
21913
21914        // Dialects without TRY_CAST: generate as regular CAST
21915        let keyword = if matches!(
21916            self.config.dialect,
21917            Some(DialectType::Hive)
21918                | Some(DialectType::MySQL)
21919                | Some(DialectType::SQLite)
21920                | Some(DialectType::Oracle)
21921                | Some(DialectType::ClickHouse)
21922                | Some(DialectType::Redshift)
21923                | Some(DialectType::PostgreSQL)
21924                | Some(DialectType::StarRocks)
21925                | Some(DialectType::Doris)
21926        ) {
21927            "CAST"
21928        } else {
21929            "TRY_CAST"
21930        };
21931
21932        self.write_keyword(keyword);
21933        self.write("(");
21934        self.generate_expression(&cast.this)?;
21935        self.write_space();
21936        self.write_keyword("AS");
21937        self.write_space();
21938        self.generate_data_type(&cast.to)?;
21939
21940        // Output FORMAT clause if present
21941        if let Some(format) = &cast.format {
21942            self.write_space();
21943            self.write_keyword("FORMAT");
21944            self.write_space();
21945            self.generate_expression(format)?;
21946        }
21947
21948        self.write(")");
21949        Ok(())
21950    }
21951
21952    fn generate_safe_cast(&mut self, cast: &Cast) -> Result<()> {
21953        self.write_keyword("SAFE_CAST");
21954        self.write("(");
21955        self.generate_expression(&cast.this)?;
21956        self.write_space();
21957        self.write_keyword("AS");
21958        self.write_space();
21959        self.generate_data_type(&cast.to)?;
21960
21961        // Output FORMAT clause if present
21962        if let Some(format) = &cast.format {
21963            self.write_space();
21964            self.write_keyword("FORMAT");
21965            self.write_space();
21966            self.generate_expression(format)?;
21967        }
21968
21969        self.write(")");
21970        Ok(())
21971    }
21972
21973    // Array/struct/map access generators
21974
21975    fn generate_subscript(&mut self, s: &Subscript) -> Result<()> {
21976        // Wrap the base expression in parentheses when it uses arrow syntax (->)
21977        // which has lower precedence than bracket subscript ([]).
21978        // E.g., (t.v -> '$.a')[s.x] instead of t.v -> '$.a'[s.x]
21979        let needs_parens = matches!(&s.this, Expression::JsonExtract(ref f) if f.arrow_syntax);
21980        if needs_parens {
21981            self.write("(");
21982        }
21983        self.generate_expression(&s.this)?;
21984        if needs_parens {
21985            self.write(")");
21986        }
21987        self.write("[");
21988        self.generate_expression(&s.index)?;
21989        self.write("]");
21990        Ok(())
21991    }
21992
21993    fn generate_dot_access(&mut self, d: &DotAccess) -> Result<()> {
21994        self.generate_expression(&d.this)?;
21995        // Snowflake uses : (colon) for first-level struct/object field access on CAST/column expressions
21996        // e.g., CAST(col AS OBJECT(fld1 OBJECT(fld2 INT))):fld1.fld2
21997        let use_colon = matches!(self.config.dialect, Some(DialectType::Snowflake))
21998            && matches!(
21999                &d.this,
22000                Expression::Cast(_) | Expression::SafeCast(_) | Expression::TryCast(_)
22001            );
22002        if use_colon {
22003            self.write(":");
22004        } else {
22005            self.write(".");
22006        }
22007        self.generate_identifier(&d.field)
22008    }
22009
22010    fn generate_method_call(&mut self, m: &MethodCall) -> Result<()> {
22011        self.generate_expression(&m.this)?;
22012        self.write(".");
22013        // Method names after a dot should not be quoted based on reserved keywords
22014        // Only quote if explicitly marked as quoted in the AST
22015        if m.method.quoted {
22016            let q = self.config.identifier_quote;
22017            self.write(&format!("{}{}{}", q, m.method.name, q));
22018        } else {
22019            self.write(&m.method.name);
22020        }
22021        self.write("(");
22022        for (i, arg) in m.args.iter().enumerate() {
22023            if i > 0 {
22024                self.write(", ");
22025            }
22026            self.generate_expression(arg)?;
22027        }
22028        self.write(")");
22029        Ok(())
22030    }
22031
22032    fn generate_array_slice(&mut self, s: &ArraySlice) -> Result<()> {
22033        // Check if we need to wrap the inner expression in parentheses
22034        // JSON arrow expressions have lower precedence than array subscript
22035        let needs_parens = matches!(
22036            &s.this,
22037            Expression::JsonExtract(f) if f.arrow_syntax
22038        ) || matches!(
22039            &s.this,
22040            Expression::JsonExtractScalar(f) if f.arrow_syntax
22041        );
22042
22043        if needs_parens {
22044            self.write("(");
22045        }
22046        self.generate_expression(&s.this)?;
22047        if needs_parens {
22048            self.write(")");
22049        }
22050        self.write("[");
22051        if let Some(start) = &s.start {
22052            self.generate_expression(start)?;
22053        }
22054        self.write(":");
22055        if let Some(end) = &s.end {
22056            self.generate_expression(end)?;
22057        }
22058        self.write("]");
22059        Ok(())
22060    }
22061
22062    fn generate_binary_op(&mut self, op: &BinaryOp, operator: &str) -> Result<()> {
22063        // Generate left expression, but skip trailing comments if they're already in left_comments
22064        // to avoid duplication (comments are captured as both expr.trailing_comments
22065        // and BinaryOp.left_comments during parsing)
22066        match &op.left {
22067            Expression::Column(col) => {
22068                // Generate column with trailing comments but skip them if they're
22069                // already captured in BinaryOp.left_comments to avoid duplication
22070                if let Some(table) = &col.table {
22071                    self.generate_identifier(table)?;
22072                    self.write(".");
22073                }
22074                self.generate_identifier(&col.name)?;
22075                // Oracle-style join marker (+)
22076                if col.join_mark && self.config.supports_column_join_marks {
22077                    self.write(" (+)");
22078                }
22079                // Output column trailing comments if they're not already in left_comments
22080                if op.left_comments.is_empty() {
22081                    for comment in &col.trailing_comments {
22082                        self.write_space();
22083                        self.write_formatted_comment(comment);
22084                    }
22085                }
22086            }
22087            Expression::Add(inner_op)
22088            | Expression::Sub(inner_op)
22089            | Expression::Mul(inner_op)
22090            | Expression::Div(inner_op)
22091            | Expression::Concat(inner_op) => {
22092                // Generate binary op without its trailing comments
22093                self.generate_binary_op_no_trailing(inner_op, match &op.left {
22094                    Expression::Add(_) => "+",
22095                    Expression::Sub(_) => "-",
22096                    Expression::Mul(_) => "*",
22097                    Expression::Div(_) => "/",
22098                    Expression::Concat(_) => "||",
22099                    _ => unreachable!("op.left variant already matched by outer arm as Add/Sub/Mul/Div/Concat"),
22100                })?;
22101            }
22102            _ => {
22103                self.generate_expression(&op.left)?;
22104            }
22105        }
22106        // Output comments after left operand
22107        for comment in &op.left_comments {
22108            self.write_space();
22109            self.write_formatted_comment(comment);
22110        }
22111        if self.config.pretty
22112            && matches!(self.config.dialect, Some(DialectType::Snowflake))
22113            && (operator == "AND" || operator == "OR")
22114        {
22115            self.write_newline();
22116            self.write_indent();
22117            self.write_keyword(operator);
22118        } else {
22119            self.write_space();
22120            if operator.chars().all(|c| c.is_alphabetic()) {
22121                self.write_keyword(operator);
22122            } else {
22123                self.write(operator);
22124            }
22125        }
22126        // Output comments after operator (before right operand)
22127        for comment in &op.operator_comments {
22128            self.write_space();
22129            self.write_formatted_comment(comment);
22130        }
22131        self.write_space();
22132        self.generate_expression(&op.right)?;
22133        // Output trailing comments after right operand
22134        for comment in &op.trailing_comments {
22135            self.write_space();
22136            self.write_formatted_comment(comment);
22137        }
22138        Ok(())
22139    }
22140
22141    fn generate_connector_op(&mut self, op: &BinaryOp, connector: ConnectorOperator) -> Result<()> {
22142        let keyword = connector.keyword();
22143        let Some(terms) = self.flatten_connector_terms(op, connector) else {
22144            return self.generate_binary_op(op, keyword);
22145        };
22146
22147        let wrap_clickhouse_or_term = |generator: &mut Self, term: &Expression| -> Result<()> {
22148            let should_wrap = matches!(connector, ConnectorOperator::Or)
22149                && matches!(generator.config.dialect, Some(DialectType::ClickHouse))
22150                && matches!(
22151                    generator.config.source_dialect,
22152                    Some(DialectType::ClickHouse)
22153                )
22154                && matches!(term, Expression::And(_));
22155            if should_wrap {
22156                generator.write("(");
22157                generator.generate_expression(term)?;
22158                generator.write(")");
22159            } else {
22160                generator.generate_expression(term)?;
22161            }
22162            Ok(())
22163        };
22164
22165        wrap_clickhouse_or_term(self, terms[0])?;
22166        for term in terms.iter().skip(1) {
22167            if self.config.pretty && matches!(self.config.dialect, Some(DialectType::Snowflake)) {
22168                self.write_newline();
22169                self.write_indent();
22170                self.write_keyword(keyword);
22171            } else {
22172                self.write_space();
22173                self.write_keyword(keyword);
22174            }
22175            self.write_space();
22176            wrap_clickhouse_or_term(self, term)?;
22177        }
22178
22179        Ok(())
22180    }
22181
22182    fn flatten_connector_terms<'a>(
22183        &self,
22184        root: &'a BinaryOp,
22185        connector: ConnectorOperator,
22186    ) -> Option<Vec<&'a Expression>> {
22187        if !root.left_comments.is_empty()
22188            || !root.operator_comments.is_empty()
22189            || !root.trailing_comments.is_empty()
22190        {
22191            return None;
22192        }
22193
22194        let mut terms = Vec::new();
22195        let mut stack: Vec<&Expression> = vec![&root.right, &root.left];
22196
22197        while let Some(expr) = stack.pop() {
22198            match (connector, expr) {
22199                (ConnectorOperator::And, Expression::And(inner))
22200                    if inner.left_comments.is_empty()
22201                        && inner.operator_comments.is_empty()
22202                        && inner.trailing_comments.is_empty() =>
22203                {
22204                    stack.push(&inner.right);
22205                    stack.push(&inner.left);
22206                }
22207                (ConnectorOperator::Or, Expression::Or(inner))
22208                    if inner.left_comments.is_empty()
22209                        && inner.operator_comments.is_empty()
22210                        && inner.trailing_comments.is_empty() =>
22211                {
22212                    stack.push(&inner.right);
22213                    stack.push(&inner.left);
22214                }
22215                _ => terms.push(expr),
22216            }
22217        }
22218
22219        if terms.len() > 1 {
22220            Some(terms)
22221        } else {
22222            None
22223        }
22224    }
22225
22226    /// Generate LIKE/ILIKE operation with optional ESCAPE clause
22227    fn generate_like_op(&mut self, op: &LikeOp, operator: &str) -> Result<()> {
22228        self.generate_expression(&op.left)?;
22229        self.write_space();
22230        // Drill backtick-quotes ILIKE
22231        if operator == "ILIKE" && matches!(self.config.dialect, Some(DialectType::Drill)) {
22232            self.write("`ILIKE`");
22233        } else {
22234            self.write_keyword(operator);
22235        }
22236        if let Some(quantifier) = &op.quantifier {
22237            self.write_space();
22238            self.write_keyword(quantifier);
22239            // Match Python sqlglot behavior:
22240            // ANY + Paren (single value): no space → ILIKE ANY('%a%')
22241            // ANY + Tuple (multiple values): space → LIKE ANY ('a', 'b')
22242            // ALL + anything: always space → LIKE ALL ('%a%'), LIKE ALL ('a', 'b')
22243            let is_any =
22244                quantifier.eq_ignore_ascii_case("ANY") || quantifier.eq_ignore_ascii_case("SOME");
22245            if !(is_any && matches!(&op.right, Expression::Paren(_))) {
22246                self.write_space();
22247            }
22248        } else {
22249            self.write_space();
22250        }
22251        self.generate_expression(&op.right)?;
22252        if let Some(escape) = &op.escape {
22253            self.write_space();
22254            self.write_keyword("ESCAPE");
22255            self.write_space();
22256            self.generate_expression(escape)?;
22257        }
22258        Ok(())
22259    }
22260
22261    /// Generate null-safe equality
22262    /// MySQL uses <=>, other dialects use IS NOT DISTINCT FROM
22263    fn generate_null_safe_eq(&mut self, op: &BinaryOp) -> Result<()> {
22264        use crate::dialects::DialectType;
22265        self.generate_expression(&op.left)?;
22266        self.write_space();
22267        if matches!(self.config.dialect, Some(DialectType::MySQL)) {
22268            self.write("<=>");
22269        } else {
22270            self.write_keyword("IS NOT DISTINCT FROM");
22271        }
22272        self.write_space();
22273        self.generate_expression(&op.right)?;
22274        Ok(())
22275    }
22276
22277    /// Generate IS DISTINCT FROM (null-safe inequality)
22278    fn generate_null_safe_neq(&mut self, op: &BinaryOp) -> Result<()> {
22279        self.generate_expression(&op.left)?;
22280        self.write_space();
22281        self.write_keyword("IS DISTINCT FROM");
22282        self.write_space();
22283        self.generate_expression(&op.right)?;
22284        Ok(())
22285    }
22286
22287    /// Generate binary op without trailing comments (used when nested inside another binary op)
22288    fn generate_binary_op_no_trailing(&mut self, op: &BinaryOp, operator: &str) -> Result<()> {
22289        // Generate left expression, but skip trailing comments
22290        match &op.left {
22291            Expression::Column(col) => {
22292                if let Some(table) = &col.table {
22293                    self.generate_identifier(table)?;
22294                    self.write(".");
22295                }
22296                self.generate_identifier(&col.name)?;
22297                // Oracle-style join marker (+)
22298                if col.join_mark && self.config.supports_column_join_marks {
22299                    self.write(" (+)");
22300                }
22301            }
22302            Expression::Add(inner_op)
22303            | Expression::Sub(inner_op)
22304            | Expression::Mul(inner_op)
22305            | Expression::Div(inner_op)
22306            | Expression::Concat(inner_op) => {
22307                self.generate_binary_op_no_trailing(inner_op, match &op.left {
22308                    Expression::Add(_) => "+",
22309                    Expression::Sub(_) => "-",
22310                    Expression::Mul(_) => "*",
22311                    Expression::Div(_) => "/",
22312                    Expression::Concat(_) => "||",
22313                    _ => unreachable!("op.left variant already matched by outer arm as Add/Sub/Mul/Div/Concat"),
22314                })?;
22315            }
22316            _ => {
22317                self.generate_expression(&op.left)?;
22318            }
22319        }
22320        // Output left_comments
22321        for comment in &op.left_comments {
22322            self.write_space();
22323            self.write_formatted_comment(comment);
22324        }
22325        self.write_space();
22326        if operator.chars().all(|c| c.is_alphabetic()) {
22327            self.write_keyword(operator);
22328        } else {
22329            self.write(operator);
22330        }
22331        // Output operator_comments
22332        for comment in &op.operator_comments {
22333            self.write_space();
22334            self.write_formatted_comment(comment);
22335        }
22336        self.write_space();
22337        // Generate right expression, but skip trailing comments if it's a Column
22338        // (the parent's left_comments will output them)
22339        match &op.right {
22340            Expression::Column(col) => {
22341                if let Some(table) = &col.table {
22342                    self.generate_identifier(table)?;
22343                    self.write(".");
22344                }
22345                self.generate_identifier(&col.name)?;
22346                // Oracle-style join marker (+)
22347                if col.join_mark && self.config.supports_column_join_marks {
22348                    self.write(" (+)");
22349                }
22350            }
22351            _ => {
22352                self.generate_expression(&op.right)?;
22353            }
22354        }
22355        // Skip trailing_comments - parent will handle them via its left_comments
22356        Ok(())
22357    }
22358
22359    fn generate_unary_op(&mut self, op: &UnaryOp, operator: &str) -> Result<()> {
22360        if operator.chars().all(|c| c.is_alphabetic()) {
22361            self.write_keyword(operator);
22362            self.write_space();
22363        } else {
22364            self.write(operator);
22365            // Add space between consecutive unary operators (e.g., "- -5" not "--5")
22366            if matches!(&op.this, Expression::Neg(_) | Expression::BitwiseNot(_)) {
22367                self.write_space();
22368            }
22369        }
22370        self.generate_expression(&op.this)
22371    }
22372
22373    fn generate_in(&mut self, in_expr: &In) -> Result<()> {
22374        // Generic mode supports two styles for negated IN:
22375        // - Prefix: NOT a IN (...)
22376        // - Infix:  a NOT IN (...)
22377        let is_generic =
22378            self.config.dialect.is_none() || self.config.dialect == Some(DialectType::Generic);
22379        let use_prefix_not =
22380            in_expr.not && is_generic && self.config.not_in_style == NotInStyle::Prefix;
22381        if use_prefix_not {
22382            self.write_keyword("NOT");
22383            self.write_space();
22384        }
22385        self.generate_expression(&in_expr.this)?;
22386        if in_expr.global {
22387            self.write_space();
22388            self.write_keyword("GLOBAL");
22389        }
22390        if in_expr.not && !use_prefix_not {
22391            self.write_space();
22392            self.write_keyword("NOT");
22393        }
22394        self.write_space();
22395        self.write_keyword("IN");
22396
22397        // BigQuery: IN UNNEST(expr)
22398        if let Some(unnest_expr) = &in_expr.unnest {
22399            self.write_space();
22400            self.write_keyword("UNNEST");
22401            self.write("(");
22402            self.generate_expression(unnest_expr)?;
22403            self.write(")");
22404            return Ok(());
22405        }
22406
22407        if let Some(query) = &in_expr.query {
22408            // Check if this is a bare identifier (PIVOT FOR foo IN y_enum)
22409            // vs a subquery (col IN (SELECT ...))
22410            let is_bare = in_expr.expressions.is_empty()
22411                && !matches!(
22412                    query,
22413                    Expression::Select(_)
22414                        | Expression::Union(_)
22415                        | Expression::Intersect(_)
22416                        | Expression::Except(_)
22417                        | Expression::Subquery(_)
22418                );
22419            if is_bare {
22420                // Bare identifier: no parentheses
22421                self.write_space();
22422                self.generate_expression(query)?;
22423            } else {
22424                // Subquery: with parentheses
22425                self.write(" (");
22426                let is_statement = matches!(
22427                    query,
22428                    Expression::Select(_)
22429                        | Expression::Union(_)
22430                        | Expression::Intersect(_)
22431                        | Expression::Except(_)
22432                        | Expression::Subquery(_)
22433                );
22434                if self.config.pretty && is_statement {
22435                    self.write_newline();
22436                    self.indent_level += 1;
22437                    self.write_indent();
22438                }
22439                self.generate_expression(query)?;
22440                if self.config.pretty && is_statement {
22441                    self.write_newline();
22442                    self.indent_level -= 1;
22443                    self.write_indent();
22444                }
22445                self.write(")");
22446            }
22447        } else {
22448            // DuckDB: IN without parentheses for single expression that is NOT a literal
22449            // (array/list membership like 'red' IN tbl.flags)
22450            // ClickHouse: IN without parentheses for single non-array expressions
22451            let is_duckdb = matches!(
22452                self.config.dialect,
22453                Some(crate::dialects::DialectType::DuckDB)
22454            );
22455            let is_clickhouse = matches!(
22456                self.config.dialect,
22457                Some(crate::dialects::DialectType::ClickHouse)
22458            );
22459            let single_expr = in_expr.expressions.len() == 1;
22460            if is_clickhouse && single_expr {
22461                if let Expression::Array(arr) = &in_expr.expressions[0] {
22462                    // ClickHouse: x IN [1, 2] -> x IN (1, 2)
22463                    self.write(" (");
22464                    for (i, expr) in arr.expressions.iter().enumerate() {
22465                        if i > 0 {
22466                            self.write(", ");
22467                        }
22468                        self.generate_expression(expr)?;
22469                    }
22470                    self.write(")");
22471                } else if in_expr.is_field {
22472                    self.write_space();
22473                    self.generate_expression(&in_expr.expressions[0])?;
22474                } else {
22475                    self.write(" (");
22476                    self.generate_expression(&in_expr.expressions[0])?;
22477                    self.write(")");
22478                }
22479            } else {
22480                let is_bare_ref = single_expr
22481                    && matches!(
22482                        &in_expr.expressions[0],
22483                        Expression::Column(_) | Expression::Identifier(_) | Expression::Dot(_)
22484                    );
22485                if (is_duckdb && is_bare_ref) || (in_expr.is_field && single_expr) {
22486                    // Bare field reference (no parens in source): IN identifier
22487                    // Also DuckDB: IN without parentheses for array/list membership
22488                    self.write_space();
22489                    self.generate_expression(&in_expr.expressions[0])?;
22490                } else {
22491                    // Standard IN (list)
22492                    self.write(" (");
22493                    for (i, expr) in in_expr.expressions.iter().enumerate() {
22494                        if i > 0 {
22495                            self.write(", ");
22496                        }
22497                        self.generate_expression(expr)?;
22498                    }
22499                    self.write(")");
22500                }
22501            }
22502        }
22503
22504        Ok(())
22505    }
22506
22507    fn generate_between(&mut self, between: &Between) -> Result<()> {
22508        // Generic mode: normalize NOT BETWEEN to prefix form: NOT a BETWEEN b AND c
22509        let use_prefix_not = between.not
22510            && (self.config.dialect.is_none() || self.config.dialect == Some(DialectType::Generic));
22511        if use_prefix_not {
22512            self.write_keyword("NOT");
22513            self.write_space();
22514        }
22515        self.generate_expression(&between.this)?;
22516        if between.not && !use_prefix_not {
22517            self.write_space();
22518            self.write_keyword("NOT");
22519        }
22520        self.write_space();
22521        self.write_keyword("BETWEEN");
22522        // Emit SYMMETRIC/ASYMMETRIC if present
22523        if let Some(sym) = between.symmetric {
22524            if sym {
22525                self.write(" SYMMETRIC");
22526            } else {
22527                self.write(" ASYMMETRIC");
22528            }
22529        }
22530        self.write_space();
22531        self.generate_expression(&between.low)?;
22532        self.write_space();
22533        self.write_keyword("AND");
22534        self.write_space();
22535        self.generate_expression(&between.high)
22536    }
22537
22538    fn generate_is_null(&mut self, is_null: &IsNull) -> Result<()> {
22539        // Generic mode: normalize IS NOT NULL to prefix form: NOT x IS NULL
22540        let use_prefix_not = is_null.not
22541            && (self.config.dialect.is_none()
22542                || self.config.dialect == Some(DialectType::Generic)
22543                || is_null.postfix_form);
22544        if use_prefix_not {
22545            // NOT x IS NULL (generic normalization and NOTNULL postfix form)
22546            self.write_keyword("NOT");
22547            self.write_space();
22548            self.generate_expression(&is_null.this)?;
22549            self.write_space();
22550            self.write_keyword("IS");
22551            self.write_space();
22552            self.write_keyword("NULL");
22553        } else {
22554            self.generate_expression(&is_null.this)?;
22555            self.write_space();
22556            self.write_keyword("IS");
22557            if is_null.not {
22558                self.write_space();
22559                self.write_keyword("NOT");
22560            }
22561            self.write_space();
22562            self.write_keyword("NULL");
22563        }
22564        Ok(())
22565    }
22566
22567    fn generate_is_true(&mut self, is_true: &IsTrueFalse) -> Result<()> {
22568        self.generate_expression(&is_true.this)?;
22569        self.write_space();
22570        self.write_keyword("IS");
22571        if is_true.not {
22572            self.write_space();
22573            self.write_keyword("NOT");
22574        }
22575        self.write_space();
22576        self.write_keyword("TRUE");
22577        Ok(())
22578    }
22579
22580    fn generate_is_false(&mut self, is_false: &IsTrueFalse) -> Result<()> {
22581        self.generate_expression(&is_false.this)?;
22582        self.write_space();
22583        self.write_keyword("IS");
22584        if is_false.not {
22585            self.write_space();
22586            self.write_keyword("NOT");
22587        }
22588        self.write_space();
22589        self.write_keyword("FALSE");
22590        Ok(())
22591    }
22592
22593    fn generate_is_json(&mut self, is_json: &IsJson) -> Result<()> {
22594        self.generate_expression(&is_json.this)?;
22595        self.write_space();
22596        self.write_keyword("IS");
22597        if is_json.negated {
22598            self.write_space();
22599            self.write_keyword("NOT");
22600        }
22601        self.write_space();
22602        self.write_keyword("JSON");
22603
22604        // Output JSON type if specified (VALUE, SCALAR, OBJECT, ARRAY)
22605        if let Some(ref json_type) = is_json.json_type {
22606            self.write_space();
22607            self.write_keyword(json_type);
22608        }
22609
22610        // Output key uniqueness constraint if specified
22611        match &is_json.unique_keys {
22612            Some(JsonUniqueKeys::With) => {
22613                self.write_space();
22614                self.write_keyword("WITH UNIQUE KEYS");
22615            }
22616            Some(JsonUniqueKeys::Without) => {
22617                self.write_space();
22618                self.write_keyword("WITHOUT UNIQUE KEYS");
22619            }
22620            Some(JsonUniqueKeys::Shorthand) => {
22621                self.write_space();
22622                self.write_keyword("UNIQUE KEYS");
22623            }
22624            None => {}
22625        }
22626
22627        Ok(())
22628    }
22629
22630    fn generate_is(&mut self, is_expr: &BinaryOp) -> Result<()> {
22631        self.generate_expression(&is_expr.left)?;
22632        self.write_space();
22633        self.write_keyword("IS");
22634        self.write_space();
22635        self.generate_expression(&is_expr.right)
22636    }
22637
22638    fn generate_exists(&mut self, exists: &Exists) -> Result<()> {
22639        if exists.not {
22640            self.write_keyword("NOT");
22641            self.write_space();
22642        }
22643        self.write_keyword("EXISTS");
22644        self.write("(");
22645        let is_statement = matches!(
22646            &exists.this,
22647            Expression::Select(_)
22648                | Expression::Union(_)
22649                | Expression::Intersect(_)
22650                | Expression::Except(_)
22651        );
22652        if self.config.pretty && is_statement {
22653            self.write_newline();
22654            self.indent_level += 1;
22655            self.write_indent();
22656            self.generate_expression(&exists.this)?;
22657            self.write_newline();
22658            self.indent_level -= 1;
22659            self.write_indent();
22660            self.write(")");
22661        } else {
22662            self.generate_expression(&exists.this)?;
22663            self.write(")");
22664        }
22665        Ok(())
22666    }
22667
22668    fn generate_member_of(&mut self, op: &BinaryOp) -> Result<()> {
22669        self.generate_expression(&op.left)?;
22670        self.write_space();
22671        self.write_keyword("MEMBER OF");
22672        self.write("(");
22673        self.generate_expression(&op.right)?;
22674        self.write(")");
22675        Ok(())
22676    }
22677
22678    fn generate_subquery(&mut self, subquery: &Subquery) -> Result<()> {
22679        if subquery.lateral {
22680            self.write_keyword("LATERAL");
22681            self.write_space();
22682        }
22683
22684        // If the inner expression is a Paren wrapping a statement, don't add extra parentheses
22685        // This handles cases like ((SELECT 1)) LIMIT 1 where we wrap Paren in Subquery
22686        // to carry the LIMIT modifier without adding more parens
22687        let skip_outer_parens = if let Expression::Paren(ref p) = &subquery.this {
22688            matches!(
22689                &p.this,
22690                Expression::Select(_)
22691                    | Expression::Union(_)
22692                    | Expression::Intersect(_)
22693                    | Expression::Except(_)
22694                    | Expression::Subquery(_)
22695            )
22696        } else {
22697            false
22698        };
22699
22700        // Check if inner expression is a statement for pretty formatting
22701        let is_statement = matches!(
22702            &subquery.this,
22703            Expression::Select(_)
22704                | Expression::Union(_)
22705                | Expression::Intersect(_)
22706                | Expression::Except(_)
22707                | Expression::Merge(_)
22708        );
22709
22710        if !skip_outer_parens {
22711            self.write("(");
22712            if self.config.pretty && is_statement {
22713                self.write_newline();
22714                self.indent_level += 1;
22715                self.write_indent();
22716            }
22717        }
22718        self.generate_expression(&subquery.this)?;
22719
22720        // Generate ORDER BY, LIMIT, OFFSET based on modifiers_inside flag
22721        if subquery.modifiers_inside {
22722            // Generate modifiers INSIDE the parentheses: (SELECT ... LIMIT 1)
22723            if let Some(order_by) = &subquery.order_by {
22724                self.write_space();
22725                self.write_keyword("ORDER BY");
22726                self.write_space();
22727                for (i, ord) in order_by.expressions.iter().enumerate() {
22728                    if i > 0 {
22729                        self.write(", ");
22730                    }
22731                    self.generate_ordered(ord)?;
22732                }
22733            }
22734
22735            if let Some(limit) = &subquery.limit {
22736                self.write_space();
22737                self.write_keyword("LIMIT");
22738                self.write_space();
22739                self.generate_expression(&limit.this)?;
22740                if limit.percent {
22741                    self.write_space();
22742                    self.write_keyword("PERCENT");
22743                }
22744            }
22745
22746            if let Some(offset) = &subquery.offset {
22747                self.write_space();
22748                self.write_keyword("OFFSET");
22749                self.write_space();
22750                self.generate_expression(&offset.this)?;
22751            }
22752        }
22753
22754        if !skip_outer_parens {
22755            if self.config.pretty && is_statement {
22756                self.write_newline();
22757                self.indent_level -= 1;
22758                self.write_indent();
22759            }
22760            self.write(")");
22761        }
22762
22763        // Generate modifiers OUTSIDE the parentheses: (SELECT ...) LIMIT 1
22764        if !subquery.modifiers_inside {
22765            if let Some(order_by) = &subquery.order_by {
22766                self.write_space();
22767                self.write_keyword("ORDER BY");
22768                self.write_space();
22769                for (i, ord) in order_by.expressions.iter().enumerate() {
22770                    if i > 0 {
22771                        self.write(", ");
22772                    }
22773                    self.generate_ordered(ord)?;
22774                }
22775            }
22776
22777            if let Some(limit) = &subquery.limit {
22778                self.write_space();
22779                self.write_keyword("LIMIT");
22780                self.write_space();
22781                self.generate_expression(&limit.this)?;
22782                if limit.percent {
22783                    self.write_space();
22784                    self.write_keyword("PERCENT");
22785                }
22786            }
22787
22788            if let Some(offset) = &subquery.offset {
22789                self.write_space();
22790                self.write_keyword("OFFSET");
22791                self.write_space();
22792                self.generate_expression(&offset.this)?;
22793            }
22794
22795            // Generate DISTRIBUTE BY (Hive/Spark)
22796            if let Some(distribute_by) = &subquery.distribute_by {
22797                self.write_space();
22798                self.write_keyword("DISTRIBUTE BY");
22799                self.write_space();
22800                for (i, expr) in distribute_by.expressions.iter().enumerate() {
22801                    if i > 0 {
22802                        self.write(", ");
22803                    }
22804                    self.generate_expression(expr)?;
22805                }
22806            }
22807
22808            // Generate SORT BY (Hive/Spark)
22809            if let Some(sort_by) = &subquery.sort_by {
22810                self.write_space();
22811                self.write_keyword("SORT BY");
22812                self.write_space();
22813                for (i, ord) in sort_by.expressions.iter().enumerate() {
22814                    if i > 0 {
22815                        self.write(", ");
22816                    }
22817                    self.generate_ordered(ord)?;
22818                }
22819            }
22820
22821            // Generate CLUSTER BY (Hive/Spark)
22822            if let Some(cluster_by) = &subquery.cluster_by {
22823                self.write_space();
22824                self.write_keyword("CLUSTER BY");
22825                self.write_space();
22826                for (i, ord) in cluster_by.expressions.iter().enumerate() {
22827                    if i > 0 {
22828                        self.write(", ");
22829                    }
22830                    self.generate_ordered(ord)?;
22831                }
22832            }
22833        }
22834
22835        if let Some(alias) = &subquery.alias {
22836            self.write_space();
22837            let skip_as = matches!(self.config.dialect, Some(DialectType::Oracle))
22838                || (matches!(self.config.dialect, Some(DialectType::ClickHouse))
22839                    && !subquery.alias_explicit_as);
22840            if !skip_as {
22841                if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
22842                    self.write(subquery.alias_keyword.as_deref().unwrap_or("AS"));
22843                } else {
22844                    self.write_keyword("AS");
22845                }
22846                self.write_space();
22847            }
22848            self.generate_identifier(alias)?;
22849            if !subquery.column_aliases.is_empty() {
22850                self.write("(");
22851                for (i, col) in subquery.column_aliases.iter().enumerate() {
22852                    if i > 0 {
22853                        self.write(", ");
22854                    }
22855                    self.generate_identifier(col)?;
22856                }
22857                self.write(")");
22858            }
22859        }
22860        // Output trailing comments
22861        for comment in &subquery.trailing_comments {
22862            self.write(" ");
22863            self.write_formatted_comment(comment);
22864        }
22865        Ok(())
22866    }
22867
22868    fn generate_pivot(&mut self, pivot: &Pivot) -> Result<()> {
22869        // Generate WITH clause if present
22870        if let Some(ref with) = pivot.with {
22871            self.generate_with(with)?;
22872            self.write_space();
22873        }
22874
22875        let direction = if pivot.unpivot { "UNPIVOT" } else { "PIVOT" };
22876
22877        // Check for Redshift UNPIVOT in FROM clause:
22878        // UNPIVOT expr [AS val AT attr]
22879        // This is when unpivot=true, expressions is empty, fields is empty, and this is not Null
22880        let is_redshift_unpivot = pivot.unpivot
22881            && pivot.expressions.is_empty()
22882            && pivot.fields.is_empty()
22883            && pivot.using.is_empty()
22884            && pivot.into.is_none()
22885            && !matches!(&pivot.this, Expression::Null(_));
22886
22887        if is_redshift_unpivot {
22888            // Redshift UNPIVOT: UNPIVOT expr [AS alias]
22889            self.write_keyword("UNPIVOT");
22890            self.write_space();
22891            self.generate_expression(&pivot.this)?;
22892            // Alias - for Redshift it can be "val AT attr" format
22893            if let Some(alias) = &pivot.alias {
22894                self.write_space();
22895                self.write_keyword("AS");
22896                self.write_space();
22897                // The alias might contain " AT " for the attr part
22898                self.write(&alias.name);
22899            }
22900            return Ok(());
22901        }
22902
22903        // Check if this is a DuckDB simplified pivot (has `using` or `into`, or no `fields`)
22904        let is_simplified = !pivot.using.is_empty()
22905            || pivot.into.is_some()
22906            || (pivot.fields.is_empty()
22907                && !pivot.expressions.is_empty()
22908                && !matches!(&pivot.this, Expression::Null(_)));
22909
22910        if is_simplified {
22911            // DuckDB simplified syntax:
22912            //   PIVOT table ON cols [IN (...)] USING agg [AS alias], ... [GROUP BY ...]
22913            //   UNPIVOT table ON cols INTO NAME col VALUE col
22914            self.write_keyword(direction);
22915            self.write_space();
22916            self.generate_expression(&pivot.this)?;
22917
22918            if !pivot.expressions.is_empty() {
22919                self.write_space();
22920                self.write_keyword("ON");
22921                self.write_space();
22922                for (i, expr) in pivot.expressions.iter().enumerate() {
22923                    if i > 0 {
22924                        self.write(", ");
22925                    }
22926                    self.generate_expression(expr)?;
22927                }
22928            }
22929
22930            // INTO (for UNPIVOT)
22931            if let Some(into) = &pivot.into {
22932                self.write_space();
22933                self.write_keyword("INTO");
22934                self.write_space();
22935                self.generate_expression(into)?;
22936            }
22937
22938            // USING (for PIVOT)
22939            if !pivot.using.is_empty() {
22940                self.write_space();
22941                self.write_keyword("USING");
22942                self.write_space();
22943                for (i, expr) in pivot.using.iter().enumerate() {
22944                    if i > 0 {
22945                        self.write(", ");
22946                    }
22947                    self.generate_expression(expr)?;
22948                }
22949            }
22950
22951            // GROUP BY
22952            if let Some(group) = &pivot.group {
22953                self.write_space();
22954                self.generate_expression(group)?;
22955            }
22956        } else {
22957            // Standard syntax:
22958            //   table PIVOT(agg [AS alias], ... FOR col IN (val [AS alias], ...) [GROUP BY ...])
22959            //   table UNPIVOT(value_col FOR name_col IN (col1, col2, ...))
22960            // Only output the table expression if it's not a Null (null is used when PIVOT comes after JOIN ON)
22961            if !matches!(&pivot.this, Expression::Null(_)) {
22962                self.generate_expression(&pivot.this)?;
22963                self.write_space();
22964            }
22965            self.write_keyword(direction);
22966            self.write("(");
22967
22968            // Aggregation expressions
22969            for (i, expr) in pivot.expressions.iter().enumerate() {
22970                if i > 0 {
22971                    self.write(", ");
22972                }
22973                self.generate_expression(expr)?;
22974            }
22975
22976            // FOR...IN fields
22977            if !pivot.fields.is_empty() {
22978                if !pivot.expressions.is_empty() {
22979                    self.write_space();
22980                }
22981                self.write_keyword("FOR");
22982                self.write_space();
22983                for (i, field) in pivot.fields.iter().enumerate() {
22984                    if i > 0 {
22985                        self.write_space();
22986                    }
22987                    // field is an In expression: column IN (values)
22988                    self.generate_expression(field)?;
22989                }
22990            }
22991
22992            // DEFAULT ON NULL
22993            if let Some(default_val) = &pivot.default_on_null {
22994                self.write_space();
22995                self.write_keyword("DEFAULT ON NULL");
22996                self.write(" (");
22997                self.generate_expression(default_val)?;
22998                self.write(")");
22999            }
23000
23001            // GROUP BY inside PIVOT parens
23002            if let Some(group) = &pivot.group {
23003                self.write_space();
23004                self.generate_expression(group)?;
23005            }
23006
23007            self.write(")");
23008        }
23009
23010        // Alias
23011        if let Some(alias) = &pivot.alias {
23012            self.write_space();
23013            self.write_keyword("AS");
23014            self.write_space();
23015            self.generate_identifier(alias)?;
23016        }
23017
23018        Ok(())
23019    }
23020
23021    fn generate_unpivot(&mut self, unpivot: &Unpivot) -> Result<()> {
23022        self.generate_expression(&unpivot.this)?;
23023        self.write_space();
23024        self.write_keyword("UNPIVOT");
23025        // Output INCLUDE NULLS or EXCLUDE NULLS if specified
23026        if let Some(include) = unpivot.include_nulls {
23027            self.write_space();
23028            if include {
23029                self.write_keyword("INCLUDE NULLS");
23030            } else {
23031                self.write_keyword("EXCLUDE NULLS");
23032            }
23033            self.write_space();
23034        }
23035        self.write("(");
23036        if unpivot.value_column_parenthesized {
23037            self.write("(");
23038        }
23039        self.generate_identifier(&unpivot.value_column)?;
23040        // Output additional value columns if present
23041        for extra_col in &unpivot.extra_value_columns {
23042            self.write(", ");
23043            self.generate_identifier(extra_col)?;
23044        }
23045        if unpivot.value_column_parenthesized {
23046            self.write(")");
23047        }
23048        self.write_space();
23049        self.write_keyword("FOR");
23050        self.write_space();
23051        self.generate_identifier(&unpivot.name_column)?;
23052        self.write_space();
23053        self.write_keyword("IN");
23054        self.write(" (");
23055        for (i, col) in unpivot.columns.iter().enumerate() {
23056            if i > 0 {
23057                self.write(", ");
23058            }
23059            self.generate_expression(col)?;
23060        }
23061        self.write("))");
23062        if let Some(alias) = &unpivot.alias {
23063            self.write_space();
23064            self.write_keyword("AS");
23065            self.write_space();
23066            self.generate_identifier(alias)?;
23067        }
23068        Ok(())
23069    }
23070
23071    fn generate_values(&mut self, values: &Values) -> Result<()> {
23072        self.write_keyword("VALUES");
23073        for (i, row) in values.expressions.iter().enumerate() {
23074            if i > 0 {
23075                self.write(",");
23076            }
23077            self.write(" (");
23078            for (j, expr) in row.expressions.iter().enumerate() {
23079                if j > 0 {
23080                    self.write(", ");
23081                }
23082                self.generate_expression(expr)?;
23083            }
23084            self.write(")");
23085        }
23086        if let Some(alias) = &values.alias {
23087            self.write_space();
23088            self.write_keyword("AS");
23089            self.write_space();
23090            self.generate_identifier(alias)?;
23091            if !values.column_aliases.is_empty() {
23092                self.write("(");
23093                for (i, col) in values.column_aliases.iter().enumerate() {
23094                    if i > 0 {
23095                        self.write(", ");
23096                    }
23097                    self.generate_identifier(col)?;
23098                }
23099                self.write(")");
23100            }
23101        }
23102        Ok(())
23103    }
23104
23105    fn generate_array(&mut self, arr: &Array) -> Result<()> {
23106        // Apply struct name inheritance for target dialects that need it
23107        let needs_inheritance = matches!(
23108            self.config.dialect,
23109            Some(DialectType::DuckDB)
23110                | Some(DialectType::Spark)
23111                | Some(DialectType::Databricks)
23112                | Some(DialectType::Hive)
23113                | Some(DialectType::Snowflake)
23114                | Some(DialectType::Presto)
23115                | Some(DialectType::Trino)
23116        );
23117        let propagated: Vec<Expression>;
23118        let expressions = if needs_inheritance && arr.expressions.len() > 1 {
23119            propagated = Self::inherit_struct_field_names(&arr.expressions);
23120            &propagated
23121        } else {
23122            &arr.expressions
23123        };
23124
23125        // Generic mode: ARRAY(1, 2, 3) with parentheses
23126        // Dialect mode: ARRAY[1, 2, 3] with brackets (or just [1, 2, 3] if array_bracket_only)
23127        let use_parens =
23128            self.config.dialect.is_none() || self.config.dialect == Some(DialectType::Generic);
23129        if !self.config.array_bracket_only {
23130            self.write_keyword("ARRAY");
23131        }
23132        if use_parens {
23133            self.write("(");
23134        } else {
23135            self.write("[");
23136        }
23137        for (i, expr) in expressions.iter().enumerate() {
23138            if i > 0 {
23139                self.write(", ");
23140            }
23141            self.generate_expression(expr)?;
23142        }
23143        if use_parens {
23144            self.write(")");
23145        } else {
23146            self.write("]");
23147        }
23148        Ok(())
23149    }
23150
23151    fn generate_tuple(&mut self, tuple: &Tuple) -> Result<()> {
23152        // Special case: Tuple(function/expr, TableAlias) pattern for table functions with typed aliases
23153        // Used for PostgreSQL functions like JSON_TO_RECORDSET: FUNC(args) AS alias(col1 type1, col2 type2)
23154        if tuple.expressions.len() == 2 {
23155            if let Expression::TableAlias(_) = &tuple.expressions[1] {
23156                // First element is the function/expression, second is the TableAlias
23157                self.generate_expression(&tuple.expressions[0])?;
23158                self.write_space();
23159                self.write_keyword("AS");
23160                self.write_space();
23161                self.generate_expression(&tuple.expressions[1])?;
23162                return Ok(());
23163            }
23164        }
23165
23166        // In pretty mode, format long tuples with each element on a new line
23167        // Only expand if total width exceeds threshold
23168        let expand_tuple = if self.config.pretty && tuple.expressions.len() > 1 {
23169            let mut expr_strings: Vec<String> = Vec::with_capacity(tuple.expressions.len());
23170            for expr in &tuple.expressions {
23171                expr_strings.push(self.generate_to_string(expr)?);
23172            }
23173            self.too_wide(&expr_strings)
23174        } else {
23175            false
23176        };
23177
23178        if expand_tuple {
23179            self.write("(");
23180            self.write_newline();
23181            self.indent_level += 1;
23182            for (i, expr) in tuple.expressions.iter().enumerate() {
23183                if i > 0 {
23184                    self.write(",");
23185                    self.write_newline();
23186                }
23187                self.write_indent();
23188                self.generate_expression(expr)?;
23189            }
23190            self.indent_level -= 1;
23191            self.write_newline();
23192            self.write_indent();
23193            self.write(")");
23194        } else {
23195            self.write("(");
23196            for (i, expr) in tuple.expressions.iter().enumerate() {
23197                if i > 0 {
23198                    self.write(", ");
23199                }
23200                self.generate_expression(expr)?;
23201            }
23202            self.write(")");
23203        }
23204        Ok(())
23205    }
23206
23207    fn generate_pipe_operator(&mut self, pipe: &PipeOperator) -> Result<()> {
23208        self.generate_expression(&pipe.this)?;
23209        self.write(" |> ");
23210        self.generate_expression(&pipe.expression)?;
23211        Ok(())
23212    }
23213
23214    fn generate_ordered(&mut self, ordered: &Ordered) -> Result<()> {
23215        self.generate_expression(&ordered.this)?;
23216        if ordered.desc {
23217            self.write_space();
23218            self.write_keyword("DESC");
23219        } else if ordered.explicit_asc {
23220            self.write_space();
23221            self.write_keyword("ASC");
23222        }
23223        if let Some(nulls_first) = ordered.nulls_first {
23224            if self.config.null_ordering_supported
23225                || !matches!(self.config.dialect, Some(DialectType::Fabric))
23226            {
23227                // Determine if we should skip outputting NULLS FIRST/LAST when it's the default
23228                // for the dialect. Different dialects have different NULL ordering defaults:
23229                //
23230                // nulls_are_large (Oracle, Postgres, Snowflake, etc.):
23231                //   - ASC: NULLS LAST is default (omit NULLS LAST for ASC)
23232                //   - DESC: NULLS FIRST is default (omit NULLS FIRST for DESC)
23233                //
23234                // nulls_are_small (Spark, Hive, BigQuery, most others):
23235                //   - ASC: NULLS FIRST is default
23236                //   - DESC: NULLS LAST is default
23237                //
23238                // nulls_are_last (DuckDB, Presto, Trino, Dremio, etc.):
23239                //   - NULLS LAST is always the default regardless of sort direction
23240                let is_asc = !ordered.desc;
23241                let is_nulls_are_large = matches!(
23242                    self.config.dialect,
23243                    Some(DialectType::Oracle)
23244                        | Some(DialectType::PostgreSQL)
23245                        | Some(DialectType::Redshift)
23246                        | Some(DialectType::Snowflake)
23247                );
23248                let is_nulls_are_last = matches!(
23249                    self.config.dialect,
23250                    Some(DialectType::Dremio)
23251                        | Some(DialectType::DuckDB)
23252                        | Some(DialectType::Presto)
23253                        | Some(DialectType::Trino)
23254                        | Some(DialectType::Athena)
23255                        | Some(DialectType::ClickHouse)
23256                        | Some(DialectType::Drill)
23257                        | Some(DialectType::Exasol)
23258                );
23259
23260                // Check if the NULLS ordering matches the default for this dialect
23261                let is_default_nulls = if is_nulls_are_large {
23262                    // For nulls_are_large: ASC + NULLS LAST or DESC + NULLS FIRST is default
23263                    (is_asc && !nulls_first) || (!is_asc && nulls_first)
23264                } else if is_nulls_are_last {
23265                    // For nulls_are_last: NULLS LAST is always default
23266                    !nulls_first
23267                } else {
23268                    false
23269                };
23270
23271                if !is_default_nulls {
23272                    self.write_space();
23273                    self.write_keyword("NULLS");
23274                    self.write_space();
23275                    self.write_keyword(if nulls_first { "FIRST" } else { "LAST" });
23276                }
23277            }
23278        }
23279        // WITH FILL clause (ClickHouse)
23280        if let Some(ref with_fill) = ordered.with_fill {
23281            self.write_space();
23282            self.generate_with_fill(with_fill)?;
23283        }
23284        Ok(())
23285    }
23286
23287    /// Write a ClickHouse type string, wrapping in Nullable unless in map key context.
23288    fn write_clickhouse_type(&mut self, type_str: &str) {
23289        if self.clickhouse_nullable_depth < 0 {
23290            // Map key context: don't wrap in Nullable
23291            self.write(type_str);
23292        } else {
23293            self.write(&format!("Nullable({})", type_str));
23294        }
23295    }
23296
23297    fn generate_data_type(&mut self, dt: &DataType) -> Result<()> {
23298        use crate::dialects::DialectType;
23299
23300        match dt {
23301            DataType::Boolean => {
23302                // Dialect-specific boolean type mappings
23303                match self.config.dialect {
23304                    Some(DialectType::TSQL) => self.write_keyword("BIT"),
23305                    Some(DialectType::MySQL) => self.write_keyword("BOOLEAN"), // alias for TINYINT(1)
23306                    Some(DialectType::Oracle) => {
23307                        // Oracle 23c+ supports BOOLEAN, older versions use NUMBER(1)
23308                        self.write_keyword("NUMBER(1)")
23309                    }
23310                    Some(DialectType::ClickHouse) => self.write("Bool"), // ClickHouse uses Bool (case-sensitive)
23311                    _ => self.write_keyword("BOOLEAN"),
23312                }
23313            }
23314            DataType::TinyInt { length } => {
23315                // PostgreSQL, Oracle, and Exasol don't have TINYINT, use SMALLINT
23316                // Dremio maps TINYINT to INT
23317                // ClickHouse maps TINYINT to Int8
23318                match self.config.dialect {
23319                    Some(DialectType::PostgreSQL)
23320                    | Some(DialectType::Redshift)
23321                    | Some(DialectType::Oracle)
23322                    | Some(DialectType::Exasol) => {
23323                        self.write_keyword("SMALLINT");
23324                    }
23325                    Some(DialectType::Teradata) => {
23326                        // Teradata uses BYTEINT for smallest integer
23327                        self.write_keyword("BYTEINT");
23328                    }
23329                    Some(DialectType::Dremio) => {
23330                        // Dremio maps TINYINT to INT
23331                        self.write_keyword("INT");
23332                    }
23333                    Some(DialectType::ClickHouse) => {
23334                        self.write_clickhouse_type("Int8");
23335                    }
23336                    _ => {
23337                        self.write_keyword("TINYINT");
23338                    }
23339                }
23340                if let Some(n) = length {
23341                    if !matches!(
23342                        self.config.dialect,
23343                        Some(DialectType::Dremio) | Some(DialectType::ClickHouse)
23344                    ) {
23345                        self.write(&format!("({})", n));
23346                    }
23347                }
23348            }
23349            DataType::SmallInt { length } => {
23350                // Dremio maps SMALLINT to INT, SQLite/Drill maps SMALLINT to INTEGER
23351                match self.config.dialect {
23352                    Some(DialectType::Dremio) => {
23353                        self.write_keyword("INT");
23354                    }
23355                    Some(DialectType::SQLite) | Some(DialectType::Drill) => {
23356                        self.write_keyword("INTEGER");
23357                    }
23358                    Some(DialectType::BigQuery) => {
23359                        self.write_keyword("INT64");
23360                    }
23361                    Some(DialectType::ClickHouse) => {
23362                        self.write_clickhouse_type("Int16");
23363                    }
23364                    _ => {
23365                        self.write_keyword("SMALLINT");
23366                        if let Some(n) = length {
23367                            self.write(&format!("({})", n));
23368                        }
23369                    }
23370                }
23371            }
23372            DataType::Int {
23373                length,
23374                integer_spelling: _,
23375            } => {
23376                // BigQuery uses INT64 for INT
23377                if matches!(self.config.dialect, Some(DialectType::BigQuery)) {
23378                    self.write_keyword("INT64");
23379                } else if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
23380                    self.write_clickhouse_type("Int32");
23381                } else {
23382                    // TSQL, Presto, Trino, SQLite, Redshift use INTEGER as the canonical form
23383                    let use_integer = match self.config.dialect {
23384                        Some(DialectType::TSQL)
23385                        | Some(DialectType::Fabric)
23386                        | Some(DialectType::Presto)
23387                        | Some(DialectType::Trino)
23388                        | Some(DialectType::SQLite)
23389                        | Some(DialectType::Redshift) => true,
23390                        _ => false,
23391                    };
23392                    if use_integer {
23393                        self.write_keyword("INTEGER");
23394                    } else {
23395                        self.write_keyword("INT");
23396                    }
23397                    if let Some(n) = length {
23398                        self.write(&format!("({})", n));
23399                    }
23400                }
23401            }
23402            DataType::BigInt { length } => {
23403                // Dialect-specific bigint type mappings
23404                match self.config.dialect {
23405                    Some(DialectType::Oracle) => {
23406                        // Oracle doesn't have BIGINT, uses INT
23407                        self.write_keyword("INT");
23408                    }
23409                    Some(DialectType::ClickHouse) => {
23410                        self.write_clickhouse_type("Int64");
23411                    }
23412                    _ => {
23413                        self.write_keyword("BIGINT");
23414                        if let Some(n) = length {
23415                            self.write(&format!("({})", n));
23416                        }
23417                    }
23418                }
23419            }
23420            DataType::Float {
23421                precision,
23422                scale,
23423                real_spelling,
23424            } => {
23425                // Dialect-specific float type mappings
23426                // If real_spelling is true, preserve REAL; otherwise use dialect default
23427                // Spark/Hive don't support REAL, always use FLOAT
23428                if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
23429                    self.write_clickhouse_type("Float32");
23430                } else if *real_spelling
23431                    && !matches!(
23432                        self.config.dialect,
23433                        Some(DialectType::Spark)
23434                            | Some(DialectType::Databricks)
23435                            | Some(DialectType::Hive)
23436                            | Some(DialectType::Snowflake)
23437                            | Some(DialectType::MySQL)
23438                            | Some(DialectType::BigQuery)
23439                    )
23440                {
23441                    self.write_keyword("REAL")
23442                } else {
23443                    match self.config.dialect {
23444                        Some(DialectType::PostgreSQL) => self.write_keyword("REAL"),
23445                        Some(DialectType::BigQuery) => self.write_keyword("FLOAT64"),
23446                        _ => self.write_keyword("FLOAT"),
23447                    }
23448                }
23449                // MySQL supports FLOAT(precision) or FLOAT(precision, scale)
23450                // Spark/Hive don't support FLOAT(precision)
23451                if !matches!(
23452                    self.config.dialect,
23453                    Some(DialectType::Spark)
23454                        | Some(DialectType::Databricks)
23455                        | Some(DialectType::Hive)
23456                        | Some(DialectType::Presto)
23457                        | Some(DialectType::Trino)
23458                ) {
23459                    if let Some(p) = precision {
23460                        self.write(&format!("({}", p));
23461                        if let Some(s) = scale {
23462                            self.write(&format!(", {})", s));
23463                        } else {
23464                            self.write(")");
23465                        }
23466                    }
23467                }
23468            }
23469            DataType::Double { precision, scale } => {
23470                // Dialect-specific double type mappings
23471                match self.config.dialect {
23472                    Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
23473                        self.write_keyword("FLOAT")
23474                    } // SQL Server/Fabric FLOAT is double
23475                    Some(DialectType::Oracle) => self.write_keyword("DOUBLE PRECISION"),
23476                    Some(DialectType::ClickHouse) => self.write_clickhouse_type("Float64"),
23477                    Some(DialectType::BigQuery) => self.write_keyword("FLOAT64"),
23478                    Some(DialectType::SQLite) => self.write_keyword("REAL"),
23479                    Some(DialectType::PostgreSQL)
23480                    | Some(DialectType::Redshift)
23481                    | Some(DialectType::Teradata)
23482                    | Some(DialectType::Materialize) => self.write_keyword("DOUBLE PRECISION"),
23483                    _ => self.write_keyword("DOUBLE"),
23484                }
23485                // MySQL supports DOUBLE(precision, scale)
23486                if let Some(p) = precision {
23487                    self.write(&format!("({}", p));
23488                    if let Some(s) = scale {
23489                        self.write(&format!(", {})", s));
23490                    } else {
23491                        self.write(")");
23492                    }
23493                }
23494            }
23495            DataType::Decimal { precision, scale } => {
23496                // Dialect-specific decimal type mappings
23497                match self.config.dialect {
23498                    Some(DialectType::ClickHouse) => {
23499                        self.write("Decimal");
23500                        if let Some(p) = precision {
23501                            self.write(&format!("({}", p));
23502                            if let Some(s) = scale {
23503                                self.write(&format!(", {}", s));
23504                            }
23505                            self.write(")");
23506                        }
23507                    }
23508                    Some(DialectType::Oracle) => {
23509                        // Oracle uses NUMBER instead of DECIMAL
23510                        self.write_keyword("NUMBER");
23511                        if let Some(p) = precision {
23512                            self.write(&format!("({}", p));
23513                            if let Some(s) = scale {
23514                                self.write(&format!(", {}", s));
23515                            }
23516                            self.write(")");
23517                        }
23518                    }
23519                    Some(DialectType::BigQuery) => {
23520                        // BigQuery uses NUMERIC instead of DECIMAL
23521                        self.write_keyword("NUMERIC");
23522                        if let Some(p) = precision {
23523                            self.write(&format!("({}", p));
23524                            if let Some(s) = scale {
23525                                self.write(&format!(", {}", s));
23526                            }
23527                            self.write(")");
23528                        }
23529                    }
23530                    _ => {
23531                        self.write_keyword("DECIMAL");
23532                        if let Some(p) = precision {
23533                            self.write(&format!("({}", p));
23534                            if let Some(s) = scale {
23535                                self.write(&format!(", {}", s));
23536                            }
23537                            self.write(")");
23538                        }
23539                    }
23540                }
23541            }
23542            DataType::Char { length } => {
23543                // Dialect-specific char type mappings
23544                match self.config.dialect {
23545                    Some(DialectType::DuckDB) | Some(DialectType::SQLite) => {
23546                        // DuckDB/SQLite maps CHAR to TEXT
23547                        self.write_keyword("TEXT");
23548                    }
23549                    Some(DialectType::Hive)
23550                    | Some(DialectType::Spark)
23551                    | Some(DialectType::Databricks) => {
23552                        // Hive/Spark/Databricks maps CHAR to STRING (when no length)
23553                        // CHAR(n) with explicit length is kept as CHAR(n) for Spark/Databricks
23554                        if length.is_some()
23555                            && !matches!(self.config.dialect, Some(DialectType::Hive))
23556                        {
23557                            self.write_keyword("CHAR");
23558                            if let Some(n) = length {
23559                                self.write(&format!("({})", n));
23560                            }
23561                        } else {
23562                            self.write_keyword("STRING");
23563                        }
23564                    }
23565                    Some(DialectType::Dremio) => {
23566                        // Dremio maps CHAR to VARCHAR
23567                        self.write_keyword("VARCHAR");
23568                        if let Some(n) = length {
23569                            self.write(&format!("({})", n));
23570                        }
23571                    }
23572                    _ => {
23573                        self.write_keyword("CHAR");
23574                        if let Some(n) = length {
23575                            self.write(&format!("({})", n));
23576                        }
23577                    }
23578                }
23579            }
23580            DataType::VarChar {
23581                length,
23582                parenthesized_length,
23583            } => {
23584                // Dialect-specific varchar type mappings
23585                match self.config.dialect {
23586                    Some(DialectType::Oracle) => {
23587                        self.write_keyword("VARCHAR2");
23588                        if let Some(n) = length {
23589                            self.write(&format!("({})", n));
23590                        }
23591                    }
23592                    Some(DialectType::DuckDB) => {
23593                        // DuckDB maps VARCHAR to TEXT, preserving length
23594                        self.write_keyword("TEXT");
23595                        if let Some(n) = length {
23596                            self.write(&format!("({})", n));
23597                        }
23598                    }
23599                    Some(DialectType::SQLite) => {
23600                        // SQLite maps VARCHAR to TEXT, preserving length
23601                        self.write_keyword("TEXT");
23602                        if let Some(n) = length {
23603                            self.write(&format!("({})", n));
23604                        }
23605                    }
23606                    Some(DialectType::MySQL) if length.is_none() => {
23607                        // MySQL requires VARCHAR to have a size - if it doesn't, use TEXT
23608                        self.write_keyword("TEXT");
23609                    }
23610                    Some(DialectType::Hive)
23611                    | Some(DialectType::Spark)
23612                    | Some(DialectType::Databricks)
23613                        if length.is_none() =>
23614                    {
23615                        // Hive/Spark/Databricks: VARCHAR without length → STRING
23616                        self.write_keyword("STRING");
23617                    }
23618                    _ => {
23619                        self.write_keyword("VARCHAR");
23620                        if let Some(n) = length {
23621                            // Hive uses VARCHAR((n)) with extra parentheses in STRUCT definitions
23622                            if *parenthesized_length {
23623                                self.write(&format!("(({}))", n));
23624                            } else {
23625                                self.write(&format!("({})", n));
23626                            }
23627                        }
23628                    }
23629                }
23630            }
23631            DataType::Text => {
23632                // Dialect-specific text type mappings
23633                match self.config.dialect {
23634                    Some(DialectType::Oracle) => self.write_keyword("CLOB"),
23635                    Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
23636                        self.write_keyword("VARCHAR(MAX)")
23637                    }
23638                    Some(DialectType::BigQuery) => self.write_keyword("STRING"),
23639                    Some(DialectType::Snowflake)
23640                    | Some(DialectType::Dremio)
23641                    | Some(DialectType::Drill) => self.write_keyword("VARCHAR"),
23642                    Some(DialectType::Exasol) => self.write_keyword("LONG VARCHAR"),
23643                    Some(DialectType::Presto)
23644                    | Some(DialectType::Trino)
23645                    | Some(DialectType::Athena) => self.write_keyword("VARCHAR"),
23646                    Some(DialectType::Spark)
23647                    | Some(DialectType::Databricks)
23648                    | Some(DialectType::Hive) => self.write_keyword("STRING"),
23649                    Some(DialectType::Redshift) => self.write_keyword("VARCHAR(MAX)"),
23650                    Some(DialectType::StarRocks) | Some(DialectType::Doris) => {
23651                        self.write_keyword("STRING")
23652                    }
23653                    Some(DialectType::ClickHouse) => self.write_clickhouse_type("String"),
23654                    _ => self.write_keyword("TEXT"),
23655                }
23656            }
23657            DataType::TextWithLength { length } => {
23658                // TEXT(n) - dialect-specific type with length
23659                match self.config.dialect {
23660                    Some(DialectType::Oracle) => self.write(&format!("CLOB({})", length)),
23661                    Some(DialectType::Hive)
23662                    | Some(DialectType::Spark)
23663                    | Some(DialectType::Databricks) => {
23664                        self.write(&format!("VARCHAR({})", length));
23665                    }
23666                    Some(DialectType::Redshift) => self.write(&format!("VARCHAR({})", length)),
23667                    Some(DialectType::BigQuery) => self.write(&format!("STRING({})", length)),
23668                    Some(DialectType::Snowflake)
23669                    | Some(DialectType::Presto)
23670                    | Some(DialectType::Trino)
23671                    | Some(DialectType::Athena)
23672                    | Some(DialectType::Drill)
23673                    | Some(DialectType::Dremio) => {
23674                        self.write(&format!("VARCHAR({})", length));
23675                    }
23676                    Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
23677                        self.write(&format!("VARCHAR({})", length))
23678                    }
23679                    Some(DialectType::StarRocks) | Some(DialectType::Doris) => {
23680                        self.write(&format!("STRING({})", length))
23681                    }
23682                    Some(DialectType::ClickHouse) => self.write_clickhouse_type("String"),
23683                    _ => self.write(&format!("TEXT({})", length)),
23684                }
23685            }
23686            DataType::String { length } => {
23687                // STRING type with optional length (BigQuery STRING(n))
23688                match self.config.dialect {
23689                    Some(DialectType::ClickHouse) => {
23690                        // ClickHouse uses String with specific casing
23691                        self.write("String");
23692                        if let Some(n) = length {
23693                            self.write(&format!("({})", n));
23694                        }
23695                    }
23696                    Some(DialectType::BigQuery)
23697                    | Some(DialectType::Hive)
23698                    | Some(DialectType::Spark)
23699                    | Some(DialectType::Databricks)
23700                    | Some(DialectType::StarRocks)
23701                    | Some(DialectType::Doris) => {
23702                        self.write_keyword("STRING");
23703                        if let Some(n) = length {
23704                            self.write(&format!("({})", n));
23705                        }
23706                    }
23707                    Some(DialectType::PostgreSQL) => {
23708                        // PostgreSQL doesn't have STRING - use VARCHAR or TEXT
23709                        if let Some(n) = length {
23710                            self.write_keyword("VARCHAR");
23711                            self.write(&format!("({})", n));
23712                        } else {
23713                            self.write_keyword("TEXT");
23714                        }
23715                    }
23716                    Some(DialectType::Redshift) => {
23717                        // Redshift: STRING -> VARCHAR(MAX)
23718                        if let Some(n) = length {
23719                            self.write_keyword("VARCHAR");
23720                            self.write(&format!("({})", n));
23721                        } else {
23722                            self.write_keyword("VARCHAR(MAX)");
23723                        }
23724                    }
23725                    Some(DialectType::MySQL) => {
23726                        // MySQL doesn't have STRING - use VARCHAR or TEXT
23727                        if let Some(n) = length {
23728                            self.write_keyword("VARCHAR");
23729                            self.write(&format!("({})", n));
23730                        } else {
23731                            self.write_keyword("TEXT");
23732                        }
23733                    }
23734                    Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
23735                        // TSQL: STRING -> VARCHAR(MAX)
23736                        if let Some(n) = length {
23737                            self.write_keyword("VARCHAR");
23738                            self.write(&format!("({})", n));
23739                        } else {
23740                            self.write_keyword("VARCHAR(MAX)");
23741                        }
23742                    }
23743                    Some(DialectType::Oracle) => {
23744                        // Oracle: STRING -> CLOB
23745                        self.write_keyword("CLOB");
23746                    }
23747                    Some(DialectType::DuckDB) | Some(DialectType::Materialize) => {
23748                        // DuckDB/Materialize uses TEXT for string types
23749                        self.write_keyword("TEXT");
23750                        if let Some(n) = length {
23751                            self.write(&format!("({})", n));
23752                        }
23753                    }
23754                    Some(DialectType::Presto)
23755                    | Some(DialectType::Trino)
23756                    | Some(DialectType::Drill)
23757                    | Some(DialectType::Dremio) => {
23758                        // Presto/Trino/Drill use VARCHAR for string types
23759                        self.write_keyword("VARCHAR");
23760                        if let Some(n) = length {
23761                            self.write(&format!("({})", n));
23762                        }
23763                    }
23764                    Some(DialectType::Snowflake) => {
23765                        // Snowflake: STRING stays as STRING (identity/DDL)
23766                        // CAST context STRING -> VARCHAR is handled in generate_cast
23767                        self.write_keyword("STRING");
23768                        if let Some(n) = length {
23769                            self.write(&format!("({})", n));
23770                        }
23771                    }
23772                    _ => {
23773                        // Default: output STRING with optional length
23774                        self.write_keyword("STRING");
23775                        if let Some(n) = length {
23776                            self.write(&format!("({})", n));
23777                        }
23778                    }
23779                }
23780            }
23781            DataType::Binary { length } => {
23782                // Dialect-specific binary type mappings
23783                match self.config.dialect {
23784                    Some(DialectType::PostgreSQL) | Some(DialectType::Materialize) => {
23785                        self.write_keyword("BYTEA");
23786                        if let Some(n) = length {
23787                            self.write(&format!("({})", n));
23788                        }
23789                    }
23790                    Some(DialectType::Redshift) => {
23791                        self.write_keyword("VARBYTE");
23792                        if let Some(n) = length {
23793                            self.write(&format!("({})", n));
23794                        }
23795                    }
23796                    Some(DialectType::DuckDB)
23797                    | Some(DialectType::SQLite)
23798                    | Some(DialectType::Oracle) => {
23799                        // DuckDB/SQLite/Oracle maps BINARY to BLOB
23800                        self.write_keyword("BLOB");
23801                        if let Some(n) = length {
23802                            self.write(&format!("({})", n));
23803                        }
23804                    }
23805                    Some(DialectType::Presto)
23806                    | Some(DialectType::Trino)
23807                    | Some(DialectType::Athena)
23808                    | Some(DialectType::Drill)
23809                    | Some(DialectType::Dremio) => {
23810                        // These dialects map BINARY to VARBINARY
23811                        self.write_keyword("VARBINARY");
23812                        if let Some(n) = length {
23813                            self.write(&format!("({})", n));
23814                        }
23815                    }
23816                    Some(DialectType::ClickHouse) => {
23817                        // ClickHouse: wrap BINARY in Nullable (unless map key context)
23818                        if self.clickhouse_nullable_depth < 0 {
23819                            self.write("BINARY");
23820                        } else {
23821                            self.write("Nullable(BINARY");
23822                        }
23823                        if let Some(n) = length {
23824                            self.write(&format!("({})", n));
23825                        }
23826                        if self.clickhouse_nullable_depth >= 0 {
23827                            self.write(")");
23828                        }
23829                    }
23830                    _ => {
23831                        self.write_keyword("BINARY");
23832                        if let Some(n) = length {
23833                            self.write(&format!("({})", n));
23834                        }
23835                    }
23836                }
23837            }
23838            DataType::VarBinary { length } => {
23839                // Dialect-specific varbinary type mappings
23840                match self.config.dialect {
23841                    Some(DialectType::PostgreSQL) | Some(DialectType::Materialize) => {
23842                        self.write_keyword("BYTEA");
23843                        if let Some(n) = length {
23844                            self.write(&format!("({})", n));
23845                        }
23846                    }
23847                    Some(DialectType::Redshift) => {
23848                        self.write_keyword("VARBYTE");
23849                        if let Some(n) = length {
23850                            self.write(&format!("({})", n));
23851                        }
23852                    }
23853                    Some(DialectType::DuckDB)
23854                    | Some(DialectType::SQLite)
23855                    | Some(DialectType::Oracle) => {
23856                        // DuckDB/SQLite/Oracle maps VARBINARY to BLOB
23857                        self.write_keyword("BLOB");
23858                        if let Some(n) = length {
23859                            self.write(&format!("({})", n));
23860                        }
23861                    }
23862                    Some(DialectType::Exasol) => {
23863                        // Exasol maps VARBINARY to VARCHAR
23864                        self.write_keyword("VARCHAR");
23865                    }
23866                    Some(DialectType::Spark)
23867                    | Some(DialectType::Hive)
23868                    | Some(DialectType::Databricks) => {
23869                        // Spark/Hive use BINARY instead of VARBINARY
23870                        self.write_keyword("BINARY");
23871                        if let Some(n) = length {
23872                            self.write(&format!("({})", n));
23873                        }
23874                    }
23875                    Some(DialectType::ClickHouse) => {
23876                        // ClickHouse maps VARBINARY to String (wrapped in Nullable unless map key)
23877                        self.write_clickhouse_type("String");
23878                    }
23879                    _ => {
23880                        self.write_keyword("VARBINARY");
23881                        if let Some(n) = length {
23882                            self.write(&format!("({})", n));
23883                        }
23884                    }
23885                }
23886            }
23887            DataType::Blob => {
23888                // Dialect-specific blob type mappings
23889                match self.config.dialect {
23890                    Some(DialectType::PostgreSQL) => self.write_keyword("BYTEA"),
23891                    Some(DialectType::Redshift) => self.write_keyword("VARBYTE"),
23892                    Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
23893                        self.write_keyword("VARBINARY")
23894                    }
23895                    Some(DialectType::BigQuery) => self.write_keyword("BYTES"),
23896                    Some(DialectType::Exasol) => self.write_keyword("VARCHAR"),
23897                    Some(DialectType::Presto)
23898                    | Some(DialectType::Trino)
23899                    | Some(DialectType::Athena) => self.write_keyword("VARBINARY"),
23900                    Some(DialectType::DuckDB) => {
23901                        // Python sqlglot: BLOB -> VARBINARY for DuckDB (base TYPE_MAPPING)
23902                        // DuckDB identity works via: BLOB -> transform VarBinary -> generator BLOB
23903                        self.write_keyword("VARBINARY");
23904                    }
23905                    Some(DialectType::Spark)
23906                    | Some(DialectType::Databricks)
23907                    | Some(DialectType::Hive) => self.write_keyword("BINARY"),
23908                    Some(DialectType::ClickHouse) => {
23909                        // BLOB maps to Nullable(String) in ClickHouse, even in column defs
23910                        // where we normally suppress Nullable wrapping (clickhouse_nullable_depth = -1).
23911                        // This matches Python sqlglot behavior.
23912                        self.write("Nullable(String)");
23913                    }
23914                    _ => self.write_keyword("BLOB"),
23915                }
23916            }
23917            DataType::Bit { length } => {
23918                // Dialect-specific bit type mappings
23919                match self.config.dialect {
23920                    Some(DialectType::Dremio)
23921                    | Some(DialectType::Spark)
23922                    | Some(DialectType::Databricks)
23923                    | Some(DialectType::Hive)
23924                    | Some(DialectType::Snowflake)
23925                    | Some(DialectType::BigQuery)
23926                    | Some(DialectType::Presto)
23927                    | Some(DialectType::Trino)
23928                    | Some(DialectType::ClickHouse)
23929                    | Some(DialectType::Redshift) => {
23930                        // These dialects don't support BIT type, use BOOLEAN
23931                        self.write_keyword("BOOLEAN");
23932                    }
23933                    _ => {
23934                        self.write_keyword("BIT");
23935                        if let Some(n) = length {
23936                            self.write(&format!("({})", n));
23937                        }
23938                    }
23939                }
23940            }
23941            DataType::VarBit { length } => {
23942                self.write_keyword("VARBIT");
23943                if let Some(n) = length {
23944                    self.write(&format!("({})", n));
23945                }
23946            }
23947            DataType::Date => self.write_keyword("DATE"),
23948            DataType::Time {
23949                precision,
23950                timezone,
23951            } => {
23952                if *timezone {
23953                    // Dialect-specific TIME WITH TIME ZONE output
23954                    match self.config.dialect {
23955                        Some(DialectType::DuckDB) => {
23956                            // DuckDB: TIMETZ (drops precision)
23957                            self.write_keyword("TIMETZ");
23958                        }
23959                        Some(DialectType::PostgreSQL) => {
23960                            // PostgreSQL: TIMETZ or TIMETZ(p)
23961                            self.write_keyword("TIMETZ");
23962                            if let Some(p) = precision {
23963                                self.write(&format!("({})", p));
23964                            }
23965                        }
23966                        _ => {
23967                            // Presto/Trino/Redshift/others: TIME(p) WITH TIME ZONE
23968                            self.write_keyword("TIME");
23969                            if let Some(p) = precision {
23970                                self.write(&format!("({})", p));
23971                            }
23972                            self.write_keyword(" WITH TIME ZONE");
23973                        }
23974                    }
23975                } else {
23976                    // Spark/Hive/Databricks: TIME -> TIMESTAMP (TIME not supported)
23977                    if matches!(
23978                        self.config.dialect,
23979                        Some(DialectType::Spark)
23980                            | Some(DialectType::Databricks)
23981                            | Some(DialectType::Hive)
23982                    ) {
23983                        self.write_keyword("TIMESTAMP");
23984                    } else {
23985                        self.write_keyword("TIME");
23986                        if let Some(p) = precision {
23987                            self.write(&format!("({})", p));
23988                        }
23989                    }
23990                }
23991            }
23992            DataType::Timestamp {
23993                precision,
23994                timezone,
23995            } => {
23996                // Dialect-specific timestamp type mappings
23997                match self.config.dialect {
23998                    Some(DialectType::ClickHouse) => {
23999                        self.write("DateTime");
24000                        if let Some(p) = precision {
24001                            self.write(&format!("({})", p));
24002                        }
24003                    }
24004                    Some(DialectType::TSQL) => {
24005                        if *timezone {
24006                            self.write_keyword("DATETIMEOFFSET");
24007                        } else {
24008                            self.write_keyword("DATETIME2");
24009                        }
24010                        if let Some(p) = precision {
24011                            self.write(&format!("({})", p));
24012                        }
24013                    }
24014                    Some(DialectType::MySQL) => {
24015                        // MySQL: TIMESTAMP stays as TIMESTAMP in DDL; CAST mapping handled separately
24016                        self.write_keyword("TIMESTAMP");
24017                        if let Some(p) = precision {
24018                            self.write(&format!("({})", p));
24019                        }
24020                    }
24021                    Some(DialectType::Doris) | Some(DialectType::StarRocks) => {
24022                        // Doris/StarRocks: TIMESTAMP -> DATETIME
24023                        self.write_keyword("DATETIME");
24024                        if let Some(p) = precision {
24025                            self.write(&format!("({})", p));
24026                        }
24027                    }
24028                    Some(DialectType::BigQuery) => {
24029                        // BigQuery: TIMESTAMP is always UTC, DATETIME is timezone-naive
24030                        if *timezone {
24031                            self.write_keyword("TIMESTAMP");
24032                        } else {
24033                            self.write_keyword("DATETIME");
24034                        }
24035                    }
24036                    Some(DialectType::DuckDB) => {
24037                        // DuckDB: TIMESTAMPTZ shorthand
24038                        if *timezone {
24039                            self.write_keyword("TIMESTAMPTZ");
24040                        } else {
24041                            self.write_keyword("TIMESTAMP");
24042                            if let Some(p) = precision {
24043                                self.write(&format!("({})", p));
24044                            }
24045                        }
24046                    }
24047                    _ => {
24048                        if *timezone && !self.config.tz_to_with_time_zone {
24049                            // Use TIMESTAMPTZ shorthand when dialect doesn't prefer WITH TIME ZONE
24050                            self.write_keyword("TIMESTAMPTZ");
24051                            if let Some(p) = precision {
24052                                self.write(&format!("({})", p));
24053                            }
24054                        } else {
24055                            self.write_keyword("TIMESTAMP");
24056                            if let Some(p) = precision {
24057                                self.write(&format!("({})", p));
24058                            }
24059                            if *timezone {
24060                                self.write_space();
24061                                self.write_keyword("WITH TIME ZONE");
24062                            }
24063                        }
24064                    }
24065                }
24066            }
24067            DataType::Interval { unit, to } => {
24068                self.write_keyword("INTERVAL");
24069                if let Some(u) = unit {
24070                    self.write_space();
24071                    self.write_keyword(u);
24072                }
24073                // Handle range intervals like DAY TO HOUR
24074                if let Some(t) = to {
24075                    self.write_space();
24076                    self.write_keyword("TO");
24077                    self.write_space();
24078                    self.write_keyword(t);
24079                }
24080            }
24081            DataType::Json => {
24082                // Dialect-specific JSON type mappings
24083                match self.config.dialect {
24084                    Some(DialectType::Oracle) => self.write_keyword("JSON"), // Oracle 21c+
24085                    Some(DialectType::TSQL) => self.write_keyword("NVARCHAR(MAX)"), // No native JSON type
24086                    Some(DialectType::MySQL) => self.write_keyword("JSON"),
24087                    Some(DialectType::Snowflake) => self.write_keyword("VARIANT"),
24088                    _ => self.write_keyword("JSON"),
24089                }
24090            }
24091            DataType::JsonB => {
24092                // JSONB is PostgreSQL specific, but Doris also supports it
24093                match self.config.dialect {
24094                    Some(DialectType::PostgreSQL) => self.write_keyword("JSONB"),
24095                    Some(DialectType::Doris) => self.write_keyword("JSONB"),
24096                    Some(DialectType::Snowflake) => self.write_keyword("VARIANT"),
24097                    Some(DialectType::TSQL) => self.write_keyword("NVARCHAR(MAX)"),
24098                    Some(DialectType::DuckDB) => self.write_keyword("JSON"), // DuckDB maps JSONB to JSON
24099                    _ => self.write_keyword("JSON"), // Fall back to JSON for other dialects
24100                }
24101            }
24102            DataType::Uuid => {
24103                // Dialect-specific UUID type mappings
24104                match self.config.dialect {
24105                    Some(DialectType::TSQL) => self.write_keyword("UNIQUEIDENTIFIER"),
24106                    Some(DialectType::MySQL) => self.write_keyword("CHAR(36)"),
24107                    Some(DialectType::Oracle) => self.write_keyword("RAW(16)"),
24108                    Some(DialectType::BigQuery)
24109                    | Some(DialectType::Spark)
24110                    | Some(DialectType::Databricks) => self.write_keyword("STRING"),
24111                    _ => self.write_keyword("UUID"),
24112                }
24113            }
24114            DataType::Array {
24115                element_type,
24116                dimension,
24117            } => {
24118                // Dialect-specific array syntax
24119                match self.config.dialect {
24120                    Some(DialectType::PostgreSQL)
24121                    | Some(DialectType::Redshift)
24122                    | Some(DialectType::DuckDB) => {
24123                        // PostgreSQL uses TYPE[] or TYPE[N] syntax
24124                        self.generate_data_type(element_type)?;
24125                        if let Some(dim) = dimension {
24126                            self.write(&format!("[{}]", dim));
24127                        } else {
24128                            self.write("[]");
24129                        }
24130                    }
24131                    Some(DialectType::BigQuery) => {
24132                        self.write_keyword("ARRAY<");
24133                        self.generate_data_type(element_type)?;
24134                        self.write(">");
24135                    }
24136                    Some(DialectType::Snowflake)
24137                    | Some(DialectType::Presto)
24138                    | Some(DialectType::Trino)
24139                    | Some(DialectType::ClickHouse) => {
24140                        // These dialects use Array(TYPE) parentheses syntax
24141                        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
24142                            self.write("Array(");
24143                        } else {
24144                            self.write_keyword("ARRAY(");
24145                        }
24146                        self.generate_data_type(element_type)?;
24147                        self.write(")");
24148                    }
24149                    Some(DialectType::TSQL)
24150                    | Some(DialectType::MySQL)
24151                    | Some(DialectType::Oracle) => {
24152                        // These dialects don't have native array types
24153                        // Fall back to JSON or use native workarounds
24154                        match self.config.dialect {
24155                            Some(DialectType::MySQL) => self.write_keyword("JSON"),
24156                            Some(DialectType::TSQL) => self.write_keyword("NVARCHAR(MAX)"),
24157                            _ => self.write_keyword("JSON"),
24158                        }
24159                    }
24160                    _ => {
24161                        // Default: use angle bracket syntax (ARRAY<T>)
24162                        self.write_keyword("ARRAY<");
24163                        self.generate_data_type(element_type)?;
24164                        self.write(">");
24165                    }
24166                }
24167            }
24168            DataType::List { element_type } => {
24169                // Materialize: element_type LIST (postfix syntax)
24170                self.generate_data_type(element_type)?;
24171                self.write_keyword(" LIST");
24172            }
24173            DataType::Map {
24174                key_type,
24175                value_type,
24176            } => {
24177                // Use parentheses for Snowflake and RisingWave, bracket syntax for Materialize, angle brackets for others
24178                match self.config.dialect {
24179                    Some(DialectType::Materialize) => {
24180                        // Materialize: MAP[key_type => value_type]
24181                        self.write_keyword("MAP[");
24182                        self.generate_data_type(key_type)?;
24183                        self.write(" => ");
24184                        self.generate_data_type(value_type)?;
24185                        self.write("]");
24186                    }
24187                    Some(DialectType::Snowflake)
24188                    | Some(DialectType::RisingWave)
24189                    | Some(DialectType::DuckDB)
24190                    | Some(DialectType::Presto)
24191                    | Some(DialectType::Trino)
24192                    | Some(DialectType::Athena) => {
24193                        self.write_keyword("MAP(");
24194                        self.generate_data_type(key_type)?;
24195                        self.write(", ");
24196                        self.generate_data_type(value_type)?;
24197                        self.write(")");
24198                    }
24199                    Some(DialectType::ClickHouse) => {
24200                        // ClickHouse: Map(key_type, value_type) with parenthesized syntax
24201                        // Key types must NOT be wrapped in Nullable
24202                        self.write("Map(");
24203                        self.clickhouse_nullable_depth = -1; // suppress Nullable for key
24204                        self.generate_data_type(key_type)?;
24205                        self.clickhouse_nullable_depth = 0;
24206                        self.write(", ");
24207                        self.generate_data_type(value_type)?;
24208                        self.write(")");
24209                    }
24210                    _ => {
24211                        self.write_keyword("MAP<");
24212                        self.generate_data_type(key_type)?;
24213                        self.write(", ");
24214                        self.generate_data_type(value_type)?;
24215                        self.write(">");
24216                    }
24217                }
24218            }
24219            DataType::Vector {
24220                element_type,
24221                dimension,
24222            } => {
24223                if matches!(self.config.dialect, Some(DialectType::SingleStore)) {
24224                    // SingleStore format: VECTOR(dimension, type_alias)
24225                    self.write_keyword("VECTOR(");
24226                    if let Some(dim) = dimension {
24227                        self.write(&dim.to_string());
24228                    }
24229                    // Map type back to SingleStore alias
24230                    let type_alias = element_type.as_ref().and_then(|et| match et.as_ref() {
24231                        DataType::TinyInt { .. } => Some("I8"),
24232                        DataType::SmallInt { .. } => Some("I16"),
24233                        DataType::Int { .. } => Some("I32"),
24234                        DataType::BigInt { .. } => Some("I64"),
24235                        DataType::Float { .. } => Some("F32"),
24236                        DataType::Double { .. } => Some("F64"),
24237                        _ => None,
24238                    });
24239                    if let Some(alias) = type_alias {
24240                        if dimension.is_some() {
24241                            self.write(", ");
24242                        }
24243                        self.write(alias);
24244                    }
24245                    self.write(")");
24246                } else {
24247                    // Snowflake format: VECTOR(type, dimension)
24248                    self.write_keyword("VECTOR(");
24249                    if let Some(ref et) = element_type {
24250                        self.generate_data_type(et)?;
24251                        if dimension.is_some() {
24252                            self.write(", ");
24253                        }
24254                    }
24255                    if let Some(dim) = dimension {
24256                        self.write(&dim.to_string());
24257                    }
24258                    self.write(")");
24259                }
24260            }
24261            DataType::Object { fields, modifier } => {
24262                self.write_keyword("OBJECT(");
24263                for (i, (name, dt, not_null)) in fields.iter().enumerate() {
24264                    if i > 0 {
24265                        self.write(", ");
24266                    }
24267                    self.write(name);
24268                    self.write(" ");
24269                    self.generate_data_type(dt)?;
24270                    if *not_null {
24271                        self.write_keyword(" NOT NULL");
24272                    }
24273                }
24274                self.write(")");
24275                if let Some(mod_str) = modifier {
24276                    self.write(" ");
24277                    self.write_keyword(mod_str);
24278                }
24279            }
24280            DataType::Struct { fields, nested } => {
24281                // Dialect-specific struct type mappings
24282                match self.config.dialect {
24283                    Some(DialectType::Snowflake) => {
24284                        // Snowflake maps STRUCT to OBJECT
24285                        self.write_keyword("OBJECT(");
24286                        for (i, field) in fields.iter().enumerate() {
24287                            if i > 0 {
24288                                self.write(", ");
24289                            }
24290                            if !field.name.is_empty() {
24291                                self.write(&field.name);
24292                                self.write(" ");
24293                            }
24294                            self.generate_data_type(&field.data_type)?;
24295                        }
24296                        self.write(")");
24297                    }
24298                    Some(DialectType::Presto) | Some(DialectType::Trino) => {
24299                        // Presto/Trino use ROW(name TYPE, ...) syntax
24300                        self.write_keyword("ROW(");
24301                        for (i, field) in fields.iter().enumerate() {
24302                            if i > 0 {
24303                                self.write(", ");
24304                            }
24305                            if !field.name.is_empty() {
24306                                self.write(&field.name);
24307                                self.write(" ");
24308                            }
24309                            self.generate_data_type(&field.data_type)?;
24310                        }
24311                        self.write(")");
24312                    }
24313                    Some(DialectType::DuckDB) => {
24314                        // DuckDB uses parenthesized syntax: STRUCT(name TYPE, ...)
24315                        self.write_keyword("STRUCT(");
24316                        for (i, field) in fields.iter().enumerate() {
24317                            if i > 0 {
24318                                self.write(", ");
24319                            }
24320                            if !field.name.is_empty() {
24321                                self.write(&field.name);
24322                                self.write(" ");
24323                            }
24324                            self.generate_data_type(&field.data_type)?;
24325                        }
24326                        self.write(")");
24327                    }
24328                    Some(DialectType::ClickHouse) => {
24329                        // ClickHouse uses Tuple(name TYPE, ...) for struct types
24330                        self.write("Tuple(");
24331                        for (i, field) in fields.iter().enumerate() {
24332                            if i > 0 {
24333                                self.write(", ");
24334                            }
24335                            if !field.name.is_empty() {
24336                                self.write(&field.name);
24337                                self.write(" ");
24338                            }
24339                            self.generate_data_type(&field.data_type)?;
24340                        }
24341                        self.write(")");
24342                    }
24343                    Some(DialectType::SingleStore) => {
24344                        // SingleStore uses RECORD(name TYPE, ...) for struct types
24345                        self.write_keyword("RECORD(");
24346                        for (i, field) in fields.iter().enumerate() {
24347                            if i > 0 {
24348                                self.write(", ");
24349                            }
24350                            if !field.name.is_empty() {
24351                                self.write(&field.name);
24352                                self.write(" ");
24353                            }
24354                            self.generate_data_type(&field.data_type)?;
24355                        }
24356                        self.write(")");
24357                    }
24358                    _ => {
24359                        // Hive/Spark always use angle bracket syntax: STRUCT<name: TYPE>
24360                        let force_angle_brackets = matches!(
24361                            self.config.dialect,
24362                            Some(DialectType::Hive)
24363                                | Some(DialectType::Spark)
24364                                | Some(DialectType::Databricks)
24365                        );
24366                        if *nested && !force_angle_brackets {
24367                            self.write_keyword("STRUCT(");
24368                            for (i, field) in fields.iter().enumerate() {
24369                                if i > 0 {
24370                                    self.write(", ");
24371                                }
24372                                if !field.name.is_empty() {
24373                                    self.write(&field.name);
24374                                    self.write(" ");
24375                                }
24376                                self.generate_data_type(&field.data_type)?;
24377                            }
24378                            self.write(")");
24379                        } else {
24380                            self.write_keyword("STRUCT<");
24381                            for (i, field) in fields.iter().enumerate() {
24382                                if i > 0 {
24383                                    self.write(", ");
24384                                }
24385                                if !field.name.is_empty() {
24386                                    // Named field: name TYPE (with configurable separator for Hive)
24387                                    self.write(&field.name);
24388                                    self.write(self.config.struct_field_sep);
24389                                }
24390                                // For anonymous fields, just output the type
24391                                self.generate_data_type(&field.data_type)?;
24392                                // Spark/Databricks: Output COMMENT clause if present
24393                                if let Some(comment) = &field.comment {
24394                                    self.write(" COMMENT '");
24395                                    self.write(comment);
24396                                    self.write("'");
24397                                }
24398                                // BigQuery: Output OPTIONS clause if present
24399                                if !field.options.is_empty() {
24400                                    self.write(" ");
24401                                    self.generate_options_clause(&field.options)?;
24402                                }
24403                            }
24404                            self.write(">");
24405                        }
24406                    }
24407                }
24408            }
24409            DataType::Enum {
24410                values,
24411                assignments,
24412            } => {
24413                // DuckDB ENUM type: ENUM('RED', 'GREEN', 'BLUE')
24414                // ClickHouse: Enum('hello' = 1, 'world' = 2)
24415                if self.config.dialect == Some(DialectType::ClickHouse) {
24416                    self.write("Enum(");
24417                } else {
24418                    self.write_keyword("ENUM(");
24419                }
24420                for (i, val) in values.iter().enumerate() {
24421                    if i > 0 {
24422                        self.write(", ");
24423                    }
24424                    self.write("'");
24425                    self.write(val);
24426                    self.write("'");
24427                    if let Some(Some(assignment)) = assignments.get(i) {
24428                        self.write(" = ");
24429                        self.write(assignment);
24430                    }
24431                }
24432                self.write(")");
24433            }
24434            DataType::Set { values } => {
24435                // MySQL SET type: SET('a', 'b', 'c')
24436                self.write_keyword("SET(");
24437                for (i, val) in values.iter().enumerate() {
24438                    if i > 0 {
24439                        self.write(", ");
24440                    }
24441                    self.write("'");
24442                    self.write(val);
24443                    self.write("'");
24444                }
24445                self.write(")");
24446            }
24447            DataType::Union { fields } => {
24448                // DuckDB UNION type: UNION(num INT, str TEXT)
24449                self.write_keyword("UNION(");
24450                for (i, (name, dt)) in fields.iter().enumerate() {
24451                    if i > 0 {
24452                        self.write(", ");
24453                    }
24454                    if !name.is_empty() {
24455                        self.write(name);
24456                        self.write(" ");
24457                    }
24458                    self.generate_data_type(dt)?;
24459                }
24460                self.write(")");
24461            }
24462            DataType::Nullable { inner } => {
24463                // ClickHouse: Nullable(T), other dialects: just the inner type
24464                if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
24465                    self.write("Nullable(");
24466                    // Suppress inner Nullable wrapping to prevent Nullable(Nullable(...))
24467                    let saved_depth = self.clickhouse_nullable_depth;
24468                    self.clickhouse_nullable_depth = -1;
24469                    self.generate_data_type(inner)?;
24470                    self.clickhouse_nullable_depth = saved_depth;
24471                    self.write(")");
24472                } else {
24473                    // Map ClickHouse-specific custom type names to standard types
24474                    match inner.as_ref() {
24475                        DataType::Custom { name } if name.eq_ignore_ascii_case("DATETIME") => {
24476                            self.generate_data_type(&DataType::Timestamp {
24477                                precision: None,
24478                                timezone: false,
24479                            })?;
24480                        }
24481                        _ => {
24482                            self.generate_data_type(inner)?;
24483                        }
24484                    }
24485                }
24486            }
24487            DataType::Custom { name } => {
24488                // Handle dialect-specific type transformations
24489                let name_upper = name.to_ascii_uppercase();
24490                match self.config.dialect {
24491                    Some(DialectType::ClickHouse) => {
24492                        let (base_upper, suffix) = if let Some(idx) = name.find('(') {
24493                            (name_upper[..idx].to_string(), &name[idx..])
24494                        } else {
24495                            (name_upper.clone(), "")
24496                        };
24497                        let mapped = match base_upper.as_str() {
24498                            "DATETIME" | "TIMESTAMPTZ" | "TIMESTAMP" | "TIMESTAMPNTZ"
24499                            | "SMALLDATETIME" | "DATETIME2" => "DateTime",
24500                            "DATETIME64" => "DateTime64",
24501                            "DATE32" => "Date32",
24502                            "INT" => "Int32",
24503                            "MEDIUMINT" => "Int32",
24504                            "INT8" => "Int8",
24505                            "INT16" => "Int16",
24506                            "INT32" => "Int32",
24507                            "INT64" => "Int64",
24508                            "INT128" => "Int128",
24509                            "INT256" => "Int256",
24510                            "UINT8" => "UInt8",
24511                            "UINT16" => "UInt16",
24512                            "UINT32" => "UInt32",
24513                            "UINT64" => "UInt64",
24514                            "UINT128" => "UInt128",
24515                            "UINT256" => "UInt256",
24516                            "FLOAT32" => "Float32",
24517                            "FLOAT64" => "Float64",
24518                            "DECIMAL32" => "Decimal32",
24519                            "DECIMAL64" => "Decimal64",
24520                            "DECIMAL128" => "Decimal128",
24521                            "DECIMAL256" => "Decimal256",
24522                            "ENUM" => "Enum",
24523                            "ENUM8" => "Enum8",
24524                            "ENUM16" => "Enum16",
24525                            "FIXEDSTRING" => "FixedString",
24526                            "NESTED" => "Nested",
24527                            "LOWCARDINALITY" => "LowCardinality",
24528                            "NULLABLE" => "Nullable",
24529                            "IPV4" => "IPv4",
24530                            "IPV6" => "IPv6",
24531                            "POINT" => "Point",
24532                            "RING" => "Ring",
24533                            "LINESTRING" => "LineString",
24534                            "MULTILINESTRING" => "MultiLineString",
24535                            "POLYGON" => "Polygon",
24536                            "MULTIPOLYGON" => "MultiPolygon",
24537                            "AGGREGATEFUNCTION" => "AggregateFunction",
24538                            "SIMPLEAGGREGATEFUNCTION" => "SimpleAggregateFunction",
24539                            "DYNAMIC" => "Dynamic",
24540                            _ => "",
24541                        };
24542                        if mapped.is_empty() {
24543                            self.write(name);
24544                        } else {
24545                            self.write(mapped);
24546                            if matches!(base_upper.as_str(), "ENUM8" | "ENUM16")
24547                                && !suffix.is_empty()
24548                            {
24549                                let escaped_suffix = suffix
24550                                    .replace('\\', "\\\\")
24551                                    .replace('\t', "\\t")
24552                                    .replace('\n', "\\n")
24553                                    .replace('\r', "\\r");
24554                                self.write(&escaped_suffix);
24555                            } else {
24556                                self.write(suffix);
24557                            }
24558                        }
24559                    }
24560                    Some(DialectType::MySQL)
24561                        if name_upper == "TIMESTAMPTZ" || name_upper == "TIMESTAMPLTZ" =>
24562                    {
24563                        // MySQL doesn't support TIMESTAMPTZ/TIMESTAMPLTZ, use TIMESTAMP
24564                        self.write_keyword("TIMESTAMP");
24565                    }
24566                    Some(DialectType::TSQL) if name_upper == "VARIANT" => {
24567                        self.write_keyword("SQL_VARIANT");
24568                    }
24569                    Some(DialectType::DuckDB) if name_upper == "DECFLOAT" => {
24570                        self.write_keyword("DECIMAL(38, 5)");
24571                    }
24572                    Some(DialectType::Exasol) => {
24573                        // Exasol type mappings for custom types
24574                        match name_upper.as_str() {
24575                            // Binary types → VARCHAR
24576                            "LONGBLOB" | "MEDIUMBLOB" | "TINYBLOB" => self.write_keyword("VARCHAR"),
24577                            // Text types → VARCHAR (TEXT → LONG VARCHAR is handled by DataType::Text)
24578                            "LONGTEXT" | "MEDIUMTEXT" | "TINYTEXT" => self.write_keyword("VARCHAR"),
24579                            // Integer types
24580                            "MEDIUMINT" => self.write_keyword("INT"),
24581                            // Decimal types → DECIMAL
24582                            "DECIMAL32" | "DECIMAL64" | "DECIMAL128" | "DECIMAL256" => {
24583                                self.write_keyword("DECIMAL")
24584                            }
24585                            // Timestamp types
24586                            "DATETIME" => self.write_keyword("TIMESTAMP"),
24587                            "TIMESTAMPLTZ" => self.write_keyword("TIMESTAMP WITH LOCAL TIME ZONE"),
24588                            _ => self.write(name),
24589                        }
24590                    }
24591                    Some(DialectType::Dremio) => {
24592                        // Dremio type mappings for custom types
24593                        match name_upper.as_str() {
24594                            "TIMESTAMPNTZ" | "DATETIME" => self.write_keyword("TIMESTAMP"),
24595                            "ARRAY" => self.write_keyword("LIST"),
24596                            "NCHAR" => self.write_keyword("VARCHAR"),
24597                            _ => self.write(name),
24598                        }
24599                    }
24600                    // Map dialect-specific custom types to standard SQL types for other dialects
24601                    _ => {
24602                        // Extract base name and args for types with parenthesized args (e.g., DATETIME2(3))
24603                        let (base_upper, _args_str) = if let Some(idx) = name_upper.find('(') {
24604                            (name_upper[..idx].to_string(), Some(&name[idx..]))
24605                        } else {
24606                            (name_upper.clone(), None)
24607                        };
24608
24609                        match base_upper.as_str() {
24610                            "INT64"
24611                                if !matches!(self.config.dialect, Some(DialectType::BigQuery)) =>
24612                            {
24613                                self.write_keyword("BIGINT");
24614                            }
24615                            "FLOAT64"
24616                                if !matches!(self.config.dialect, Some(DialectType::BigQuery)) =>
24617                            {
24618                                self.write_keyword("DOUBLE");
24619                            }
24620                            "BOOL"
24621                                if !matches!(self.config.dialect, Some(DialectType::BigQuery)) =>
24622                            {
24623                                self.write_keyword("BOOLEAN");
24624                            }
24625                            "BYTES"
24626                                if matches!(
24627                                    self.config.dialect,
24628                                    Some(DialectType::Spark)
24629                                        | Some(DialectType::Hive)
24630                                        | Some(DialectType::Databricks)
24631                                ) =>
24632                            {
24633                                self.write_keyword("BINARY");
24634                            }
24635                            "BYTES"
24636                                if !matches!(self.config.dialect, Some(DialectType::BigQuery)) =>
24637                            {
24638                                self.write_keyword("VARBINARY");
24639                            }
24640                            // TSQL DATETIME2/SMALLDATETIME -> TIMESTAMP
24641                            "DATETIME2" | "SMALLDATETIME"
24642                                if !matches!(
24643                                    self.config.dialect,
24644                                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
24645                                ) =>
24646                            {
24647                                // PostgreSQL preserves precision, others don't
24648                                if matches!(
24649                                    self.config.dialect,
24650                                    Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
24651                                ) {
24652                                    self.write_keyword("TIMESTAMP");
24653                                    if let Some(args) = _args_str {
24654                                        self.write(args);
24655                                    }
24656                                } else {
24657                                    self.write_keyword("TIMESTAMP");
24658                                }
24659                            }
24660                            // TSQL DATETIMEOFFSET -> TIMESTAMPTZ
24661                            "DATETIMEOFFSET"
24662                                if !matches!(
24663                                    self.config.dialect,
24664                                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
24665                                ) =>
24666                            {
24667                                if matches!(
24668                                    self.config.dialect,
24669                                    Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
24670                                ) {
24671                                    self.write_keyword("TIMESTAMPTZ");
24672                                    if let Some(args) = _args_str {
24673                                        self.write(args);
24674                                    }
24675                                } else {
24676                                    self.write_keyword("TIMESTAMPTZ");
24677                                }
24678                            }
24679                            // TSQL UNIQUEIDENTIFIER -> UUID or STRING
24680                            "UNIQUEIDENTIFIER"
24681                                if !matches!(
24682                                    self.config.dialect,
24683                                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
24684                                ) =>
24685                            {
24686                                match self.config.dialect {
24687                                    Some(DialectType::Spark)
24688                                    | Some(DialectType::Databricks)
24689                                    | Some(DialectType::Hive) => self.write_keyword("STRING"),
24690                                    _ => self.write_keyword("UUID"),
24691                                }
24692                            }
24693                            // TSQL BIT -> BOOLEAN for most dialects
24694                            "BIT"
24695                                if !matches!(
24696                                    self.config.dialect,
24697                                    Some(DialectType::TSQL)
24698                                        | Some(DialectType::Fabric)
24699                                        | Some(DialectType::PostgreSQL)
24700                                        | Some(DialectType::MySQL)
24701                                        | Some(DialectType::DuckDB)
24702                                ) =>
24703                            {
24704                                self.write_keyword("BOOLEAN");
24705                            }
24706                            // TSQL NVARCHAR -> VARCHAR (with default size 30 for some dialects)
24707                            "NVARCHAR"
24708                                if !matches!(
24709                                    self.config.dialect,
24710                                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
24711                                ) =>
24712                            {
24713                                match self.config.dialect {
24714                                    Some(DialectType::Oracle) => {
24715                                        // Oracle: NVARCHAR -> NVARCHAR2
24716                                        self.write_keyword("NVARCHAR2");
24717                                        if let Some(args) = _args_str {
24718                                            self.write(args);
24719                                        }
24720                                    }
24721                                    Some(DialectType::BigQuery) => {
24722                                        // BigQuery: NVARCHAR -> STRING
24723                                        self.write_keyword("STRING");
24724                                    }
24725                                    Some(DialectType::SQLite) | Some(DialectType::DuckDB) => {
24726                                        self.write_keyword("TEXT");
24727                                        if let Some(args) = _args_str {
24728                                            self.write(args);
24729                                        }
24730                                    }
24731                                    Some(DialectType::Hive) => {
24732                                        // Hive: NVARCHAR -> STRING
24733                                        self.write_keyword("STRING");
24734                                    }
24735                                    Some(DialectType::Spark) | Some(DialectType::Databricks) => {
24736                                        if _args_str.is_some() {
24737                                            self.write_keyword("VARCHAR");
24738                                            self.write(_args_str.unwrap());
24739                                        } else {
24740                                            self.write_keyword("STRING");
24741                                        }
24742                                    }
24743                                    _ => {
24744                                        self.write_keyword("VARCHAR");
24745                                        if let Some(args) = _args_str {
24746                                            self.write(args);
24747                                        }
24748                                    }
24749                                }
24750                            }
24751                            // NCHAR -> CHAR (NCHAR for Oracle/TSQL, STRING for BigQuery/Hive)
24752                            "NCHAR"
24753                                if !matches!(
24754                                    self.config.dialect,
24755                                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
24756                                ) =>
24757                            {
24758                                match self.config.dialect {
24759                                    Some(DialectType::Oracle) => {
24760                                        // Oracle natively supports NCHAR
24761                                        self.write_keyword("NCHAR");
24762                                        if let Some(args) = _args_str {
24763                                            self.write(args);
24764                                        }
24765                                    }
24766                                    Some(DialectType::BigQuery) => {
24767                                        // BigQuery: NCHAR -> STRING
24768                                        self.write_keyword("STRING");
24769                                    }
24770                                    Some(DialectType::Hive) => {
24771                                        // Hive: NCHAR -> STRING
24772                                        self.write_keyword("STRING");
24773                                    }
24774                                    Some(DialectType::SQLite) | Some(DialectType::DuckDB) => {
24775                                        self.write_keyword("TEXT");
24776                                        if let Some(args) = _args_str {
24777                                            self.write(args);
24778                                        }
24779                                    }
24780                                    Some(DialectType::Spark) | Some(DialectType::Databricks) => {
24781                                        if _args_str.is_some() {
24782                                            self.write_keyword("CHAR");
24783                                            self.write(_args_str.unwrap());
24784                                        } else {
24785                                            self.write_keyword("STRING");
24786                                        }
24787                                    }
24788                                    _ => {
24789                                        self.write_keyword("CHAR");
24790                                        if let Some(args) = _args_str {
24791                                            self.write(args);
24792                                        }
24793                                    }
24794                                }
24795                            }
24796                            // MySQL text variant types -> map to appropriate target type
24797                            // For MySQL/SingleStore: keep original name (column definitions), CAST handling is in generate_cast
24798                            "LONGTEXT" | "MEDIUMTEXT" | "TINYTEXT" => match self.config.dialect {
24799                                Some(DialectType::MySQL)
24800                                | Some(DialectType::SingleStore)
24801                                | Some(DialectType::TiDB) => self.write_keyword(&base_upper),
24802                                Some(DialectType::Spark)
24803                                | Some(DialectType::Databricks)
24804                                | Some(DialectType::Hive) => self.write_keyword("TEXT"),
24805                                Some(DialectType::BigQuery) => self.write_keyword("STRING"),
24806                                Some(DialectType::Presto)
24807                                | Some(DialectType::Trino)
24808                                | Some(DialectType::Athena) => self.write_keyword("VARCHAR"),
24809                                Some(DialectType::Snowflake)
24810                                | Some(DialectType::Redshift)
24811                                | Some(DialectType::Dremio) => self.write_keyword("VARCHAR"),
24812                                _ => self.write_keyword("TEXT"),
24813                            },
24814                            // MySQL blob variant types -> map to appropriate target type
24815                            // For MySQL/SingleStore: keep original name (column definitions), CAST handling is in generate_cast
24816                            "LONGBLOB" | "MEDIUMBLOB" | "TINYBLOB" => match self.config.dialect {
24817                                Some(DialectType::MySQL)
24818                                | Some(DialectType::SingleStore)
24819                                | Some(DialectType::TiDB) => self.write_keyword(&base_upper),
24820                                Some(DialectType::Spark)
24821                                | Some(DialectType::Databricks)
24822                                | Some(DialectType::Hive) => self.write_keyword("BLOB"),
24823                                Some(DialectType::DuckDB) => self.write_keyword("VARBINARY"),
24824                                Some(DialectType::BigQuery) => self.write_keyword("BYTES"),
24825                                Some(DialectType::Presto)
24826                                | Some(DialectType::Trino)
24827                                | Some(DialectType::Athena) => self.write_keyword("VARBINARY"),
24828                                Some(DialectType::Snowflake)
24829                                | Some(DialectType::Redshift)
24830                                | Some(DialectType::Dremio) => self.write_keyword("VARBINARY"),
24831                                _ => self.write_keyword("BLOB"),
24832                            },
24833                            // LONGVARCHAR -> TEXT for SQLite, VARCHAR for others
24834                            "LONGVARCHAR" => match self.config.dialect {
24835                                Some(DialectType::SQLite) => self.write_keyword("TEXT"),
24836                                _ => self.write_keyword("VARCHAR"),
24837                            },
24838                            // DATETIME -> TIMESTAMP for most, DATETIME for MySQL/Doris/StarRocks/Snowflake
24839                            "DATETIME" => {
24840                                match self.config.dialect {
24841                                    Some(DialectType::MySQL)
24842                                    | Some(DialectType::Doris)
24843                                    | Some(DialectType::StarRocks)
24844                                    | Some(DialectType::TSQL)
24845                                    | Some(DialectType::Fabric)
24846                                    | Some(DialectType::BigQuery)
24847                                    | Some(DialectType::SQLite)
24848                                    | Some(DialectType::Snowflake) => {
24849                                        self.write_keyword("DATETIME");
24850                                        if let Some(args) = _args_str {
24851                                            self.write(args);
24852                                        }
24853                                    }
24854                                    Some(_) => {
24855                                        // Only map to TIMESTAMP when we have a specific target dialect
24856                                        self.write_keyword("TIMESTAMP");
24857                                        if let Some(args) = _args_str {
24858                                            self.write(args);
24859                                        }
24860                                    }
24861                                    None => {
24862                                        // No dialect - preserve original
24863                                        self.write(name);
24864                                    }
24865                                }
24866                            }
24867                            // VARCHAR2/NVARCHAR2 (Oracle) -> VARCHAR for non-Oracle targets
24868                            "VARCHAR2"
24869                                if !matches!(self.config.dialect, Some(DialectType::Oracle)) =>
24870                            {
24871                                match self.config.dialect {
24872                                    Some(DialectType::DuckDB) | Some(DialectType::SQLite) => {
24873                                        self.write_keyword("TEXT");
24874                                    }
24875                                    Some(DialectType::Hive)
24876                                    | Some(DialectType::Spark)
24877                                    | Some(DialectType::Databricks)
24878                                    | Some(DialectType::BigQuery)
24879                                    | Some(DialectType::ClickHouse)
24880                                    | Some(DialectType::StarRocks)
24881                                    | Some(DialectType::Doris) => {
24882                                        self.write_keyword("STRING");
24883                                    }
24884                                    _ => {
24885                                        self.write_keyword("VARCHAR");
24886                                        if let Some(args) = _args_str {
24887                                            self.write(args);
24888                                        }
24889                                    }
24890                                }
24891                            }
24892                            "NVARCHAR2"
24893                                if !matches!(self.config.dialect, Some(DialectType::Oracle)) =>
24894                            {
24895                                match self.config.dialect {
24896                                    Some(DialectType::DuckDB) | Some(DialectType::SQLite) => {
24897                                        self.write_keyword("TEXT");
24898                                    }
24899                                    Some(DialectType::Hive)
24900                                    | Some(DialectType::Spark)
24901                                    | Some(DialectType::Databricks)
24902                                    | Some(DialectType::BigQuery)
24903                                    | Some(DialectType::ClickHouse)
24904                                    | Some(DialectType::StarRocks)
24905                                    | Some(DialectType::Doris) => {
24906                                        self.write_keyword("STRING");
24907                                    }
24908                                    _ => {
24909                                        self.write_keyword("VARCHAR");
24910                                        if let Some(args) = _args_str {
24911                                            self.write(args);
24912                                        }
24913                                    }
24914                                }
24915                            }
24916                            _ => self.write(name),
24917                        }
24918                    }
24919                }
24920            }
24921            DataType::Geometry { subtype, srid } => {
24922                // Dialect-specific geometry type mappings
24923                match self.config.dialect {
24924                    Some(DialectType::MySQL) => {
24925                        // MySQL uses POINT SRID 4326 syntax for specific types
24926                        if let Some(sub) = subtype {
24927                            self.write_keyword(sub);
24928                            if let Some(s) = srid {
24929                                self.write(" SRID ");
24930                                self.write(&s.to_string());
24931                            }
24932                        } else {
24933                            self.write_keyword("GEOMETRY");
24934                        }
24935                    }
24936                    Some(DialectType::BigQuery) => {
24937                        // BigQuery only supports GEOGRAPHY, not GEOMETRY
24938                        self.write_keyword("GEOGRAPHY");
24939                    }
24940                    Some(DialectType::Teradata) => {
24941                        // Teradata uses ST_GEOMETRY
24942                        self.write_keyword("ST_GEOMETRY");
24943                        if subtype.is_some() || srid.is_some() {
24944                            self.write("(");
24945                            if let Some(sub) = subtype {
24946                                self.write_keyword(sub);
24947                            }
24948                            if let Some(s) = srid {
24949                                if subtype.is_some() {
24950                                    self.write(", ");
24951                                }
24952                                self.write(&s.to_string());
24953                            }
24954                            self.write(")");
24955                        }
24956                    }
24957                    _ => {
24958                        // PostgreSQL, Snowflake, DuckDB use GEOMETRY(subtype, srid) syntax
24959                        self.write_keyword("GEOMETRY");
24960                        if subtype.is_some() || srid.is_some() {
24961                            self.write("(");
24962                            if let Some(sub) = subtype {
24963                                self.write_keyword(sub);
24964                            }
24965                            if let Some(s) = srid {
24966                                if subtype.is_some() {
24967                                    self.write(", ");
24968                                }
24969                                self.write(&s.to_string());
24970                            }
24971                            self.write(")");
24972                        }
24973                    }
24974                }
24975            }
24976            DataType::Geography { subtype, srid } => {
24977                // Dialect-specific geography type mappings
24978                match self.config.dialect {
24979                    Some(DialectType::MySQL) => {
24980                        // MySQL doesn't have native GEOGRAPHY, use GEOMETRY with SRID 4326
24981                        if let Some(sub) = subtype {
24982                            self.write_keyword(sub);
24983                        } else {
24984                            self.write_keyword("GEOMETRY");
24985                        }
24986                        // Geography implies SRID 4326 (WGS84)
24987                        let effective_srid = srid.unwrap_or(4326);
24988                        self.write(" SRID ");
24989                        self.write(&effective_srid.to_string());
24990                    }
24991                    Some(DialectType::BigQuery) => {
24992                        // BigQuery uses simple GEOGRAPHY without parameters
24993                        self.write_keyword("GEOGRAPHY");
24994                    }
24995                    Some(DialectType::Snowflake) => {
24996                        // Snowflake uses GEOGRAPHY without parameters
24997                        self.write_keyword("GEOGRAPHY");
24998                    }
24999                    _ => {
25000                        // PostgreSQL uses GEOGRAPHY(subtype, srid) syntax
25001                        self.write_keyword("GEOGRAPHY");
25002                        if subtype.is_some() || srid.is_some() {
25003                            self.write("(");
25004                            if let Some(sub) = subtype {
25005                                self.write_keyword(sub);
25006                            }
25007                            if let Some(s) = srid {
25008                                if subtype.is_some() {
25009                                    self.write(", ");
25010                                }
25011                                self.write(&s.to_string());
25012                            }
25013                            self.write(")");
25014                        }
25015                    }
25016                }
25017            }
25018            DataType::CharacterSet { name } => {
25019                // For MySQL CONVERT USING - output as CHAR CHARACTER SET name
25020                self.write_keyword("CHAR CHARACTER SET ");
25021                self.write(name);
25022            }
25023            _ => self.write("UNKNOWN"),
25024        }
25025        Ok(())
25026    }
25027
25028    // === Helper methods ===
25029
25030    #[inline]
25031    fn write(&mut self, s: &str) {
25032        self.output.push_str(s);
25033    }
25034
25035    #[inline]
25036    fn write_space(&mut self) {
25037        self.output.push(' ');
25038    }
25039
25040    #[inline]
25041    fn write_keyword(&mut self, keyword: &str) {
25042        if self.config.uppercase_keywords {
25043            self.output.push_str(keyword);
25044        } else {
25045            for b in keyword.bytes() {
25046                self.output.push(b.to_ascii_lowercase() as char);
25047            }
25048        }
25049    }
25050
25051    /// Write a function name respecting the normalize_functions config setting
25052    fn write_func_name(&mut self, name: &str) {
25053        let normalized = self.normalize_func_name(name);
25054        self.output.push_str(normalized.as_ref());
25055    }
25056
25057    /// Convert strptime format string to Exasol format string
25058    /// Exasol TIME_MAPPING (reverse of Python sqlglot):
25059    /// %Y -> YYYY, %y -> YY, %m -> MM, %d -> DD, %H -> HH, %M -> MI, %S -> SS, %a -> DY
25060    fn convert_strptime_to_exasol_format(format: &str) -> String {
25061        let mut result = String::new();
25062        let chars: Vec<char> = format.chars().collect();
25063        let mut i = 0;
25064        while i < chars.len() {
25065            if chars[i] == '%' && i + 1 < chars.len() {
25066                let spec = chars[i + 1];
25067                let exasol_spec = match spec {
25068                    'Y' => "YYYY",
25069                    'y' => "YY",
25070                    'm' => "MM",
25071                    'd' => "DD",
25072                    'H' => "HH",
25073                    'M' => "MI",
25074                    'S' => "SS",
25075                    'a' => "DY",    // abbreviated weekday name
25076                    'A' => "DAY",   // full weekday name
25077                    'b' => "MON",   // abbreviated month name
25078                    'B' => "MONTH", // full month name
25079                    'I' => "H12",   // 12-hour format
25080                    'u' => "ID",    // ISO weekday (1-7)
25081                    'V' => "IW",    // ISO week number
25082                    'G' => "IYYY",  // ISO year
25083                    'W' => "UW",    // Week number (Monday as first day)
25084                    'U' => "UW",    // Week number (Sunday as first day)
25085                    'z' => "Z",     // timezone offset
25086                    _ => {
25087                        // Unknown specifier, keep as-is
25088                        result.push('%');
25089                        result.push(spec);
25090                        i += 2;
25091                        continue;
25092                    }
25093                };
25094                result.push_str(exasol_spec);
25095                i += 2;
25096            } else {
25097                result.push(chars[i]);
25098                i += 1;
25099            }
25100        }
25101        result
25102    }
25103
25104    /// Convert strptime format string to PostgreSQL/Redshift format string
25105    /// PostgreSQL INVERSE_TIME_MAPPING from Python sqlglot:
25106    /// %Y -> YYYY, %y -> YY, %m -> MM, %d -> DD, %H -> HH24, %M -> MI, %S -> SS, %f -> US, etc.
25107    fn convert_strptime_to_postgres_format(format: &str) -> String {
25108        let mut result = String::new();
25109        let chars: Vec<char> = format.chars().collect();
25110        let mut i = 0;
25111        while i < chars.len() {
25112            if chars[i] == '%' && i + 1 < chars.len() {
25113                // Check for %-d, %-m, etc. (non-padded, 3-char sequence)
25114                if chars[i + 1] == '-' && i + 2 < chars.len() {
25115                    let spec = chars[i + 2];
25116                    let pg_spec = match spec {
25117                        'd' => "FMDD",
25118                        'm' => "FMMM",
25119                        'H' => "FMHH24",
25120                        'M' => "FMMI",
25121                        'S' => "FMSS",
25122                        _ => {
25123                            result.push('%');
25124                            result.push('-');
25125                            result.push(spec);
25126                            i += 3;
25127                            continue;
25128                        }
25129                    };
25130                    result.push_str(pg_spec);
25131                    i += 3;
25132                    continue;
25133                }
25134                let spec = chars[i + 1];
25135                let pg_spec = match spec {
25136                    'Y' => "YYYY",
25137                    'y' => "YY",
25138                    'm' => "MM",
25139                    'd' => "DD",
25140                    'H' => "HH24",
25141                    'I' => "HH12",
25142                    'M' => "MI",
25143                    'S' => "SS",
25144                    'f' => "US",      // microseconds
25145                    'u' => "D",       // day of week (1=Monday)
25146                    'j' => "DDD",     // day of year
25147                    'z' => "OF",      // UTC offset
25148                    'Z' => "TZ",      // timezone name
25149                    'A' => "TMDay",   // full weekday name
25150                    'a' => "TMDy",    // abbreviated weekday name
25151                    'b' => "TMMon",   // abbreviated month name
25152                    'B' => "TMMonth", // full month name
25153                    'U' => "WW",      // week number
25154                    _ => {
25155                        // Unknown specifier, keep as-is
25156                        result.push('%');
25157                        result.push(spec);
25158                        i += 2;
25159                        continue;
25160                    }
25161                };
25162                result.push_str(pg_spec);
25163                i += 2;
25164            } else {
25165                result.push(chars[i]);
25166                i += 1;
25167            }
25168        }
25169        result
25170    }
25171
25172    /// Write a LIMIT expression value, evaluating constant expressions if limit_only_literals is set
25173    fn write_limit_expr(&mut self, expr: &Expression) -> Result<()> {
25174        if self.config.limit_only_literals {
25175            if let Some(value) = Self::try_evaluate_constant(expr) {
25176                self.write(&value.to_string());
25177                return Ok(());
25178            }
25179        }
25180        self.generate_expression(expr)
25181    }
25182
25183    /// Format a comment with proper spacing.
25184    /// Converts `/*text*/` to `/* text */` (adding internal spaces if not present).
25185    /// Python SQLGlot normalizes comment format to have spaces inside block comments.
25186    fn write_formatted_comment(&mut self, comment: &str) {
25187        // Normalize all comments to block comment format /* ... */
25188        // This matches Python sqlglot behavior which always outputs block comments
25189        let content = if comment.starts_with("/*") && comment.ends_with("*/") {
25190            // Already block comment - extract inner content
25191            // Preserve internal whitespace, but ensure at least one space padding
25192            &comment[2..comment.len() - 2]
25193        } else if comment.starts_with("--") {
25194            // Line comment - extract content after --
25195            // Preserve internal whitespace (e.g., "--       x" -> "/*       x */")
25196            &comment[2..]
25197        } else {
25198            // Raw content (no delimiters)
25199            comment
25200        };
25201        // Skip empty comments (e.g., bare "--" with no content)
25202        if content.trim().is_empty() {
25203            return;
25204        }
25205        // Escape nested block comment markers to prevent premature closure or unintended nesting.
25206        // This matches Python sqlglot's sanitize_comment behavior.
25207        let sanitized = content.replace("*/", "* /").replace("/*", "/ *");
25208        let content = &sanitized;
25209        // Ensure at least one space after /* and before */
25210        self.output.push_str("/*");
25211        if !content.starts_with(' ') {
25212            self.output.push(' ');
25213        }
25214        self.output.push_str(content);
25215        if !content.ends_with(' ') {
25216            self.output.push(' ');
25217        }
25218        self.output.push_str("*/");
25219    }
25220
25221    /// Escape a raw block content (from dollar-quoted string) for single-quoted output.
25222    /// Escapes single quotes with backslash, and for Snowflake also escapes backslashes.
25223    fn escape_block_for_single_quote(&self, block: &str) -> String {
25224        let escape_backslash = matches!(
25225            self.config.dialect,
25226            Some(crate::dialects::DialectType::Snowflake)
25227        );
25228        let mut escaped = String::with_capacity(block.len() + 4);
25229        for ch in block.chars() {
25230            if ch == '\'' {
25231                escaped.push('\\');
25232                escaped.push('\'');
25233            } else if escape_backslash && ch == '\\' {
25234                escaped.push('\\');
25235                escaped.push('\\');
25236            } else {
25237                escaped.push(ch);
25238            }
25239        }
25240        escaped
25241    }
25242
25243    fn write_newline(&mut self) {
25244        self.output.push('\n');
25245    }
25246
25247    fn write_indent(&mut self) {
25248        for _ in 0..self.indent_level {
25249            self.output.push_str(self.config.indent);
25250        }
25251    }
25252
25253    // === SQLGlot-style pretty printing helpers ===
25254
25255    /// Returns the separator string for pretty printing.
25256    /// Check if the total length of arguments exceeds max_text_width.
25257    /// Used for dynamic line breaking in expressions() formatting.
25258    fn too_wide(&self, args: &[String]) -> bool {
25259        args.iter().map(|s| s.len()).sum::<usize>() > self.config.max_text_width
25260    }
25261
25262    /// Generate an expression to a string using a temporary non-pretty generator.
25263    /// Useful for width calculations before deciding on formatting.
25264    fn generate_to_string(&self, expr: &Expression) -> Result<String> {
25265        let config = GeneratorConfig {
25266            pretty: false,
25267            dialect: self.config.dialect,
25268            ..Default::default()
25269        };
25270        let mut gen = Generator::with_config(config);
25271        gen.generate_expression(expr)?;
25272        Ok(gen.output)
25273    }
25274
25275    /// Writes a clause with a single condition (WHERE, HAVING, QUALIFY).
25276    /// In pretty mode: newline + indented keyword + newline + indented condition
25277    fn write_clause_condition(&mut self, keyword: &str, condition: &Expression) -> Result<()> {
25278        if self.config.pretty {
25279            self.write_newline();
25280            self.write_indent();
25281            self.write_keyword(keyword);
25282            self.write_newline();
25283            self.indent_level += 1;
25284            self.write_indent();
25285            self.generate_expression(condition)?;
25286            self.indent_level -= 1;
25287        } else {
25288            self.write_space();
25289            self.write_keyword(keyword);
25290            self.write_space();
25291            self.generate_expression(condition)?;
25292        }
25293        Ok(())
25294    }
25295
25296    /// Writes a clause with a list of expressions (GROUP BY, DISTRIBUTE BY, CLUSTER BY).
25297    /// In pretty mode: each expression on new line with indentation
25298    fn write_clause_expressions(&mut self, keyword: &str, exprs: &[Expression]) -> Result<()> {
25299        if exprs.is_empty() {
25300            return Ok(());
25301        }
25302
25303        if self.config.pretty {
25304            self.write_newline();
25305            self.write_indent();
25306            self.write_keyword(keyword);
25307            self.write_newline();
25308            self.indent_level += 1;
25309            for (i, expr) in exprs.iter().enumerate() {
25310                if i > 0 {
25311                    self.write(",");
25312                    self.write_newline();
25313                }
25314                self.write_indent();
25315                self.generate_expression(expr)?;
25316            }
25317            self.indent_level -= 1;
25318        } else {
25319            self.write_space();
25320            self.write_keyword(keyword);
25321            self.write_space();
25322            for (i, expr) in exprs.iter().enumerate() {
25323                if i > 0 {
25324                    self.write(", ");
25325                }
25326                self.generate_expression(expr)?;
25327            }
25328        }
25329        Ok(())
25330    }
25331
25332    /// Writes ORDER BY / SORT BY clause with Ordered expressions
25333    fn write_order_clause(&mut self, keyword: &str, orderings: &[Ordered]) -> Result<()> {
25334        if orderings.is_empty() {
25335            return Ok(());
25336        }
25337
25338        if self.config.pretty {
25339            self.write_newline();
25340            self.write_indent();
25341            self.write_keyword(keyword);
25342            self.write_newline();
25343            self.indent_level += 1;
25344            for (i, ordered) in orderings.iter().enumerate() {
25345                if i > 0 {
25346                    self.write(",");
25347                    self.write_newline();
25348                }
25349                self.write_indent();
25350                self.generate_ordered(ordered)?;
25351            }
25352            self.indent_level -= 1;
25353        } else {
25354            self.write_space();
25355            self.write_keyword(keyword);
25356            self.write_space();
25357            for (i, ordered) in orderings.iter().enumerate() {
25358                if i > 0 {
25359                    self.write(", ");
25360                }
25361                self.generate_ordered(ordered)?;
25362            }
25363        }
25364        Ok(())
25365    }
25366
25367    /// Writes WINDOW clause with named window definitions
25368    fn write_window_clause(&mut self, windows: &[NamedWindow]) -> Result<()> {
25369        if windows.is_empty() {
25370            return Ok(());
25371        }
25372
25373        if self.config.pretty {
25374            self.write_newline();
25375            self.write_indent();
25376            self.write_keyword("WINDOW");
25377            self.write_newline();
25378            self.indent_level += 1;
25379            for (i, named_window) in windows.iter().enumerate() {
25380                if i > 0 {
25381                    self.write(",");
25382                    self.write_newline();
25383                }
25384                self.write_indent();
25385                self.generate_identifier(&named_window.name)?;
25386                self.write_space();
25387                self.write_keyword("AS");
25388                self.write(" (");
25389                self.generate_over(&named_window.spec)?;
25390                self.write(")");
25391            }
25392            self.indent_level -= 1;
25393        } else {
25394            self.write_space();
25395            self.write_keyword("WINDOW");
25396            self.write_space();
25397            for (i, named_window) in windows.iter().enumerate() {
25398                if i > 0 {
25399                    self.write(", ");
25400                }
25401                self.generate_identifier(&named_window.name)?;
25402                self.write_space();
25403                self.write_keyword("AS");
25404                self.write(" (");
25405                self.generate_over(&named_window.spec)?;
25406                self.write(")");
25407            }
25408        }
25409        Ok(())
25410    }
25411
25412    // === BATCH-GENERATED STUB METHODS (481 variants) ===
25413    fn generate_ai_agg(&mut self, e: &AIAgg) -> Result<()> {
25414        // AI_AGG(this, expression)
25415        self.write_keyword("AI_AGG");
25416        self.write("(");
25417        self.generate_expression(&e.this)?;
25418        self.write(", ");
25419        self.generate_expression(&e.expression)?;
25420        self.write(")");
25421        Ok(())
25422    }
25423
25424    fn generate_ai_classify(&mut self, e: &AIClassify) -> Result<()> {
25425        // AI_CLASSIFY(input, [categories], [config])
25426        self.write_keyword("AI_CLASSIFY");
25427        self.write("(");
25428        self.generate_expression(&e.this)?;
25429        if let Some(categories) = &e.categories {
25430            self.write(", ");
25431            self.generate_expression(categories)?;
25432        }
25433        if let Some(config) = &e.config {
25434            self.write(", ");
25435            self.generate_expression(config)?;
25436        }
25437        self.write(")");
25438        Ok(())
25439    }
25440
25441    fn generate_add_partition(&mut self, e: &AddPartition) -> Result<()> {
25442        // Python: return f"ADD {exists}{self.sql(expression.this)}{location}"
25443        self.write_keyword("ADD");
25444        self.write_space();
25445        if e.exists {
25446            self.write_keyword("IF NOT EXISTS");
25447            self.write_space();
25448        }
25449        self.generate_expression(&e.this)?;
25450        if let Some(location) = &e.location {
25451            self.write_space();
25452            self.generate_expression(location)?;
25453        }
25454        Ok(())
25455    }
25456
25457    fn generate_algorithm_property(&mut self, e: &AlgorithmProperty) -> Result<()> {
25458        // Python: return f"ALGORITHM={self.sql(expression, 'this')}"
25459        self.write_keyword("ALGORITHM");
25460        self.write("=");
25461        self.generate_expression(&e.this)?;
25462        Ok(())
25463    }
25464
25465    fn generate_aliases(&mut self, e: &Aliases) -> Result<()> {
25466        // Python: return f"{self.sql(expression, 'this')} AS ({self.expressions(expression, flat=True)})"
25467        self.generate_expression(&e.this)?;
25468        self.write_space();
25469        self.write_keyword("AS");
25470        self.write(" (");
25471        for (i, expr) in e.expressions.iter().enumerate() {
25472            if i > 0 {
25473                self.write(", ");
25474            }
25475            self.generate_expression(expr)?;
25476        }
25477        self.write(")");
25478        Ok(())
25479    }
25480
25481    fn generate_allowed_values_property(&mut self, e: &AllowedValuesProperty) -> Result<()> {
25482        // Python: return f"ALLOWED_VALUES {self.expressions(e, flat=True)}"
25483        self.write_keyword("ALLOWED_VALUES");
25484        self.write_space();
25485        for (i, expr) in e.expressions.iter().enumerate() {
25486            if i > 0 {
25487                self.write(", ");
25488            }
25489            self.generate_expression(expr)?;
25490        }
25491        Ok(())
25492    }
25493
25494    fn generate_alter_column(&mut self, e: &AlterColumn) -> Result<()> {
25495        // Python: complex logic based on dtype, default, comment, visible, etc.
25496        self.write_keyword("ALTER COLUMN");
25497        self.write_space();
25498        self.generate_expression(&e.this)?;
25499
25500        if let Some(dtype) = &e.dtype {
25501            self.write_space();
25502            self.write_keyword("SET DATA TYPE");
25503            self.write_space();
25504            self.generate_expression(dtype)?;
25505            if let Some(collate) = &e.collate {
25506                self.write_space();
25507                self.write_keyword("COLLATE");
25508                self.write_space();
25509                self.generate_expression(collate)?;
25510            }
25511            if let Some(using) = &e.using {
25512                self.write_space();
25513                self.write_keyword("USING");
25514                self.write_space();
25515                self.generate_expression(using)?;
25516            }
25517        } else if let Some(default) = &e.default {
25518            self.write_space();
25519            self.write_keyword("SET DEFAULT");
25520            self.write_space();
25521            self.generate_expression(default)?;
25522        } else if let Some(comment) = &e.comment {
25523            self.write_space();
25524            self.write_keyword("COMMENT");
25525            self.write_space();
25526            self.generate_expression(comment)?;
25527        } else if let Some(drop) = &e.drop {
25528            self.write_space();
25529            self.write_keyword("DROP");
25530            self.write_space();
25531            self.generate_expression(drop)?;
25532        } else if let Some(visible) = &e.visible {
25533            self.write_space();
25534            self.generate_expression(visible)?;
25535        } else if let Some(rename_to) = &e.rename_to {
25536            self.write_space();
25537            self.write_keyword("RENAME TO");
25538            self.write_space();
25539            self.generate_expression(rename_to)?;
25540        } else if let Some(allow_null) = &e.allow_null {
25541            self.write_space();
25542            self.generate_expression(allow_null)?;
25543        }
25544        Ok(())
25545    }
25546
25547    fn generate_alter_session(&mut self, e: &AlterSession) -> Result<()> {
25548        // Python: keyword = "UNSET" if expression.args.get("unset") else "SET"; return f"{keyword} {items_sql}"
25549        self.write_keyword("ALTER SESSION");
25550        self.write_space();
25551        if e.unset.is_some() {
25552            self.write_keyword("UNSET");
25553        } else {
25554            self.write_keyword("SET");
25555        }
25556        self.write_space();
25557        for (i, expr) in e.expressions.iter().enumerate() {
25558            if i > 0 {
25559                self.write(", ");
25560            }
25561            self.generate_expression(expr)?;
25562        }
25563        Ok(())
25564    }
25565
25566    fn generate_alter_set(&mut self, e: &AlterSet) -> Result<()> {
25567        // Python (Snowflake): return f"SET{exprs}{file_format}{copy_options}{tag}"
25568        self.write_keyword("SET");
25569
25570        // Generate option (e.g., AUTHORIZATION, LOGGED, UNLOGGED, etc.)
25571        if let Some(opt) = &e.option {
25572            self.write_space();
25573            self.generate_expression(opt)?;
25574        }
25575
25576        // Generate PROPERTIES (for Trino SET PROPERTIES x = y, ...)
25577        // Check if expressions look like property assignments
25578        if !e.expressions.is_empty() {
25579            // Check if this looks like property assignments (for SET PROPERTIES)
25580            let is_properties = e
25581                .expressions
25582                .iter()
25583                .any(|expr| matches!(expr, Expression::Eq(_)));
25584            if is_properties && e.option.is_none() {
25585                self.write_space();
25586                self.write_keyword("PROPERTIES");
25587            }
25588            self.write_space();
25589            for (i, expr) in e.expressions.iter().enumerate() {
25590                if i > 0 {
25591                    self.write(", ");
25592                }
25593                self.generate_expression(expr)?;
25594            }
25595        }
25596
25597        // Generate STAGE_FILE_FORMAT = (...) with space-separated properties
25598        if let Some(file_format) = &e.file_format {
25599            self.write(" ");
25600            self.write_keyword("STAGE_FILE_FORMAT");
25601            self.write(" = (");
25602            self.generate_space_separated_properties(file_format)?;
25603            self.write(")");
25604        }
25605
25606        // Generate STAGE_COPY_OPTIONS = (...) with space-separated properties
25607        if let Some(copy_options) = &e.copy_options {
25608            self.write(" ");
25609            self.write_keyword("STAGE_COPY_OPTIONS");
25610            self.write(" = (");
25611            self.generate_space_separated_properties(copy_options)?;
25612            self.write(")");
25613        }
25614
25615        // Generate TAG ...
25616        if let Some(tag) = &e.tag {
25617            self.write(" ");
25618            self.write_keyword("TAG");
25619            self.write(" ");
25620            self.generate_expression(tag)?;
25621        }
25622
25623        Ok(())
25624    }
25625
25626    /// Generate space-separated properties (for Snowflake STAGE_FILE_FORMAT, etc.)
25627    fn generate_space_separated_properties(&mut self, expr: &Expression) -> Result<()> {
25628        match expr {
25629            Expression::Tuple(t) => {
25630                for (i, prop) in t.expressions.iter().enumerate() {
25631                    if i > 0 {
25632                        self.write(" ");
25633                    }
25634                    self.generate_expression(prop)?;
25635                }
25636            }
25637            _ => {
25638                self.generate_expression(expr)?;
25639            }
25640        }
25641        Ok(())
25642    }
25643
25644    fn generate_alter_sort_key(&mut self, e: &AlterSortKey) -> Result<()> {
25645        // Python: return f"ALTER{compound} SORTKEY {this or expressions}"
25646        self.write_keyword("ALTER");
25647        if e.compound.is_some() {
25648            self.write_space();
25649            self.write_keyword("COMPOUND");
25650        }
25651        self.write_space();
25652        self.write_keyword("SORTKEY");
25653        self.write_space();
25654        if let Some(this) = &e.this {
25655            self.generate_expression(this)?;
25656        } else if !e.expressions.is_empty() {
25657            self.write("(");
25658            for (i, expr) in e.expressions.iter().enumerate() {
25659                if i > 0 {
25660                    self.write(", ");
25661                }
25662                self.generate_expression(expr)?;
25663            }
25664            self.write(")");
25665        }
25666        Ok(())
25667    }
25668
25669    fn generate_analyze(&mut self, e: &Analyze) -> Result<()> {
25670        // Python: return f"ANALYZE{options}{kind}{this}{partition}{mode}{inner_expression}{properties}"
25671        self.write_keyword("ANALYZE");
25672        if !e.options.is_empty() {
25673            self.write_space();
25674            for (i, opt) in e.options.iter().enumerate() {
25675                if i > 0 {
25676                    self.write_space();
25677                }
25678                // Write options as keywords (not identifiers) to avoid quoting reserved words like FULL
25679                if let Expression::Identifier(id) = opt {
25680                    self.write_keyword(&id.name);
25681                } else {
25682                    self.generate_expression(opt)?;
25683                }
25684            }
25685        }
25686        if let Some(kind) = &e.kind {
25687            self.write_space();
25688            self.write_keyword(kind);
25689        }
25690        if let Some(this) = &e.this {
25691            self.write_space();
25692            self.generate_expression(this)?;
25693        }
25694        // Column list: ANALYZE tbl(col1, col2) (PostgreSQL)
25695        if !e.columns.is_empty() {
25696            self.write("(");
25697            for (i, col) in e.columns.iter().enumerate() {
25698                if i > 0 {
25699                    self.write(", ");
25700                }
25701                self.write(col);
25702            }
25703            self.write(")");
25704        }
25705        if let Some(partition) = &e.partition {
25706            self.write_space();
25707            self.generate_expression(partition)?;
25708        }
25709        if let Some(mode) = &e.mode {
25710            self.write_space();
25711            self.generate_expression(mode)?;
25712        }
25713        if let Some(expression) = &e.expression {
25714            self.write_space();
25715            self.generate_expression(expression)?;
25716        }
25717        if !e.properties.is_empty() {
25718            self.write_space();
25719            self.write_keyword(self.config.with_properties_prefix);
25720            self.write(" (");
25721            for (i, prop) in e.properties.iter().enumerate() {
25722                if i > 0 {
25723                    self.write(", ");
25724                }
25725                self.generate_expression(prop)?;
25726            }
25727            self.write(")");
25728        }
25729        Ok(())
25730    }
25731
25732    fn generate_analyze_delete(&mut self, e: &AnalyzeDelete) -> Result<()> {
25733        // Python: return f"DELETE{kind} STATISTICS"
25734        self.write_keyword("DELETE");
25735        if let Some(kind) = &e.kind {
25736            self.write_space();
25737            self.write_keyword(kind);
25738        }
25739        self.write_space();
25740        self.write_keyword("STATISTICS");
25741        Ok(())
25742    }
25743
25744    fn generate_analyze_histogram(&mut self, e: &AnalyzeHistogram) -> Result<()> {
25745        // Python: return f"{this} HISTOGRAM ON {columns}{inner_expression}{update_options}"
25746        // Write `this` (UPDATE or DROP) as keyword to avoid quoting reserved words
25747        if let Expression::Identifier(id) = e.this.as_ref() {
25748            self.write_keyword(&id.name);
25749        } else {
25750            self.generate_expression(&e.this)?;
25751        }
25752        self.write_space();
25753        self.write_keyword("HISTOGRAM ON");
25754        self.write_space();
25755        for (i, expr) in e.expressions.iter().enumerate() {
25756            if i > 0 {
25757                self.write(", ");
25758            }
25759            self.generate_expression(expr)?;
25760        }
25761        if let Some(expression) = &e.expression {
25762            self.write_space();
25763            self.generate_expression(expression)?;
25764        }
25765        if let Some(update_options) = &e.update_options {
25766            self.write_space();
25767            self.generate_expression(update_options)?;
25768            self.write_space();
25769            self.write_keyword("UPDATE");
25770        }
25771        Ok(())
25772    }
25773
25774    fn generate_analyze_list_chained_rows(&mut self, e: &AnalyzeListChainedRows) -> Result<()> {
25775        // Python: return f"LIST CHAINED ROWS{inner_expression}"
25776        self.write_keyword("LIST CHAINED ROWS");
25777        if let Some(expression) = &e.expression {
25778            self.write_space();
25779            self.write_keyword("INTO");
25780            self.write_space();
25781            self.generate_expression(expression)?;
25782        }
25783        Ok(())
25784    }
25785
25786    fn generate_analyze_sample(&mut self, e: &AnalyzeSample) -> Result<()> {
25787        // Python: return f"SAMPLE {sample} {kind}"
25788        self.write_keyword("SAMPLE");
25789        self.write_space();
25790        if let Some(sample) = &e.sample {
25791            self.generate_expression(sample)?;
25792            self.write_space();
25793        }
25794        self.write_keyword(&e.kind);
25795        Ok(())
25796    }
25797
25798    fn generate_analyze_statistics(&mut self, e: &AnalyzeStatistics) -> Result<()> {
25799        // Python: return f"{kind}{option} STATISTICS{this}{columns}"
25800        self.write_keyword(&e.kind);
25801        if let Some(option) = &e.option {
25802            self.write_space();
25803            self.generate_expression(option)?;
25804        }
25805        self.write_space();
25806        self.write_keyword("STATISTICS");
25807        if let Some(this) = &e.this {
25808            self.write_space();
25809            self.generate_expression(this)?;
25810        }
25811        if !e.expressions.is_empty() {
25812            self.write_space();
25813            for (i, expr) in e.expressions.iter().enumerate() {
25814                if i > 0 {
25815                    self.write(", ");
25816                }
25817                self.generate_expression(expr)?;
25818            }
25819        }
25820        Ok(())
25821    }
25822
25823    fn generate_analyze_validate(&mut self, e: &AnalyzeValidate) -> Result<()> {
25824        // Python: return f"VALIDATE {kind}{this}{inner_expression}"
25825        self.write_keyword("VALIDATE");
25826        self.write_space();
25827        self.write_keyword(&e.kind);
25828        if let Some(this) = &e.this {
25829            self.write_space();
25830            // this is a keyword string like "UPDATE", "CASCADE FAST", etc. - write as keywords
25831            if let Expression::Identifier(id) = this.as_ref() {
25832                self.write_keyword(&id.name);
25833            } else {
25834                self.generate_expression(this)?;
25835            }
25836        }
25837        if let Some(expression) = &e.expression {
25838            self.write_space();
25839            self.write_keyword("INTO");
25840            self.write_space();
25841            self.generate_expression(expression)?;
25842        }
25843        Ok(())
25844    }
25845
25846    fn generate_analyze_with(&mut self, e: &AnalyzeWith) -> Result<()> {
25847        // Python: return f"WITH {expressions}"
25848        self.write_keyword("WITH");
25849        self.write_space();
25850        for (i, expr) in e.expressions.iter().enumerate() {
25851            if i > 0 {
25852                self.write(", ");
25853            }
25854            self.generate_expression(expr)?;
25855        }
25856        Ok(())
25857    }
25858
25859    fn generate_anonymous(&mut self, e: &Anonymous) -> Result<()> {
25860        // Anonymous represents a generic function call: FUNC_NAME(args...)
25861        // Python: return self.func(self.sql(expression, "this"), *expression.expressions)
25862        self.generate_expression(&e.this)?;
25863        self.write("(");
25864        for (i, arg) in e.expressions.iter().enumerate() {
25865            if i > 0 {
25866                self.write(", ");
25867            }
25868            self.generate_expression(arg)?;
25869        }
25870        self.write(")");
25871        Ok(())
25872    }
25873
25874    fn generate_anonymous_agg_func(&mut self, e: &AnonymousAggFunc) -> Result<()> {
25875        // Same as Anonymous but for aggregate functions
25876        self.generate_expression(&e.this)?;
25877        self.write("(");
25878        for (i, arg) in e.expressions.iter().enumerate() {
25879            if i > 0 {
25880                self.write(", ");
25881            }
25882            self.generate_expression(arg)?;
25883        }
25884        self.write(")");
25885        Ok(())
25886    }
25887
25888    fn generate_apply(&mut self, e: &Apply) -> Result<()> {
25889        // Python: return f"{this} APPLY({expr})"
25890        self.generate_expression(&e.this)?;
25891        self.write_space();
25892        self.write_keyword("APPLY");
25893        self.write("(");
25894        self.generate_expression(&e.expression)?;
25895        self.write(")");
25896        Ok(())
25897    }
25898
25899    fn generate_approx_percentile_estimate(&mut self, e: &ApproxPercentileEstimate) -> Result<()> {
25900        // APPROX_PERCENTILE_ESTIMATE(this, percentile)
25901        self.write_keyword("APPROX_PERCENTILE_ESTIMATE");
25902        self.write("(");
25903        self.generate_expression(&e.this)?;
25904        if let Some(percentile) = &e.percentile {
25905            self.write(", ");
25906            self.generate_expression(percentile)?;
25907        }
25908        self.write(")");
25909        Ok(())
25910    }
25911
25912    fn generate_approx_quantile(&mut self, e: &ApproxQuantile) -> Result<()> {
25913        // APPROX_QUANTILE(this, quantile[, accuracy][, weight])
25914        self.write_keyword("APPROX_QUANTILE");
25915        self.write("(");
25916        self.generate_expression(&e.this)?;
25917        if let Some(quantile) = &e.quantile {
25918            self.write(", ");
25919            self.generate_expression(quantile)?;
25920        }
25921        if let Some(accuracy) = &e.accuracy {
25922            self.write(", ");
25923            self.generate_expression(accuracy)?;
25924        }
25925        if let Some(weight) = &e.weight {
25926            self.write(", ");
25927            self.generate_expression(weight)?;
25928        }
25929        self.write(")");
25930        Ok(())
25931    }
25932
25933    fn generate_approx_quantiles(&mut self, e: &ApproxQuantiles) -> Result<()> {
25934        // APPROX_QUANTILES(this, expression)
25935        self.write_keyword("APPROX_QUANTILES");
25936        self.write("(");
25937        self.generate_expression(&e.this)?;
25938        if let Some(expression) = &e.expression {
25939            self.write(", ");
25940            self.generate_expression(expression)?;
25941        }
25942        self.write(")");
25943        Ok(())
25944    }
25945
25946    fn generate_approx_top_k(&mut self, e: &ApproxTopK) -> Result<()> {
25947        // APPROX_TOP_K(this[, expression][, counters])
25948        self.write_keyword("APPROX_TOP_K");
25949        self.write("(");
25950        self.generate_expression(&e.this)?;
25951        if let Some(expression) = &e.expression {
25952            self.write(", ");
25953            self.generate_expression(expression)?;
25954        }
25955        if let Some(counters) = &e.counters {
25956            self.write(", ");
25957            self.generate_expression(counters)?;
25958        }
25959        self.write(")");
25960        Ok(())
25961    }
25962
25963    fn generate_approx_top_k_accumulate(&mut self, e: &ApproxTopKAccumulate) -> Result<()> {
25964        // APPROX_TOP_K_ACCUMULATE(this[, expression])
25965        self.write_keyword("APPROX_TOP_K_ACCUMULATE");
25966        self.write("(");
25967        self.generate_expression(&e.this)?;
25968        if let Some(expression) = &e.expression {
25969            self.write(", ");
25970            self.generate_expression(expression)?;
25971        }
25972        self.write(")");
25973        Ok(())
25974    }
25975
25976    fn generate_approx_top_k_combine(&mut self, e: &ApproxTopKCombine) -> Result<()> {
25977        // APPROX_TOP_K_COMBINE(this[, expression])
25978        self.write_keyword("APPROX_TOP_K_COMBINE");
25979        self.write("(");
25980        self.generate_expression(&e.this)?;
25981        if let Some(expression) = &e.expression {
25982            self.write(", ");
25983            self.generate_expression(expression)?;
25984        }
25985        self.write(")");
25986        Ok(())
25987    }
25988
25989    fn generate_approx_top_k_estimate(&mut self, e: &ApproxTopKEstimate) -> Result<()> {
25990        // APPROX_TOP_K_ESTIMATE(this[, expression])
25991        self.write_keyword("APPROX_TOP_K_ESTIMATE");
25992        self.write("(");
25993        self.generate_expression(&e.this)?;
25994        if let Some(expression) = &e.expression {
25995            self.write(", ");
25996            self.generate_expression(expression)?;
25997        }
25998        self.write(")");
25999        Ok(())
26000    }
26001
26002    fn generate_approx_top_sum(&mut self, e: &ApproxTopSum) -> Result<()> {
26003        // APPROX_TOP_SUM(this, expression[, count])
26004        self.write_keyword("APPROX_TOP_SUM");
26005        self.write("(");
26006        self.generate_expression(&e.this)?;
26007        self.write(", ");
26008        self.generate_expression(&e.expression)?;
26009        if let Some(count) = &e.count {
26010            self.write(", ");
26011            self.generate_expression(count)?;
26012        }
26013        self.write(")");
26014        Ok(())
26015    }
26016
26017    fn generate_arg_max(&mut self, e: &ArgMax) -> Result<()> {
26018        // ARG_MAX(this, expression[, count])
26019        self.write_keyword("ARG_MAX");
26020        self.write("(");
26021        self.generate_expression(&e.this)?;
26022        self.write(", ");
26023        self.generate_expression(&e.expression)?;
26024        if let Some(count) = &e.count {
26025            self.write(", ");
26026            self.generate_expression(count)?;
26027        }
26028        self.write(")");
26029        Ok(())
26030    }
26031
26032    fn generate_arg_min(&mut self, e: &ArgMin) -> Result<()> {
26033        // ARG_MIN(this, expression[, count])
26034        self.write_keyword("ARG_MIN");
26035        self.write("(");
26036        self.generate_expression(&e.this)?;
26037        self.write(", ");
26038        self.generate_expression(&e.expression)?;
26039        if let Some(count) = &e.count {
26040            self.write(", ");
26041            self.generate_expression(count)?;
26042        }
26043        self.write(")");
26044        Ok(())
26045    }
26046
26047    fn generate_array_all(&mut self, e: &ArrayAll) -> Result<()> {
26048        // ARRAY_ALL(this, expression)
26049        self.write_keyword("ARRAY_ALL");
26050        self.write("(");
26051        self.generate_expression(&e.this)?;
26052        self.write(", ");
26053        self.generate_expression(&e.expression)?;
26054        self.write(")");
26055        Ok(())
26056    }
26057
26058    fn generate_array_any(&mut self, e: &ArrayAny) -> Result<()> {
26059        // ARRAY_ANY(this, expression) - fallback implementation
26060        self.write_keyword("ARRAY_ANY");
26061        self.write("(");
26062        self.generate_expression(&e.this)?;
26063        self.write(", ");
26064        self.generate_expression(&e.expression)?;
26065        self.write(")");
26066        Ok(())
26067    }
26068
26069    fn generate_array_construct_compact(&mut self, e: &ArrayConstructCompact) -> Result<()> {
26070        // ARRAY_CONSTRUCT_COMPACT(expressions...)
26071        self.write_keyword("ARRAY_CONSTRUCT_COMPACT");
26072        self.write("(");
26073        for (i, expr) in e.expressions.iter().enumerate() {
26074            if i > 0 {
26075                self.write(", ");
26076            }
26077            self.generate_expression(expr)?;
26078        }
26079        self.write(")");
26080        Ok(())
26081    }
26082
26083    fn generate_array_sum(&mut self, e: &ArraySum) -> Result<()> {
26084        // ARRAY_SUM(this[, expression])
26085        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
26086            self.write("arraySum");
26087        } else {
26088            self.write_keyword("ARRAY_SUM");
26089        }
26090        self.write("(");
26091        self.generate_expression(&e.this)?;
26092        if let Some(expression) = &e.expression {
26093            self.write(", ");
26094            self.generate_expression(expression)?;
26095        }
26096        self.write(")");
26097        Ok(())
26098    }
26099
26100    fn generate_at_index(&mut self, e: &AtIndex) -> Result<()> {
26101        // Python: return f"{this} AT {index}"
26102        self.generate_expression(&e.this)?;
26103        self.write_space();
26104        self.write_keyword("AT");
26105        self.write_space();
26106        self.generate_expression(&e.expression)?;
26107        Ok(())
26108    }
26109
26110    fn generate_attach(&mut self, e: &Attach) -> Result<()> {
26111        // Python: return f"ATTACH{exists_sql} {this}{expressions}"
26112        self.write_keyword("ATTACH");
26113        if e.exists {
26114            self.write_space();
26115            self.write_keyword("IF NOT EXISTS");
26116        }
26117        self.write_space();
26118        self.generate_expression(&e.this)?;
26119        if !e.expressions.is_empty() {
26120            self.write(" (");
26121            for (i, expr) in e.expressions.iter().enumerate() {
26122                if i > 0 {
26123                    self.write(", ");
26124                }
26125                self.generate_expression(expr)?;
26126            }
26127            self.write(")");
26128        }
26129        Ok(())
26130    }
26131
26132    fn generate_attach_option(&mut self, e: &AttachOption) -> Result<()> {
26133        // AttachOption: this [expression]
26134        // Python sqlglot: no equals sign, just space-separated
26135        self.generate_expression(&e.this)?;
26136        if let Some(expression) = &e.expression {
26137            self.write_space();
26138            self.generate_expression(expression)?;
26139        }
26140        Ok(())
26141    }
26142
26143    /// Generate the auto_increment keyword and options for a column definition.
26144    /// Different dialects use different syntax: IDENTITY, AUTOINCREMENT, AUTO_INCREMENT,
26145    /// GENERATED AS IDENTITY, etc.
26146    fn generate_auto_increment_keyword(
26147        &mut self,
26148        col: &crate::expressions::ColumnDef,
26149    ) -> Result<()> {
26150        use crate::dialects::DialectType;
26151        if matches!(self.config.dialect, Some(DialectType::Redshift)) {
26152            self.write_keyword("IDENTITY");
26153            if col.auto_increment_start.is_some() || col.auto_increment_increment.is_some() {
26154                self.write("(");
26155                if let Some(ref start) = col.auto_increment_start {
26156                    self.generate_expression(start)?;
26157                } else {
26158                    self.write("0");
26159                }
26160                self.write(", ");
26161                if let Some(ref inc) = col.auto_increment_increment {
26162                    self.generate_expression(inc)?;
26163                } else {
26164                    self.write("1");
26165                }
26166                self.write(")");
26167            }
26168        } else if matches!(
26169            self.config.dialect,
26170            Some(DialectType::Snowflake) | Some(DialectType::SQLite)
26171        ) {
26172            self.write_keyword("AUTOINCREMENT");
26173            if let Some(ref start) = col.auto_increment_start {
26174                self.write_space();
26175                self.write_keyword("START");
26176                self.write_space();
26177                self.generate_expression(start)?;
26178            }
26179            if let Some(ref inc) = col.auto_increment_increment {
26180                self.write_space();
26181                self.write_keyword("INCREMENT");
26182                self.write_space();
26183                self.generate_expression(inc)?;
26184            }
26185            if let Some(order) = col.auto_increment_order {
26186                self.write_space();
26187                if order {
26188                    self.write_keyword("ORDER");
26189                } else {
26190                    self.write_keyword("NOORDER");
26191                }
26192            }
26193        } else if matches!(self.config.dialect, Some(DialectType::PostgreSQL)) {
26194            self.write_keyword("GENERATED BY DEFAULT AS IDENTITY");
26195            if col.auto_increment_start.is_some() || col.auto_increment_increment.is_some() {
26196                self.write(" (");
26197                let mut first = true;
26198                if let Some(ref start) = col.auto_increment_start {
26199                    self.write_keyword("START WITH");
26200                    self.write_space();
26201                    self.generate_expression(start)?;
26202                    first = false;
26203                }
26204                if let Some(ref inc) = col.auto_increment_increment {
26205                    if !first {
26206                        self.write_space();
26207                    }
26208                    self.write_keyword("INCREMENT BY");
26209                    self.write_space();
26210                    self.generate_expression(inc)?;
26211                }
26212                self.write(")");
26213            }
26214        } else if matches!(self.config.dialect, Some(DialectType::Databricks)) {
26215            // IDENTITY(start, increment) -> GENERATED BY DEFAULT AS IDENTITY
26216            // Plain IDENTITY/AUTO_INCREMENT -> GENERATED ALWAYS AS IDENTITY
26217            if col.auto_increment_start.is_some() || col.auto_increment_increment.is_some() {
26218                self.write_keyword("GENERATED BY DEFAULT AS IDENTITY");
26219            } else {
26220                self.write_keyword("GENERATED ALWAYS AS IDENTITY");
26221            }
26222            if col.auto_increment_start.is_some() || col.auto_increment_increment.is_some() {
26223                self.write(" (");
26224                let mut first = true;
26225                if let Some(ref start) = col.auto_increment_start {
26226                    self.write_keyword("START WITH");
26227                    self.write_space();
26228                    self.generate_expression(start)?;
26229                    first = false;
26230                }
26231                if let Some(ref inc) = col.auto_increment_increment {
26232                    if !first {
26233                        self.write_space();
26234                    }
26235                    self.write_keyword("INCREMENT BY");
26236                    self.write_space();
26237                    self.generate_expression(inc)?;
26238                }
26239                self.write(")");
26240            }
26241        } else if matches!(
26242            self.config.dialect,
26243            Some(DialectType::TSQL) | Some(DialectType::Fabric)
26244        ) {
26245            self.write_keyword("IDENTITY");
26246            if col.auto_increment_start.is_some() || col.auto_increment_increment.is_some() {
26247                self.write("(");
26248                if let Some(ref start) = col.auto_increment_start {
26249                    self.generate_expression(start)?;
26250                } else {
26251                    self.write("0");
26252                }
26253                self.write(", ");
26254                if let Some(ref inc) = col.auto_increment_increment {
26255                    self.generate_expression(inc)?;
26256                } else {
26257                    self.write("1");
26258                }
26259                self.write(")");
26260            }
26261        } else {
26262            self.write_keyword("AUTO_INCREMENT");
26263            if let Some(ref start) = col.auto_increment_start {
26264                self.write_space();
26265                self.write_keyword("START");
26266                self.write_space();
26267                self.generate_expression(start)?;
26268            }
26269            if let Some(ref inc) = col.auto_increment_increment {
26270                self.write_space();
26271                self.write_keyword("INCREMENT");
26272                self.write_space();
26273                self.generate_expression(inc)?;
26274            }
26275            if let Some(order) = col.auto_increment_order {
26276                self.write_space();
26277                if order {
26278                    self.write_keyword("ORDER");
26279                } else {
26280                    self.write_keyword("NOORDER");
26281                }
26282            }
26283        }
26284        Ok(())
26285    }
26286
26287    fn generate_auto_increment_property(&mut self, e: &AutoIncrementProperty) -> Result<()> {
26288        // AUTO_INCREMENT=value
26289        self.write_keyword("AUTO_INCREMENT");
26290        self.write("=");
26291        self.generate_expression(&e.this)?;
26292        Ok(())
26293    }
26294
26295    fn generate_auto_refresh_property(&mut self, e: &AutoRefreshProperty) -> Result<()> {
26296        // AUTO_REFRESH=value
26297        self.write_keyword("AUTO_REFRESH");
26298        self.write("=");
26299        self.generate_expression(&e.this)?;
26300        Ok(())
26301    }
26302
26303    fn generate_backup_property(&mut self, e: &BackupProperty) -> Result<()> {
26304        // BACKUP YES|NO (Redshift syntax uses space, not equals)
26305        self.write_keyword("BACKUP");
26306        self.write_space();
26307        self.generate_expression(&e.this)?;
26308        Ok(())
26309    }
26310
26311    fn generate_base64_decode_binary(&mut self, e: &Base64DecodeBinary) -> Result<()> {
26312        // BASE64_DECODE_BINARY(this[, alphabet])
26313        self.write_keyword("BASE64_DECODE_BINARY");
26314        self.write("(");
26315        self.generate_expression(&e.this)?;
26316        if let Some(alphabet) = &e.alphabet {
26317            self.write(", ");
26318            self.generate_expression(alphabet)?;
26319        }
26320        self.write(")");
26321        Ok(())
26322    }
26323
26324    fn generate_base64_decode_string(&mut self, e: &Base64DecodeString) -> Result<()> {
26325        // BASE64_DECODE_STRING(this[, alphabet])
26326        self.write_keyword("BASE64_DECODE_STRING");
26327        self.write("(");
26328        self.generate_expression(&e.this)?;
26329        if let Some(alphabet) = &e.alphabet {
26330            self.write(", ");
26331            self.generate_expression(alphabet)?;
26332        }
26333        self.write(")");
26334        Ok(())
26335    }
26336
26337    fn generate_base64_encode(&mut self, e: &Base64Encode) -> Result<()> {
26338        // BASE64_ENCODE(this[, max_line_length][, alphabet])
26339        self.write_keyword("BASE64_ENCODE");
26340        self.write("(");
26341        self.generate_expression(&e.this)?;
26342        if let Some(max_line_length) = &e.max_line_length {
26343            self.write(", ");
26344            self.generate_expression(max_line_length)?;
26345        }
26346        if let Some(alphabet) = &e.alphabet {
26347            self.write(", ");
26348            self.generate_expression(alphabet)?;
26349        }
26350        self.write(")");
26351        Ok(())
26352    }
26353
26354    fn generate_block_compression_property(&mut self, e: &BlockCompressionProperty) -> Result<()> {
26355        // BLOCKCOMPRESSION=... (complex Teradata property)
26356        self.write_keyword("BLOCKCOMPRESSION");
26357        self.write("=");
26358        if let Some(autotemp) = &e.autotemp {
26359            self.write_keyword("AUTOTEMP");
26360            self.write("(");
26361            self.generate_expression(autotemp)?;
26362            self.write(")");
26363        }
26364        if let Some(always) = &e.always {
26365            self.generate_expression(always)?;
26366        }
26367        if let Some(default) = &e.default {
26368            self.generate_expression(default)?;
26369        }
26370        if let Some(manual) = &e.manual {
26371            self.generate_expression(manual)?;
26372        }
26373        if let Some(never) = &e.never {
26374            self.generate_expression(never)?;
26375        }
26376        Ok(())
26377    }
26378
26379    fn generate_booland(&mut self, e: &Booland) -> Result<()> {
26380        // Python: return f"(({self.sql(expression, 'this')}) AND ({self.sql(expression, 'expression')}))"
26381        self.write("((");
26382        self.generate_expression(&e.this)?;
26383        self.write(") ");
26384        self.write_keyword("AND");
26385        self.write(" (");
26386        self.generate_expression(&e.expression)?;
26387        self.write("))");
26388        Ok(())
26389    }
26390
26391    fn generate_boolor(&mut self, e: &Boolor) -> Result<()> {
26392        // Python: return f"(({self.sql(expression, 'this')}) OR ({self.sql(expression, 'expression')}))"
26393        self.write("((");
26394        self.generate_expression(&e.this)?;
26395        self.write(") ");
26396        self.write_keyword("OR");
26397        self.write(" (");
26398        self.generate_expression(&e.expression)?;
26399        self.write("))");
26400        Ok(())
26401    }
26402
26403    fn generate_build_property(&mut self, e: &BuildProperty) -> Result<()> {
26404        // BUILD value (e.g., BUILD IMMEDIATE, BUILD DEFERRED)
26405        self.write_keyword("BUILD");
26406        self.write_space();
26407        self.generate_expression(&e.this)?;
26408        Ok(())
26409    }
26410
26411    fn generate_byte_string(&mut self, e: &ByteString) -> Result<()> {
26412        // Byte string literal like B'...' or X'...'
26413        self.generate_expression(&e.this)?;
26414        Ok(())
26415    }
26416
26417    fn generate_case_specific_column_constraint(
26418        &mut self,
26419        e: &CaseSpecificColumnConstraint,
26420    ) -> Result<()> {
26421        // CASESPECIFIC or NOT CASESPECIFIC (Teradata)
26422        if e.not_.is_some() {
26423            self.write_keyword("NOT");
26424            self.write_space();
26425        }
26426        self.write_keyword("CASESPECIFIC");
26427        Ok(())
26428    }
26429
26430    fn generate_cast_to_str_type(&mut self, e: &CastToStrType) -> Result<()> {
26431        // Cast to string type (dialect-specific)
26432        self.write_keyword("CAST");
26433        self.write("(");
26434        self.generate_expression(&e.this)?;
26435        if self.config.dialect == Some(DialectType::ClickHouse) {
26436            // ClickHouse: CAST(expr, 'type_string')
26437            self.write(", ");
26438        } else {
26439            self.write_space();
26440            self.write_keyword("AS");
26441            self.write_space();
26442        }
26443        if let Some(to) = &e.to {
26444            self.generate_expression(to)?;
26445        }
26446        self.write(")");
26447        Ok(())
26448    }
26449
26450    fn generate_changes(&mut self, e: &Changes) -> Result<()> {
26451        // CHANGES (INFORMATION => value) AT|BEFORE (...) END (...)
26452        // Python: f"CHANGES ({information}){at_before}{end}"
26453        self.write_keyword("CHANGES");
26454        self.write(" (");
26455        if let Some(information) = &e.information {
26456            self.write_keyword("INFORMATION");
26457            self.write(" => ");
26458            self.generate_expression(information)?;
26459        }
26460        self.write(")");
26461        // at_before and end are HistoricalData expressions that generate their own keywords
26462        if let Some(at_before) = &e.at_before {
26463            self.write(" ");
26464            self.generate_expression(at_before)?;
26465        }
26466        if let Some(end) = &e.end {
26467            self.write(" ");
26468            self.generate_expression(end)?;
26469        }
26470        Ok(())
26471    }
26472
26473    fn generate_character_set_column_constraint(
26474        &mut self,
26475        e: &CharacterSetColumnConstraint,
26476    ) -> Result<()> {
26477        // CHARACTER SET charset_name
26478        self.write_keyword("CHARACTER SET");
26479        self.write_space();
26480        self.generate_expression(&e.this)?;
26481        Ok(())
26482    }
26483
26484    fn generate_character_set_property(&mut self, e: &CharacterSetProperty) -> Result<()> {
26485        // [DEFAULT] CHARACTER SET=value
26486        if e.default.is_some() {
26487            self.write_keyword("DEFAULT");
26488            self.write_space();
26489        }
26490        self.write_keyword("CHARACTER SET");
26491        self.write("=");
26492        self.generate_expression(&e.this)?;
26493        Ok(())
26494    }
26495
26496    fn generate_check_column_constraint(&mut self, e: &CheckColumnConstraint) -> Result<()> {
26497        // Python: return f"CHECK ({self.sql(expression, 'this')}){enforced}"
26498        self.write_keyword("CHECK");
26499        self.write(" (");
26500        self.generate_expression(&e.this)?;
26501        self.write(")");
26502        if e.enforced.is_some() {
26503            self.write_space();
26504            self.write_keyword("ENFORCED");
26505        }
26506        Ok(())
26507    }
26508
26509    fn generate_assume_column_constraint(&mut self, e: &AssumeColumnConstraint) -> Result<()> {
26510        // Python: return f"ASSUME ({self.sql(e, 'this')})"
26511        self.write_keyword("ASSUME");
26512        self.write(" (");
26513        self.generate_expression(&e.this)?;
26514        self.write(")");
26515        Ok(())
26516    }
26517
26518    fn generate_check_json(&mut self, e: &CheckJson) -> Result<()> {
26519        // CHECK_JSON(this)
26520        self.write_keyword("CHECK_JSON");
26521        self.write("(");
26522        self.generate_expression(&e.this)?;
26523        self.write(")");
26524        Ok(())
26525    }
26526
26527    fn generate_check_xml(&mut self, e: &CheckXml) -> Result<()> {
26528        // CHECK_XML(this)
26529        self.write_keyword("CHECK_XML");
26530        self.write("(");
26531        self.generate_expression(&e.this)?;
26532        self.write(")");
26533        Ok(())
26534    }
26535
26536    fn generate_checksum_property(&mut self, e: &ChecksumProperty) -> Result<()> {
26537        // CHECKSUM=[ON|OFF|DEFAULT]
26538        self.write_keyword("CHECKSUM");
26539        self.write("=");
26540        if e.on.is_some() {
26541            self.write_keyword("ON");
26542        } else if e.default.is_some() {
26543            self.write_keyword("DEFAULT");
26544        } else {
26545            self.write_keyword("OFF");
26546        }
26547        Ok(())
26548    }
26549
26550    fn generate_clone(&mut self, e: &Clone) -> Result<()> {
26551        // Python: return f"{shallow}{keyword} {this}"
26552        if e.shallow.is_some() {
26553            self.write_keyword("SHALLOW");
26554            self.write_space();
26555        }
26556        if e.copy.is_some() {
26557            self.write_keyword("COPY");
26558        } else {
26559            self.write_keyword("CLONE");
26560        }
26561        self.write_space();
26562        self.generate_expression(&e.this)?;
26563        Ok(())
26564    }
26565
26566    fn generate_cluster_by(&mut self, e: &ClusterBy) -> Result<()> {
26567        // CLUSTER BY (expressions)
26568        self.write_keyword("CLUSTER BY");
26569        self.write(" (");
26570        for (i, ord) in e.expressions.iter().enumerate() {
26571            if i > 0 {
26572                self.write(", ");
26573            }
26574            self.generate_ordered(ord)?;
26575        }
26576        self.write(")");
26577        Ok(())
26578    }
26579
26580    fn generate_cluster_by_columns_property(&mut self, e: &ClusterByColumnsProperty) -> Result<()> {
26581        // BigQuery table property: CLUSTER BY col1, col2
26582        self.write_keyword("CLUSTER BY");
26583        self.write_space();
26584        for (i, col) in e.columns.iter().enumerate() {
26585            if i > 0 {
26586                self.write(", ");
26587            }
26588            self.generate_identifier(col)?;
26589        }
26590        Ok(())
26591    }
26592
26593    fn generate_clustered_by_property(&mut self, e: &ClusteredByProperty) -> Result<()> {
26594        // Python: return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS"
26595        self.write_keyword("CLUSTERED BY");
26596        self.write(" (");
26597        for (i, expr) in e.expressions.iter().enumerate() {
26598            if i > 0 {
26599                self.write(", ");
26600            }
26601            self.generate_expression(expr)?;
26602        }
26603        self.write(")");
26604        if let Some(sorted_by) = &e.sorted_by {
26605            self.write_space();
26606            self.write_keyword("SORTED BY");
26607            self.write(" (");
26608            // Unwrap Tuple to avoid double parentheses
26609            if let Expression::Tuple(t) = sorted_by.as_ref() {
26610                for (i, expr) in t.expressions.iter().enumerate() {
26611                    if i > 0 {
26612                        self.write(", ");
26613                    }
26614                    self.generate_expression(expr)?;
26615                }
26616            } else {
26617                self.generate_expression(sorted_by)?;
26618            }
26619            self.write(")");
26620        }
26621        if let Some(buckets) = &e.buckets {
26622            self.write_space();
26623            self.write_keyword("INTO");
26624            self.write_space();
26625            self.generate_expression(buckets)?;
26626            self.write_space();
26627            self.write_keyword("BUCKETS");
26628        }
26629        Ok(())
26630    }
26631
26632    fn generate_collate_property(&mut self, e: &CollateProperty) -> Result<()> {
26633        // [DEFAULT] COLLATE [=] value
26634        // BigQuery uses space: DEFAULT COLLATE 'en'
26635        // Others use equals: COLLATE='en'
26636        if e.default.is_some() {
26637            self.write_keyword("DEFAULT");
26638            self.write_space();
26639        }
26640        self.write_keyword("COLLATE");
26641        // BigQuery uses space between COLLATE and value
26642        match self.config.dialect {
26643            Some(DialectType::BigQuery) => self.write_space(),
26644            _ => self.write("="),
26645        }
26646        self.generate_expression(&e.this)?;
26647        Ok(())
26648    }
26649
26650    fn generate_column_constraint(&mut self, e: &ColumnConstraint) -> Result<()> {
26651        // ColumnConstraint is an enum
26652        match e {
26653            ColumnConstraint::NotNull => {
26654                self.write_keyword("NOT NULL");
26655            }
26656            ColumnConstraint::Null => {
26657                self.write_keyword("NULL");
26658            }
26659            ColumnConstraint::Unique => {
26660                self.write_keyword("UNIQUE");
26661            }
26662            ColumnConstraint::PrimaryKey => {
26663                self.write_keyword("PRIMARY KEY");
26664            }
26665            ColumnConstraint::Default(expr) => {
26666                self.write_keyword("DEFAULT");
26667                self.write_space();
26668                self.generate_expression(expr)?;
26669            }
26670            ColumnConstraint::Check(expr) => {
26671                self.write_keyword("CHECK");
26672                self.write(" (");
26673                self.generate_expression(expr)?;
26674                self.write(")");
26675            }
26676            ColumnConstraint::References(fk_ref) => {
26677                if fk_ref.has_foreign_key_keywords {
26678                    self.write_keyword("FOREIGN KEY");
26679                    self.write_space();
26680                }
26681                self.write_keyword("REFERENCES");
26682                self.write_space();
26683                self.generate_table(&fk_ref.table)?;
26684                if !fk_ref.columns.is_empty() {
26685                    self.write(" (");
26686                    for (i, col) in fk_ref.columns.iter().enumerate() {
26687                        if i > 0 {
26688                            self.write(", ");
26689                        }
26690                        self.generate_identifier(col)?;
26691                    }
26692                    self.write(")");
26693                }
26694            }
26695            ColumnConstraint::GeneratedAsIdentity(gen) => {
26696                self.write_keyword("GENERATED");
26697                self.write_space();
26698                if gen.always {
26699                    self.write_keyword("ALWAYS");
26700                } else {
26701                    self.write_keyword("BY DEFAULT");
26702                    if gen.on_null {
26703                        self.write_space();
26704                        self.write_keyword("ON NULL");
26705                    }
26706                }
26707                self.write_space();
26708                self.write_keyword("AS IDENTITY");
26709            }
26710            ColumnConstraint::Collate(collation) => {
26711                self.write_keyword("COLLATE");
26712                self.write_space();
26713                self.generate_identifier(collation)?;
26714            }
26715            ColumnConstraint::Comment(comment) => {
26716                self.write_keyword("COMMENT");
26717                self.write(" '");
26718                self.write(comment);
26719                self.write("'");
26720            }
26721            ColumnConstraint::ComputedColumn(cc) => {
26722                self.generate_computed_column_inline(cc)?;
26723            }
26724            ColumnConstraint::GeneratedAsRow(gar) => {
26725                self.generate_generated_as_row_inline(gar)?;
26726            }
26727            ColumnConstraint::Tags(tags) => {
26728                self.write_keyword("TAG");
26729                self.write(" (");
26730                for (i, expr) in tags.expressions.iter().enumerate() {
26731                    if i > 0 {
26732                        self.write(", ");
26733                    }
26734                    self.generate_expression(expr)?;
26735                }
26736                self.write(")");
26737            }
26738            ColumnConstraint::Path(path_expr) => {
26739                self.write_keyword("PATH");
26740                self.write_space();
26741                self.generate_expression(path_expr)?;
26742            }
26743        }
26744        Ok(())
26745    }
26746
26747    fn generate_column_position(&mut self, e: &ColumnPosition) -> Result<()> {
26748        // ColumnPosition is an enum
26749        match e {
26750            ColumnPosition::First => {
26751                self.write_keyword("FIRST");
26752            }
26753            ColumnPosition::After(ident) => {
26754                self.write_keyword("AFTER");
26755                self.write_space();
26756                self.generate_identifier(ident)?;
26757            }
26758        }
26759        Ok(())
26760    }
26761
26762    fn generate_column_prefix(&mut self, e: &ColumnPrefix) -> Result<()> {
26763        // column(prefix)
26764        self.generate_expression(&e.this)?;
26765        self.write("(");
26766        self.generate_expression(&e.expression)?;
26767        self.write(")");
26768        Ok(())
26769    }
26770
26771    fn generate_columns(&mut self, e: &Columns) -> Result<()> {
26772        // If unpack is true, this came from * COLUMNS(pattern)
26773        // DuckDB syntax: * COLUMNS(c ILIKE '%suffix') or COLUMNS(pattern)
26774        if let Some(ref unpack) = e.unpack {
26775            if let Expression::Boolean(b) = unpack.as_ref() {
26776                if b.value {
26777                    self.write("*");
26778                }
26779            }
26780        }
26781        self.write_keyword("COLUMNS");
26782        self.write("(");
26783        self.generate_expression(&e.this)?;
26784        self.write(")");
26785        Ok(())
26786    }
26787
26788    fn generate_combined_agg_func(&mut self, e: &CombinedAggFunc) -> Result<()> {
26789        // Combined aggregate: FUNC(args) combined
26790        self.generate_expression(&e.this)?;
26791        self.write("(");
26792        for (i, expr) in e.expressions.iter().enumerate() {
26793            if i > 0 {
26794                self.write(", ");
26795            }
26796            self.generate_expression(expr)?;
26797        }
26798        self.write(")");
26799        Ok(())
26800    }
26801
26802    fn generate_combined_parameterized_agg(&mut self, e: &CombinedParameterizedAgg) -> Result<()> {
26803        // Combined parameterized aggregate: FUNC(params)(expressions)
26804        self.generate_expression(&e.this)?;
26805        self.write("(");
26806        for (i, param) in e.params.iter().enumerate() {
26807            if i > 0 {
26808                self.write(", ");
26809            }
26810            self.generate_expression(param)?;
26811        }
26812        self.write(")(");
26813        for (i, expr) in e.expressions.iter().enumerate() {
26814            if i > 0 {
26815                self.write(", ");
26816            }
26817            self.generate_expression(expr)?;
26818        }
26819        self.write(")");
26820        Ok(())
26821    }
26822
26823    fn generate_commit(&mut self, e: &Commit) -> Result<()> {
26824        // COMMIT [TRANSACTION [transaction_name]] [WITH (DELAYED_DURABILITY = ON|OFF)] [AND [NO] CHAIN]
26825        self.write_keyword("COMMIT");
26826
26827        // TSQL always uses COMMIT TRANSACTION
26828        if e.this.is_none()
26829            && matches!(
26830                self.config.dialect,
26831                Some(DialectType::TSQL) | Some(DialectType::Fabric)
26832            )
26833        {
26834            self.write_space();
26835            self.write_keyword("TRANSACTION");
26836        }
26837
26838        // Check if this has TRANSACTION keyword or transaction name
26839        if let Some(this) = &e.this {
26840            // Check if it's just the "TRANSACTION" marker or an actual transaction name
26841            let is_transaction_marker = matches!(
26842                this.as_ref(),
26843                Expression::Identifier(id) if id.name == "TRANSACTION"
26844            );
26845
26846            self.write_space();
26847            self.write_keyword("TRANSACTION");
26848
26849            // If it's a real transaction name, output it
26850            if !is_transaction_marker {
26851                self.write_space();
26852                self.generate_expression(this)?;
26853            }
26854        }
26855
26856        // Output WITH (DELAYED_DURABILITY = ON|OFF) for TSQL
26857        if let Some(durability) = &e.durability {
26858            self.write_space();
26859            self.write_keyword("WITH");
26860            self.write(" (");
26861            self.write_keyword("DELAYED_DURABILITY");
26862            self.write(" = ");
26863            if let Expression::Boolean(BooleanLiteral { value: true }) = durability.as_ref() {
26864                self.write_keyword("ON");
26865            } else {
26866                self.write_keyword("OFF");
26867            }
26868            self.write(")");
26869        }
26870
26871        // Output AND [NO] CHAIN
26872        if let Some(chain) = &e.chain {
26873            self.write_space();
26874            if let Expression::Boolean(BooleanLiteral { value: false }) = chain.as_ref() {
26875                self.write_keyword("AND NO CHAIN");
26876            } else {
26877                self.write_keyword("AND CHAIN");
26878            }
26879        }
26880        Ok(())
26881    }
26882
26883    fn generate_comprehension(&mut self, e: &Comprehension) -> Result<()> {
26884        // Python-style comprehension: [expr FOR var[, pos] IN iterator IF condition]
26885        self.write("[");
26886        self.generate_expression(&e.this)?;
26887        self.write_space();
26888        self.write_keyword("FOR");
26889        self.write_space();
26890        self.generate_expression(&e.expression)?;
26891        // Handle optional position variable (for enumerate-like syntax)
26892        if let Some(pos) = &e.position {
26893            self.write(", ");
26894            self.generate_expression(pos)?;
26895        }
26896        if let Some(iterator) = &e.iterator {
26897            self.write_space();
26898            self.write_keyword("IN");
26899            self.write_space();
26900            self.generate_expression(iterator)?;
26901        }
26902        if let Some(condition) = &e.condition {
26903            self.write_space();
26904            self.write_keyword("IF");
26905            self.write_space();
26906            self.generate_expression(condition)?;
26907        }
26908        self.write("]");
26909        Ok(())
26910    }
26911
26912    fn generate_compress(&mut self, e: &Compress) -> Result<()> {
26913        // COMPRESS(this[, method])
26914        self.write_keyword("COMPRESS");
26915        self.write("(");
26916        self.generate_expression(&e.this)?;
26917        if let Some(method) = &e.method {
26918            self.write(", '");
26919            self.write(method);
26920            self.write("'");
26921        }
26922        self.write(")");
26923        Ok(())
26924    }
26925
26926    fn generate_compress_column_constraint(&mut self, e: &CompressColumnConstraint) -> Result<()> {
26927        // Python: return f"COMPRESS {this}"
26928        self.write_keyword("COMPRESS");
26929        if let Some(this) = &e.this {
26930            self.write_space();
26931            self.generate_expression(this)?;
26932        }
26933        Ok(())
26934    }
26935
26936    fn generate_computed_column_constraint(&mut self, e: &ComputedColumnConstraint) -> Result<()> {
26937        // Python: return f"AS {this}{persisted}"
26938        self.write_keyword("AS");
26939        self.write_space();
26940        self.generate_expression(&e.this)?;
26941        if e.not_null.is_some() {
26942            self.write_space();
26943            self.write_keyword("PERSISTED NOT NULL");
26944        } else if e.persisted.is_some() {
26945            self.write_space();
26946            self.write_keyword("PERSISTED");
26947        }
26948        Ok(())
26949    }
26950
26951    /// Generate a ComputedColumn constraint inline within a column definition.
26952    /// Handles MySQL/PostgreSQL: GENERATED ALWAYS AS (expr) STORED|VIRTUAL
26953    /// Handles TSQL: AS (expr) [PERSISTED] [NOT NULL]
26954    fn generate_computed_column_inline(&mut self, cc: &ComputedColumn) -> Result<()> {
26955        let computed_expr = if matches!(
26956            self.config.dialect,
26957            Some(DialectType::TSQL) | Some(DialectType::Fabric)
26958        ) {
26959            match &*cc.expression {
26960                Expression::Year(y) if !matches!(&y.this, Expression::Cast(c) if matches!(c.to, DataType::Date)) =>
26961                {
26962                    let wrapped = Expression::Cast(Box::new(Cast {
26963                        this: y.this.clone(),
26964                        to: DataType::Date,
26965                        trailing_comments: Vec::new(),
26966                        double_colon_syntax: false,
26967                        format: None,
26968                        default: None,
26969                        inferred_type: None,
26970                    }));
26971                    Expression::Year(Box::new(UnaryFunc::new(wrapped)))
26972                }
26973                Expression::Function(f)
26974                    if f.name.eq_ignore_ascii_case("YEAR")
26975                        && f.args.len() == 1
26976                        && !matches!(&f.args[0], Expression::Cast(c) if matches!(c.to, DataType::Date)) =>
26977                {
26978                    let wrapped = Expression::Cast(Box::new(Cast {
26979                        this: f.args[0].clone(),
26980                        to: DataType::Date,
26981                        trailing_comments: Vec::new(),
26982                        double_colon_syntax: false,
26983                        format: None,
26984                        default: None,
26985                        inferred_type: None,
26986                    }));
26987                    Expression::Function(Box::new(Function::new("YEAR".to_string(), vec![wrapped])))
26988                }
26989                _ => *cc.expression.clone(),
26990            }
26991        } else {
26992            *cc.expression.clone()
26993        };
26994
26995        match cc.persistence_kind.as_deref() {
26996            Some("STORED") | Some("VIRTUAL") => {
26997                // MySQL/PostgreSQL: GENERATED ALWAYS AS (expr) STORED|VIRTUAL
26998                self.write_keyword("GENERATED ALWAYS AS");
26999                self.write(" (");
27000                self.generate_expression(&computed_expr)?;
27001                self.write(")");
27002                self.write_space();
27003                if cc.persisted {
27004                    self.write_keyword("STORED");
27005                } else {
27006                    self.write_keyword("VIRTUAL");
27007                }
27008            }
27009            Some("PERSISTED") => {
27010                // TSQL/SingleStore: AS (expr) PERSISTED [TYPE] [NOT NULL]
27011                self.write_keyword("AS");
27012                self.write(" (");
27013                self.generate_expression(&computed_expr)?;
27014                self.write(")");
27015                self.write_space();
27016                self.write_keyword("PERSISTED");
27017                // Output data type if present (SingleStore: PERSISTED TYPE NOT NULL)
27018                if let Some(ref dt) = cc.data_type {
27019                    self.write_space();
27020                    self.generate_data_type(dt)?;
27021                }
27022                if cc.not_null {
27023                    self.write_space();
27024                    self.write_keyword("NOT NULL");
27025                }
27026            }
27027            _ => {
27028                // Spark/Databricks/Hive: GENERATED ALWAYS AS (expr)
27029                // TSQL computed column without PERSISTED: AS (expr)
27030                if matches!(
27031                    self.config.dialect,
27032                    Some(DialectType::Spark)
27033                        | Some(DialectType::Databricks)
27034                        | Some(DialectType::Hive)
27035                ) {
27036                    self.write_keyword("GENERATED ALWAYS AS");
27037                    self.write(" (");
27038                    self.generate_expression(&computed_expr)?;
27039                    self.write(")");
27040                } else if matches!(
27041                    self.config.dialect,
27042                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
27043                ) {
27044                    self.write_keyword("AS");
27045                    let omit_parens = matches!(computed_expr, Expression::Year(_))
27046                        || matches!(&computed_expr, Expression::Function(f) if f.name.eq_ignore_ascii_case("YEAR"));
27047                    if omit_parens {
27048                        self.write_space();
27049                        self.generate_expression(&computed_expr)?;
27050                    } else {
27051                        self.write(" (");
27052                        self.generate_expression(&computed_expr)?;
27053                        self.write(")");
27054                    }
27055                } else {
27056                    self.write_keyword("AS");
27057                    self.write(" (");
27058                    self.generate_expression(&computed_expr)?;
27059                    self.write(")");
27060                }
27061            }
27062        }
27063        Ok(())
27064    }
27065
27066    /// Generate a GeneratedAsRow constraint inline within a column definition.
27067    /// TSQL temporal: GENERATED ALWAYS AS ROW START|END [HIDDEN]
27068    fn generate_generated_as_row_inline(&mut self, gar: &GeneratedAsRow) -> Result<()> {
27069        self.write_keyword("GENERATED ALWAYS AS ROW ");
27070        if gar.start {
27071            self.write_keyword("START");
27072        } else {
27073            self.write_keyword("END");
27074        }
27075        if gar.hidden {
27076            self.write_space();
27077            self.write_keyword("HIDDEN");
27078        }
27079        Ok(())
27080    }
27081
27082    /// Generate just the SYSTEM_VERSIONING=ON(...) content without WITH() wrapper.
27083    fn generate_system_versioning_content(
27084        &mut self,
27085        e: &WithSystemVersioningProperty,
27086    ) -> Result<()> {
27087        let mut parts = Vec::new();
27088
27089        if let Some(this) = &e.this {
27090            let mut s = String::from("HISTORY_TABLE=");
27091            let mut gen = Generator::with_arc_config(self.config.clone());
27092            gen.generate_expression(this)?;
27093            s.push_str(&gen.output);
27094            parts.push(s);
27095        }
27096
27097        if let Some(data_consistency) = &e.data_consistency {
27098            let mut s = String::from("DATA_CONSISTENCY_CHECK=");
27099            let mut gen = Generator::with_arc_config(self.config.clone());
27100            gen.generate_expression(data_consistency)?;
27101            s.push_str(&gen.output);
27102            parts.push(s);
27103        }
27104
27105        if let Some(retention_period) = &e.retention_period {
27106            let mut s = String::from("HISTORY_RETENTION_PERIOD=");
27107            let mut gen = Generator::with_arc_config(self.config.clone());
27108            gen.generate_expression(retention_period)?;
27109            s.push_str(&gen.output);
27110            parts.push(s);
27111        }
27112
27113        self.write_keyword("SYSTEM_VERSIONING");
27114        self.write("=");
27115
27116        if !parts.is_empty() {
27117            self.write_keyword("ON");
27118            self.write("(");
27119            self.write(&parts.join(", "));
27120            self.write(")");
27121        } else if e.on.is_some() {
27122            self.write_keyword("ON");
27123        } else {
27124            self.write_keyword("OFF");
27125        }
27126
27127        Ok(())
27128    }
27129
27130    fn generate_conditional_insert(&mut self, e: &ConditionalInsert) -> Result<()> {
27131        // Conditional INSERT for multi-table inserts
27132        // Output: [WHEN cond THEN | ELSE] INTO table [(cols)] [VALUES (...)]
27133        if e.else_.is_some() {
27134            self.write_keyword("ELSE");
27135            self.write_space();
27136        } else if let Some(expression) = &e.expression {
27137            self.write_keyword("WHEN");
27138            self.write_space();
27139            self.generate_expression(expression)?;
27140            self.write_space();
27141            self.write_keyword("THEN");
27142            self.write_space();
27143        }
27144
27145        // Handle Insert expression specially - output "INTO table (cols) VALUES (...)"
27146        // without the "INSERT " prefix
27147        if let Expression::Insert(insert) = e.this.as_ref() {
27148            self.write_keyword("INTO");
27149            self.write_space();
27150            self.generate_table(&insert.table)?;
27151
27152            // Optional column list
27153            if !insert.columns.is_empty() {
27154                self.write(" (");
27155                for (i, col) in insert.columns.iter().enumerate() {
27156                    if i > 0 {
27157                        self.write(", ");
27158                    }
27159                    self.generate_identifier(col)?;
27160                }
27161                self.write(")");
27162            }
27163
27164            // Optional VALUES clause
27165            if !insert.values.is_empty() {
27166                self.write_space();
27167                self.write_keyword("VALUES");
27168                for (row_idx, row) in insert.values.iter().enumerate() {
27169                    if row_idx > 0 {
27170                        self.write(", ");
27171                    }
27172                    self.write(" (");
27173                    for (i, val) in row.iter().enumerate() {
27174                        if i > 0 {
27175                            self.write(", ");
27176                        }
27177                        self.generate_expression(val)?;
27178                    }
27179                    self.write(")");
27180                }
27181            }
27182        } else {
27183            // Fallback for non-Insert expressions
27184            self.generate_expression(&e.this)?;
27185        }
27186        Ok(())
27187    }
27188
27189    fn generate_constraint(&mut self, e: &Constraint) -> Result<()> {
27190        // Python: return f"CONSTRAINT {this} {expressions}"
27191        self.write_keyword("CONSTRAINT");
27192        self.write_space();
27193        self.generate_expression(&e.this)?;
27194        if !e.expressions.is_empty() {
27195            self.write_space();
27196            for (i, expr) in e.expressions.iter().enumerate() {
27197                if i > 0 {
27198                    self.write_space();
27199                }
27200                self.generate_expression(expr)?;
27201            }
27202        }
27203        Ok(())
27204    }
27205
27206    fn generate_convert_timezone(&mut self, e: &ConvertTimezone) -> Result<()> {
27207        // CONVERT_TIMEZONE([source_tz,] target_tz, timestamp)
27208        self.write_keyword("CONVERT_TIMEZONE");
27209        self.write("(");
27210        let mut first = true;
27211        if let Some(source_tz) = &e.source_tz {
27212            self.generate_expression(source_tz)?;
27213            first = false;
27214        }
27215        if let Some(target_tz) = &e.target_tz {
27216            if !first {
27217                self.write(", ");
27218            }
27219            self.generate_expression(target_tz)?;
27220            first = false;
27221        }
27222        if let Some(timestamp) = &e.timestamp {
27223            if !first {
27224                self.write(", ");
27225            }
27226            self.generate_expression(timestamp)?;
27227        }
27228        self.write(")");
27229        Ok(())
27230    }
27231
27232    fn generate_convert_to_charset(&mut self, e: &ConvertToCharset) -> Result<()> {
27233        // CONVERT(this USING dest)
27234        self.write_keyword("CONVERT");
27235        self.write("(");
27236        self.generate_expression(&e.this)?;
27237        if let Some(dest) = &e.dest {
27238            self.write_space();
27239            self.write_keyword("USING");
27240            self.write_space();
27241            self.generate_expression(dest)?;
27242        }
27243        self.write(")");
27244        Ok(())
27245    }
27246
27247    fn generate_copy(&mut self, e: &CopyStmt) -> Result<()> {
27248        self.write_keyword("COPY");
27249        if e.is_into {
27250            self.write_space();
27251            self.write_keyword("INTO");
27252        }
27253        self.write_space();
27254
27255        // Generate target table or query (or stage for COPY INTO @stage)
27256        if let Expression::Literal(lit) = &e.this {
27257            if let Literal::String(s) = lit.as_ref() {
27258                if s.starts_with('@') {
27259                    self.write(s);
27260                } else {
27261                    self.generate_expression(&e.this)?;
27262                }
27263            }
27264        } else {
27265            self.generate_expression(&e.this)?;
27266        }
27267
27268        // FROM or TO based on kind
27269        if e.kind {
27270            // kind=true means FROM (loading into table)
27271            if self.config.pretty {
27272                self.write_newline();
27273            } else {
27274                self.write_space();
27275            }
27276            self.write_keyword("FROM");
27277            self.write_space();
27278        } else if !e.files.is_empty() {
27279            // kind=false means TO (exporting)
27280            if self.config.pretty {
27281                self.write_newline();
27282            } else {
27283                self.write_space();
27284            }
27285            self.write_keyword("TO");
27286            self.write_space();
27287        }
27288
27289        // Generate source/destination files
27290        for (i, file) in e.files.iter().enumerate() {
27291            if i > 0 {
27292                self.write_space();
27293            }
27294            // For stage references (strings starting with @), output without quotes
27295            if let Expression::Literal(lit) = file {
27296                if let Literal::String(s) = lit.as_ref() {
27297                    if s.starts_with('@') {
27298                        self.write(s);
27299                    } else {
27300                        self.generate_expression(file)?;
27301                    }
27302                }
27303            } else if let Expression::Identifier(id) = file {
27304                // Backtick-quoted file path (Databricks style: `s3://link`)
27305                if id.quoted {
27306                    self.write("`");
27307                    self.write(&id.name);
27308                    self.write("`");
27309                } else {
27310                    self.generate_expression(file)?;
27311                }
27312            } else {
27313                self.generate_expression(file)?;
27314            }
27315        }
27316
27317        // Generate credentials if present (Snowflake style - not wrapped in WITH)
27318        if !e.with_wrapped {
27319            if let Some(ref creds) = e.credentials {
27320                if let Some(ref storage) = creds.storage {
27321                    if self.config.pretty {
27322                        self.write_newline();
27323                    } else {
27324                        self.write_space();
27325                    }
27326                    self.write_keyword("STORAGE_INTEGRATION");
27327                    self.write(" = ");
27328                    self.write(storage);
27329                }
27330                if creds.credentials.is_empty() {
27331                    // Empty credentials: CREDENTIALS = ()
27332                    if self.config.pretty {
27333                        self.write_newline();
27334                    } else {
27335                        self.write_space();
27336                    }
27337                    self.write_keyword("CREDENTIALS");
27338                    self.write(" = ()");
27339                } else {
27340                    if self.config.pretty {
27341                        self.write_newline();
27342                    } else {
27343                        self.write_space();
27344                    }
27345                    self.write_keyword("CREDENTIALS");
27346                    // Check if this is Redshift-style (single value with empty key)
27347                    // vs Snowflake-style (multiple key=value pairs)
27348                    if creds.credentials.len() == 1 && creds.credentials[0].0.is_empty() {
27349                        // Redshift style: CREDENTIALS 'value'
27350                        self.write(" '");
27351                        self.write(&creds.credentials[0].1);
27352                        self.write("'");
27353                    } else {
27354                        // Snowflake style: CREDENTIALS = (KEY='value' ...)
27355                        self.write(" = (");
27356                        for (i, (k, v)) in creds.credentials.iter().enumerate() {
27357                            if i > 0 {
27358                                self.write_space();
27359                            }
27360                            self.write(k);
27361                            self.write("='");
27362                            self.write(v);
27363                            self.write("'");
27364                        }
27365                        self.write(")");
27366                    }
27367                }
27368                if let Some(ref encryption) = creds.encryption {
27369                    self.write_space();
27370                    self.write_keyword("ENCRYPTION");
27371                    self.write(" = ");
27372                    self.write(encryption);
27373                }
27374            }
27375        }
27376
27377        // Generate parameters
27378        if !e.params.is_empty() {
27379            if e.with_wrapped {
27380                // DuckDB/PostgreSQL/TSQL WITH (...) format
27381                self.write_space();
27382                self.write_keyword("WITH");
27383                self.write(" (");
27384                for (i, param) in e.params.iter().enumerate() {
27385                    if i > 0 {
27386                        self.write(", ");
27387                    }
27388                    self.generate_copy_param_with_format(param)?;
27389                }
27390                self.write(")");
27391            } else {
27392                // Snowflake/Redshift format: KEY = VALUE or KEY VALUE (space separated, no WITH wrapper)
27393                // For Redshift: IAM_ROLE value, CREDENTIALS 'value', REGION 'value', FORMAT type
27394                // For Snowflake: KEY = VALUE
27395                for param in &e.params {
27396                    if self.config.pretty {
27397                        self.write_newline();
27398                    } else {
27399                        self.write_space();
27400                    }
27401                    // Preserve original case of parameter name (important for Redshift COPY options)
27402                    self.write(&param.name);
27403                    if let Some(ref value) = param.value {
27404                        // Use = only if it was present in the original (param.eq)
27405                        if param.eq {
27406                            self.write(" = ");
27407                        } else {
27408                            self.write(" ");
27409                        }
27410                        if !param.values.is_empty() {
27411                            self.write("(");
27412                            for (i, v) in param.values.iter().enumerate() {
27413                                if i > 0 {
27414                                    self.write_space();
27415                                }
27416                                self.generate_copy_nested_param(v)?;
27417                            }
27418                            self.write(")");
27419                        } else {
27420                            // For COPY parameter values, output identifiers without quoting
27421                            self.generate_copy_param_value(value)?;
27422                        }
27423                    } else if !param.values.is_empty() {
27424                        // For varlen options like FORMAT_OPTIONS, COPY_OPTIONS - no = before (
27425                        if param.eq {
27426                            self.write(" = (");
27427                        } else {
27428                            self.write(" (");
27429                        }
27430                        // Determine separator for values inside parentheses:
27431                        // - Snowflake FILE_FORMAT = (TYPE=CSV FIELD_DELIMITER='|') → space-separated (has = before parens)
27432                        // - Databricks FORMAT_OPTIONS ('opt1'='true', 'opt2'='test') → comma-separated (no = before parens)
27433                        // - Simple value lists like FILES = ('file1', 'file2') → comma-separated
27434                        let is_key_value_pairs = param
27435                            .values
27436                            .first()
27437                            .map_or(false, |v| matches!(v, Expression::Eq(_)));
27438                        let sep = if is_key_value_pairs && param.eq {
27439                            " "
27440                        } else {
27441                            ", "
27442                        };
27443                        for (i, v) in param.values.iter().enumerate() {
27444                            if i > 0 {
27445                                self.write(sep);
27446                            }
27447                            self.generate_copy_nested_param(v)?;
27448                        }
27449                        self.write(")");
27450                    }
27451                }
27452            }
27453        }
27454
27455        Ok(())
27456    }
27457
27458    /// Generate a COPY parameter in WITH (...) format
27459    /// Handles both KEY = VALUE (TSQL) and KEY VALUE (DuckDB/PostgreSQL) formats
27460    fn generate_copy_param_with_format(&mut self, param: &CopyParameter) -> Result<()> {
27461        self.write_keyword(&param.name);
27462        if !param.values.is_empty() {
27463            // Nested values: CREDENTIAL = (IDENTITY='...', SECRET='...')
27464            self.write(" = (");
27465            for (i, v) in param.values.iter().enumerate() {
27466                if i > 0 {
27467                    self.write(", ");
27468                }
27469                self.generate_copy_nested_param(v)?;
27470            }
27471            self.write(")");
27472        } else if let Some(ref value) = param.value {
27473            if param.eq {
27474                self.write(" = ");
27475            } else {
27476                self.write(" ");
27477            }
27478            self.generate_expression(value)?;
27479        }
27480        Ok(())
27481    }
27482
27483    /// Generate nested parameter for COPY statements (KEY=VALUE without spaces)
27484    fn generate_copy_nested_param(&mut self, expr: &Expression) -> Result<()> {
27485        match expr {
27486            Expression::Eq(eq) => {
27487                // Generate key
27488                match &eq.left {
27489                    Expression::Column(c) => self.write(&c.name.name),
27490                    _ => self.generate_expression(&eq.left)?,
27491                }
27492                self.write("=");
27493                // Generate value
27494                match &eq.right {
27495                    Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
27496                        let Literal::String(s) = lit.as_ref() else {
27497                            unreachable!()
27498                        };
27499                        self.write("'");
27500                        self.write(s);
27501                        self.write("'");
27502                    }
27503                    Expression::Tuple(t) => {
27504                        // For lists like NULL_IF=('', 'str1')
27505                        self.write("(");
27506                        if self.config.pretty {
27507                            self.write_newline();
27508                            self.indent_level += 1;
27509                            for (i, item) in t.expressions.iter().enumerate() {
27510                                if i > 0 {
27511                                    self.write(", ");
27512                                }
27513                                self.write_indent();
27514                                self.generate_expression(item)?;
27515                            }
27516                            self.write_newline();
27517                            self.indent_level -= 1;
27518                        } else {
27519                            for (i, item) in t.expressions.iter().enumerate() {
27520                                if i > 0 {
27521                                    self.write(", ");
27522                                }
27523                                self.generate_expression(item)?;
27524                            }
27525                        }
27526                        self.write(")");
27527                    }
27528                    _ => self.generate_expression(&eq.right)?,
27529                }
27530                Ok(())
27531            }
27532            Expression::Column(c) => {
27533                // Standalone keyword like COMPRESSION
27534                self.write(&c.name.name);
27535                Ok(())
27536            }
27537            _ => self.generate_expression(expr),
27538        }
27539    }
27540
27541    /// Generate a COPY parameter value, outputting identifiers/columns without quoting
27542    /// This is needed for Redshift-style COPY params like: IAM_ROLE default, FORMAT orc
27543    fn generate_copy_param_value(&mut self, expr: &Expression) -> Result<()> {
27544        match expr {
27545            Expression::Column(c) => {
27546                // Output identifier, preserving quotes if originally quoted
27547                if c.name.quoted {
27548                    self.write("\"");
27549                    self.write(&c.name.name);
27550                    self.write("\"");
27551                } else {
27552                    self.write(&c.name.name);
27553                }
27554                Ok(())
27555            }
27556            Expression::Identifier(id) => {
27557                // Output identifier, preserving quotes if originally quoted
27558                if id.quoted {
27559                    self.write("\"");
27560                    self.write(&id.name);
27561                    self.write("\"");
27562                } else {
27563                    self.write(&id.name);
27564                }
27565                Ok(())
27566            }
27567            Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
27568                let Literal::String(s) = lit.as_ref() else {
27569                    unreachable!()
27570                };
27571                // Output string with quotes
27572                self.write("'");
27573                self.write(s);
27574                self.write("'");
27575                Ok(())
27576            }
27577            _ => self.generate_expression(expr),
27578        }
27579    }
27580
27581    fn generate_copy_parameter(&mut self, e: &CopyParameter) -> Result<()> {
27582        self.write_keyword(&e.name);
27583        if let Some(ref value) = e.value {
27584            if e.eq {
27585                self.write(" = ");
27586            } else {
27587                self.write(" ");
27588            }
27589            self.generate_expression(value)?;
27590        }
27591        if !e.values.is_empty() {
27592            if e.eq {
27593                self.write(" = ");
27594            } else {
27595                self.write(" ");
27596            }
27597            self.write("(");
27598            for (i, v) in e.values.iter().enumerate() {
27599                if i > 0 {
27600                    self.write(", ");
27601                }
27602                self.generate_expression(v)?;
27603            }
27604            self.write(")");
27605        }
27606        Ok(())
27607    }
27608
27609    fn generate_corr(&mut self, e: &Corr) -> Result<()> {
27610        // CORR(this, expression)
27611        self.write_keyword("CORR");
27612        self.write("(");
27613        self.generate_expression(&e.this)?;
27614        self.write(", ");
27615        self.generate_expression(&e.expression)?;
27616        self.write(")");
27617        Ok(())
27618    }
27619
27620    fn generate_cosine_distance(&mut self, e: &CosineDistance) -> Result<()> {
27621        // COSINE_DISTANCE(this, expression)
27622        self.write_keyword("COSINE_DISTANCE");
27623        self.write("(");
27624        self.generate_expression(&e.this)?;
27625        self.write(", ");
27626        self.generate_expression(&e.expression)?;
27627        self.write(")");
27628        Ok(())
27629    }
27630
27631    fn generate_covar_pop(&mut self, e: &CovarPop) -> Result<()> {
27632        // COVAR_POP(this, expression)
27633        self.write_keyword("COVAR_POP");
27634        self.write("(");
27635        self.generate_expression(&e.this)?;
27636        self.write(", ");
27637        self.generate_expression(&e.expression)?;
27638        self.write(")");
27639        Ok(())
27640    }
27641
27642    fn generate_covar_samp(&mut self, e: &CovarSamp) -> Result<()> {
27643        // COVAR_SAMP(this, expression)
27644        self.write_keyword("COVAR_SAMP");
27645        self.write("(");
27646        self.generate_expression(&e.this)?;
27647        self.write(", ");
27648        self.generate_expression(&e.expression)?;
27649        self.write(")");
27650        Ok(())
27651    }
27652
27653    fn generate_credentials(&mut self, e: &Credentials) -> Result<()> {
27654        // CREDENTIALS (key1='value1', key2='value2')
27655        self.write_keyword("CREDENTIALS");
27656        self.write(" (");
27657        for (i, (key, value)) in e.credentials.iter().enumerate() {
27658            if i > 0 {
27659                self.write(", ");
27660            }
27661            self.write(key);
27662            self.write("='");
27663            self.write(value);
27664            self.write("'");
27665        }
27666        self.write(")");
27667        Ok(())
27668    }
27669
27670    fn generate_credentials_property(&mut self, e: &CredentialsProperty) -> Result<()> {
27671        // CREDENTIALS=(expressions)
27672        self.write_keyword("CREDENTIALS");
27673        self.write("=(");
27674        for (i, expr) in e.expressions.iter().enumerate() {
27675            if i > 0 {
27676                self.write(", ");
27677            }
27678            self.generate_expression(expr)?;
27679        }
27680        self.write(")");
27681        Ok(())
27682    }
27683
27684    fn generate_cte(&mut self, e: &Cte) -> Result<()> {
27685        use crate::dialects::DialectType;
27686
27687        // Python: return f"{alias_sql}{key_expressions} AS {materialized or ''}{self.wrap(expression)}"
27688        // Output: alias [(col1, col2, ...)] AS [MATERIALIZED|NOT MATERIALIZED] (subquery)
27689        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) && !e.alias_first {
27690            self.generate_expression(&e.this)?;
27691            self.write_space();
27692            self.write_keyword("AS");
27693            self.write_space();
27694            self.generate_identifier(&e.alias)?;
27695            return Ok(());
27696        }
27697        self.write(&e.alias.name);
27698
27699        // BigQuery doesn't support column aliases in CTE definitions
27700        let skip_cte_columns = matches!(self.config.dialect, Some(DialectType::BigQuery));
27701
27702        if !e.columns.is_empty() && !skip_cte_columns {
27703            self.write("(");
27704            for (i, col) in e.columns.iter().enumerate() {
27705                if i > 0 {
27706                    self.write(", ");
27707                }
27708                self.write(&col.name);
27709            }
27710            self.write(")");
27711        }
27712        // USING KEY (columns) for DuckDB recursive CTEs
27713        if !e.key_expressions.is_empty() {
27714            self.write_space();
27715            self.write_keyword("USING KEY");
27716            self.write(" (");
27717            for (i, key) in e.key_expressions.iter().enumerate() {
27718                if i > 0 {
27719                    self.write(", ");
27720                }
27721                self.write(&key.name);
27722            }
27723            self.write(")");
27724        }
27725        self.write_space();
27726        self.write_keyword("AS");
27727        self.write_space();
27728        if let Some(materialized) = e.materialized {
27729            if materialized {
27730                self.write_keyword("MATERIALIZED");
27731            } else {
27732                self.write_keyword("NOT MATERIALIZED");
27733            }
27734            self.write_space();
27735        }
27736        self.write("(");
27737        self.generate_expression(&e.this)?;
27738        self.write(")");
27739        Ok(())
27740    }
27741
27742    fn generate_cube(&mut self, e: &Cube) -> Result<()> {
27743        // Python: return f"CUBE {self.wrap(expressions)}" if expressions else "WITH CUBE"
27744        if e.expressions.is_empty() {
27745            self.write_keyword("WITH CUBE");
27746        } else {
27747            self.write_keyword("CUBE");
27748            self.write("(");
27749            for (i, expr) in e.expressions.iter().enumerate() {
27750                if i > 0 {
27751                    self.write(", ");
27752                }
27753                self.generate_expression(expr)?;
27754            }
27755            self.write(")");
27756        }
27757        Ok(())
27758    }
27759
27760    fn generate_current_datetime(&mut self, e: &CurrentDatetime) -> Result<()> {
27761        // CURRENT_DATETIME or CURRENT_DATETIME(timezone)
27762        self.write_keyword("CURRENT_DATETIME");
27763        if let Some(this) = &e.this {
27764            self.write("(");
27765            self.generate_expression(this)?;
27766            self.write(")");
27767        }
27768        Ok(())
27769    }
27770
27771    fn generate_current_schema(&mut self, _e: &CurrentSchema) -> Result<()> {
27772        // CURRENT_SCHEMA - no arguments
27773        self.write_keyword("CURRENT_SCHEMA");
27774        Ok(())
27775    }
27776
27777    fn generate_current_schemas(&mut self, e: &CurrentSchemas) -> Result<()> {
27778        // CURRENT_SCHEMAS(include_implicit)
27779        self.write_keyword("CURRENT_SCHEMAS");
27780        self.write("(");
27781        // Snowflake: drop the argument (CURRENT_SCHEMAS() takes no args)
27782        if !matches!(
27783            self.config.dialect,
27784            Some(crate::dialects::DialectType::Snowflake)
27785        ) {
27786            if let Some(this) = &e.this {
27787                self.generate_expression(this)?;
27788            }
27789        }
27790        self.write(")");
27791        Ok(())
27792    }
27793
27794    fn generate_current_user(&mut self, e: &CurrentUser) -> Result<()> {
27795        // CURRENT_USER or CURRENT_USER()
27796        self.write_keyword("CURRENT_USER");
27797        // Some dialects always need parens: Snowflake, Spark, Hive, DuckDB, BigQuery, MySQL, Databricks
27798        let needs_parens = e.this.is_some()
27799            || matches!(
27800                self.config.dialect,
27801                Some(DialectType::Snowflake)
27802                    | Some(DialectType::Spark)
27803                    | Some(DialectType::Hive)
27804                    | Some(DialectType::DuckDB)
27805                    | Some(DialectType::BigQuery)
27806                    | Some(DialectType::MySQL)
27807                    | Some(DialectType::Databricks)
27808            );
27809        if needs_parens {
27810            self.write("()");
27811        }
27812        Ok(())
27813    }
27814
27815    fn generate_d_pipe(&mut self, e: &DPipe) -> Result<()> {
27816        // In Solr, || is OR, not string concatenation (DPIPE_IS_STRING_CONCAT = False)
27817        if self.config.dialect == Some(DialectType::Solr) {
27818            self.generate_expression(&e.this)?;
27819            self.write(" ");
27820            self.write_keyword("OR");
27821            self.write(" ");
27822            self.generate_expression(&e.expression)?;
27823        } else if self.config.dialect == Some(DialectType::MySQL) {
27824            self.generate_mysql_concat_from_dpipe(e)?;
27825        } else {
27826            // String concatenation: this || expression
27827            self.generate_expression(&e.this)?;
27828            self.write(" || ");
27829            self.generate_expression(&e.expression)?;
27830        }
27831        Ok(())
27832    }
27833
27834    fn generate_data_blocksize_property(&mut self, e: &DataBlocksizeProperty) -> Result<()> {
27835        // DATABLOCKSIZE=... (Teradata)
27836        self.write_keyword("DATABLOCKSIZE");
27837        self.write("=");
27838        if let Some(size) = e.size {
27839            self.write(&size.to_string());
27840            if let Some(units) = &e.units {
27841                self.write_space();
27842                self.generate_expression(units)?;
27843            }
27844        } else if e.minimum.is_some() {
27845            self.write_keyword("MINIMUM");
27846        } else if e.maximum.is_some() {
27847            self.write_keyword("MAXIMUM");
27848        } else if e.default.is_some() {
27849            self.write_keyword("DEFAULT");
27850        }
27851        Ok(())
27852    }
27853
27854    fn generate_data_deletion_property(&mut self, e: &DataDeletionProperty) -> Result<()> {
27855        // DATA_DELETION=ON or DATA_DELETION=OFF or DATA_DELETION=ON(FILTER_COLUMN=col, RETENTION_PERIOD=...)
27856        self.write_keyword("DATA_DELETION");
27857        self.write("=");
27858
27859        let is_on = matches!(&*e.on, Expression::Boolean(BooleanLiteral { value: true }));
27860        let has_options = e.filter_column.is_some() || e.retention_period.is_some();
27861
27862        if is_on {
27863            self.write_keyword("ON");
27864            if has_options {
27865                self.write("(");
27866                let mut first = true;
27867                if let Some(filter_column) = &e.filter_column {
27868                    self.write_keyword("FILTER_COLUMN");
27869                    self.write("=");
27870                    self.generate_expression(filter_column)?;
27871                    first = false;
27872                }
27873                if let Some(retention_period) = &e.retention_period {
27874                    if !first {
27875                        self.write(", ");
27876                    }
27877                    self.write_keyword("RETENTION_PERIOD");
27878                    self.write("=");
27879                    self.generate_expression(retention_period)?;
27880                }
27881                self.write(")");
27882            }
27883        } else {
27884            self.write_keyword("OFF");
27885        }
27886        Ok(())
27887    }
27888
27889    /// Generate a Date function expression
27890    /// For Exasol: {d'value'} -> TO_DATE('value')
27891    /// For other dialects: DATE('value')
27892    fn generate_date_func(&mut self, e: &UnaryFunc) -> Result<()> {
27893        use crate::dialects::DialectType;
27894        use crate::expressions::Literal;
27895
27896        match self.config.dialect {
27897            // Exasol uses TO_DATE for Date expressions
27898            Some(DialectType::Exasol) => {
27899                self.write_keyword("TO_DATE");
27900                self.write("(");
27901                // Extract the string value from the expression if it's a string literal
27902                match &e.this {
27903                    Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
27904                        let Literal::String(s) = lit.as_ref() else {
27905                            unreachable!()
27906                        };
27907                        self.write("'");
27908                        self.write(s);
27909                        self.write("'");
27910                    }
27911                    _ => {
27912                        self.generate_expression(&e.this)?;
27913                    }
27914                }
27915                self.write(")");
27916            }
27917            // Standard: DATE(value)
27918            _ => {
27919                self.write_keyword("DATE");
27920                self.write("(");
27921                self.generate_expression(&e.this)?;
27922                self.write(")");
27923            }
27924        }
27925        Ok(())
27926    }
27927
27928    fn generate_date_bin(&mut self, e: &DateBin) -> Result<()> {
27929        // DATE_BIN(interval, timestamp[, origin])
27930        self.write_keyword("DATE_BIN");
27931        self.write("(");
27932        self.generate_expression(&e.this)?;
27933        self.write(", ");
27934        self.generate_expression(&e.expression)?;
27935        if let Some(origin) = &e.origin {
27936            self.write(", ");
27937            self.generate_expression(origin)?;
27938        }
27939        self.write(")");
27940        Ok(())
27941    }
27942
27943    fn generate_date_format_column_constraint(
27944        &mut self,
27945        e: &DateFormatColumnConstraint,
27946    ) -> Result<()> {
27947        // FORMAT 'format_string' (Teradata)
27948        self.write_keyword("FORMAT");
27949        self.write_space();
27950        self.generate_expression(&e.this)?;
27951        Ok(())
27952    }
27953
27954    fn generate_date_from_parts(&mut self, e: &DateFromParts) -> Result<()> {
27955        // DATE_FROM_PARTS(year, month, day) or DATEFROMPARTS(year, month, day)
27956        self.write_keyword("DATE_FROM_PARTS");
27957        self.write("(");
27958        let mut first = true;
27959        if let Some(year) = &e.year {
27960            self.generate_expression(year)?;
27961            first = false;
27962        }
27963        if let Some(month) = &e.month {
27964            if !first {
27965                self.write(", ");
27966            }
27967            self.generate_expression(month)?;
27968            first = false;
27969        }
27970        if let Some(day) = &e.day {
27971            if !first {
27972                self.write(", ");
27973            }
27974            self.generate_expression(day)?;
27975        }
27976        self.write(")");
27977        Ok(())
27978    }
27979
27980    fn generate_datetime(&mut self, e: &Datetime) -> Result<()> {
27981        // DATETIME(this) or DATETIME(this, expression)
27982        self.write_keyword("DATETIME");
27983        self.write("(");
27984        self.generate_expression(&e.this)?;
27985        if let Some(expr) = &e.expression {
27986            self.write(", ");
27987            self.generate_expression(expr)?;
27988        }
27989        self.write(")");
27990        Ok(())
27991    }
27992
27993    fn generate_datetime_add(&mut self, e: &DatetimeAdd) -> Result<()> {
27994        // DATETIME_ADD(this, expression, unit)
27995        self.write_keyword("DATETIME_ADD");
27996        self.write("(");
27997        self.generate_expression(&e.this)?;
27998        self.write(", ");
27999        self.generate_expression(&e.expression)?;
28000        if let Some(unit) = &e.unit {
28001            self.write(", ");
28002            self.write_keyword(unit);
28003        }
28004        self.write(")");
28005        Ok(())
28006    }
28007
28008    fn generate_datetime_diff(&mut self, e: &DatetimeDiff) -> Result<()> {
28009        // DATETIME_DIFF(this, expression, unit)
28010        self.write_keyword("DATETIME_DIFF");
28011        self.write("(");
28012        self.generate_expression(&e.this)?;
28013        self.write(", ");
28014        self.generate_expression(&e.expression)?;
28015        if let Some(unit) = &e.unit {
28016            self.write(", ");
28017            self.write_keyword(unit);
28018        }
28019        self.write(")");
28020        Ok(())
28021    }
28022
28023    fn generate_datetime_sub(&mut self, e: &DatetimeSub) -> Result<()> {
28024        // DATETIME_SUB(this, expression, unit)
28025        self.write_keyword("DATETIME_SUB");
28026        self.write("(");
28027        self.generate_expression(&e.this)?;
28028        self.write(", ");
28029        self.generate_expression(&e.expression)?;
28030        if let Some(unit) = &e.unit {
28031            self.write(", ");
28032            self.write_keyword(unit);
28033        }
28034        self.write(")");
28035        Ok(())
28036    }
28037
28038    fn generate_datetime_trunc(&mut self, e: &DatetimeTrunc) -> Result<()> {
28039        // DATETIME_TRUNC(this, unit, zone)
28040        self.write_keyword("DATETIME_TRUNC");
28041        self.write("(");
28042        self.generate_expression(&e.this)?;
28043        self.write(", ");
28044        self.write_keyword(&e.unit);
28045        if let Some(zone) = &e.zone {
28046            self.write(", ");
28047            self.generate_expression(zone)?;
28048        }
28049        self.write(")");
28050        Ok(())
28051    }
28052
28053    fn generate_dayname(&mut self, e: &Dayname) -> Result<()> {
28054        // DAYNAME(this)
28055        self.write_keyword("DAYNAME");
28056        self.write("(");
28057        self.generate_expression(&e.this)?;
28058        self.write(")");
28059        Ok(())
28060    }
28061
28062    fn generate_declare(&mut self, e: &Declare) -> Result<()> {
28063        // DECLARE [OR REPLACE] var1 AS type1, var2 AS type2, ...
28064        self.write_keyword("DECLARE");
28065        self.write_space();
28066        if e.replace {
28067            self.write_keyword("OR");
28068            self.write_space();
28069            self.write_keyword("REPLACE");
28070            self.write_space();
28071        }
28072        for (i, expr) in e.expressions.iter().enumerate() {
28073            if i > 0 {
28074                self.write(", ");
28075            }
28076            self.generate_expression(expr)?;
28077        }
28078        Ok(())
28079    }
28080
28081    fn generate_declare_item(&mut self, e: &DeclareItem) -> Result<()> {
28082        use crate::dialects::DialectType;
28083
28084        // variable TYPE [DEFAULT default]
28085        self.generate_expression(&e.this)?;
28086        // BigQuery multi-variable: DECLARE X, Y, Z INT64
28087        for name in &e.additional_names {
28088            self.write(", ");
28089            self.generate_expression(name)?;
28090        }
28091        if let Some(kind) = &e.kind {
28092            self.write_space();
28093            // BigQuery uses: DECLARE x INT64 DEFAULT value (no AS)
28094            // TSQL: Always includes AS (normalization)
28095            // Others: Include AS if present in original
28096            match self.config.dialect {
28097                Some(DialectType::BigQuery) => {
28098                    self.write(kind);
28099                }
28100                Some(DialectType::TSQL) => {
28101                    // TSQL DECLARE: no AS keyword (sqlglot convention)
28102                    // Normalize INT to INTEGER for simple declarations
28103                    // Complex TABLE declarations (with CLUSTERED/INDEX) are preserved as-is
28104                    let is_complex_table = kind.starts_with("TABLE")
28105                        && (kind.contains("CLUSTERED") || kind.contains("INDEX"));
28106                    if is_complex_table {
28107                        self.write(kind);
28108                    } else if kind == "INT" {
28109                        self.write("INTEGER");
28110                    } else if kind.starts_with("TABLE") {
28111                        // Normalize INT to INTEGER inside simple TABLE column definitions
28112                        let normalized = kind
28113                            .replace(" INT ", " INTEGER ")
28114                            .replace(" INT,", " INTEGER,")
28115                            .replace(" INT)", " INTEGER)")
28116                            .replace("(INT ", "(INTEGER ");
28117                        self.write(&normalized);
28118                    } else {
28119                        self.write(kind);
28120                    }
28121                }
28122                _ => {
28123                    if e.has_as {
28124                        self.write_keyword("AS");
28125                        self.write_space();
28126                    }
28127                    self.write(kind);
28128                }
28129            }
28130        }
28131        if let Some(default) = &e.default {
28132            // BigQuery uses DEFAULT, others use =
28133            match self.config.dialect {
28134                Some(DialectType::BigQuery) => {
28135                    self.write_space();
28136                    self.write_keyword("DEFAULT");
28137                    self.write_space();
28138                }
28139                _ => {
28140                    self.write(" = ");
28141                }
28142            }
28143            self.generate_expression(default)?;
28144        }
28145        Ok(())
28146    }
28147
28148    fn generate_decode_case(&mut self, e: &DecodeCase) -> Result<()> {
28149        // DECODE(expr, search1, result1, search2, result2, ..., default)
28150        self.write_keyword("DECODE");
28151        self.write("(");
28152        for (i, expr) in e.expressions.iter().enumerate() {
28153            if i > 0 {
28154                self.write(", ");
28155            }
28156            self.generate_expression(expr)?;
28157        }
28158        self.write(")");
28159        Ok(())
28160    }
28161
28162    fn generate_decompress_binary(&mut self, e: &DecompressBinary) -> Result<()> {
28163        // DECOMPRESS(expr, 'method')
28164        self.write_keyword("DECOMPRESS");
28165        self.write("(");
28166        self.generate_expression(&e.this)?;
28167        self.write(", '");
28168        self.write(&e.method);
28169        self.write("')");
28170        Ok(())
28171    }
28172
28173    fn generate_decompress_string(&mut self, e: &DecompressString) -> Result<()> {
28174        // DECOMPRESS(expr, 'method')
28175        self.write_keyword("DECOMPRESS");
28176        self.write("(");
28177        self.generate_expression(&e.this)?;
28178        self.write(", '");
28179        self.write(&e.method);
28180        self.write("')");
28181        Ok(())
28182    }
28183
28184    fn generate_decrypt(&mut self, e: &Decrypt) -> Result<()> {
28185        // DECRYPT(value, passphrase [, aad [, algorithm]])
28186        self.write_keyword("DECRYPT");
28187        self.write("(");
28188        self.generate_expression(&e.this)?;
28189        if let Some(passphrase) = &e.passphrase {
28190            self.write(", ");
28191            self.generate_expression(passphrase)?;
28192        }
28193        if let Some(aad) = &e.aad {
28194            self.write(", ");
28195            self.generate_expression(aad)?;
28196        }
28197        if let Some(method) = &e.encryption_method {
28198            self.write(", ");
28199            self.generate_expression(method)?;
28200        }
28201        self.write(")");
28202        Ok(())
28203    }
28204
28205    fn generate_decrypt_raw(&mut self, e: &DecryptRaw) -> Result<()> {
28206        // DECRYPT_RAW(value, key [, iv [, aad [, algorithm]]])
28207        self.write_keyword("DECRYPT_RAW");
28208        self.write("(");
28209        self.generate_expression(&e.this)?;
28210        if let Some(key) = &e.key {
28211            self.write(", ");
28212            self.generate_expression(key)?;
28213        }
28214        if let Some(iv) = &e.iv {
28215            self.write(", ");
28216            self.generate_expression(iv)?;
28217        }
28218        if let Some(aad) = &e.aad {
28219            self.write(", ");
28220            self.generate_expression(aad)?;
28221        }
28222        if let Some(method) = &e.encryption_method {
28223            self.write(", ");
28224            self.generate_expression(method)?;
28225        }
28226        self.write(")");
28227        Ok(())
28228    }
28229
28230    fn generate_definer_property(&mut self, e: &DefinerProperty) -> Result<()> {
28231        // DEFINER = user
28232        self.write_keyword("DEFINER");
28233        self.write(" = ");
28234        self.generate_expression(&e.this)?;
28235        Ok(())
28236    }
28237
28238    fn generate_detach(&mut self, e: &Detach) -> Result<()> {
28239        // Python: DETACH[DATABASE IF EXISTS] this
28240        self.write_keyword("DETACH");
28241        if e.exists {
28242            self.write_keyword(" DATABASE IF EXISTS");
28243        }
28244        self.write_space();
28245        self.generate_expression(&e.this)?;
28246        Ok(())
28247    }
28248
28249    fn generate_dict_property(&mut self, e: &DictProperty) -> Result<()> {
28250        let property_name = match e.this.as_ref() {
28251            Expression::Identifier(id) => id.name.as_str(),
28252            Expression::Var(v) => v.this.as_str(),
28253            _ => "DICTIONARY",
28254        };
28255        self.write_keyword(property_name);
28256        self.write("(");
28257        self.write(&e.kind);
28258        if let Some(settings) = &e.settings {
28259            self.write("(");
28260            if let Expression::Tuple(t) = settings.as_ref() {
28261                if self.config.pretty && !t.expressions.is_empty() {
28262                    self.write_newline();
28263                    self.indent_level += 1;
28264                    for (i, pair) in t.expressions.iter().enumerate() {
28265                        if i > 0 {
28266                            self.write(",");
28267                            self.write_newline();
28268                        }
28269                        self.write_indent();
28270                        if let Expression::Tuple(pair_tuple) = pair {
28271                            if let Some(k) = pair_tuple.expressions.first() {
28272                                self.generate_expression(k)?;
28273                            }
28274                            if let Some(v) = pair_tuple.expressions.get(1) {
28275                                self.write(" ");
28276                                self.generate_expression(v)?;
28277                            }
28278                        } else {
28279                            self.generate_expression(pair)?;
28280                        }
28281                    }
28282                    self.indent_level -= 1;
28283                    self.write_newline();
28284                    self.write_indent();
28285                } else {
28286                    for (i, pair) in t.expressions.iter().enumerate() {
28287                        if i > 0 {
28288                            // ClickHouse dict properties are space-separated, not comma-separated
28289                            self.write(" ");
28290                        }
28291                        if let Expression::Tuple(pair_tuple) = pair {
28292                            if let Some(k) = pair_tuple.expressions.first() {
28293                                self.generate_expression(k)?;
28294                            }
28295                            if let Some(v) = pair_tuple.expressions.get(1) {
28296                                self.write(" ");
28297                                self.generate_expression(v)?;
28298                            }
28299                        } else {
28300                            self.generate_expression(pair)?;
28301                        }
28302                    }
28303                }
28304            } else {
28305                self.generate_expression(settings)?;
28306            }
28307            self.write(")");
28308        } else {
28309            // No settings but kind had parens (e.g., SOURCE(NULL()), LAYOUT(FLAT()))
28310            self.write("()");
28311        }
28312        self.write(")");
28313        Ok(())
28314    }
28315
28316    fn generate_dict_range(&mut self, e: &DictRange) -> Result<()> {
28317        let property_name = match e.this.as_ref() {
28318            Expression::Identifier(id) => id.name.as_str(),
28319            Expression::Var(v) => v.this.as_str(),
28320            _ => "RANGE",
28321        };
28322        self.write_keyword(property_name);
28323        self.write("(");
28324        if let Some(min) = &e.min {
28325            self.write_keyword("MIN");
28326            self.write_space();
28327            self.generate_expression(min)?;
28328        }
28329        if let Some(max) = &e.max {
28330            self.write_space();
28331            self.write_keyword("MAX");
28332            self.write_space();
28333            self.generate_expression(max)?;
28334        }
28335        self.write(")");
28336        Ok(())
28337    }
28338
28339    fn generate_directory(&mut self, e: &Directory) -> Result<()> {
28340        // Python: {local}DIRECTORY {this}{row_format}
28341        if e.local.is_some() {
28342            self.write_keyword("LOCAL ");
28343        }
28344        self.write_keyword("DIRECTORY");
28345        self.write_space();
28346        self.generate_expression(&e.this)?;
28347        if let Some(row_format) = &e.row_format {
28348            self.write_space();
28349            self.generate_expression(row_format)?;
28350        }
28351        Ok(())
28352    }
28353
28354    fn generate_dist_key_property(&mut self, e: &DistKeyProperty) -> Result<()> {
28355        // Redshift: DISTKEY(column)
28356        self.write_keyword("DISTKEY");
28357        self.write("(");
28358        self.generate_expression(&e.this)?;
28359        self.write(")");
28360        Ok(())
28361    }
28362
28363    fn generate_dist_style_property(&mut self, e: &DistStyleProperty) -> Result<()> {
28364        // Redshift: DISTSTYLE KEY|ALL|EVEN|AUTO
28365        self.write_keyword("DISTSTYLE");
28366        self.write_space();
28367        self.generate_expression(&e.this)?;
28368        Ok(())
28369    }
28370
28371    fn generate_distribute_by(&mut self, e: &DistributeBy) -> Result<()> {
28372        // Python: "DISTRIBUTE BY" expressions
28373        self.write_keyword("DISTRIBUTE BY");
28374        self.write_space();
28375        for (i, expr) in e.expressions.iter().enumerate() {
28376            if i > 0 {
28377                self.write(", ");
28378            }
28379            self.generate_expression(expr)?;
28380        }
28381        Ok(())
28382    }
28383
28384    fn generate_distributed_by_property(&mut self, e: &DistributedByProperty) -> Result<()> {
28385        // Python: DISTRIBUTED BY kind (expressions) BUCKETS buckets order
28386        self.write_keyword("DISTRIBUTED BY");
28387        self.write_space();
28388        self.write(&e.kind);
28389        if !e.expressions.is_empty() {
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        }
28399        if let Some(buckets) = &e.buckets {
28400            self.write_space();
28401            self.write_keyword("BUCKETS");
28402            self.write_space();
28403            self.generate_expression(buckets)?;
28404        }
28405        if let Some(order) = &e.order {
28406            self.write_space();
28407            self.generate_expression(order)?;
28408        }
28409        Ok(())
28410    }
28411
28412    fn generate_dot_product(&mut self, e: &DotProduct) -> Result<()> {
28413        // DOT_PRODUCT(vector1, vector2)
28414        self.write_keyword("DOT_PRODUCT");
28415        self.write("(");
28416        self.generate_expression(&e.this)?;
28417        self.write(", ");
28418        self.generate_expression(&e.expression)?;
28419        self.write(")");
28420        Ok(())
28421    }
28422
28423    fn generate_drop_partition(&mut self, e: &DropPartition) -> Result<()> {
28424        // Python: DROP{IF EXISTS }expressions
28425        self.write_keyword("DROP");
28426        if e.exists {
28427            self.write_keyword(" IF EXISTS ");
28428        } else {
28429            self.write_space();
28430        }
28431        for (i, expr) in e.expressions.iter().enumerate() {
28432            if i > 0 {
28433                self.write(", ");
28434            }
28435            self.generate_expression(expr)?;
28436        }
28437        Ok(())
28438    }
28439
28440    fn generate_duplicate_key_property(&mut self, e: &DuplicateKeyProperty) -> Result<()> {
28441        // Python: DUPLICATE KEY (expressions)
28442        self.write_keyword("DUPLICATE KEY");
28443        self.write(" (");
28444        for (i, expr) in e.expressions.iter().enumerate() {
28445            if i > 0 {
28446                self.write(", ");
28447            }
28448            self.generate_expression(expr)?;
28449        }
28450        self.write(")");
28451        Ok(())
28452    }
28453
28454    fn generate_elt(&mut self, e: &Elt) -> Result<()> {
28455        // ELT(index, str1, str2, ...)
28456        self.write_keyword("ELT");
28457        self.write("(");
28458        self.generate_expression(&e.this)?;
28459        for expr in &e.expressions {
28460            self.write(", ");
28461            self.generate_expression(expr)?;
28462        }
28463        self.write(")");
28464        Ok(())
28465    }
28466
28467    fn generate_encode(&mut self, e: &Encode) -> Result<()> {
28468        // ENCODE(string, charset)
28469        self.write_keyword("ENCODE");
28470        self.write("(");
28471        self.generate_expression(&e.this)?;
28472        if let Some(charset) = &e.charset {
28473            self.write(", ");
28474            self.generate_expression(charset)?;
28475        }
28476        self.write(")");
28477        Ok(())
28478    }
28479
28480    fn generate_encode_property(&mut self, e: &EncodeProperty) -> Result<()> {
28481        // Python: [KEY ]ENCODE this [properties]
28482        if e.key.is_some() {
28483            self.write_keyword("KEY ");
28484        }
28485        self.write_keyword("ENCODE");
28486        self.write_space();
28487        self.generate_expression(&e.this)?;
28488        if !e.properties.is_empty() {
28489            self.write(" (");
28490            for (i, prop) in e.properties.iter().enumerate() {
28491                if i > 0 {
28492                    self.write(", ");
28493                }
28494                self.generate_expression(prop)?;
28495            }
28496            self.write(")");
28497        }
28498        Ok(())
28499    }
28500
28501    fn generate_encrypt(&mut self, e: &Encrypt) -> Result<()> {
28502        // ENCRYPT(value, passphrase [, aad [, algorithm]])
28503        self.write_keyword("ENCRYPT");
28504        self.write("(");
28505        self.generate_expression(&e.this)?;
28506        if let Some(passphrase) = &e.passphrase {
28507            self.write(", ");
28508            self.generate_expression(passphrase)?;
28509        }
28510        if let Some(aad) = &e.aad {
28511            self.write(", ");
28512            self.generate_expression(aad)?;
28513        }
28514        if let Some(method) = &e.encryption_method {
28515            self.write(", ");
28516            self.generate_expression(method)?;
28517        }
28518        self.write(")");
28519        Ok(())
28520    }
28521
28522    fn generate_encrypt_raw(&mut self, e: &EncryptRaw) -> Result<()> {
28523        // ENCRYPT_RAW(value, key [, iv [, aad [, algorithm]]])
28524        self.write_keyword("ENCRYPT_RAW");
28525        self.write("(");
28526        self.generate_expression(&e.this)?;
28527        if let Some(key) = &e.key {
28528            self.write(", ");
28529            self.generate_expression(key)?;
28530        }
28531        if let Some(iv) = &e.iv {
28532            self.write(", ");
28533            self.generate_expression(iv)?;
28534        }
28535        if let Some(aad) = &e.aad {
28536            self.write(", ");
28537            self.generate_expression(aad)?;
28538        }
28539        if let Some(method) = &e.encryption_method {
28540            self.write(", ");
28541            self.generate_expression(method)?;
28542        }
28543        self.write(")");
28544        Ok(())
28545    }
28546
28547    fn generate_engine_property(&mut self, e: &EngineProperty) -> Result<()> {
28548        // MySQL: ENGINE = InnoDB
28549        self.write_keyword("ENGINE");
28550        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
28551            self.write("=");
28552        } else {
28553            self.write(" = ");
28554        }
28555        self.generate_expression(&e.this)?;
28556        Ok(())
28557    }
28558
28559    fn generate_enviroment_property(&mut self, e: &EnviromentProperty) -> Result<()> {
28560        // ENVIRONMENT (expressions)
28561        self.write_keyword("ENVIRONMENT");
28562        self.write(" (");
28563        for (i, expr) in e.expressions.iter().enumerate() {
28564            if i > 0 {
28565                self.write(", ");
28566            }
28567            self.generate_expression(expr)?;
28568        }
28569        self.write(")");
28570        Ok(())
28571    }
28572
28573    fn generate_ephemeral_column_constraint(
28574        &mut self,
28575        e: &EphemeralColumnConstraint,
28576    ) -> Result<()> {
28577        // MySQL: EPHEMERAL [expr]
28578        self.write_keyword("EPHEMERAL");
28579        if let Some(this) = &e.this {
28580            self.write_space();
28581            self.generate_expression(this)?;
28582        }
28583        Ok(())
28584    }
28585
28586    fn generate_equal_null(&mut self, e: &EqualNull) -> Result<()> {
28587        // Snowflake: EQUAL_NULL(a, b)
28588        self.write_keyword("EQUAL_NULL");
28589        self.write("(");
28590        self.generate_expression(&e.this)?;
28591        self.write(", ");
28592        self.generate_expression(&e.expression)?;
28593        self.write(")");
28594        Ok(())
28595    }
28596
28597    fn generate_euclidean_distance(&mut self, e: &EuclideanDistance) -> Result<()> {
28598        use crate::dialects::DialectType;
28599
28600        // PostgreSQL uses <-> operator syntax
28601        match self.config.dialect {
28602            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift) => {
28603                self.generate_expression(&e.this)?;
28604                self.write(" <-> ");
28605                self.generate_expression(&e.expression)?;
28606            }
28607            _ => {
28608                // Other dialects use EUCLIDEAN_DISTANCE function
28609                self.write_keyword("EUCLIDEAN_DISTANCE");
28610                self.write("(");
28611                self.generate_expression(&e.this)?;
28612                self.write(", ");
28613                self.generate_expression(&e.expression)?;
28614                self.write(")");
28615            }
28616        }
28617        Ok(())
28618    }
28619
28620    fn generate_execute_as_property(&mut self, e: &ExecuteAsProperty) -> Result<()> {
28621        // EXECUTE AS CALLER|OWNER|user
28622        self.write_keyword("EXECUTE AS");
28623        self.write_space();
28624        self.generate_expression(&e.this)?;
28625        Ok(())
28626    }
28627
28628    fn generate_export(&mut self, e: &Export) -> Result<()> {
28629        // BigQuery: EXPORT DATA [WITH CONNECTION connection] OPTIONS (...) AS query
28630        self.write_keyword("EXPORT DATA");
28631        if let Some(connection) = &e.connection {
28632            self.write_space();
28633            self.write_keyword("WITH CONNECTION");
28634            self.write_space();
28635            self.generate_expression(connection)?;
28636        }
28637        if !e.options.is_empty() {
28638            self.write_space();
28639            self.generate_options_clause(&e.options)?;
28640        }
28641        self.write_space();
28642        self.write_keyword("AS");
28643        self.write_space();
28644        self.generate_expression(&e.this)?;
28645        Ok(())
28646    }
28647
28648    fn generate_external_property(&mut self, e: &ExternalProperty) -> Result<()> {
28649        // EXTERNAL [this]
28650        self.write_keyword("EXTERNAL");
28651        if let Some(this) = &e.this {
28652            self.write_space();
28653            self.generate_expression(this)?;
28654        }
28655        Ok(())
28656    }
28657
28658    fn generate_fallback_property(&mut self, e: &FallbackProperty) -> Result<()> {
28659        // Python: {no}FALLBACK{protection}
28660        if e.no.is_some() {
28661            self.write_keyword("NO ");
28662        }
28663        self.write_keyword("FALLBACK");
28664        if e.protection.is_some() {
28665            self.write_keyword(" PROTECTION");
28666        }
28667        Ok(())
28668    }
28669
28670    fn generate_farm_fingerprint(&mut self, e: &FarmFingerprint) -> Result<()> {
28671        // BigQuery: FARM_FINGERPRINT(value)
28672        self.write_keyword("FARM_FINGERPRINT");
28673        self.write("(");
28674        for (i, expr) in e.expressions.iter().enumerate() {
28675            if i > 0 {
28676                self.write(", ");
28677            }
28678            self.generate_expression(expr)?;
28679        }
28680        self.write(")");
28681        Ok(())
28682    }
28683
28684    fn generate_features_at_time(&mut self, e: &FeaturesAtTime) -> Result<()> {
28685        // BigQuery ML: FEATURES_AT_TIME(feature_view, time, [num_rows], [ignore_feature_nulls])
28686        self.write_keyword("FEATURES_AT_TIME");
28687        self.write("(");
28688        self.generate_expression(&e.this)?;
28689        if let Some(time) = &e.time {
28690            self.write(", ");
28691            self.generate_expression(time)?;
28692        }
28693        if let Some(num_rows) = &e.num_rows {
28694            self.write(", ");
28695            self.generate_expression(num_rows)?;
28696        }
28697        if let Some(ignore_nulls) = &e.ignore_feature_nulls {
28698            self.write(", ");
28699            self.generate_expression(ignore_nulls)?;
28700        }
28701        self.write(")");
28702        Ok(())
28703    }
28704
28705    fn generate_fetch(&mut self, e: &Fetch) -> Result<()> {
28706        // For dialects that prefer LIMIT, convert simple FETCH to LIMIT
28707        let use_limit = !e.percent
28708            && !e.with_ties
28709            && e.count.is_some()
28710            && matches!(
28711                self.config.dialect,
28712                Some(DialectType::Spark)
28713                    | Some(DialectType::Hive)
28714                    | Some(DialectType::DuckDB)
28715                    | Some(DialectType::SQLite)
28716                    | Some(DialectType::MySQL)
28717                    | Some(DialectType::BigQuery)
28718                    | Some(DialectType::Databricks)
28719                    | Some(DialectType::StarRocks)
28720                    | Some(DialectType::Doris)
28721                    | Some(DialectType::Athena)
28722                    | Some(DialectType::ClickHouse)
28723            );
28724
28725        if use_limit {
28726            self.write_keyword("LIMIT");
28727            self.write_space();
28728            self.generate_expression(e.count.as_ref().unwrap())?;
28729            return Ok(());
28730        }
28731
28732        // Python: FETCH direction count limit_options
28733        self.write_keyword("FETCH");
28734        if !e.direction.is_empty() {
28735            self.write_space();
28736            self.write_keyword(&e.direction);
28737        }
28738        if let Some(count) = &e.count {
28739            self.write_space();
28740            self.generate_expression(count)?;
28741        }
28742        // Generate PERCENT, ROWS, WITH TIES/ONLY
28743        if e.percent {
28744            self.write_keyword(" PERCENT");
28745        }
28746        if e.rows {
28747            self.write_keyword(" ROWS");
28748        }
28749        if e.with_ties {
28750            self.write_keyword(" WITH TIES");
28751        } else if e.rows {
28752            self.write_keyword(" ONLY");
28753        } else {
28754            self.write_keyword(" ROWS ONLY");
28755        }
28756        Ok(())
28757    }
28758
28759    fn generate_file_format_property(&mut self, e: &FileFormatProperty) -> Result<()> {
28760        // For Hive format: STORED AS this or STORED AS INPUTFORMAT x OUTPUTFORMAT y
28761        // For Spark/Databricks without hive_format: USING this
28762        // For Snowflake/others: FILE_FORMAT = this or FILE_FORMAT = (expressions)
28763        if e.hive_format.is_some() {
28764            // Hive format: STORED AS ...
28765            self.write_keyword("STORED AS");
28766            self.write_space();
28767            if let Some(this) = &e.this {
28768                // Uppercase the format name (e.g., parquet -> PARQUET)
28769                if let Expression::Identifier(id) = this.as_ref() {
28770                    self.write_keyword(&id.name.to_ascii_uppercase());
28771                } else {
28772                    self.generate_expression(this)?;
28773                }
28774            }
28775        } else if matches!(self.config.dialect, Some(DialectType::Hive)) {
28776            // Hive: STORED AS format
28777            self.write_keyword("STORED AS");
28778            self.write_space();
28779            if let Some(this) = &e.this {
28780                if let Expression::Identifier(id) = this.as_ref() {
28781                    self.write_keyword(&id.name.to_ascii_uppercase());
28782                } else {
28783                    self.generate_expression(this)?;
28784                }
28785            }
28786        } else if matches!(
28787            self.config.dialect,
28788            Some(DialectType::Spark) | Some(DialectType::Databricks)
28789        ) {
28790            // Spark/Databricks: USING format (e.g., USING DELTA)
28791            self.write_keyword("USING");
28792            self.write_space();
28793            if let Some(this) = &e.this {
28794                self.generate_expression(this)?;
28795            }
28796        } else {
28797            // Snowflake/standard format
28798            self.write_keyword("FILE_FORMAT");
28799            self.write(" = ");
28800            if let Some(this) = &e.this {
28801                self.generate_expression(this)?;
28802            } else if !e.expressions.is_empty() {
28803                self.write("(");
28804                for (i, expr) in e.expressions.iter().enumerate() {
28805                    if i > 0 {
28806                        self.write(", ");
28807                    }
28808                    self.generate_expression(expr)?;
28809                }
28810                self.write(")");
28811            }
28812        }
28813        Ok(())
28814    }
28815
28816    fn generate_filter(&mut self, e: &Filter) -> Result<()> {
28817        // agg_func FILTER(WHERE condition)
28818        self.generate_expression(&e.this)?;
28819        self.write_space();
28820        self.write_keyword("FILTER");
28821        self.write("(");
28822        self.write_keyword("WHERE");
28823        self.write_space();
28824        self.generate_expression(&e.expression)?;
28825        self.write(")");
28826        Ok(())
28827    }
28828
28829    fn generate_float64(&mut self, e: &Float64) -> Result<()> {
28830        // FLOAT64(this) or FLOAT64(this, expression)
28831        self.write_keyword("FLOAT64");
28832        self.write("(");
28833        self.generate_expression(&e.this)?;
28834        if let Some(expr) = &e.expression {
28835            self.write(", ");
28836            self.generate_expression(expr)?;
28837        }
28838        self.write(")");
28839        Ok(())
28840    }
28841
28842    fn generate_for_in(&mut self, e: &ForIn) -> Result<()> {
28843        // FOR this DO expression
28844        self.write_keyword("FOR");
28845        self.write_space();
28846        self.generate_expression(&e.this)?;
28847        self.write_space();
28848        self.write_keyword("DO");
28849        self.write_space();
28850        self.generate_expression(&e.expression)?;
28851        Ok(())
28852    }
28853
28854    fn generate_foreign_key(&mut self, e: &ForeignKey) -> Result<()> {
28855        // FOREIGN KEY (cols) REFERENCES table(cols) ON DELETE action ON UPDATE action
28856        self.write_keyword("FOREIGN KEY");
28857        if !e.expressions.is_empty() {
28858            self.write(" (");
28859            for (i, expr) in e.expressions.iter().enumerate() {
28860                if i > 0 {
28861                    self.write(", ");
28862                }
28863                self.generate_expression(expr)?;
28864            }
28865            self.write(")");
28866        }
28867        if let Some(reference) = &e.reference {
28868            self.write_space();
28869            self.generate_expression(reference)?;
28870        }
28871        if let Some(delete) = &e.delete {
28872            self.write_space();
28873            self.write_keyword("ON DELETE");
28874            self.write_space();
28875            self.generate_expression(delete)?;
28876        }
28877        if let Some(update) = &e.update {
28878            self.write_space();
28879            self.write_keyword("ON UPDATE");
28880            self.write_space();
28881            self.generate_expression(update)?;
28882        }
28883        if !e.options.is_empty() {
28884            self.write_space();
28885            for (i, opt) in e.options.iter().enumerate() {
28886                if i > 0 {
28887                    self.write_space();
28888                }
28889                self.generate_expression(opt)?;
28890            }
28891        }
28892        Ok(())
28893    }
28894
28895    fn generate_format(&mut self, e: &Format) -> Result<()> {
28896        // FORMAT(this, expressions...)
28897        self.write_keyword("FORMAT");
28898        self.write("(");
28899        self.generate_expression(&e.this)?;
28900        for expr in &e.expressions {
28901            self.write(", ");
28902            self.generate_expression(expr)?;
28903        }
28904        self.write(")");
28905        Ok(())
28906    }
28907
28908    fn generate_format_phrase(&mut self, e: &FormatPhrase) -> Result<()> {
28909        // Teradata: column (FORMAT 'format_string')
28910        self.generate_expression(&e.this)?;
28911        self.write(" (");
28912        self.write_keyword("FORMAT");
28913        self.write(" '");
28914        self.write(&e.format);
28915        self.write("')");
28916        Ok(())
28917    }
28918
28919    fn generate_freespace_property(&mut self, e: &FreespaceProperty) -> Result<()> {
28920        // Python: FREESPACE=this[PERCENT]
28921        self.write_keyword("FREESPACE");
28922        self.write("=");
28923        self.generate_expression(&e.this)?;
28924        if e.percent.is_some() {
28925            self.write_keyword(" PERCENT");
28926        }
28927        Ok(())
28928    }
28929
28930    fn generate_from(&mut self, e: &From) -> Result<()> {
28931        // Python: return f"{self.seg('FROM')} {self.sql(expression, 'this')}"
28932        self.write_keyword("FROM");
28933        self.write_space();
28934
28935        // BigQuery, Hive, Spark, Databricks, SQLite, and ClickHouse prefer explicit CROSS JOIN over comma syntax
28936        // But keep commas when TABLESAMPLE is present
28937        // Also keep commas when the source dialect is Generic/None and target is one of these dialects
28938        use crate::dialects::DialectType;
28939        let has_tablesample = e
28940            .expressions
28941            .iter()
28942            .any(|expr| matches!(expr, Expression::TableSample(_)));
28943        let is_cross_join_dialect = matches!(
28944            self.config.dialect,
28945            Some(DialectType::BigQuery)
28946                | Some(DialectType::Hive)
28947                | Some(DialectType::Spark)
28948                | Some(DialectType::Databricks)
28949                | Some(DialectType::SQLite)
28950                | Some(DialectType::ClickHouse)
28951        );
28952        let source_is_same_as_target2 = self.config.source_dialect.is_some()
28953            && self.config.source_dialect == self.config.dialect;
28954        let source_is_cross_join_dialect2 = matches!(
28955            self.config.source_dialect,
28956            Some(DialectType::BigQuery)
28957                | Some(DialectType::Hive)
28958                | Some(DialectType::Spark)
28959                | Some(DialectType::Databricks)
28960                | Some(DialectType::SQLite)
28961                | Some(DialectType::ClickHouse)
28962        );
28963        let use_cross_join = !has_tablesample
28964            && is_cross_join_dialect
28965            && (source_is_same_as_target2
28966                || source_is_cross_join_dialect2
28967                || self.config.source_dialect.is_none());
28968
28969        // Snowflake wraps standalone VALUES in FROM clause with parentheses
28970        let wrap_values_in_parens = matches!(self.config.dialect, Some(DialectType::Snowflake));
28971
28972        for (i, expr) in e.expressions.iter().enumerate() {
28973            if i > 0 {
28974                if use_cross_join {
28975                    self.write(" CROSS JOIN ");
28976                } else {
28977                    self.write(", ");
28978                }
28979            }
28980            if wrap_values_in_parens && matches!(expr, Expression::Values(_)) {
28981                self.write("(");
28982                self.generate_expression(expr)?;
28983                self.write(")");
28984            } else {
28985                self.generate_expression(expr)?;
28986            }
28987            // Output leading comments that were on the table name before FROM
28988            // (e.g., FROM \n/* comment */\n tbl PIVOT(...) -> ... PIVOT(...) /* comment */)
28989            let leading = Self::extract_table_leading_comments(expr);
28990            for comment in &leading {
28991                self.write_space();
28992                self.write_formatted_comment(comment);
28993            }
28994        }
28995        Ok(())
28996    }
28997
28998    /// Extract leading_comments from a table expression (possibly wrapped in PIVOT/UNPIVOT)
28999    fn extract_table_leading_comments(expr: &Expression) -> Vec<String> {
29000        match expr {
29001            Expression::Table(t) => t.leading_comments.clone(),
29002            Expression::Pivot(p) => {
29003                if let Expression::Table(t) = &p.this {
29004                    t.leading_comments.clone()
29005                } else {
29006                    Vec::new()
29007                }
29008            }
29009            _ => Vec::new(),
29010        }
29011    }
29012
29013    fn generate_from_base(&mut self, e: &FromBase) -> Result<()> {
29014        // FROM_BASE(this, expression) - convert from base N
29015        self.write_keyword("FROM_BASE");
29016        self.write("(");
29017        self.generate_expression(&e.this)?;
29018        self.write(", ");
29019        self.generate_expression(&e.expression)?;
29020        self.write(")");
29021        Ok(())
29022    }
29023
29024    fn generate_from_time_zone(&mut self, e: &FromTimeZone) -> Result<()> {
29025        // this AT TIME ZONE zone AT TIME ZONE 'UTC'
29026        self.generate_expression(&e.this)?;
29027        if let Some(zone) = &e.zone {
29028            self.write_space();
29029            self.write_keyword("AT TIME ZONE");
29030            self.write_space();
29031            self.generate_expression(zone)?;
29032            self.write_space();
29033            self.write_keyword("AT TIME ZONE");
29034            self.write(" 'UTC'");
29035        }
29036        Ok(())
29037    }
29038
29039    fn generate_gap_fill(&mut self, e: &GapFill) -> Result<()> {
29040        // GAP_FILL(this, ts_column, bucket_width, ...)
29041        self.write_keyword("GAP_FILL");
29042        self.write("(");
29043        self.generate_expression(&e.this)?;
29044        if let Some(ts_column) = &e.ts_column {
29045            self.write(", ");
29046            self.generate_expression(ts_column)?;
29047        }
29048        if let Some(bucket_width) = &e.bucket_width {
29049            self.write(", ");
29050            self.generate_expression(bucket_width)?;
29051        }
29052        if let Some(partitioning_columns) = &e.partitioning_columns {
29053            self.write(", ");
29054            self.generate_expression(partitioning_columns)?;
29055        }
29056        if let Some(value_columns) = &e.value_columns {
29057            self.write(", ");
29058            self.generate_expression(value_columns)?;
29059        }
29060        self.write(")");
29061        Ok(())
29062    }
29063
29064    fn generate_generate_date_array(&mut self, e: &GenerateDateArray) -> Result<()> {
29065        // GENERATE_DATE_ARRAY(start, end, step)
29066        self.write_keyword("GENERATE_DATE_ARRAY");
29067        self.write("(");
29068        let mut first = true;
29069        if let Some(start) = &e.start {
29070            self.generate_expression(start)?;
29071            first = false;
29072        }
29073        if let Some(end) = &e.end {
29074            if !first {
29075                self.write(", ");
29076            }
29077            self.generate_expression(end)?;
29078            first = false;
29079        }
29080        if let Some(step) = &e.step {
29081            if !first {
29082                self.write(", ");
29083            }
29084            self.generate_expression(step)?;
29085        }
29086        self.write(")");
29087        Ok(())
29088    }
29089
29090    fn generate_generate_embedding(&mut self, e: &GenerateEmbedding) -> Result<()> {
29091        // ML.GENERATE_EMBEDDING(model, content, params)
29092        self.write_keyword("ML.GENERATE_EMBEDDING");
29093        self.write("(");
29094        self.generate_expression(&e.this)?;
29095        self.write(", ");
29096        self.generate_expression(&e.expression)?;
29097        if let Some(params) = &e.params_struct {
29098            self.write(", ");
29099            self.generate_expression(params)?;
29100        }
29101        self.write(")");
29102        Ok(())
29103    }
29104
29105    fn generate_generate_series(&mut self, e: &GenerateSeries) -> Result<()> {
29106        // Dialect-specific function name
29107        let fn_name = match self.config.dialect {
29108            Some(DialectType::Presto)
29109            | Some(DialectType::Trino)
29110            | Some(DialectType::Athena)
29111            | Some(DialectType::Spark)
29112            | Some(DialectType::Databricks)
29113            | Some(DialectType::Hive) => "SEQUENCE",
29114            _ => "GENERATE_SERIES",
29115        };
29116        self.write_keyword(fn_name);
29117        self.write("(");
29118        let mut first = true;
29119        if let Some(start) = &e.start {
29120            self.generate_expression(start)?;
29121            first = false;
29122        }
29123        if let Some(end) = &e.end {
29124            if !first {
29125                self.write(", ");
29126            }
29127            self.generate_expression(end)?;
29128            first = false;
29129        }
29130        if let Some(step) = &e.step {
29131            if !first {
29132                self.write(", ");
29133            }
29134            // For Presto/Trino: convert WEEK intervals to DAY multiples
29135            // e.g., INTERVAL '1' WEEK -> (1 * INTERVAL '7' DAY)
29136            if matches!(
29137                self.config.dialect,
29138                Some(DialectType::Presto) | Some(DialectType::Trino) | Some(DialectType::Athena)
29139            ) {
29140                if let Some(converted) = self.convert_week_interval_to_day(step) {
29141                    self.generate_expression(&converted)?;
29142                } else {
29143                    self.generate_expression(step)?;
29144                }
29145            } else {
29146                self.generate_expression(step)?;
29147            }
29148        }
29149        self.write(")");
29150        Ok(())
29151    }
29152
29153    /// Convert a WEEK interval to a DAY-based multiplication expression for Presto/Trino.
29154    /// INTERVAL N WEEK -> (N * INTERVAL '7' DAY)
29155    fn convert_week_interval_to_day(&self, expr: &Expression) -> Option<Expression> {
29156        use crate::expressions::*;
29157        if let Expression::Interval(ref iv) = expr {
29158            // Check for structured WEEK unit
29159            let (is_week, count_str) = if let Some(IntervalUnitSpec::Simple {
29160                unit: IntervalUnit::Week,
29161                ..
29162            }) = &iv.unit
29163            {
29164                // Value is in iv.this
29165                let count = match &iv.this {
29166                    Some(Expression::Literal(lit)) => match lit.as_ref() {
29167                        Literal::String(s) | Literal::Number(s) => s.clone(),
29168                        _ => return None,
29169                    },
29170                    _ => return None,
29171                };
29172                (true, count)
29173            } else if iv.unit.is_none() {
29174                // Check for string-encoded interval like "1 WEEK"
29175                if let Some(Expression::Literal(lit)) = &iv.this {
29176                    if let Literal::String(s) = lit.as_ref() {
29177                        let parts: Vec<&str> = s.trim().splitn(2, char::is_whitespace).collect();
29178                        if parts.len() == 2 && parts[1].eq_ignore_ascii_case("WEEK") {
29179                            (true, parts[0].to_string())
29180                        } else {
29181                            (false, String::new())
29182                        }
29183                    } else {
29184                        (false, String::new())
29185                    }
29186                } else {
29187                    (false, String::new())
29188                }
29189            } else {
29190                (false, String::new())
29191            };
29192
29193            if is_week {
29194                // Build: (N * INTERVAL '7' DAY)
29195                let count_expr = Expression::Literal(Box::new(Literal::Number(count_str)));
29196                let day_interval = Expression::Interval(Box::new(Interval {
29197                    this: Some(Expression::Literal(Box::new(Literal::String(
29198                        "7".to_string(),
29199                    )))),
29200                    unit: Some(IntervalUnitSpec::Simple {
29201                        unit: IntervalUnit::Day,
29202                        use_plural: false,
29203                    }),
29204                }));
29205                let mul = Expression::Mul(Box::new(BinaryOp {
29206                    left: count_expr,
29207                    right: day_interval,
29208                    left_comments: vec![],
29209                    operator_comments: vec![],
29210                    trailing_comments: vec![],
29211                    inferred_type: None,
29212                }));
29213                return Some(Expression::Paren(Box::new(Paren {
29214                    this: mul,
29215                    trailing_comments: vec![],
29216                })));
29217            }
29218        }
29219        None
29220    }
29221
29222    fn generate_generate_timestamp_array(&mut self, e: &GenerateTimestampArray) -> Result<()> {
29223        // GENERATE_TIMESTAMP_ARRAY(start, end, step)
29224        self.write_keyword("GENERATE_TIMESTAMP_ARRAY");
29225        self.write("(");
29226        let mut first = true;
29227        if let Some(start) = &e.start {
29228            self.generate_expression(start)?;
29229            first = false;
29230        }
29231        if let Some(end) = &e.end {
29232            if !first {
29233                self.write(", ");
29234            }
29235            self.generate_expression(end)?;
29236            first = false;
29237        }
29238        if let Some(step) = &e.step {
29239            if !first {
29240                self.write(", ");
29241            }
29242            self.generate_expression(step)?;
29243        }
29244        self.write(")");
29245        Ok(())
29246    }
29247
29248    fn generate_generated_as_identity_column_constraint(
29249        &mut self,
29250        e: &GeneratedAsIdentityColumnConstraint,
29251    ) -> Result<()> {
29252        use crate::dialects::DialectType;
29253
29254        // For Snowflake, use AUTOINCREMENT START x INCREMENT y syntax
29255        if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
29256            self.write_keyword("AUTOINCREMENT");
29257            if let Some(start) = &e.start {
29258                self.write_keyword(" START ");
29259                self.generate_expression(start)?;
29260            }
29261            if let Some(increment) = &e.increment {
29262                self.write_keyword(" INCREMENT ");
29263                self.generate_expression(increment)?;
29264            }
29265            return Ok(());
29266        }
29267
29268        // Python: GENERATED [ALWAYS|BY DEFAULT [ON NULL]] AS IDENTITY [(start, increment, ...)]
29269        self.write_keyword("GENERATED");
29270        if let Some(this) = &e.this {
29271            // Check if it's a truthy boolean expression
29272            if let Expression::Boolean(b) = this.as_ref() {
29273                if b.value {
29274                    self.write_keyword(" ALWAYS");
29275                } else {
29276                    self.write_keyword(" BY DEFAULT");
29277                    if e.on_null.is_some() {
29278                        self.write_keyword(" ON NULL");
29279                    }
29280                }
29281            } else {
29282                self.write_keyword(" ALWAYS");
29283            }
29284        }
29285        self.write_keyword(" AS IDENTITY");
29286        // Add sequence options if any
29287        let has_options = e.start.is_some()
29288            || e.increment.is_some()
29289            || e.minvalue.is_some()
29290            || e.maxvalue.is_some();
29291        if has_options {
29292            self.write(" (");
29293            let mut first = true;
29294            if let Some(start) = &e.start {
29295                self.write_keyword("START WITH ");
29296                self.generate_expression(start)?;
29297                first = false;
29298            }
29299            if let Some(increment) = &e.increment {
29300                if !first {
29301                    self.write(" ");
29302                }
29303                self.write_keyword("INCREMENT BY ");
29304                self.generate_expression(increment)?;
29305                first = false;
29306            }
29307            if let Some(minvalue) = &e.minvalue {
29308                if !first {
29309                    self.write(" ");
29310                }
29311                self.write_keyword("MINVALUE ");
29312                self.generate_expression(minvalue)?;
29313                first = false;
29314            }
29315            if let Some(maxvalue) = &e.maxvalue {
29316                if !first {
29317                    self.write(" ");
29318                }
29319                self.write_keyword("MAXVALUE ");
29320                self.generate_expression(maxvalue)?;
29321            }
29322            self.write(")");
29323        }
29324        Ok(())
29325    }
29326
29327    fn generate_generated_as_row_column_constraint(
29328        &mut self,
29329        e: &GeneratedAsRowColumnConstraint,
29330    ) -> Result<()> {
29331        // Python: GENERATED ALWAYS AS ROW START|END [HIDDEN]
29332        self.write_keyword("GENERATED ALWAYS AS ROW ");
29333        if e.start.is_some() {
29334            self.write_keyword("START");
29335        } else {
29336            self.write_keyword("END");
29337        }
29338        if e.hidden.is_some() {
29339            self.write_keyword(" HIDDEN");
29340        }
29341        Ok(())
29342    }
29343
29344    fn generate_get(&mut self, e: &Get) -> Result<()> {
29345        // GET this target properties
29346        self.write_keyword("GET");
29347        self.write_space();
29348        self.generate_expression(&e.this)?;
29349        if let Some(target) = &e.target {
29350            self.write_space();
29351            self.generate_expression(target)?;
29352        }
29353        for prop in &e.properties {
29354            self.write_space();
29355            self.generate_expression(prop)?;
29356        }
29357        Ok(())
29358    }
29359
29360    fn generate_get_extract(&mut self, e: &GetExtract) -> Result<()> {
29361        // GetExtract generates bracket access: this[expression]
29362        self.generate_expression(&e.this)?;
29363        self.write("[");
29364        self.generate_expression(&e.expression)?;
29365        self.write("]");
29366        Ok(())
29367    }
29368
29369    fn generate_getbit(&mut self, e: &Getbit) -> Result<()> {
29370        // GETBIT(this, expression) or GET_BIT(this, expression)
29371        self.write_keyword("GETBIT");
29372        self.write("(");
29373        self.generate_expression(&e.this)?;
29374        self.write(", ");
29375        self.generate_expression(&e.expression)?;
29376        self.write(")");
29377        Ok(())
29378    }
29379
29380    fn generate_grant_principal(&mut self, e: &GrantPrincipal) -> Result<()> {
29381        // [ROLE|GROUP|SHARE] name (e.g., "ROLE admin", "GROUP qa_users", "SHARE s1", or just "user1")
29382        if e.is_role {
29383            self.write_keyword("ROLE");
29384            self.write_space();
29385        } else if e.is_group {
29386            self.write_keyword("GROUP");
29387            self.write_space();
29388        } else if e.is_share {
29389            self.write_keyword("SHARE");
29390            self.write_space();
29391        }
29392        self.write(&e.name.name);
29393        Ok(())
29394    }
29395
29396    fn generate_grant_privilege(&mut self, e: &GrantPrivilege) -> Result<()> {
29397        // privilege(columns) or just privilege
29398        self.generate_expression(&e.this)?;
29399        if !e.expressions.is_empty() {
29400            self.write("(");
29401            for (i, expr) in e.expressions.iter().enumerate() {
29402                if i > 0 {
29403                    self.write(", ");
29404                }
29405                self.generate_expression(expr)?;
29406            }
29407            self.write(")");
29408        }
29409        Ok(())
29410    }
29411
29412    fn generate_group(&mut self, e: &Group) -> Result<()> {
29413        // Python handles GROUP BY ALL/DISTINCT modifiers and grouping expressions
29414        self.write_keyword("GROUP BY");
29415        // Handle ALL/DISTINCT modifier: Some(true) = ALL, Some(false) = DISTINCT
29416        match e.all {
29417            Some(true) => {
29418                self.write_space();
29419                self.write_keyword("ALL");
29420            }
29421            Some(false) => {
29422                self.write_space();
29423                self.write_keyword("DISTINCT");
29424            }
29425            None => {}
29426        }
29427        if !e.expressions.is_empty() {
29428            self.write_space();
29429            for (i, expr) in e.expressions.iter().enumerate() {
29430                if i > 0 {
29431                    self.write(", ");
29432                }
29433                self.generate_expression(expr)?;
29434            }
29435        }
29436        // Handle CUBE, ROLLUP, GROUPING SETS
29437        if let Some(cube) = &e.cube {
29438            if !e.expressions.is_empty() {
29439                self.write(", ");
29440            } else {
29441                self.write_space();
29442            }
29443            self.generate_expression(cube)?;
29444        }
29445        if let Some(rollup) = &e.rollup {
29446            if !e.expressions.is_empty() || e.cube.is_some() {
29447                self.write(", ");
29448            } else {
29449                self.write_space();
29450            }
29451            self.generate_expression(rollup)?;
29452        }
29453        if let Some(grouping_sets) = &e.grouping_sets {
29454            if !e.expressions.is_empty() || e.cube.is_some() || e.rollup.is_some() {
29455                self.write(", ");
29456            } else {
29457                self.write_space();
29458            }
29459            self.generate_expression(grouping_sets)?;
29460        }
29461        if let Some(totals) = &e.totals {
29462            self.write_space();
29463            self.write_keyword("WITH TOTALS");
29464            self.generate_expression(totals)?;
29465        }
29466        Ok(())
29467    }
29468
29469    fn generate_group_by(&mut self, e: &GroupBy) -> Result<()> {
29470        // GROUP BY expressions
29471        self.write_keyword("GROUP BY");
29472        // Handle ALL/DISTINCT modifier: Some(true) = ALL, Some(false) = DISTINCT
29473        match e.all {
29474            Some(true) => {
29475                self.write_space();
29476                self.write_keyword("ALL");
29477            }
29478            Some(false) => {
29479                self.write_space();
29480                self.write_keyword("DISTINCT");
29481            }
29482            None => {}
29483        }
29484
29485        // Check for trailing WITH CUBE or WITH ROLLUP (Hive/MySQL syntax)
29486        // These are represented as Cube/Rollup expressions with empty expressions at the end
29487        let mut trailing_cube = false;
29488        let mut trailing_rollup = false;
29489        let mut regular_expressions: Vec<&Expression> = Vec::new();
29490
29491        for expr in &e.expressions {
29492            match expr {
29493                Expression::Cube(c) if c.expressions.is_empty() => {
29494                    trailing_cube = true;
29495                }
29496                Expression::Rollup(r) if r.expressions.is_empty() => {
29497                    trailing_rollup = true;
29498                }
29499                _ => {
29500                    regular_expressions.push(expr);
29501                }
29502            }
29503        }
29504
29505        // In pretty mode, put columns on separate lines
29506        if self.config.pretty {
29507            self.write_newline();
29508            self.indent_level += 1;
29509            for (i, expr) in regular_expressions.iter().enumerate() {
29510                if i > 0 {
29511                    self.write(",");
29512                    self.write_newline();
29513                }
29514                self.write_indent();
29515                self.generate_expression(expr)?;
29516            }
29517            self.indent_level -= 1;
29518        } else {
29519            self.write_space();
29520            for (i, expr) in regular_expressions.iter().enumerate() {
29521                if i > 0 {
29522                    self.write(", ");
29523                }
29524                self.generate_expression(expr)?;
29525            }
29526        }
29527
29528        // Output trailing WITH CUBE or WITH ROLLUP
29529        if trailing_cube {
29530            self.write_space();
29531            self.write_keyword("WITH CUBE");
29532        } else if trailing_rollup {
29533            self.write_space();
29534            self.write_keyword("WITH ROLLUP");
29535        }
29536
29537        // ClickHouse: WITH TOTALS
29538        if e.totals {
29539            self.write_space();
29540            self.write_keyword("WITH TOTALS");
29541        }
29542
29543        Ok(())
29544    }
29545
29546    fn generate_grouping(&mut self, e: &Grouping) -> Result<()> {
29547        // GROUPING(col1, col2, ...)
29548        self.write_keyword("GROUPING");
29549        self.write("(");
29550        for (i, expr) in e.expressions.iter().enumerate() {
29551            if i > 0 {
29552                self.write(", ");
29553            }
29554            self.generate_expression(expr)?;
29555        }
29556        self.write(")");
29557        Ok(())
29558    }
29559
29560    fn generate_grouping_id(&mut self, e: &GroupingId) -> Result<()> {
29561        // GROUPING_ID(col1, col2, ...)
29562        self.write_keyword("GROUPING_ID");
29563        self.write("(");
29564        for (i, expr) in e.expressions.iter().enumerate() {
29565            if i > 0 {
29566                self.write(", ");
29567            }
29568            self.generate_expression(expr)?;
29569        }
29570        self.write(")");
29571        Ok(())
29572    }
29573
29574    fn generate_grouping_sets(&mut self, e: &GroupingSets) -> Result<()> {
29575        // Python: return f"GROUPING SETS {self.wrap(grouping_sets)}"
29576        self.write_keyword("GROUPING SETS");
29577        self.write(" (");
29578        for (i, expr) in e.expressions.iter().enumerate() {
29579            if i > 0 {
29580                self.write(", ");
29581            }
29582            self.generate_expression(expr)?;
29583        }
29584        self.write(")");
29585        Ok(())
29586    }
29587
29588    fn generate_hash_agg(&mut self, e: &HashAgg) -> Result<()> {
29589        // HASH_AGG(this, expressions...)
29590        self.write_keyword("HASH_AGG");
29591        self.write("(");
29592        self.generate_expression(&e.this)?;
29593        for expr in &e.expressions {
29594            self.write(", ");
29595            self.generate_expression(expr)?;
29596        }
29597        self.write(")");
29598        Ok(())
29599    }
29600
29601    fn generate_having(&mut self, e: &Having) -> Result<()> {
29602        // Python: return f"{self.seg('HAVING')}{self.sep()}{this}"
29603        self.write_keyword("HAVING");
29604        self.write_space();
29605        self.generate_expression(&e.this)?;
29606        Ok(())
29607    }
29608
29609    fn generate_having_max(&mut self, e: &HavingMax) -> Result<()> {
29610        // Python: this HAVING MAX|MIN expression
29611        self.generate_expression(&e.this)?;
29612        self.write_space();
29613        self.write_keyword("HAVING");
29614        self.write_space();
29615        if e.max.is_some() {
29616            self.write_keyword("MAX");
29617        } else {
29618            self.write_keyword("MIN");
29619        }
29620        self.write_space();
29621        self.generate_expression(&e.expression)?;
29622        Ok(())
29623    }
29624
29625    fn generate_heredoc(&mut self, e: &Heredoc) -> Result<()> {
29626        use crate::dialects::DialectType;
29627        // DuckDB: convert dollar-tagged strings to single-quoted
29628        if matches!(self.config.dialect, Some(DialectType::DuckDB)) {
29629            // Extract the string content and output as single-quoted
29630            if let Expression::Literal(ref lit) = *e.this {
29631                if let Literal::String(ref s) = lit.as_ref() {
29632                    return self.generate_string_literal(s);
29633                }
29634            }
29635        }
29636        // PostgreSQL: preserve dollar-quoting
29637        if matches!(
29638            self.config.dialect,
29639            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
29640        ) {
29641            self.write("$");
29642            if let Some(tag) = &e.tag {
29643                self.generate_expression(tag)?;
29644            }
29645            self.write("$");
29646            self.generate_expression(&e.this)?;
29647            self.write("$");
29648            if let Some(tag) = &e.tag {
29649                self.generate_expression(tag)?;
29650            }
29651            self.write("$");
29652            return Ok(());
29653        }
29654        // Default: output as dollar-tagged
29655        self.write("$");
29656        if let Some(tag) = &e.tag {
29657            self.generate_expression(tag)?;
29658        }
29659        self.write("$");
29660        self.generate_expression(&e.this)?;
29661        self.write("$");
29662        if let Some(tag) = &e.tag {
29663            self.generate_expression(tag)?;
29664        }
29665        self.write("$");
29666        Ok(())
29667    }
29668
29669    fn generate_hex_encode(&mut self, e: &HexEncode) -> Result<()> {
29670        // HEX_ENCODE(this)
29671        self.write_keyword("HEX_ENCODE");
29672        self.write("(");
29673        self.generate_expression(&e.this)?;
29674        self.write(")");
29675        Ok(())
29676    }
29677
29678    fn generate_historical_data(&mut self, e: &HistoricalData) -> Result<()> {
29679        // Python: this (kind => expression)
29680        // Write the keyword (AT/BEFORE/END) directly to avoid quoting it as a reserved word
29681        match e.this.as_ref() {
29682            Expression::Identifier(id) => self.write(&id.name),
29683            other => self.generate_expression(other)?,
29684        }
29685        self.write(" (");
29686        self.write(&e.kind);
29687        self.write(" => ");
29688        self.generate_expression(&e.expression)?;
29689        self.write(")");
29690        Ok(())
29691    }
29692
29693    fn generate_hll(&mut self, e: &Hll) -> Result<()> {
29694        // HLL(this, expressions...)
29695        self.write_keyword("HLL");
29696        self.write("(");
29697        self.generate_expression(&e.this)?;
29698        for expr in &e.expressions {
29699            self.write(", ");
29700            self.generate_expression(expr)?;
29701        }
29702        self.write(")");
29703        Ok(())
29704    }
29705
29706    fn generate_in_out_column_constraint(&mut self, e: &InOutColumnConstraint) -> Result<()> {
29707        // Python: IN|OUT|IN OUT
29708        if e.input_.is_some() && e.output.is_some() {
29709            self.write_keyword("IN OUT");
29710        } else if e.input_.is_some() {
29711            self.write_keyword("IN");
29712        } else if e.output.is_some() {
29713            self.write_keyword("OUT");
29714        }
29715        Ok(())
29716    }
29717
29718    fn generate_include_property(&mut self, e: &IncludeProperty) -> Result<()> {
29719        // Python: INCLUDE this [column_def] [AS alias]
29720        self.write_keyword("INCLUDE");
29721        self.write_space();
29722        self.generate_expression(&e.this)?;
29723        if let Some(column_def) = &e.column_def {
29724            self.write_space();
29725            self.generate_expression(column_def)?;
29726        }
29727        if let Some(alias) = &e.alias {
29728            self.write_space();
29729            self.write_keyword("AS");
29730            self.write_space();
29731            self.write(alias);
29732        }
29733        Ok(())
29734    }
29735
29736    fn generate_index(&mut self, e: &Index) -> Result<()> {
29737        // [UNIQUE] [PRIMARY] [AMP] INDEX [name] [ON table] (params)
29738        if e.unique {
29739            self.write_keyword("UNIQUE");
29740            self.write_space();
29741        }
29742        if e.primary.is_some() {
29743            self.write_keyword("PRIMARY");
29744            self.write_space();
29745        }
29746        if e.amp.is_some() {
29747            self.write_keyword("AMP");
29748            self.write_space();
29749        }
29750        if e.table.is_none() {
29751            self.write_keyword("INDEX");
29752            self.write_space();
29753        }
29754        if let Some(name) = &e.this {
29755            self.generate_expression(name)?;
29756            self.write_space();
29757        }
29758        if let Some(table) = &e.table {
29759            self.write_keyword("ON");
29760            self.write_space();
29761            self.generate_expression(table)?;
29762        }
29763        if !e.params.is_empty() {
29764            self.write("(");
29765            for (i, param) in e.params.iter().enumerate() {
29766                if i > 0 {
29767                    self.write(", ");
29768                }
29769                self.generate_expression(param)?;
29770            }
29771            self.write(")");
29772        }
29773        Ok(())
29774    }
29775
29776    fn generate_index_column_constraint(&mut self, e: &IndexColumnConstraint) -> Result<()> {
29777        // Python: kind INDEX [this] [USING index_type] (expressions) [options]
29778        if let Some(kind) = &e.kind {
29779            self.write(kind);
29780            self.write_space();
29781        }
29782        self.write_keyword("INDEX");
29783        if let Some(this) = &e.this {
29784            self.write_space();
29785            self.generate_expression(this)?;
29786        }
29787        if let Some(index_type) = &e.index_type {
29788            self.write_space();
29789            self.write_keyword("USING");
29790            self.write_space();
29791            self.generate_expression(index_type)?;
29792        }
29793        if !e.expressions.is_empty() {
29794            self.write(" (");
29795            for (i, expr) in e.expressions.iter().enumerate() {
29796                if i > 0 {
29797                    self.write(", ");
29798                }
29799                self.generate_expression(expr)?;
29800            }
29801            self.write(")");
29802        }
29803        for opt in &e.options {
29804            self.write_space();
29805            self.generate_expression(opt)?;
29806        }
29807        Ok(())
29808    }
29809
29810    fn generate_index_constraint_option(&mut self, e: &IndexConstraintOption) -> Result<()> {
29811        // Python: KEY_BLOCK_SIZE = x | USING x | WITH PARSER x | COMMENT x | visible | engine_attr | secondary_engine_attr
29812        if let Some(key_block_size) = &e.key_block_size {
29813            self.write_keyword("KEY_BLOCK_SIZE");
29814            self.write(" = ");
29815            self.generate_expression(key_block_size)?;
29816        } else if let Some(using) = &e.using {
29817            self.write_keyword("USING");
29818            self.write_space();
29819            self.generate_expression(using)?;
29820        } else if let Some(parser) = &e.parser {
29821            self.write_keyword("WITH PARSER");
29822            self.write_space();
29823            self.generate_expression(parser)?;
29824        } else if let Some(comment) = &e.comment {
29825            self.write_keyword("COMMENT");
29826            self.write_space();
29827            self.generate_expression(comment)?;
29828        } else if let Some(visible) = &e.visible {
29829            self.generate_expression(visible)?;
29830        } else if let Some(engine_attr) = &e.engine_attr {
29831            self.write_keyword("ENGINE_ATTRIBUTE");
29832            self.write(" = ");
29833            self.generate_expression(engine_attr)?;
29834        } else if let Some(secondary_engine_attr) = &e.secondary_engine_attr {
29835            self.write_keyword("SECONDARY_ENGINE_ATTRIBUTE");
29836            self.write(" = ");
29837            self.generate_expression(secondary_engine_attr)?;
29838        }
29839        Ok(())
29840    }
29841
29842    fn generate_index_parameters(&mut self, e: &IndexParameters) -> Result<()> {
29843        // Python: [USING using] (columns) [PARTITION BY partition_by] [where] [INCLUDE (include)] [WITH (with_storage)] [USING INDEX TABLESPACE tablespace]
29844        if let Some(using) = &e.using {
29845            self.write_keyword("USING");
29846            self.write_space();
29847            self.generate_expression(using)?;
29848        }
29849        if !e.columns.is_empty() {
29850            self.write("(");
29851            for (i, col) in e.columns.iter().enumerate() {
29852                if i > 0 {
29853                    self.write(", ");
29854                }
29855                self.generate_expression(col)?;
29856            }
29857            self.write(")");
29858        }
29859        if let Some(partition_by) = &e.partition_by {
29860            self.write_space();
29861            self.write_keyword("PARTITION BY");
29862            self.write_space();
29863            self.generate_expression(partition_by)?;
29864        }
29865        if let Some(where_) = &e.where_ {
29866            self.write_space();
29867            self.generate_expression(where_)?;
29868        }
29869        if let Some(include) = &e.include {
29870            self.write_space();
29871            self.write_keyword("INCLUDE");
29872            self.write(" (");
29873            self.generate_expression(include)?;
29874            self.write(")");
29875        }
29876        if let Some(with_storage) = &e.with_storage {
29877            self.write_space();
29878            self.write_keyword("WITH");
29879            self.write(" (");
29880            self.generate_expression(with_storage)?;
29881            self.write(")");
29882        }
29883        if let Some(tablespace) = &e.tablespace {
29884            self.write_space();
29885            self.write_keyword("USING INDEX TABLESPACE");
29886            self.write_space();
29887            self.generate_expression(tablespace)?;
29888        }
29889        Ok(())
29890    }
29891
29892    fn generate_index_table_hint(&mut self, e: &IndexTableHint) -> Result<()> {
29893        // Python: this INDEX [FOR target] (expressions)
29894        // Write hint type (USE/IGNORE/FORCE) as keyword, not through generate_expression
29895        // to avoid quoting reserved keywords like IGNORE, FORCE, JOIN
29896        if let Expression::Identifier(id) = &*e.this {
29897            self.write_keyword(&id.name);
29898        } else {
29899            self.generate_expression(&e.this)?;
29900        }
29901        self.write_space();
29902        self.write_keyword("INDEX");
29903        if let Some(target) = &e.target {
29904            self.write_space();
29905            self.write_keyword("FOR");
29906            self.write_space();
29907            if let Expression::Identifier(id) = &**target {
29908                self.write_keyword(&id.name);
29909            } else {
29910                self.generate_expression(target)?;
29911            }
29912        }
29913        // Always output parentheses (even if empty, e.g. USE INDEX ())
29914        self.write(" (");
29915        for (i, expr) in e.expressions.iter().enumerate() {
29916            if i > 0 {
29917                self.write(", ");
29918            }
29919            self.generate_expression(expr)?;
29920        }
29921        self.write(")");
29922        Ok(())
29923    }
29924
29925    fn generate_inherits_property(&mut self, e: &InheritsProperty) -> Result<()> {
29926        // INHERITS (table1, table2, ...)
29927        self.write_keyword("INHERITS");
29928        self.write(" (");
29929        for (i, expr) in e.expressions.iter().enumerate() {
29930            if i > 0 {
29931                self.write(", ");
29932            }
29933            self.generate_expression(expr)?;
29934        }
29935        self.write(")");
29936        Ok(())
29937    }
29938
29939    fn generate_input_model_property(&mut self, e: &InputModelProperty) -> Result<()> {
29940        // INPUT(model)
29941        self.write_keyword("INPUT");
29942        self.write("(");
29943        self.generate_expression(&e.this)?;
29944        self.write(")");
29945        Ok(())
29946    }
29947
29948    fn generate_input_output_format(&mut self, e: &InputOutputFormat) -> Result<()> {
29949        // Python: INPUTFORMAT input_format OUTPUTFORMAT output_format
29950        if let Some(input_format) = &e.input_format {
29951            self.write_keyword("INPUTFORMAT");
29952            self.write_space();
29953            self.generate_expression(input_format)?;
29954        }
29955        if let Some(output_format) = &e.output_format {
29956            if e.input_format.is_some() {
29957                self.write(" ");
29958            }
29959            self.write_keyword("OUTPUTFORMAT");
29960            self.write_space();
29961            self.generate_expression(output_format)?;
29962        }
29963        Ok(())
29964    }
29965
29966    fn generate_install(&mut self, e: &Install) -> Result<()> {
29967        // [FORCE] INSTALL extension [FROM source]
29968        if e.force.is_some() {
29969            self.write_keyword("FORCE");
29970            self.write_space();
29971        }
29972        self.write_keyword("INSTALL");
29973        self.write_space();
29974        self.generate_expression(&e.this)?;
29975        if let Some(from) = &e.from_ {
29976            self.write_space();
29977            self.write_keyword("FROM");
29978            self.write_space();
29979            self.generate_expression(from)?;
29980        }
29981        Ok(())
29982    }
29983
29984    fn generate_interval_op(&mut self, e: &IntervalOp) -> Result<()> {
29985        // INTERVAL 'expression' unit
29986        self.write_keyword("INTERVAL");
29987        self.write_space();
29988        // When a unit is specified and the expression is a number,
29989        self.generate_expression(&e.expression)?;
29990        if let Some(unit) = &e.unit {
29991            self.write_space();
29992            self.write(unit);
29993        }
29994        Ok(())
29995    }
29996
29997    fn generate_interval_span(&mut self, e: &IntervalSpan) -> Result<()> {
29998        // unit TO unit (e.g., HOUR TO SECOND)
29999        self.write(&format!("{:?}", e.this).to_ascii_uppercase());
30000        self.write_space();
30001        self.write_keyword("TO");
30002        self.write_space();
30003        self.write(&format!("{:?}", e.expression).to_ascii_uppercase());
30004        Ok(())
30005    }
30006
30007    fn generate_into_clause(&mut self, e: &IntoClause) -> Result<()> {
30008        // INTO [TEMPORARY|UNLOGGED] table
30009        self.write_keyword("INTO");
30010        if e.temporary {
30011            self.write_keyword(" TEMPORARY");
30012        }
30013        if e.unlogged.is_some() {
30014            self.write_keyword(" UNLOGGED");
30015        }
30016        if let Some(this) = &e.this {
30017            self.write_space();
30018            self.generate_expression(this)?;
30019        }
30020        if !e.expressions.is_empty() {
30021            self.write(" (");
30022            for (i, expr) in e.expressions.iter().enumerate() {
30023                if i > 0 {
30024                    self.write(", ");
30025                }
30026                self.generate_expression(expr)?;
30027            }
30028            self.write(")");
30029        }
30030        Ok(())
30031    }
30032
30033    fn generate_introducer(&mut self, e: &Introducer) -> Result<()> {
30034        // Python: this expression (e.g., _utf8 'string')
30035        self.generate_expression(&e.this)?;
30036        self.write_space();
30037        self.generate_expression(&e.expression)?;
30038        Ok(())
30039    }
30040
30041    fn generate_isolated_loading_property(&mut self, e: &IsolatedLoadingProperty) -> Result<()> {
30042        // Python: WITH [NO] [CONCURRENT] ISOLATED LOADING [target]
30043        self.write_keyword("WITH");
30044        if e.no.is_some() {
30045            self.write_keyword(" NO");
30046        }
30047        if e.concurrent.is_some() {
30048            self.write_keyword(" CONCURRENT");
30049        }
30050        self.write_keyword(" ISOLATED LOADING");
30051        if let Some(target) = &e.target {
30052            self.write_space();
30053            self.generate_expression(target)?;
30054        }
30055        Ok(())
30056    }
30057
30058    fn generate_json(&mut self, e: &JSON) -> Result<()> {
30059        // Python: JSON [this] [WITHOUT|WITH] [UNIQUE KEYS]
30060        self.write_keyword("JSON");
30061        if let Some(this) = &e.this {
30062            self.write_space();
30063            self.generate_expression(this)?;
30064        }
30065        if let Some(with_) = &e.with_ {
30066            // Check if it's a truthy boolean
30067            if let Expression::Boolean(b) = with_.as_ref() {
30068                if b.value {
30069                    self.write_keyword(" WITH");
30070                } else {
30071                    self.write_keyword(" WITHOUT");
30072                }
30073            }
30074        }
30075        if e.unique {
30076            self.write_keyword(" UNIQUE KEYS");
30077        }
30078        Ok(())
30079    }
30080
30081    fn generate_json_array(&mut self, e: &JSONArray) -> Result<()> {
30082        // Python: return self.func("JSON_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})")
30083        self.write_keyword("JSON_ARRAY");
30084        self.write("(");
30085        for (i, expr) in e.expressions.iter().enumerate() {
30086            if i > 0 {
30087                self.write(", ");
30088            }
30089            self.generate_expression(expr)?;
30090        }
30091        if let Some(null_handling) = &e.null_handling {
30092            self.write_space();
30093            self.generate_expression(null_handling)?;
30094        }
30095        if let Some(return_type) = &e.return_type {
30096            self.write_space();
30097            self.write_keyword("RETURNING");
30098            self.write_space();
30099            self.generate_expression(return_type)?;
30100        }
30101        if e.strict.is_some() {
30102            self.write_space();
30103            self.write_keyword("STRICT");
30104        }
30105        self.write(")");
30106        Ok(())
30107    }
30108
30109    fn generate_json_array_agg_struct(&mut self, e: &JSONArrayAgg) -> Result<()> {
30110        // JSON_ARRAYAGG(this [ORDER BY ...] [NULL ON NULL | ABSENT ON NULL] [RETURNING type] [STRICT])
30111        self.write_keyword("JSON_ARRAYAGG");
30112        self.write("(");
30113        self.generate_expression(&e.this)?;
30114        if let Some(order) = &e.order {
30115            self.write_space();
30116            // Order is stored as an OrderBy expression
30117            if let Expression::OrderBy(ob) = order.as_ref() {
30118                self.write_keyword("ORDER BY");
30119                self.write_space();
30120                for (i, ord) in ob.expressions.iter().enumerate() {
30121                    if i > 0 {
30122                        self.write(", ");
30123                    }
30124                    self.generate_ordered(ord)?;
30125                }
30126            } else {
30127                // Fallback: generate the expression directly
30128                self.generate_expression(order)?;
30129            }
30130        }
30131        if let Some(null_handling) = &e.null_handling {
30132            self.write_space();
30133            self.generate_expression(null_handling)?;
30134        }
30135        if let Some(return_type) = &e.return_type {
30136            self.write_space();
30137            self.write_keyword("RETURNING");
30138            self.write_space();
30139            self.generate_expression(return_type)?;
30140        }
30141        if e.strict.is_some() {
30142            self.write_space();
30143            self.write_keyword("STRICT");
30144        }
30145        self.write(")");
30146        Ok(())
30147    }
30148
30149    fn generate_json_object_agg_struct(&mut self, e: &JSONObjectAgg) -> Result<()> {
30150        // JSON_OBJECTAGG(key: value [NULL ON NULL | ABSENT ON NULL] [WITH UNIQUE KEYS] [RETURNING type])
30151        self.write_keyword("JSON_OBJECTAGG");
30152        self.write("(");
30153        for (i, expr) in e.expressions.iter().enumerate() {
30154            if i > 0 {
30155                self.write(", ");
30156            }
30157            self.generate_expression(expr)?;
30158        }
30159        if let Some(null_handling) = &e.null_handling {
30160            self.write_space();
30161            self.generate_expression(null_handling)?;
30162        }
30163        if let Some(unique_keys) = &e.unique_keys {
30164            self.write_space();
30165            if let Expression::Boolean(b) = unique_keys.as_ref() {
30166                if b.value {
30167                    self.write_keyword("WITH UNIQUE KEYS");
30168                } else {
30169                    self.write_keyword("WITHOUT UNIQUE KEYS");
30170                }
30171            }
30172        }
30173        if let Some(return_type) = &e.return_type {
30174            self.write_space();
30175            self.write_keyword("RETURNING");
30176            self.write_space();
30177            self.generate_expression(return_type)?;
30178        }
30179        self.write(")");
30180        Ok(())
30181    }
30182
30183    fn generate_json_array_append(&mut self, e: &JSONArrayAppend) -> Result<()> {
30184        // JSON_ARRAY_APPEND(this, path, value, ...)
30185        self.write_keyword("JSON_ARRAY_APPEND");
30186        self.write("(");
30187        self.generate_expression(&e.this)?;
30188        for expr in &e.expressions {
30189            self.write(", ");
30190            self.generate_expression(expr)?;
30191        }
30192        self.write(")");
30193        Ok(())
30194    }
30195
30196    fn generate_json_array_contains(&mut self, e: &JSONArrayContains) -> Result<()> {
30197        // JSON_ARRAY_CONTAINS(this, expression)
30198        self.write_keyword("JSON_ARRAY_CONTAINS");
30199        self.write("(");
30200        self.generate_expression(&e.this)?;
30201        self.write(", ");
30202        self.generate_expression(&e.expression)?;
30203        self.write(")");
30204        Ok(())
30205    }
30206
30207    fn generate_json_array_insert(&mut self, e: &JSONArrayInsert) -> Result<()> {
30208        // JSON_ARRAY_INSERT(this, path, value, ...)
30209        self.write_keyword("JSON_ARRAY_INSERT");
30210        self.write("(");
30211        self.generate_expression(&e.this)?;
30212        for expr in &e.expressions {
30213            self.write(", ");
30214            self.generate_expression(expr)?;
30215        }
30216        self.write(")");
30217        Ok(())
30218    }
30219
30220    fn generate_jsonb_exists(&mut self, e: &JSONBExists) -> Result<()> {
30221        // JSONB_EXISTS(this, path)
30222        self.write_keyword("JSONB_EXISTS");
30223        self.write("(");
30224        self.generate_expression(&e.this)?;
30225        if let Some(path) = &e.path {
30226            self.write(", ");
30227            self.generate_expression(path)?;
30228        }
30229        self.write(")");
30230        Ok(())
30231    }
30232
30233    fn generate_jsonb_extract_scalar(&mut self, e: &JSONBExtractScalar) -> Result<()> {
30234        // JSONB_EXTRACT_SCALAR(this, expression)
30235        self.write_keyword("JSONB_EXTRACT_SCALAR");
30236        self.write("(");
30237        self.generate_expression(&e.this)?;
30238        self.write(", ");
30239        self.generate_expression(&e.expression)?;
30240        self.write(")");
30241        Ok(())
30242    }
30243
30244    fn generate_jsonb_object_agg(&mut self, e: &JSONBObjectAgg) -> Result<()> {
30245        // JSONB_OBJECT_AGG(this, expression)
30246        self.write_keyword("JSONB_OBJECT_AGG");
30247        self.write("(");
30248        self.generate_expression(&e.this)?;
30249        self.write(", ");
30250        self.generate_expression(&e.expression)?;
30251        self.write(")");
30252        Ok(())
30253    }
30254
30255    fn generate_json_column_def(&mut self, e: &JSONColumnDef) -> Result<()> {
30256        // Python: NESTED PATH path schema | this kind PATH path [FOR ORDINALITY]
30257        if let Some(nested_schema) = &e.nested_schema {
30258            self.write_keyword("NESTED");
30259            if let Some(path) = &e.path {
30260                self.write_space();
30261                self.write_keyword("PATH");
30262                self.write_space();
30263                self.generate_expression(path)?;
30264            }
30265            self.write_space();
30266            self.generate_expression(nested_schema)?;
30267        } else {
30268            if let Some(this) = &e.this {
30269                self.generate_expression(this)?;
30270            }
30271            if let Some(kind) = &e.kind {
30272                self.write_space();
30273                self.write(kind);
30274            }
30275            if e.format_json {
30276                self.write_space();
30277                self.write_keyword("FORMAT JSON");
30278            }
30279            if let Some(path) = &e.path {
30280                self.write_space();
30281                self.write_keyword("PATH");
30282                self.write_space();
30283                self.generate_expression(path)?;
30284            }
30285            if e.ordinality.is_some() {
30286                self.write_keyword(" FOR ORDINALITY");
30287            }
30288        }
30289        Ok(())
30290    }
30291
30292    fn generate_json_exists(&mut self, e: &JSONExists) -> Result<()> {
30293        // JSON_EXISTS(this, path PASSING vars ON ERROR/EMPTY condition)
30294        self.write_keyword("JSON_EXISTS");
30295        self.write("(");
30296        self.generate_expression(&e.this)?;
30297        if let Some(path) = &e.path {
30298            self.write(", ");
30299            self.generate_expression(path)?;
30300        }
30301        if let Some(passing) = &e.passing {
30302            self.write_space();
30303            self.write_keyword("PASSING");
30304            self.write_space();
30305            self.generate_expression(passing)?;
30306        }
30307        if let Some(on_condition) = &e.on_condition {
30308            self.write_space();
30309            self.generate_expression(on_condition)?;
30310        }
30311        self.write(")");
30312        Ok(())
30313    }
30314
30315    fn generate_json_cast(&mut self, e: &JSONCast) -> Result<()> {
30316        self.generate_expression(&e.this)?;
30317        self.write(".:");
30318        // If the data type has nested type parameters (like Array(JSON), Map(String, Int)),
30319        // wrap the entire type string in double quotes.
30320        // This matches Python sqlglot's ClickHouse _json_cast_sql behavior.
30321        if Self::data_type_has_nested_expressions(&e.to) {
30322            // Generate the data type to a temporary string buffer, then wrap in quotes
30323            let saved = std::mem::take(&mut self.output);
30324            self.generate_data_type(&e.to)?;
30325            let type_sql = std::mem::replace(&mut self.output, saved);
30326            self.write("\"");
30327            self.write(&type_sql);
30328            self.write("\"");
30329        } else {
30330            self.generate_data_type(&e.to)?;
30331        }
30332        Ok(())
30333    }
30334
30335    /// Check if a DataType has nested type expressions (sub-types).
30336    /// This corresponds to Python sqlglot's `to.expressions` being non-empty.
30337    fn data_type_has_nested_expressions(dt: &DataType) -> bool {
30338        matches!(
30339            dt,
30340            DataType::Array { .. } | DataType::Map { .. } | DataType::Struct { .. }
30341        )
30342    }
30343
30344    fn generate_json_extract_array(&mut self, e: &JSONExtractArray) -> Result<()> {
30345        // JSON_EXTRACT_ARRAY(this, expression)
30346        self.write_keyword("JSON_EXTRACT_ARRAY");
30347        self.write("(");
30348        self.generate_expression(&e.this)?;
30349        if let Some(expr) = &e.expression {
30350            self.write(", ");
30351            self.generate_expression(expr)?;
30352        }
30353        self.write(")");
30354        Ok(())
30355    }
30356
30357    fn generate_json_extract_quote(&mut self, e: &JSONExtractQuote) -> Result<()> {
30358        // Snowflake: KEEP [OMIT] QUOTES [SCALAR_ONLY] for JSON extraction
30359        if let Some(option) = &e.option {
30360            self.generate_expression(option)?;
30361            self.write_space();
30362        }
30363        self.write_keyword("QUOTES");
30364        if e.scalar.is_some() {
30365            self.write_keyword(" SCALAR_ONLY");
30366        }
30367        Ok(())
30368    }
30369
30370    fn generate_json_extract_scalar(&mut self, e: &JSONExtractScalar) -> Result<()> {
30371        // JSON_EXTRACT_SCALAR(this, expression)
30372        self.write_keyword("JSON_EXTRACT_SCALAR");
30373        self.write("(");
30374        self.generate_expression(&e.this)?;
30375        self.write(", ");
30376        self.generate_expression(&e.expression)?;
30377        self.write(")");
30378        Ok(())
30379    }
30380
30381    fn generate_json_extract_path(&mut self, e: &JSONExtract) -> Result<()> {
30382        // For variant_extract (Snowflake/Databricks colon syntax like a:field)
30383        // Databricks uses col:path syntax, Snowflake uses GET_PATH(col, 'path')
30384        // Otherwise output JSON_EXTRACT(this, expression)
30385        if e.variant_extract.is_some() {
30386            use crate::dialects::DialectType;
30387            if matches!(self.config.dialect, Some(DialectType::Databricks)) {
30388                // Databricks: output col:path syntax (e.g., c1:price, c1:price.foo, c1:price.bar[1])
30389                // Keys that are not safe identifiers (contain hyphens, spaces, etc.) must use
30390                // bracket notation: c:["x-y"] instead of c:x-y
30391                self.generate_expression(&e.this)?;
30392                self.write(":");
30393                match e.expression.as_ref() {
30394                    Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
30395                        let Literal::String(s) = lit.as_ref() else {
30396                            unreachable!()
30397                        };
30398                        self.write_databricks_json_path(s);
30399                    }
30400                    _ => {
30401                        // Fallback: generate as-is (shouldn't happen in typical cases)
30402                        self.generate_expression(&e.expression)?;
30403                    }
30404                }
30405            } else {
30406                // Snowflake and others: use GET_PATH(col, 'path')
30407                self.write_keyword("GET_PATH");
30408                self.write("(");
30409                self.generate_expression(&e.this)?;
30410                self.write(", ");
30411                self.generate_expression(&e.expression)?;
30412                self.write(")");
30413            }
30414        } else {
30415            self.write_keyword("JSON_EXTRACT");
30416            self.write("(");
30417            self.generate_expression(&e.this)?;
30418            self.write(", ");
30419            self.generate_expression(&e.expression)?;
30420            for expr in &e.expressions {
30421                self.write(", ");
30422                self.generate_expression(expr)?;
30423            }
30424            self.write(")");
30425        }
30426        Ok(())
30427    }
30428
30429    /// Write a Databricks JSON colon-path, using bracket notation for keys
30430    /// that are not safe identifiers (e.g., contain hyphens, spaces, etc.)
30431    /// Safe identifier regex: ^[_a-zA-Z]\w*$
30432    fn write_databricks_json_path(&mut self, path: &str) {
30433        // If the path already starts with bracket notation (e.g., '["fr\'uit"]'),
30434        // it was already formatted by the parser - output as-is
30435        if path.starts_with("[\"") || path.starts_with("['") {
30436            self.write(path);
30437            return;
30438        }
30439        // Split the path into segments at '.' boundaries, but preserve bracket subscripts
30440        // e.g., "price.items[0].name" -> ["price", "items[0]", "name"]
30441        // e.g., "x-y" -> ["x-y"]
30442        let mut first = true;
30443        for segment in path.split('.') {
30444            if !first {
30445                self.write(".");
30446            }
30447            first = false;
30448            // Check if there's a bracket subscript in this segment: "items[0]"
30449            if let Some(bracket_pos) = segment.find('[') {
30450                let key = &segment[..bracket_pos];
30451                let subscript = &segment[bracket_pos..];
30452                if key.is_empty() {
30453                    // Bracket notation at start of segment (e.g., already formatted)
30454                    self.write(segment);
30455                } else if Self::is_safe_json_path_key(key) {
30456                    self.write(key);
30457                    self.write(subscript);
30458                } else {
30459                    self.write("[\"");
30460                    self.write(key);
30461                    self.write("\"]");
30462                    self.write(subscript);
30463                }
30464            } else if Self::is_safe_json_path_key(segment) {
30465                self.write(segment);
30466            } else {
30467                self.write("[\"");
30468                self.write(segment);
30469                self.write("\"]");
30470            }
30471        }
30472    }
30473
30474    /// Check if a JSON path key is a safe identifier that doesn't need bracket quoting.
30475    /// Matches Python sqlglot's SAFE_IDENTIFIER_RE: ^[_a-zA-Z]\w*$
30476    fn is_safe_json_path_key(key: &str) -> bool {
30477        if key.is_empty() {
30478            return false;
30479        }
30480        let mut chars = key.chars();
30481        let first = chars.next().unwrap();
30482        if first != '_' && !first.is_ascii_alphabetic() {
30483            return false;
30484        }
30485        chars.all(|c| c == '_' || c.is_ascii_alphanumeric())
30486    }
30487
30488    fn generate_json_format(&mut self, e: &JSONFormat) -> Result<()> {
30489        // Output: {expr} FORMAT JSON
30490        // This wraps an expression with FORMAT JSON suffix (Oracle JSON function syntax)
30491        if let Some(this) = &e.this {
30492            self.generate_expression(this)?;
30493            self.write_space();
30494        }
30495        self.write_keyword("FORMAT JSON");
30496        Ok(())
30497    }
30498
30499    fn generate_json_key_value(&mut self, e: &JSONKeyValue) -> Result<()> {
30500        // key: value (for JSON objects)
30501        self.generate_expression(&e.this)?;
30502        self.write(": ");
30503        self.generate_expression(&e.expression)?;
30504        Ok(())
30505    }
30506
30507    fn generate_json_keys(&mut self, e: &JSONKeys) -> Result<()> {
30508        // JSON_KEYS(this, expression, expressions...)
30509        self.write_keyword("JSON_KEYS");
30510        self.write("(");
30511        self.generate_expression(&e.this)?;
30512        if let Some(expr) = &e.expression {
30513            self.write(", ");
30514            self.generate_expression(expr)?;
30515        }
30516        for expr in &e.expressions {
30517            self.write(", ");
30518            self.generate_expression(expr)?;
30519        }
30520        self.write(")");
30521        Ok(())
30522    }
30523
30524    fn generate_json_keys_at_depth(&mut self, e: &JSONKeysAtDepth) -> Result<()> {
30525        // JSON_KEYS(this, expression)
30526        self.write_keyword("JSON_KEYS");
30527        self.write("(");
30528        self.generate_expression(&e.this)?;
30529        if let Some(expr) = &e.expression {
30530            self.write(", ");
30531            self.generate_expression(expr)?;
30532        }
30533        self.write(")");
30534        Ok(())
30535    }
30536
30537    fn generate_json_path_expr(&mut self, e: &JSONPath) -> Result<()> {
30538        // JSONPath expression: generates a quoted path like '$.foo' or '$[0]'
30539        // The path components are concatenated without spaces
30540        let mut path_str = String::new();
30541        for expr in &e.expressions {
30542            match expr {
30543                Expression::JSONPathRoot(_) => {
30544                    path_str.push('$');
30545                }
30546                Expression::JSONPathKey(k) => {
30547                    // .key or ."key" (quote if key has special characters)
30548                    if let Expression::Literal(lit) = k.this.as_ref() {
30549                        if let crate::expressions::Literal::String(s) = lit.as_ref() {
30550                            path_str.push('.');
30551                            // Quote the key if it contains non-alphanumeric characters (hyphens, spaces, etc.)
30552                            let needs_quoting = s.chars().any(|c| !c.is_alphanumeric() && c != '_');
30553                            if needs_quoting {
30554                                path_str.push('"');
30555                                path_str.push_str(s);
30556                                path_str.push('"');
30557                            } else {
30558                                path_str.push_str(s);
30559                            }
30560                        }
30561                    }
30562                }
30563                Expression::JSONPathSubscript(s) => {
30564                    // [index]
30565                    if let Expression::Literal(lit) = s.this.as_ref() {
30566                        if let crate::expressions::Literal::Number(n) = lit.as_ref() {
30567                            path_str.push('[');
30568                            path_str.push_str(n);
30569                            path_str.push(']');
30570                        }
30571                    }
30572                }
30573                _ => {
30574                    // For other path parts, try to generate them
30575                    let mut temp_gen = Self::with_arc_config(self.config.clone());
30576                    temp_gen.generate_expression(expr)?;
30577                    path_str.push_str(&temp_gen.output);
30578                }
30579            }
30580        }
30581        // Output as quoted string
30582        self.write("'");
30583        self.write(&path_str);
30584        self.write("'");
30585        Ok(())
30586    }
30587
30588    fn generate_json_path_filter(&mut self, e: &JSONPathFilter) -> Result<()> {
30589        // JSON path filter: ?(predicate)
30590        self.write("?(");
30591        self.generate_expression(&e.this)?;
30592        self.write(")");
30593        Ok(())
30594    }
30595
30596    fn generate_json_path_key(&mut self, e: &JSONPathKey) -> Result<()> {
30597        // JSON path key: .key or ["key"]
30598        self.write(".");
30599        self.generate_expression(&e.this)?;
30600        Ok(())
30601    }
30602
30603    fn generate_json_path_recursive(&mut self, e: &JSONPathRecursive) -> Result<()> {
30604        // JSON path recursive descent: ..
30605        self.write("..");
30606        if let Some(this) = &e.this {
30607            self.generate_expression(this)?;
30608        }
30609        Ok(())
30610    }
30611
30612    fn generate_json_path_root(&mut self) -> Result<()> {
30613        // JSON path root: $
30614        self.write("$");
30615        Ok(())
30616    }
30617
30618    fn generate_json_path_script(&mut self, e: &JSONPathScript) -> Result<()> {
30619        // JSON path script: (expression)
30620        self.write("(");
30621        self.generate_expression(&e.this)?;
30622        self.write(")");
30623        Ok(())
30624    }
30625
30626    fn generate_json_path_selector(&mut self, e: &JSONPathSelector) -> Result<()> {
30627        // JSON path selector: *
30628        self.generate_expression(&e.this)?;
30629        Ok(())
30630    }
30631
30632    fn generate_json_path_slice(&mut self, e: &JSONPathSlice) -> Result<()> {
30633        // JSON path slice: [start:end:step]
30634        self.write("[");
30635        if let Some(start) = &e.start {
30636            self.generate_expression(start)?;
30637        }
30638        self.write(":");
30639        if let Some(end) = &e.end {
30640            self.generate_expression(end)?;
30641        }
30642        if let Some(step) = &e.step {
30643            self.write(":");
30644            self.generate_expression(step)?;
30645        }
30646        self.write("]");
30647        Ok(())
30648    }
30649
30650    fn generate_json_path_subscript(&mut self, e: &JSONPathSubscript) -> Result<()> {
30651        // JSON path subscript: [index] or [*]
30652        self.write("[");
30653        self.generate_expression(&e.this)?;
30654        self.write("]");
30655        Ok(())
30656    }
30657
30658    fn generate_json_path_union(&mut self, e: &JSONPathUnion) -> Result<()> {
30659        // JSON path union: [key1, key2, ...]
30660        self.write("[");
30661        for (i, expr) in e.expressions.iter().enumerate() {
30662            if i > 0 {
30663                self.write(", ");
30664            }
30665            self.generate_expression(expr)?;
30666        }
30667        self.write("]");
30668        Ok(())
30669    }
30670
30671    fn generate_json_remove(&mut self, e: &JSONRemove) -> Result<()> {
30672        // JSON_REMOVE(this, path1, path2, ...)
30673        self.write_keyword("JSON_REMOVE");
30674        self.write("(");
30675        self.generate_expression(&e.this)?;
30676        for expr in &e.expressions {
30677            self.write(", ");
30678            self.generate_expression(expr)?;
30679        }
30680        self.write(")");
30681        Ok(())
30682    }
30683
30684    fn generate_json_schema(&mut self, e: &JSONSchema) -> Result<()> {
30685        // COLUMNS(col1 type, col2 type, ...)
30686        // When pretty printing and content is too wide, format with each column on a separate line
30687        self.write_keyword("COLUMNS");
30688        self.write("(");
30689
30690        if self.config.pretty && !e.expressions.is_empty() {
30691            // First, generate all expressions into strings to check width
30692            let mut expr_strings: Vec<String> = Vec::with_capacity(e.expressions.len());
30693            for expr in &e.expressions {
30694                let mut temp_gen = Generator::with_arc_config(self.config.clone());
30695                temp_gen.generate_expression(expr)?;
30696                expr_strings.push(temp_gen.output);
30697            }
30698
30699            // Check if total width exceeds max_text_width
30700            if self.too_wide(&expr_strings) {
30701                // Pretty print: each column on its own line
30702                self.write_newline();
30703                self.indent_level += 1;
30704                for (i, expr_str) in expr_strings.iter().enumerate() {
30705                    if i > 0 {
30706                        self.write(",");
30707                        self.write_newline();
30708                    }
30709                    self.write_indent();
30710                    self.write(expr_str);
30711                }
30712                self.write_newline();
30713                self.indent_level -= 1;
30714                self.write_indent();
30715            } else {
30716                // Compact: all on one line
30717                for (i, expr_str) in expr_strings.iter().enumerate() {
30718                    if i > 0 {
30719                        self.write(", ");
30720                    }
30721                    self.write(expr_str);
30722                }
30723            }
30724        } else {
30725            // Non-pretty mode: compact format
30726            for (i, expr) in e.expressions.iter().enumerate() {
30727                if i > 0 {
30728                    self.write(", ");
30729                }
30730                self.generate_expression(expr)?;
30731            }
30732        }
30733        self.write(")");
30734        Ok(())
30735    }
30736
30737    fn generate_json_set(&mut self, e: &JSONSet) -> Result<()> {
30738        // JSON_SET(this, path, value, ...)
30739        self.write_keyword("JSON_SET");
30740        self.write("(");
30741        self.generate_expression(&e.this)?;
30742        for expr in &e.expressions {
30743            self.write(", ");
30744            self.generate_expression(expr)?;
30745        }
30746        self.write(")");
30747        Ok(())
30748    }
30749
30750    fn generate_json_strip_nulls(&mut self, e: &JSONStripNulls) -> Result<()> {
30751        // JSON_STRIP_NULLS(this, expression)
30752        self.write_keyword("JSON_STRIP_NULLS");
30753        self.write("(");
30754        self.generate_expression(&e.this)?;
30755        if let Some(expr) = &e.expression {
30756            self.write(", ");
30757            self.generate_expression(expr)?;
30758        }
30759        self.write(")");
30760        Ok(())
30761    }
30762
30763    fn generate_json_table(&mut self, e: &JSONTable) -> Result<()> {
30764        // JSON_TABLE(this, path [error_handling] [empty_handling] schema)
30765        self.write_keyword("JSON_TABLE");
30766        self.write("(");
30767        self.generate_expression(&e.this)?;
30768        if let Some(path) = &e.path {
30769            self.write(", ");
30770            self.generate_expression(path)?;
30771        }
30772        if let Some(error_handling) = &e.error_handling {
30773            self.write_space();
30774            self.generate_expression(error_handling)?;
30775        }
30776        if let Some(empty_handling) = &e.empty_handling {
30777            self.write_space();
30778            self.generate_expression(empty_handling)?;
30779        }
30780        if let Some(schema) = &e.schema {
30781            self.write_space();
30782            self.generate_expression(schema)?;
30783        }
30784        self.write(")");
30785        Ok(())
30786    }
30787
30788    fn generate_json_type(&mut self, e: &JSONType) -> Result<()> {
30789        // JSON_TYPE(this)
30790        self.write_keyword("JSON_TYPE");
30791        self.write("(");
30792        self.generate_expression(&e.this)?;
30793        self.write(")");
30794        Ok(())
30795    }
30796
30797    fn generate_json_value(&mut self, e: &JSONValue) -> Result<()> {
30798        // JSON_VALUE(this, path RETURNING type ON condition)
30799        self.write_keyword("JSON_VALUE");
30800        self.write("(");
30801        self.generate_expression(&e.this)?;
30802        if let Some(path) = &e.path {
30803            self.write(", ");
30804            self.generate_expression(path)?;
30805        }
30806        if let Some(returning) = &e.returning {
30807            self.write_space();
30808            self.write_keyword("RETURNING");
30809            self.write_space();
30810            self.generate_expression(returning)?;
30811        }
30812        if let Some(on_condition) = &e.on_condition {
30813            self.write_space();
30814            self.generate_expression(on_condition)?;
30815        }
30816        self.write(")");
30817        Ok(())
30818    }
30819
30820    fn generate_json_value_array(&mut self, e: &JSONValueArray) -> Result<()> {
30821        // JSON_VALUE_ARRAY(this)
30822        self.write_keyword("JSON_VALUE_ARRAY");
30823        self.write("(");
30824        self.generate_expression(&e.this)?;
30825        self.write(")");
30826        Ok(())
30827    }
30828
30829    fn generate_jarowinkler_similarity(&mut self, e: &JarowinklerSimilarity) -> Result<()> {
30830        // JAROWINKLER_SIMILARITY(str1, str2)
30831        self.write_keyword("JAROWINKLER_SIMILARITY");
30832        self.write("(");
30833        self.generate_expression(&e.this)?;
30834        self.write(", ");
30835        self.generate_expression(&e.expression)?;
30836        self.write(")");
30837        Ok(())
30838    }
30839
30840    fn generate_join_hint(&mut self, e: &JoinHint) -> Result<()> {
30841        // Python: this(expressions)
30842        self.generate_expression(&e.this)?;
30843        self.write("(");
30844        for (i, expr) in e.expressions.iter().enumerate() {
30845            if i > 0 {
30846                self.write(", ");
30847            }
30848            self.generate_expression(expr)?;
30849        }
30850        self.write(")");
30851        Ok(())
30852    }
30853
30854    fn generate_journal_property(&mut self, e: &JournalProperty) -> Result<()> {
30855        // Python: {no}{local}{dual}{before}{after}JOURNAL
30856        if e.no.is_some() {
30857            self.write_keyword("NO ");
30858        }
30859        if let Some(local) = &e.local {
30860            self.generate_expression(local)?;
30861            self.write_space();
30862        }
30863        if e.dual.is_some() {
30864            self.write_keyword("DUAL ");
30865        }
30866        if e.before.is_some() {
30867            self.write_keyword("BEFORE ");
30868        }
30869        if e.after.is_some() {
30870            self.write_keyword("AFTER ");
30871        }
30872        self.write_keyword("JOURNAL");
30873        Ok(())
30874    }
30875
30876    fn generate_language_property(&mut self, e: &LanguageProperty) -> Result<()> {
30877        // LANGUAGE language_name
30878        self.write_keyword("LANGUAGE");
30879        self.write_space();
30880        self.generate_expression(&e.this)?;
30881        Ok(())
30882    }
30883
30884    fn generate_lateral(&mut self, e: &Lateral) -> Result<()> {
30885        // Python: handles LATERAL VIEW (Hive/Spark) and regular LATERAL
30886        if e.view.is_some() {
30887            // LATERAL VIEW [OUTER] expression [alias] [AS columns]
30888            self.write_keyword("LATERAL VIEW");
30889            if e.outer.is_some() {
30890                self.write_space();
30891                self.write_keyword("OUTER");
30892            }
30893            self.write_space();
30894            self.generate_expression(&e.this)?;
30895            if let Some(alias) = &e.alias {
30896                self.write_space();
30897                self.write(alias);
30898            }
30899        } else {
30900            // LATERAL subquery/function [WITH ORDINALITY] [AS alias(columns)]
30901            self.write_keyword("LATERAL");
30902            self.write_space();
30903            self.generate_expression(&e.this)?;
30904            if e.ordinality.is_some() {
30905                self.write_space();
30906                self.write_keyword("WITH ORDINALITY");
30907            }
30908            if let Some(alias) = &e.alias {
30909                self.write_space();
30910                self.write_keyword("AS");
30911                self.write_space();
30912                self.write(alias);
30913                if !e.column_aliases.is_empty() {
30914                    self.write("(");
30915                    for (i, col) in e.column_aliases.iter().enumerate() {
30916                        if i > 0 {
30917                            self.write(", ");
30918                        }
30919                        self.write(col);
30920                    }
30921                    self.write(")");
30922                }
30923            }
30924        }
30925        Ok(())
30926    }
30927
30928    fn generate_like_property(&mut self, e: &LikeProperty) -> Result<()> {
30929        // Python: LIKE this [options]
30930        self.write_keyword("LIKE");
30931        self.write_space();
30932        self.generate_expression(&e.this)?;
30933        for expr in &e.expressions {
30934            self.write_space();
30935            self.generate_expression(expr)?;
30936        }
30937        Ok(())
30938    }
30939
30940    fn generate_limit(&mut self, e: &Limit) -> Result<()> {
30941        self.write_keyword("LIMIT");
30942        self.write_space();
30943        self.write_limit_expr(&e.this)?;
30944        if e.percent {
30945            self.write_space();
30946            self.write_keyword("PERCENT");
30947        }
30948        // Emit any comments that were captured from before the LIMIT keyword
30949        for comment in &e.comments {
30950            self.write(" ");
30951            self.write_formatted_comment(comment);
30952        }
30953        Ok(())
30954    }
30955
30956    fn generate_limit_options(&mut self, e: &LimitOptions) -> Result<()> {
30957        // Python: [PERCENT][ROWS][WITH TIES|ONLY]
30958        if e.percent.is_some() {
30959            self.write_keyword(" PERCENT");
30960        }
30961        if e.rows.is_some() {
30962            self.write_keyword(" ROWS");
30963        }
30964        if e.with_ties.is_some() {
30965            self.write_keyword(" WITH TIES");
30966        } else if e.rows.is_some() {
30967            self.write_keyword(" ONLY");
30968        }
30969        Ok(())
30970    }
30971
30972    fn generate_list(&mut self, e: &List) -> Result<()> {
30973        use crate::dialects::DialectType;
30974        let is_materialize = matches!(self.config.dialect, Some(DialectType::Materialize));
30975
30976        // Check if this is a subquery-based list (LIST(SELECT ...))
30977        if e.expressions.len() == 1 {
30978            if let Expression::Select(_) = &e.expressions[0] {
30979                self.write_keyword("LIST");
30980                self.write("(");
30981                self.generate_expression(&e.expressions[0])?;
30982                self.write(")");
30983                return Ok(());
30984            }
30985        }
30986
30987        // For Materialize, output as LIST[expr, expr, ...]
30988        if is_materialize {
30989            self.write_keyword("LIST");
30990            self.write("[");
30991            for (i, expr) in e.expressions.iter().enumerate() {
30992                if i > 0 {
30993                    self.write(", ");
30994                }
30995                self.generate_expression(expr)?;
30996            }
30997            self.write("]");
30998        } else {
30999            // For other dialects, output as LIST(expr, expr, ...)
31000            self.write_keyword("LIST");
31001            self.write("(");
31002            for (i, expr) in e.expressions.iter().enumerate() {
31003                if i > 0 {
31004                    self.write(", ");
31005                }
31006                self.generate_expression(expr)?;
31007            }
31008            self.write(")");
31009        }
31010        Ok(())
31011    }
31012
31013    fn generate_tomap(&mut self, e: &ToMap) -> Result<()> {
31014        // Check if this is a subquery-based map (MAP(SELECT ...))
31015        if let Expression::Select(_) = &*e.this {
31016            self.write_keyword("MAP");
31017            self.write("(");
31018            self.generate_expression(&e.this)?;
31019            self.write(")");
31020            return Ok(());
31021        }
31022
31023        let is_duckdb = matches!(self.config.dialect, Some(DialectType::DuckDB));
31024
31025        // For Struct-based map: DuckDB uses MAP {'key': value}, Materialize uses MAP['key' => value]
31026        self.write_keyword("MAP");
31027        if is_duckdb {
31028            self.write(" {");
31029        } else {
31030            self.write("[");
31031        }
31032        if let Expression::Struct(s) = &*e.this {
31033            for (i, (_, expr)) in s.fields.iter().enumerate() {
31034                if i > 0 {
31035                    self.write(", ");
31036                }
31037                if let Expression::PropertyEQ(op) = expr {
31038                    self.generate_expression(&op.left)?;
31039                    if is_duckdb {
31040                        self.write(": ");
31041                    } else {
31042                        self.write(" => ");
31043                    }
31044                    self.generate_expression(&op.right)?;
31045                } else {
31046                    self.generate_expression(expr)?;
31047                }
31048            }
31049        }
31050        if is_duckdb {
31051            self.write("}");
31052        } else {
31053            self.write("]");
31054        }
31055        Ok(())
31056    }
31057
31058    fn generate_localtime(&mut self, e: &Localtime) -> Result<()> {
31059        // Python: LOCALTIME or LOCALTIME(precision)
31060        self.write_keyword("LOCALTIME");
31061        if let Some(precision) = &e.this {
31062            self.write("(");
31063            self.generate_expression(precision)?;
31064            self.write(")");
31065        }
31066        Ok(())
31067    }
31068
31069    fn generate_localtimestamp(&mut self, e: &Localtimestamp) -> Result<()> {
31070        // Python: LOCALTIMESTAMP or LOCALTIMESTAMP(precision)
31071        self.write_keyword("LOCALTIMESTAMP");
31072        if let Some(precision) = &e.this {
31073            self.write("(");
31074            self.generate_expression(precision)?;
31075            self.write(")");
31076        }
31077        Ok(())
31078    }
31079
31080    fn generate_location_property(&mut self, e: &LocationProperty) -> Result<()> {
31081        // LOCATION 'path'
31082        self.write_keyword("LOCATION");
31083        self.write_space();
31084        self.generate_expression(&e.this)?;
31085        Ok(())
31086    }
31087
31088    fn generate_lock(&mut self, e: &Lock) -> Result<()> {
31089        // Python: FOR UPDATE|FOR SHARE [OF tables] [NOWAIT|WAIT n]
31090        if e.update.is_some() {
31091            if e.key.is_some() {
31092                self.write_keyword("FOR NO KEY UPDATE");
31093            } else {
31094                self.write_keyword("FOR UPDATE");
31095            }
31096        } else {
31097            if e.key.is_some() {
31098                self.write_keyword("FOR KEY SHARE");
31099            } else {
31100                self.write_keyword("FOR SHARE");
31101            }
31102        }
31103        if !e.expressions.is_empty() {
31104            self.write_keyword(" OF ");
31105            for (i, expr) in e.expressions.iter().enumerate() {
31106                if i > 0 {
31107                    self.write(", ");
31108                }
31109                self.generate_expression(expr)?;
31110            }
31111        }
31112        // Handle wait option following Python sqlglot convention:
31113        // - Boolean(true) -> NOWAIT
31114        // - Boolean(false) -> SKIP LOCKED
31115        // - Literal (number) -> WAIT n
31116        if let Some(wait) = &e.wait {
31117            match wait.as_ref() {
31118                Expression::Boolean(b) => {
31119                    if b.value {
31120                        self.write_keyword(" NOWAIT");
31121                    } else {
31122                        self.write_keyword(" SKIP LOCKED");
31123                    }
31124                }
31125                _ => {
31126                    // It's a literal (number), output WAIT n
31127                    self.write_keyword(" WAIT ");
31128                    self.generate_expression(wait)?;
31129                }
31130            }
31131        }
31132        Ok(())
31133    }
31134
31135    fn generate_lock_property(&mut self, e: &LockProperty) -> Result<()> {
31136        // LOCK property
31137        self.write_keyword("LOCK");
31138        self.write_space();
31139        self.generate_expression(&e.this)?;
31140        Ok(())
31141    }
31142
31143    fn generate_locking_property(&mut self, e: &LockingProperty) -> Result<()> {
31144        // Python: LOCKING kind [this] [for_or_in] lock_type [OVERRIDE]
31145        self.write_keyword("LOCKING");
31146        self.write_space();
31147        self.write(&e.kind);
31148        if let Some(this) = &e.this {
31149            self.write_space();
31150            self.generate_expression(this)?;
31151        }
31152        if let Some(for_or_in) = &e.for_or_in {
31153            self.write_space();
31154            self.generate_expression(for_or_in)?;
31155        }
31156        if let Some(lock_type) = &e.lock_type {
31157            self.write_space();
31158            self.generate_expression(lock_type)?;
31159        }
31160        if e.override_.is_some() {
31161            self.write_keyword(" OVERRIDE");
31162        }
31163        Ok(())
31164    }
31165
31166    fn generate_locking_statement(&mut self, e: &LockingStatement) -> Result<()> {
31167        // this expression
31168        self.generate_expression(&e.this)?;
31169        self.write_space();
31170        self.generate_expression(&e.expression)?;
31171        Ok(())
31172    }
31173
31174    fn generate_log_property(&mut self, e: &LogProperty) -> Result<()> {
31175        // [NO] LOG
31176        if e.no.is_some() {
31177            self.write_keyword("NO ");
31178        }
31179        self.write_keyword("LOG");
31180        Ok(())
31181    }
31182
31183    fn generate_md5_digest(&mut self, e: &MD5Digest) -> Result<()> {
31184        // MD5(this, expressions...)
31185        self.write_keyword("MD5");
31186        self.write("(");
31187        self.generate_expression(&e.this)?;
31188        for expr in &e.expressions {
31189            self.write(", ");
31190            self.generate_expression(expr)?;
31191        }
31192        self.write(")");
31193        Ok(())
31194    }
31195
31196    fn generate_ml_forecast(&mut self, e: &MLForecast) -> Result<()> {
31197        // ML.FORECAST(model, [params])
31198        self.write_keyword("ML.FORECAST");
31199        self.write("(");
31200        self.generate_expression(&e.this)?;
31201        if let Some(expression) = &e.expression {
31202            self.write(", ");
31203            self.generate_expression(expression)?;
31204        }
31205        if let Some(params) = &e.params_struct {
31206            self.write(", ");
31207            self.generate_expression(params)?;
31208        }
31209        self.write(")");
31210        Ok(())
31211    }
31212
31213    fn generate_ml_translate(&mut self, e: &MLTranslate) -> Result<()> {
31214        // ML.TRANSLATE(model, input, [params])
31215        self.write_keyword("ML.TRANSLATE");
31216        self.write("(");
31217        self.generate_expression(&e.this)?;
31218        self.write(", ");
31219        self.generate_expression(&e.expression)?;
31220        if let Some(params) = &e.params_struct {
31221            self.write(", ");
31222            self.generate_expression(params)?;
31223        }
31224        self.write(")");
31225        Ok(())
31226    }
31227
31228    fn generate_make_interval(&mut self, e: &MakeInterval) -> Result<()> {
31229        // MAKE_INTERVAL(years => x, months => y, ...)
31230        self.write_keyword("MAKE_INTERVAL");
31231        self.write("(");
31232        let mut first = true;
31233        if let Some(year) = &e.year {
31234            self.write("years => ");
31235            self.generate_expression(year)?;
31236            first = false;
31237        }
31238        if let Some(month) = &e.month {
31239            if !first {
31240                self.write(", ");
31241            }
31242            self.write("months => ");
31243            self.generate_expression(month)?;
31244            first = false;
31245        }
31246        if let Some(week) = &e.week {
31247            if !first {
31248                self.write(", ");
31249            }
31250            self.write("weeks => ");
31251            self.generate_expression(week)?;
31252            first = false;
31253        }
31254        if let Some(day) = &e.day {
31255            if !first {
31256                self.write(", ");
31257            }
31258            self.write("days => ");
31259            self.generate_expression(day)?;
31260            first = false;
31261        }
31262        if let Some(hour) = &e.hour {
31263            if !first {
31264                self.write(", ");
31265            }
31266            self.write("hours => ");
31267            self.generate_expression(hour)?;
31268            first = false;
31269        }
31270        if let Some(minute) = &e.minute {
31271            if !first {
31272                self.write(", ");
31273            }
31274            self.write("mins => ");
31275            self.generate_expression(minute)?;
31276            first = false;
31277        }
31278        if let Some(second) = &e.second {
31279            if !first {
31280                self.write(", ");
31281            }
31282            self.write("secs => ");
31283            self.generate_expression(second)?;
31284        }
31285        self.write(")");
31286        Ok(())
31287    }
31288
31289    fn generate_manhattan_distance(&mut self, e: &ManhattanDistance) -> Result<()> {
31290        // MANHATTAN_DISTANCE(vector1, vector2)
31291        self.write_keyword("MANHATTAN_DISTANCE");
31292        self.write("(");
31293        self.generate_expression(&e.this)?;
31294        self.write(", ");
31295        self.generate_expression(&e.expression)?;
31296        self.write(")");
31297        Ok(())
31298    }
31299
31300    fn generate_map(&mut self, e: &Map) -> Result<()> {
31301        // MAP(key1, value1, key2, value2, ...)
31302        self.write_keyword("MAP");
31303        self.write("(");
31304        for (i, (key, value)) in e.keys.iter().zip(e.values.iter()).enumerate() {
31305            if i > 0 {
31306                self.write(", ");
31307            }
31308            self.generate_expression(key)?;
31309            self.write(", ");
31310            self.generate_expression(value)?;
31311        }
31312        self.write(")");
31313        Ok(())
31314    }
31315
31316    fn generate_map_cat(&mut self, e: &MapCat) -> Result<()> {
31317        // MAP_CAT(map1, map2)
31318        self.write_keyword("MAP_CAT");
31319        self.write("(");
31320        self.generate_expression(&e.this)?;
31321        self.write(", ");
31322        self.generate_expression(&e.expression)?;
31323        self.write(")");
31324        Ok(())
31325    }
31326
31327    fn generate_map_delete(&mut self, e: &MapDelete) -> Result<()> {
31328        // MAP_DELETE(map, key1, key2, ...)
31329        self.write_keyword("MAP_DELETE");
31330        self.write("(");
31331        self.generate_expression(&e.this)?;
31332        for expr in &e.expressions {
31333            self.write(", ");
31334            self.generate_expression(expr)?;
31335        }
31336        self.write(")");
31337        Ok(())
31338    }
31339
31340    fn generate_map_insert(&mut self, e: &MapInsert) -> Result<()> {
31341        // MAP_INSERT(map, key, value, [update_flag])
31342        self.write_keyword("MAP_INSERT");
31343        self.write("(");
31344        self.generate_expression(&e.this)?;
31345        if let Some(key) = &e.key {
31346            self.write(", ");
31347            self.generate_expression(key)?;
31348        }
31349        if let Some(value) = &e.value {
31350            self.write(", ");
31351            self.generate_expression(value)?;
31352        }
31353        if let Some(update_flag) = &e.update_flag {
31354            self.write(", ");
31355            self.generate_expression(update_flag)?;
31356        }
31357        self.write(")");
31358        Ok(())
31359    }
31360
31361    fn generate_map_pick(&mut self, e: &MapPick) -> Result<()> {
31362        // MAP_PICK(map, key1, key2, ...)
31363        self.write_keyword("MAP_PICK");
31364        self.write("(");
31365        self.generate_expression(&e.this)?;
31366        for expr in &e.expressions {
31367            self.write(", ");
31368            self.generate_expression(expr)?;
31369        }
31370        self.write(")");
31371        Ok(())
31372    }
31373
31374    fn generate_masking_policy_column_constraint(
31375        &mut self,
31376        e: &MaskingPolicyColumnConstraint,
31377    ) -> Result<()> {
31378        // Python: MASKING POLICY name [USING (cols)]
31379        self.write_keyword("MASKING POLICY");
31380        self.write_space();
31381        self.generate_expression(&e.this)?;
31382        if !e.expressions.is_empty() {
31383            self.write_keyword(" USING");
31384            self.write(" (");
31385            for (i, expr) in e.expressions.iter().enumerate() {
31386                if i > 0 {
31387                    self.write(", ");
31388                }
31389                self.generate_expression(expr)?;
31390            }
31391            self.write(")");
31392        }
31393        Ok(())
31394    }
31395
31396    fn generate_match_against(&mut self, e: &MatchAgainst) -> Result<()> {
31397        if matches!(
31398            self.config.dialect,
31399            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
31400        ) {
31401            if e.expressions.len() > 1 {
31402                self.write("(");
31403            }
31404            for (i, expr) in e.expressions.iter().enumerate() {
31405                if i > 0 {
31406                    self.write_keyword(" OR ");
31407                }
31408                self.generate_expression(expr)?;
31409                self.write_space();
31410                self.write("@@");
31411                self.write_space();
31412                self.generate_expression(&e.this)?;
31413            }
31414            if e.expressions.len() > 1 {
31415                self.write(")");
31416            }
31417            return Ok(());
31418        }
31419
31420        // MATCH(columns) AGAINST(expr [modifier])
31421        self.write_keyword("MATCH");
31422        self.write("(");
31423        for (i, expr) in e.expressions.iter().enumerate() {
31424            if i > 0 {
31425                self.write(", ");
31426            }
31427            self.generate_expression(expr)?;
31428        }
31429        self.write(")");
31430        self.write_keyword(" AGAINST");
31431        self.write("(");
31432        self.generate_expression(&e.this)?;
31433        if let Some(modifier) = &e.modifier {
31434            self.write_space();
31435            self.generate_expression(modifier)?;
31436        }
31437        self.write(")");
31438        Ok(())
31439    }
31440
31441    fn generate_match_recognize_measure(&mut self, e: &MatchRecognizeMeasure) -> Result<()> {
31442        // Python: [window_frame] this
31443        if let Some(window_frame) = &e.window_frame {
31444            self.write(&format!("{:?}", window_frame).to_ascii_uppercase());
31445            self.write_space();
31446        }
31447        self.generate_expression(&e.this)?;
31448        Ok(())
31449    }
31450
31451    fn generate_materialized_property(&mut self, e: &MaterializedProperty) -> Result<()> {
31452        // MATERIALIZED [this]
31453        self.write_keyword("MATERIALIZED");
31454        if let Some(this) = &e.this {
31455            self.write_space();
31456            self.generate_expression(this)?;
31457        }
31458        Ok(())
31459    }
31460
31461    fn generate_merge(&mut self, e: &Merge) -> Result<()> {
31462        // MERGE INTO target USING source ON condition WHEN ...
31463        // DuckDB variant: MERGE INTO target USING source USING (key_columns) WHEN ...
31464        if let Some(with_) = &e.with_ {
31465            if let Expression::With(with_clause) = with_.as_ref() {
31466                self.generate_with(with_clause)?;
31467                self.write_space();
31468            } else {
31469                self.generate_expression(with_)?;
31470                self.write_space();
31471            }
31472        }
31473        self.write_keyword("MERGE INTO");
31474        self.write_space();
31475        if matches!(self.config.dialect, Some(crate::DialectType::Oracle)) {
31476            if let Expression::Alias(alias) = e.this.as_ref() {
31477                self.generate_expression(&alias.this)?;
31478                self.write_space();
31479                self.generate_identifier(&alias.alias)?;
31480            } else {
31481                self.generate_expression(&e.this)?;
31482            }
31483        } else {
31484            self.generate_expression(&e.this)?;
31485        }
31486
31487        // USING clause - newline before in pretty mode
31488        if self.config.pretty {
31489            self.write_newline();
31490            self.write_indent();
31491        } else {
31492            self.write_space();
31493        }
31494        self.write_keyword("USING");
31495        self.write_space();
31496        self.generate_expression(&e.using)?;
31497
31498        // ON clause - newline before in pretty mode
31499        if let Some(on) = &e.on {
31500            if self.config.pretty {
31501                self.write_newline();
31502                self.write_indent();
31503            } else {
31504                self.write_space();
31505            }
31506            self.write_keyword("ON");
31507            self.write_space();
31508            self.generate_expression(on)?;
31509        }
31510        // DuckDB USING (key_columns) clause
31511        if let Some(using_cond) = &e.using_cond {
31512            self.write_space();
31513            self.write_keyword("USING");
31514            self.write_space();
31515            self.write("(");
31516            // using_cond is a Tuple containing the column identifiers
31517            if let Expression::Tuple(tuple) = using_cond.as_ref() {
31518                for (i, col) in tuple.expressions.iter().enumerate() {
31519                    if i > 0 {
31520                        self.write(", ");
31521                    }
31522                    self.generate_expression(col)?;
31523                }
31524            } else {
31525                self.generate_expression(using_cond)?;
31526            }
31527            self.write(")");
31528        }
31529        // For PostgreSQL dialect, extract target table name/alias to strip from UPDATE SET
31530        let saved_merge_strip = std::mem::take(&mut self.merge_strip_qualifiers);
31531        if matches!(
31532            self.config.dialect,
31533            Some(crate::DialectType::PostgreSQL)
31534                | Some(crate::DialectType::Redshift)
31535                | Some(crate::DialectType::Trino)
31536                | Some(crate::DialectType::Presto)
31537                | Some(crate::DialectType::Athena)
31538        ) {
31539            let mut names = Vec::new();
31540            match e.this.as_ref() {
31541                Expression::Alias(a) => {
31542                    // e.g., "x AS z" -> strip both "x" and "z"
31543                    if let Expression::Table(t) = &a.this {
31544                        names.push(t.name.name.clone());
31545                    } else if let Expression::Identifier(id) = &a.this {
31546                        names.push(id.name.clone());
31547                    }
31548                    names.push(a.alias.name.clone());
31549                }
31550                Expression::Table(t) => {
31551                    names.push(t.name.name.clone());
31552                }
31553                Expression::Identifier(id) => {
31554                    names.push(id.name.clone());
31555                }
31556                _ => {}
31557            }
31558            self.merge_strip_qualifiers = names;
31559        }
31560
31561        // WHEN clauses - newline before each in pretty mode
31562        if let Some(whens) = &e.whens {
31563            if self.config.pretty {
31564                self.write_newline();
31565                self.write_indent();
31566            } else {
31567                self.write_space();
31568            }
31569            self.generate_expression(whens)?;
31570        }
31571
31572        // Restore merge_strip_qualifiers
31573        self.merge_strip_qualifiers = saved_merge_strip;
31574
31575        // OUTPUT/RETURNING clause - newline before in pretty mode
31576        if let Some(returning) = &e.returning {
31577            if self.config.pretty {
31578                self.write_newline();
31579                self.write_indent();
31580            } else {
31581                self.write_space();
31582            }
31583            self.generate_expression(returning)?;
31584        }
31585        Ok(())
31586    }
31587
31588    fn generate_merge_block_ratio_property(&mut self, e: &MergeBlockRatioProperty) -> Result<()> {
31589        // Python: NO MERGEBLOCKRATIO | DEFAULT MERGEBLOCKRATIO | MERGEBLOCKRATIO=this [PERCENT]
31590        if e.no.is_some() {
31591            self.write_keyword("NO MERGEBLOCKRATIO");
31592        } else if e.default.is_some() {
31593            self.write_keyword("DEFAULT MERGEBLOCKRATIO");
31594        } else {
31595            self.write_keyword("MERGEBLOCKRATIO");
31596            self.write("=");
31597            if let Some(this) = &e.this {
31598                self.generate_expression(this)?;
31599            }
31600            if e.percent.is_some() {
31601                self.write_keyword(" PERCENT");
31602            }
31603        }
31604        Ok(())
31605    }
31606
31607    fn generate_merge_tree_ttl(&mut self, e: &MergeTreeTTL) -> Result<()> {
31608        // TTL expressions [WHERE where] [GROUP BY group] [SET aggregates]
31609        self.write_keyword("TTL");
31610        let pretty_clickhouse = self.config.pretty
31611            && matches!(
31612                self.config.dialect,
31613                Some(crate::dialects::DialectType::ClickHouse)
31614            );
31615
31616        if pretty_clickhouse {
31617            self.write_newline();
31618            self.indent_level += 1;
31619            for (i, expr) in e.expressions.iter().enumerate() {
31620                if i > 0 {
31621                    self.write(",");
31622                    self.write_newline();
31623                }
31624                self.write_indent();
31625                self.generate_expression(expr)?;
31626            }
31627            self.indent_level -= 1;
31628        } else {
31629            self.write_space();
31630            for (i, expr) in e.expressions.iter().enumerate() {
31631                if i > 0 {
31632                    self.write(", ");
31633                }
31634                self.generate_expression(expr)?;
31635            }
31636        }
31637
31638        if let Some(where_) = &e.where_ {
31639            if pretty_clickhouse {
31640                self.write_newline();
31641                if let Expression::Where(w) = where_.as_ref() {
31642                    self.write_indent();
31643                    self.write_keyword("WHERE");
31644                    self.write_newline();
31645                    self.indent_level += 1;
31646                    self.write_indent();
31647                    self.generate_expression(&w.this)?;
31648                    self.indent_level -= 1;
31649                } else {
31650                    self.write_indent();
31651                    self.generate_expression(where_)?;
31652                }
31653            } else {
31654                self.write_space();
31655                self.generate_expression(where_)?;
31656            }
31657        }
31658        if let Some(group) = &e.group {
31659            if pretty_clickhouse {
31660                self.write_newline();
31661                if let Expression::Group(g) = group.as_ref() {
31662                    self.write_indent();
31663                    self.write_keyword("GROUP BY");
31664                    self.write_newline();
31665                    self.indent_level += 1;
31666                    for (i, expr) in g.expressions.iter().enumerate() {
31667                        if i > 0 {
31668                            self.write(",");
31669                            self.write_newline();
31670                        }
31671                        self.write_indent();
31672                        self.generate_expression(expr)?;
31673                    }
31674                    self.indent_level -= 1;
31675                } else {
31676                    self.write_indent();
31677                    self.generate_expression(group)?;
31678                }
31679            } else {
31680                self.write_space();
31681                self.generate_expression(group)?;
31682            }
31683        }
31684        if let Some(aggregates) = &e.aggregates {
31685            if pretty_clickhouse {
31686                self.write_newline();
31687                self.write_indent();
31688                self.write_keyword("SET");
31689                self.write_newline();
31690                self.indent_level += 1;
31691                if let Expression::Tuple(t) = aggregates.as_ref() {
31692                    for (i, agg) in t.expressions.iter().enumerate() {
31693                        if i > 0 {
31694                            self.write(",");
31695                            self.write_newline();
31696                        }
31697                        self.write_indent();
31698                        self.generate_expression(agg)?;
31699                    }
31700                } else {
31701                    self.write_indent();
31702                    self.generate_expression(aggregates)?;
31703                }
31704                self.indent_level -= 1;
31705            } else {
31706                self.write_space();
31707                self.write_keyword("SET");
31708                self.write_space();
31709                if let Expression::Tuple(t) = aggregates.as_ref() {
31710                    for (i, agg) in t.expressions.iter().enumerate() {
31711                        if i > 0 {
31712                            self.write(", ");
31713                        }
31714                        self.generate_expression(agg)?;
31715                    }
31716                } else {
31717                    self.generate_expression(aggregates)?;
31718                }
31719            }
31720        }
31721        Ok(())
31722    }
31723
31724    fn generate_merge_tree_ttl_action(&mut self, e: &MergeTreeTTLAction) -> Result<()> {
31725        // Python: this [DELETE] [RECOMPRESS codec] [TO DISK disk] [TO VOLUME volume]
31726        self.generate_expression(&e.this)?;
31727        if e.delete.is_some() {
31728            self.write_keyword(" DELETE");
31729        }
31730        if let Some(recompress) = &e.recompress {
31731            self.write_keyword(" RECOMPRESS ");
31732            self.generate_expression(recompress)?;
31733        }
31734        if let Some(to_disk) = &e.to_disk {
31735            self.write_keyword(" TO DISK ");
31736            self.generate_expression(to_disk)?;
31737        }
31738        if let Some(to_volume) = &e.to_volume {
31739            self.write_keyword(" TO VOLUME ");
31740            self.generate_expression(to_volume)?;
31741        }
31742        Ok(())
31743    }
31744
31745    fn generate_minhash(&mut self, e: &Minhash) -> Result<()> {
31746        // MINHASH(this, expressions...)
31747        self.write_keyword("MINHASH");
31748        self.write("(");
31749        self.generate_expression(&e.this)?;
31750        for expr in &e.expressions {
31751            self.write(", ");
31752            self.generate_expression(expr)?;
31753        }
31754        self.write(")");
31755        Ok(())
31756    }
31757
31758    fn generate_model_attribute(&mut self, e: &ModelAttribute) -> Result<()> {
31759        // model!attribute - Snowflake syntax
31760        self.generate_expression(&e.this)?;
31761        self.write("!");
31762        self.generate_expression(&e.expression)?;
31763        Ok(())
31764    }
31765
31766    fn generate_monthname(&mut self, e: &Monthname) -> Result<()> {
31767        // MONTHNAME(this)
31768        self.write_keyword("MONTHNAME");
31769        self.write("(");
31770        self.generate_expression(&e.this)?;
31771        self.write(")");
31772        Ok(())
31773    }
31774
31775    fn generate_multitable_inserts(&mut self, e: &MultitableInserts) -> Result<()> {
31776        // Output leading comments
31777        for comment in &e.leading_comments {
31778            self.write_formatted_comment(comment);
31779            if self.config.pretty {
31780                self.write_newline();
31781                self.write_indent();
31782            } else {
31783                self.write_space();
31784            }
31785        }
31786        // Python: INSERT [OVERWRITE] kind expressions source
31787        self.write_keyword("INSERT");
31788        if e.overwrite {
31789            self.write_space();
31790            self.write_keyword("OVERWRITE");
31791        }
31792        self.write_space();
31793        self.write(&e.kind);
31794        if self.config.pretty {
31795            self.indent_level += 1;
31796            for expr in &e.expressions {
31797                self.write_newline();
31798                self.write_indent();
31799                self.generate_expression(expr)?;
31800            }
31801            self.indent_level -= 1;
31802        } else {
31803            for expr in &e.expressions {
31804                self.write_space();
31805                self.generate_expression(expr)?;
31806            }
31807        }
31808        if let Some(source) = &e.source {
31809            if self.config.pretty {
31810                self.write_newline();
31811                self.write_indent();
31812            } else {
31813                self.write_space();
31814            }
31815            self.generate_expression(source)?;
31816        }
31817        Ok(())
31818    }
31819
31820    fn generate_next_value_for(&mut self, e: &NextValueFor) -> Result<()> {
31821        // Python: NEXT VALUE FOR this [OVER (order)]
31822        self.write_keyword("NEXT VALUE FOR");
31823        self.write_space();
31824        self.generate_expression(&e.this)?;
31825        if let Some(order) = &e.order {
31826            self.write_space();
31827            self.write_keyword("OVER");
31828            self.write(" (");
31829            self.generate_expression(order)?;
31830            self.write(")");
31831        }
31832        Ok(())
31833    }
31834
31835    fn generate_normal(&mut self, e: &Normal) -> Result<()> {
31836        // NORMAL(mean, stddev, gen)
31837        self.write_keyword("NORMAL");
31838        self.write("(");
31839        self.generate_expression(&e.this)?;
31840        if let Some(stddev) = &e.stddev {
31841            self.write(", ");
31842            self.generate_expression(stddev)?;
31843        }
31844        if let Some(gen) = &e.gen {
31845            self.write(", ");
31846            self.generate_expression(gen)?;
31847        }
31848        self.write(")");
31849        Ok(())
31850    }
31851
31852    fn generate_normalize(&mut self, e: &Normalize) -> Result<()> {
31853        // NORMALIZE(this, form) or CASEFOLD version
31854        if e.is_casefold.is_some() {
31855            self.write_keyword("NORMALIZE_AND_CASEFOLD");
31856        } else {
31857            self.write_keyword("NORMALIZE");
31858        }
31859        self.write("(");
31860        self.generate_expression(&e.this)?;
31861        if let Some(form) = &e.form {
31862            self.write(", ");
31863            self.generate_expression(form)?;
31864        }
31865        self.write(")");
31866        Ok(())
31867    }
31868
31869    fn generate_not_null_column_constraint(&mut self, e: &NotNullColumnConstraint) -> Result<()> {
31870        // Python: [NOT ]NULL
31871        if e.allow_null.is_none() {
31872            self.write_keyword("NOT ");
31873        }
31874        self.write_keyword("NULL");
31875        Ok(())
31876    }
31877
31878    fn generate_nullif(&mut self, e: &Nullif) -> Result<()> {
31879        // NULLIF(this, expression)
31880        self.write_keyword("NULLIF");
31881        self.write("(");
31882        self.generate_expression(&e.this)?;
31883        self.write(", ");
31884        self.generate_expression(&e.expression)?;
31885        self.write(")");
31886        Ok(())
31887    }
31888
31889    fn generate_number_to_str(&mut self, e: &NumberToStr) -> Result<()> {
31890        // FORMAT(this, format, culture)
31891        self.write_keyword("FORMAT");
31892        self.write("(");
31893        self.generate_expression(&e.this)?;
31894        self.write(", '");
31895        self.write(&e.format);
31896        self.write("'");
31897        if let Some(culture) = &e.culture {
31898            self.write(", ");
31899            self.generate_expression(culture)?;
31900        }
31901        self.write(")");
31902        Ok(())
31903    }
31904
31905    fn generate_object_agg(&mut self, e: &ObjectAgg) -> Result<()> {
31906        // OBJECT_AGG(key, value)
31907        self.write_keyword("OBJECT_AGG");
31908        self.write("(");
31909        self.generate_expression(&e.this)?;
31910        self.write(", ");
31911        self.generate_expression(&e.expression)?;
31912        self.write(")");
31913        Ok(())
31914    }
31915
31916    fn generate_object_identifier(&mut self, e: &ObjectIdentifier) -> Result<()> {
31917        // Python: Just returns the name
31918        self.generate_expression(&e.this)?;
31919        Ok(())
31920    }
31921
31922    fn generate_object_insert(&mut self, e: &ObjectInsert) -> Result<()> {
31923        // OBJECT_INSERT(obj, key, value, [update_flag])
31924        self.write_keyword("OBJECT_INSERT");
31925        self.write("(");
31926        self.generate_expression(&e.this)?;
31927        if let Some(key) = &e.key {
31928            self.write(", ");
31929            self.generate_expression(key)?;
31930        }
31931        if let Some(value) = &e.value {
31932            self.write(", ");
31933            self.generate_expression(value)?;
31934        }
31935        if let Some(update_flag) = &e.update_flag {
31936            self.write(", ");
31937            self.generate_expression(update_flag)?;
31938        }
31939        self.write(")");
31940        Ok(())
31941    }
31942
31943    fn generate_offset(&mut self, e: &Offset) -> Result<()> {
31944        // OFFSET value [ROW|ROWS]
31945        self.write_keyword("OFFSET");
31946        self.write_space();
31947        self.generate_expression(&e.this)?;
31948        // Output ROWS keyword only for TSQL/Oracle targets
31949        if e.rows == Some(true)
31950            && matches!(
31951                self.config.dialect,
31952                Some(crate::dialects::DialectType::TSQL)
31953                    | Some(crate::dialects::DialectType::Oracle)
31954            )
31955        {
31956            self.write_space();
31957            self.write_keyword("ROWS");
31958        }
31959        Ok(())
31960    }
31961
31962    fn generate_qualify(&mut self, e: &Qualify) -> Result<()> {
31963        // QUALIFY condition (Snowflake/BigQuery)
31964        self.write_keyword("QUALIFY");
31965        self.write_space();
31966        self.generate_expression(&e.this)?;
31967        Ok(())
31968    }
31969
31970    fn generate_on_cluster(&mut self, e: &OnCluster) -> Result<()> {
31971        // ON CLUSTER cluster_name
31972        self.write_keyword("ON CLUSTER");
31973        self.write_space();
31974        self.generate_expression(&e.this)?;
31975        Ok(())
31976    }
31977
31978    fn generate_on_commit_property(&mut self, e: &OnCommitProperty) -> Result<()> {
31979        // ON COMMIT [DELETE ROWS | PRESERVE ROWS]
31980        self.write_keyword("ON COMMIT");
31981        if e.delete.is_some() {
31982            self.write_keyword(" DELETE ROWS");
31983        } else {
31984            self.write_keyword(" PRESERVE ROWS");
31985        }
31986        Ok(())
31987    }
31988
31989    fn generate_on_condition(&mut self, e: &OnCondition) -> Result<()> {
31990        // Python: error/empty/null handling
31991        if let Some(empty) = &e.empty {
31992            self.generate_expression(empty)?;
31993            self.write_keyword(" ON EMPTY");
31994        }
31995        if let Some(error) = &e.error {
31996            if e.empty.is_some() {
31997                self.write_space();
31998            }
31999            self.generate_expression(error)?;
32000            self.write_keyword(" ON ERROR");
32001        }
32002        if let Some(null) = &e.null {
32003            if e.empty.is_some() || e.error.is_some() {
32004                self.write_space();
32005            }
32006            self.generate_expression(null)?;
32007            self.write_keyword(" ON NULL");
32008        }
32009        Ok(())
32010    }
32011
32012    fn generate_on_conflict(&mut self, e: &OnConflict) -> Result<()> {
32013        // Materialize doesn't support ON CONFLICT - skip entirely
32014        if matches!(self.config.dialect, Some(DialectType::Materialize)) {
32015            return Ok(());
32016        }
32017        // Python: ON CONFLICT|ON DUPLICATE KEY [ON CONSTRAINT constraint] [conflict_keys] action
32018        if e.duplicate.is_some() {
32019            // MySQL: ON DUPLICATE KEY UPDATE col = val, ...
32020            self.write_keyword("ON DUPLICATE KEY UPDATE");
32021            for (i, expr) in e.expressions.iter().enumerate() {
32022                if i > 0 {
32023                    self.write(",");
32024                }
32025                self.write_space();
32026                self.generate_expression(expr)?;
32027            }
32028            return Ok(());
32029        } else {
32030            self.write_keyword("ON CONFLICT");
32031        }
32032        if let Some(constraint) = &e.constraint {
32033            self.write_keyword(" ON CONSTRAINT ");
32034            self.generate_expression(constraint)?;
32035        }
32036        if let Some(conflict_keys) = &e.conflict_keys {
32037            // conflict_keys can be a Tuple containing expressions
32038            if let Expression::Tuple(t) = conflict_keys.as_ref() {
32039                self.write("(");
32040                for (i, expr) in t.expressions.iter().enumerate() {
32041                    if i > 0 {
32042                        self.write(", ");
32043                    }
32044                    self.generate_expression(expr)?;
32045                }
32046                self.write(")");
32047            } else {
32048                self.write("(");
32049                self.generate_expression(conflict_keys)?;
32050                self.write(")");
32051            }
32052        }
32053        if let Some(index_predicate) = &e.index_predicate {
32054            self.write_keyword(" WHERE ");
32055            self.generate_expression(index_predicate)?;
32056        }
32057        if let Some(action) = &e.action {
32058            // Check if action is "NOTHING" or an UPDATE set
32059            if let Expression::Identifier(id) = action.as_ref() {
32060                if id.name.eq_ignore_ascii_case("NOTHING") {
32061                    self.write_keyword(" DO NOTHING");
32062                } else {
32063                    self.write_keyword(" DO ");
32064                    self.generate_expression(action)?;
32065                }
32066            } else if let Expression::Tuple(t) = action.as_ref() {
32067                // DO UPDATE SET col1 = val1, col2 = val2
32068                self.write_keyword(" DO UPDATE SET ");
32069                for (i, expr) in t.expressions.iter().enumerate() {
32070                    if i > 0 {
32071                        self.write(", ");
32072                    }
32073                    self.generate_expression(expr)?;
32074                }
32075            } else {
32076                self.write_keyword(" DO ");
32077                self.generate_expression(action)?;
32078            }
32079        }
32080        // WHERE clause for the UPDATE action
32081        if let Some(where_) = &e.where_ {
32082            self.write_keyword(" WHERE ");
32083            self.generate_expression(where_)?;
32084        }
32085        Ok(())
32086    }
32087
32088    fn generate_on_property(&mut self, e: &OnProperty) -> Result<()> {
32089        // ON property_value
32090        self.write_keyword("ON");
32091        self.write_space();
32092        self.generate_expression(&e.this)?;
32093        Ok(())
32094    }
32095
32096    fn generate_opclass(&mut self, e: &Opclass) -> Result<()> {
32097        // Python: this expression (e.g., column opclass)
32098        self.generate_expression(&e.this)?;
32099        self.write_space();
32100        self.generate_expression(&e.expression)?;
32101        Ok(())
32102    }
32103
32104    fn generate_open_json(&mut self, e: &OpenJSON) -> Result<()> {
32105        // Python: OPENJSON(this[, path]) [WITH (columns)]
32106        self.write_keyword("OPENJSON");
32107        self.write("(");
32108        self.generate_expression(&e.this)?;
32109        if let Some(path) = &e.path {
32110            self.write(", ");
32111            self.generate_expression(path)?;
32112        }
32113        self.write(")");
32114        if !e.expressions.is_empty() {
32115            self.write_keyword(" WITH");
32116            if self.config.pretty {
32117                self.write(" (\n");
32118                self.indent_level += 2;
32119                for (i, expr) in e.expressions.iter().enumerate() {
32120                    if i > 0 {
32121                        self.write(",\n");
32122                    }
32123                    self.write_indent();
32124                    self.generate_expression(expr)?;
32125                }
32126                self.write("\n");
32127                self.indent_level -= 2;
32128                self.write(")");
32129            } else {
32130                self.write(" (");
32131                for (i, expr) in e.expressions.iter().enumerate() {
32132                    if i > 0 {
32133                        self.write(", ");
32134                    }
32135                    self.generate_expression(expr)?;
32136                }
32137                self.write(")");
32138            }
32139        }
32140        Ok(())
32141    }
32142
32143    fn generate_open_json_column_def(&mut self, e: &OpenJSONColumnDef) -> Result<()> {
32144        // Python: this kind [path] [AS JSON]
32145        self.generate_expression(&e.this)?;
32146        self.write_space();
32147        // Use parsed data_type if available, otherwise fall back to kind string
32148        if let Some(ref dt) = e.data_type {
32149            self.generate_data_type(dt)?;
32150        } else if !e.kind.is_empty() {
32151            self.write(&e.kind);
32152        }
32153        if let Some(path) = &e.path {
32154            self.write_space();
32155            self.generate_expression(path)?;
32156        }
32157        if e.as_json.is_some() {
32158            self.write_keyword(" AS JSON");
32159        }
32160        Ok(())
32161    }
32162
32163    fn generate_operator(&mut self, e: &Operator) -> Result<()> {
32164        // this OPERATOR(op) expression
32165        self.generate_expression(&e.this)?;
32166        self.write_space();
32167        if let Some(op) = &e.operator {
32168            self.write_keyword("OPERATOR");
32169            self.write("(");
32170            self.generate_expression(op)?;
32171            self.write(")");
32172        }
32173        // Emit inline comments between OPERATOR() and the RHS
32174        for comment in &e.comments {
32175            self.write_space();
32176            self.write_formatted_comment(comment);
32177        }
32178        self.write_space();
32179        self.generate_expression(&e.expression)?;
32180        Ok(())
32181    }
32182
32183    fn generate_order_by(&mut self, e: &OrderBy) -> Result<()> {
32184        // ORDER BY expr1 [ASC|DESC] [NULLS FIRST|LAST], expr2 ...
32185        self.write_keyword("ORDER BY");
32186        let pretty_clickhouse_single_paren = self.config.pretty
32187            && matches!(self.config.dialect, Some(DialectType::ClickHouse))
32188            && e.expressions.len() == 1
32189            && matches!(e.expressions[0].this, Expression::Paren(ref p) if !matches!(p.this, Expression::Tuple(_)));
32190        let clickhouse_single_tuple = matches!(self.config.dialect, Some(DialectType::ClickHouse))
32191            && e.expressions.len() == 1
32192            && matches!(e.expressions[0].this, Expression::Tuple(_))
32193            && !e.expressions[0].desc
32194            && e.expressions[0].nulls_first.is_none();
32195
32196        if pretty_clickhouse_single_paren {
32197            self.write_space();
32198            if let Expression::Paren(p) = &e.expressions[0].this {
32199                self.write("(");
32200                self.write_newline();
32201                self.indent_level += 1;
32202                self.write_indent();
32203                self.generate_expression(&p.this)?;
32204                self.indent_level -= 1;
32205                self.write_newline();
32206                self.write(")");
32207            }
32208            return Ok(());
32209        }
32210
32211        if clickhouse_single_tuple {
32212            self.write_space();
32213            if let Expression::Tuple(t) = &e.expressions[0].this {
32214                self.write("(");
32215                for (i, expr) in t.expressions.iter().enumerate() {
32216                    if i > 0 {
32217                        self.write(", ");
32218                    }
32219                    self.generate_expression(expr)?;
32220                }
32221                self.write(")");
32222            }
32223            return Ok(());
32224        }
32225
32226        self.write_space();
32227        for (i, ordered) in e.expressions.iter().enumerate() {
32228            if i > 0 {
32229                self.write(", ");
32230            }
32231            self.generate_expression(&ordered.this)?;
32232            if ordered.desc {
32233                self.write_space();
32234                self.write_keyword("DESC");
32235            } else if ordered.explicit_asc {
32236                self.write_space();
32237                self.write_keyword("ASC");
32238            }
32239            if let Some(nulls_first) = ordered.nulls_first {
32240                // In Dremio, NULLS LAST is the default, so skip generating it
32241                let skip_nulls_last =
32242                    !nulls_first && matches!(self.config.dialect, Some(DialectType::Dremio));
32243                if !skip_nulls_last {
32244                    self.write_space();
32245                    self.write_keyword("NULLS");
32246                    self.write_space();
32247                    if nulls_first {
32248                        self.write_keyword("FIRST");
32249                    } else {
32250                        self.write_keyword("LAST");
32251                    }
32252                }
32253            }
32254        }
32255        Ok(())
32256    }
32257
32258    fn generate_output_model_property(&mut self, e: &OutputModelProperty) -> Result<()> {
32259        // OUTPUT(model)
32260        self.write_keyword("OUTPUT");
32261        self.write("(");
32262        if self.config.pretty {
32263            self.indent_level += 1;
32264            self.write_newline();
32265            self.write_indent();
32266            self.generate_expression(&e.this)?;
32267            self.indent_level -= 1;
32268            self.write_newline();
32269        } else {
32270            self.generate_expression(&e.this)?;
32271        }
32272        self.write(")");
32273        Ok(())
32274    }
32275
32276    fn generate_overflow_truncate_behavior(&mut self, e: &OverflowTruncateBehavior) -> Result<()> {
32277        // Python: TRUNCATE [filler] WITH|WITHOUT COUNT
32278        self.write_keyword("TRUNCATE");
32279        if let Some(this) = &e.this {
32280            self.write_space();
32281            self.generate_expression(this)?;
32282        }
32283        if e.with_count.is_some() {
32284            self.write_keyword(" WITH COUNT");
32285        } else {
32286            self.write_keyword(" WITHOUT COUNT");
32287        }
32288        Ok(())
32289    }
32290
32291    fn generate_parameterized_agg(&mut self, e: &ParameterizedAgg) -> Result<()> {
32292        // Python: name(expressions)(params)
32293        self.generate_expression(&e.this)?;
32294        self.write("(");
32295        for (i, expr) in e.expressions.iter().enumerate() {
32296            if i > 0 {
32297                self.write(", ");
32298            }
32299            self.generate_expression(expr)?;
32300        }
32301        self.write(")(");
32302        for (i, param) in e.params.iter().enumerate() {
32303            if i > 0 {
32304                self.write(", ");
32305            }
32306            self.generate_expression(param)?;
32307        }
32308        self.write(")");
32309        Ok(())
32310    }
32311
32312    fn generate_parse_datetime(&mut self, e: &ParseDatetime) -> Result<()> {
32313        // PARSE_DATETIME(format, this) or similar
32314        self.write_keyword("PARSE_DATETIME");
32315        self.write("(");
32316        if let Some(format) = &e.format {
32317            self.write("'");
32318            self.write(format);
32319            self.write("', ");
32320        }
32321        self.generate_expression(&e.this)?;
32322        if let Some(zone) = &e.zone {
32323            self.write(", ");
32324            self.generate_expression(zone)?;
32325        }
32326        self.write(")");
32327        Ok(())
32328    }
32329
32330    fn generate_parse_ip(&mut self, e: &ParseIp) -> Result<()> {
32331        // PARSE_IP(this, type, permissive)
32332        self.write_keyword("PARSE_IP");
32333        self.write("(");
32334        self.generate_expression(&e.this)?;
32335        if let Some(type_) = &e.type_ {
32336            self.write(", ");
32337            self.generate_expression(type_)?;
32338        }
32339        if let Some(permissive) = &e.permissive {
32340            self.write(", ");
32341            self.generate_expression(permissive)?;
32342        }
32343        self.write(")");
32344        Ok(())
32345    }
32346
32347    fn generate_parse_json(&mut self, e: &ParseJSON) -> Result<()> {
32348        // PARSE_JSON(this, [expression])
32349        self.write_keyword("PARSE_JSON");
32350        self.write("(");
32351        self.generate_expression(&e.this)?;
32352        if let Some(expression) = &e.expression {
32353            self.write(", ");
32354            self.generate_expression(expression)?;
32355        }
32356        self.write(")");
32357        Ok(())
32358    }
32359
32360    fn generate_parse_time(&mut self, e: &ParseTime) -> Result<()> {
32361        // PARSE_TIME(format, this) or STR_TO_TIME(this, format)
32362        self.write_keyword("PARSE_TIME");
32363        self.write("(");
32364        self.write(&format!("'{}'", e.format));
32365        self.write(", ");
32366        self.generate_expression(&e.this)?;
32367        self.write(")");
32368        Ok(())
32369    }
32370
32371    fn generate_parse_url(&mut self, e: &ParseUrl) -> Result<()> {
32372        // PARSE_URL(this, [part_to_extract], [key], [permissive])
32373        self.write_keyword("PARSE_URL");
32374        self.write("(");
32375        self.generate_expression(&e.this)?;
32376        if let Some(part) = &e.part_to_extract {
32377            self.write(", ");
32378            self.generate_expression(part)?;
32379        }
32380        if let Some(key) = &e.key {
32381            self.write(", ");
32382            self.generate_expression(key)?;
32383        }
32384        if let Some(permissive) = &e.permissive {
32385            self.write(", ");
32386            self.generate_expression(permissive)?;
32387        }
32388        self.write(")");
32389        Ok(())
32390    }
32391
32392    fn generate_partition_expr(&mut self, e: &Partition) -> Result<()> {
32393        // PARTITION(expr1, expr2, ...) or SUBPARTITION(expr1, expr2, ...)
32394        if e.subpartition {
32395            self.write_keyword("SUBPARTITION");
32396        } else {
32397            self.write_keyword("PARTITION");
32398        }
32399        self.write("(");
32400        for (i, expr) in e.expressions.iter().enumerate() {
32401            if i > 0 {
32402                self.write(", ");
32403            }
32404            self.generate_expression(expr)?;
32405        }
32406        self.write(")");
32407        Ok(())
32408    }
32409
32410    fn generate_partition_bound_spec(&mut self, e: &PartitionBoundSpec) -> Result<()> {
32411        // IN (values) or WITH (MODULUS this, REMAINDER expression) or FROM (from) TO (to)
32412        if let Some(this) = &e.this {
32413            if let Some(expression) = &e.expression {
32414                // WITH (MODULUS this, REMAINDER expression)
32415                self.write_keyword("WITH");
32416                self.write(" (");
32417                self.write_keyword("MODULUS");
32418                self.write_space();
32419                self.generate_expression(this)?;
32420                self.write(", ");
32421                self.write_keyword("REMAINDER");
32422                self.write_space();
32423                self.generate_expression(expression)?;
32424                self.write(")");
32425            } else {
32426                // IN (this) - this could be a list
32427                self.write_keyword("IN");
32428                self.write(" (");
32429                self.generate_partition_bound_values(this)?;
32430                self.write(")");
32431            }
32432        } else if let (Some(from), Some(to)) = (&e.from_expressions, &e.to_expressions) {
32433            // FROM (from_expressions) TO (to_expressions)
32434            self.write_keyword("FROM");
32435            self.write(" (");
32436            self.generate_partition_bound_values(from)?;
32437            self.write(") ");
32438            self.write_keyword("TO");
32439            self.write(" (");
32440            self.generate_partition_bound_values(to)?;
32441            self.write(")");
32442        }
32443        Ok(())
32444    }
32445
32446    /// Generate partition bound values - handles Tuple expressions by outputting
32447    /// contents without wrapping parens (since caller provides the parens)
32448    fn generate_partition_bound_values(&mut self, expr: &Expression) -> Result<()> {
32449        if let Expression::Tuple(t) = expr {
32450            for (i, e) in t.expressions.iter().enumerate() {
32451                if i > 0 {
32452                    self.write(", ");
32453                }
32454                self.generate_expression(e)?;
32455            }
32456            Ok(())
32457        } else {
32458            self.generate_expression(expr)
32459        }
32460    }
32461
32462    fn generate_partition_by_list_property(&mut self, e: &PartitionByListProperty) -> Result<()> {
32463        // PARTITION BY LIST (partition_expressions) (create_expressions)
32464        self.write_keyword("PARTITION BY LIST");
32465        if let Some(partition_exprs) = &e.partition_expressions {
32466            self.write(" (");
32467            // Unwrap Tuple for partition columns (don't generate outer parens from Tuple)
32468            self.generate_doris_partition_expressions(partition_exprs)?;
32469            self.write(")");
32470        }
32471        if let Some(create_exprs) = &e.create_expressions {
32472            self.write(" (");
32473            // Unwrap Tuple for partition definitions
32474            self.generate_doris_partition_definitions(create_exprs)?;
32475            self.write(")");
32476        }
32477        Ok(())
32478    }
32479
32480    fn generate_partition_by_range_property(&mut self, e: &PartitionByRangeProperty) -> Result<()> {
32481        // PARTITION BY RANGE (partition_expressions) (create_expressions)
32482        self.write_keyword("PARTITION BY RANGE");
32483        if let Some(partition_exprs) = &e.partition_expressions {
32484            self.write(" (");
32485            // Unwrap Tuple for partition columns (don't generate outer parens from Tuple)
32486            self.generate_doris_partition_expressions(partition_exprs)?;
32487            self.write(")");
32488        }
32489        if let Some(create_exprs) = &e.create_expressions {
32490            self.write(" (");
32491            // Check for dynamic partition (PartitionByRangePropertyDynamic) or static (Tuple of Partition)
32492            self.generate_doris_partition_definitions(create_exprs)?;
32493            self.write(")");
32494        }
32495        Ok(())
32496    }
32497
32498    /// Generate Doris partition column expressions (unwrap Tuple)
32499    fn generate_doris_partition_expressions(&mut self, expr: &Expression) -> Result<()> {
32500        if let Expression::Tuple(t) = expr {
32501            for (i, e) in t.expressions.iter().enumerate() {
32502                if i > 0 {
32503                    self.write(", ");
32504                }
32505                self.generate_expression(e)?;
32506            }
32507        } else {
32508            self.generate_expression(expr)?;
32509        }
32510        Ok(())
32511    }
32512
32513    /// Generate Doris partition definitions (comma-separated Partition expressions)
32514    fn generate_doris_partition_definitions(&mut self, expr: &Expression) -> Result<()> {
32515        match expr {
32516            Expression::Tuple(t) => {
32517                // Multiple partitions, comma-separated
32518                for (i, part) in t.expressions.iter().enumerate() {
32519                    if i > 0 {
32520                        self.write(", ");
32521                    }
32522                    // For Partition expressions, generate the inner PartitionRange/PartitionList directly
32523                    if let Expression::Partition(p) = part {
32524                        for (j, inner) in p.expressions.iter().enumerate() {
32525                            if j > 0 {
32526                                self.write(", ");
32527                            }
32528                            self.generate_expression(inner)?;
32529                        }
32530                    } else {
32531                        self.generate_expression(part)?;
32532                    }
32533                }
32534            }
32535            Expression::PartitionByRangePropertyDynamic(_) => {
32536                // Dynamic partition - FROM/TO/INTERVAL
32537                self.generate_expression(expr)?;
32538            }
32539            _ => {
32540                self.generate_expression(expr)?;
32541            }
32542        }
32543        Ok(())
32544    }
32545
32546    fn generate_partition_by_range_property_dynamic(
32547        &mut self,
32548        e: &PartitionByRangePropertyDynamic,
32549    ) -> Result<()> {
32550        if e.use_start_end {
32551            // StarRocks: START ('val') END ('val') EVERY (expr)
32552            if let Some(start) = &e.start {
32553                self.write_keyword("START");
32554                self.write(" (");
32555                self.generate_expression(start)?;
32556                self.write(")");
32557            }
32558            if let Some(end) = &e.end {
32559                self.write_space();
32560                self.write_keyword("END");
32561                self.write(" (");
32562                self.generate_expression(end)?;
32563                self.write(")");
32564            }
32565            if let Some(every) = &e.every {
32566                self.write_space();
32567                self.write_keyword("EVERY");
32568                self.write(" (");
32569                // Use unquoted interval format for StarRocks
32570                self.generate_doris_interval(every)?;
32571                self.write(")");
32572            }
32573        } else {
32574            // Doris: FROM (start) TO (end) INTERVAL n UNIT
32575            if let Some(start) = &e.start {
32576                self.write_keyword("FROM");
32577                self.write(" (");
32578                self.generate_expression(start)?;
32579                self.write(")");
32580            }
32581            if let Some(end) = &e.end {
32582                self.write_space();
32583                self.write_keyword("TO");
32584                self.write(" (");
32585                self.generate_expression(end)?;
32586                self.write(")");
32587            }
32588            if let Some(every) = &e.every {
32589                self.write_space();
32590                // Generate INTERVAL n UNIT (not quoted, for Doris dynamic partition)
32591                self.generate_doris_interval(every)?;
32592            }
32593        }
32594        Ok(())
32595    }
32596
32597    /// Generate Doris-style interval without quoting numbers: INTERVAL n UNIT
32598    fn generate_doris_interval(&mut self, expr: &Expression) -> Result<()> {
32599        if let Expression::Interval(interval) = expr {
32600            self.write_keyword("INTERVAL");
32601            if let Some(ref value) = interval.this {
32602                self.write_space();
32603                // If the value is a string literal that looks like a number,
32604                // output it without quotes (matching Python sqlglot's
32605                // partitionbyrangepropertydynamic_sql which converts back to number)
32606                match value {
32607                    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()) => {
32608                        if let Literal::String(s) = lit.as_ref() {
32609                            self.write(s);
32610                        }
32611                    }
32612                    _ => {
32613                        self.generate_expression(value)?;
32614                    }
32615                }
32616            }
32617            if let Some(ref unit_spec) = interval.unit {
32618                self.write_space();
32619                self.write_interval_unit_spec(unit_spec)?;
32620            }
32621            Ok(())
32622        } else {
32623            self.generate_expression(expr)
32624        }
32625    }
32626
32627    fn generate_partition_by_truncate(&mut self, e: &PartitionByTruncate) -> Result<()> {
32628        // TRUNCATE(expression, this)
32629        self.write_keyword("TRUNCATE");
32630        self.write("(");
32631        self.generate_expression(&e.expression)?;
32632        self.write(", ");
32633        self.generate_expression(&e.this)?;
32634        self.write(")");
32635        Ok(())
32636    }
32637
32638    fn generate_partition_list(&mut self, e: &PartitionList) -> Result<()> {
32639        // Doris: PARTITION name VALUES IN (val1, val2)
32640        self.write_keyword("PARTITION");
32641        self.write_space();
32642        self.generate_expression(&e.this)?;
32643        self.write_space();
32644        self.write_keyword("VALUES IN");
32645        self.write(" (");
32646        for (i, expr) in e.expressions.iter().enumerate() {
32647            if i > 0 {
32648                self.write(", ");
32649            }
32650            self.generate_expression(expr)?;
32651        }
32652        self.write(")");
32653        Ok(())
32654    }
32655
32656    fn generate_partition_range(&mut self, e: &PartitionRange) -> Result<()> {
32657        // Check if this is a TSQL-style simple range (e.g., "2 TO 5")
32658        // TSQL ranges have no expressions and just use `this TO expression`
32659        if e.expressions.is_empty() && e.expression.is_some() {
32660            // TSQL: simple range like "2 TO 5" - no PARTITION keyword
32661            self.generate_expression(&e.this)?;
32662            self.write_space();
32663            self.write_keyword("TO");
32664            self.write_space();
32665            self.generate_expression(e.expression.as_ref().unwrap())?;
32666            return Ok(());
32667        }
32668
32669        // Doris: PARTITION name VALUES LESS THAN (val) or PARTITION name VALUES [(val1), (val2))
32670        self.write_keyword("PARTITION");
32671        self.write_space();
32672        self.generate_expression(&e.this)?;
32673        self.write_space();
32674
32675        // Check if expressions contain Tuple (bracket notation) or single values (LESS THAN)
32676        if e.expressions.len() == 1 {
32677            // Single value: VALUES LESS THAN (val)
32678            self.write_keyword("VALUES LESS THAN");
32679            self.write(" (");
32680            self.generate_expression(&e.expressions[0])?;
32681            self.write(")");
32682        } else if !e.expressions.is_empty() {
32683            // Multiple values with Tuple: VALUES [(val1), (val2))
32684            self.write_keyword("VALUES");
32685            self.write(" [");
32686            for (i, expr) in e.expressions.iter().enumerate() {
32687                if i > 0 {
32688                    self.write(", ");
32689                }
32690                // If the expr is a Tuple, generate its contents wrapped in parens
32691                if let Expression::Tuple(t) = expr {
32692                    self.write("(");
32693                    for (j, inner) in t.expressions.iter().enumerate() {
32694                        if j > 0 {
32695                            self.write(", ");
32696                        }
32697                        self.generate_expression(inner)?;
32698                    }
32699                    self.write(")");
32700                } else {
32701                    self.write("(");
32702                    self.generate_expression(expr)?;
32703                    self.write(")");
32704                }
32705            }
32706            self.write(")");
32707        }
32708        Ok(())
32709    }
32710
32711    fn generate_partitioned_by_bucket(&mut self, e: &PartitionedByBucket) -> Result<()> {
32712        // BUCKET(this, expression)
32713        self.write_keyword("BUCKET");
32714        self.write("(");
32715        self.generate_expression(&e.this)?;
32716        self.write(", ");
32717        self.generate_expression(&e.expression)?;
32718        self.write(")");
32719        Ok(())
32720    }
32721
32722    fn generate_partition_by_property(&mut self, e: &PartitionByProperty) -> Result<()> {
32723        // BigQuery table property: PARTITION BY expression [, expression ...]
32724        self.write_keyword("PARTITION BY");
32725        self.write_space();
32726        for (i, expr) in e.expressions.iter().enumerate() {
32727            if i > 0 {
32728                self.write(", ");
32729            }
32730            self.generate_expression(expr)?;
32731        }
32732        Ok(())
32733    }
32734
32735    fn generate_partitioned_by_property(&mut self, e: &PartitionedByProperty) -> Result<()> {
32736        // PARTITIONED BY this (Teradata/ClickHouse use PARTITION BY)
32737        if matches!(
32738            self.config.dialect,
32739            Some(crate::dialects::DialectType::Teradata)
32740                | Some(crate::dialects::DialectType::ClickHouse)
32741        ) {
32742            self.write_keyword("PARTITION BY");
32743        } else {
32744            self.write_keyword("PARTITIONED BY");
32745        }
32746        self.write_space();
32747        // In pretty mode, always use multiline tuple format for PARTITIONED BY
32748        if self.config.pretty {
32749            if let Expression::Tuple(ref tuple) = *e.this {
32750                self.write("(");
32751                self.write_newline();
32752                self.indent_level += 1;
32753                for (i, expr) in tuple.expressions.iter().enumerate() {
32754                    if i > 0 {
32755                        self.write(",");
32756                        self.write_newline();
32757                    }
32758                    self.write_indent();
32759                    self.generate_expression(expr)?;
32760                }
32761                self.indent_level -= 1;
32762                self.write_newline();
32763                self.write(")");
32764            } else {
32765                self.generate_expression(&e.this)?;
32766            }
32767        } else {
32768            self.generate_expression(&e.this)?;
32769        }
32770        Ok(())
32771    }
32772
32773    fn generate_partitioned_of_property(&mut self, e: &PartitionedOfProperty) -> Result<()> {
32774        // PARTITION OF this FOR VALUES expression or PARTITION OF this DEFAULT
32775        self.write_keyword("PARTITION OF");
32776        self.write_space();
32777        self.generate_expression(&e.this)?;
32778        // Check if expression is a PartitionBoundSpec
32779        if let Expression::PartitionBoundSpec(_) = e.expression.as_ref() {
32780            self.write_space();
32781            self.write_keyword("FOR VALUES");
32782            self.write_space();
32783            self.generate_expression(&e.expression)?;
32784        } else {
32785            self.write_space();
32786            self.write_keyword("DEFAULT");
32787        }
32788        Ok(())
32789    }
32790
32791    fn generate_period_for_system_time_constraint(
32792        &mut self,
32793        e: &PeriodForSystemTimeConstraint,
32794    ) -> Result<()> {
32795        // PERIOD FOR SYSTEM_TIME (this, expression)
32796        self.write_keyword("PERIOD FOR SYSTEM_TIME");
32797        self.write(" (");
32798        self.generate_expression(&e.this)?;
32799        self.write(", ");
32800        self.generate_expression(&e.expression)?;
32801        self.write(")");
32802        Ok(())
32803    }
32804
32805    fn generate_pivot_alias(&mut self, e: &PivotAlias) -> Result<()> {
32806        // value AS alias
32807        // The alias can be an identifier or an expression (e.g., string concatenation)
32808        self.generate_expression(&e.this)?;
32809        self.write_space();
32810        self.write_keyword("AS");
32811        self.write_space();
32812        // When target dialect uses identifiers for UNPIVOT aliases, convert literals to identifiers
32813        if self.config.unpivot_aliases_are_identifiers {
32814            match &e.alias {
32815                Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
32816                    let Literal::String(s) = lit.as_ref() else {
32817                        unreachable!()
32818                    };
32819                    // Convert string literal to identifier
32820                    self.generate_identifier(&Identifier::new(s.clone()))?;
32821                }
32822                Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(_)) => {
32823                    let Literal::Number(n) = lit.as_ref() else {
32824                        unreachable!()
32825                    };
32826                    // Convert number literal to quoted identifier
32827                    let mut id = Identifier::new(n.clone());
32828                    id.quoted = true;
32829                    self.generate_identifier(&id)?;
32830                }
32831                other => {
32832                    self.generate_expression(other)?;
32833                }
32834            }
32835        } else {
32836            self.generate_expression(&e.alias)?;
32837        }
32838        Ok(())
32839    }
32840
32841    fn generate_pivot_any(&mut self, e: &PivotAny) -> Result<()> {
32842        // ANY or ANY [expression]
32843        self.write_keyword("ANY");
32844        if let Some(this) = &e.this {
32845            self.write_space();
32846            self.generate_expression(this)?;
32847        }
32848        Ok(())
32849    }
32850
32851    fn generate_predict(&mut self, e: &Predict) -> Result<()> {
32852        // ML.PREDICT(MODEL this, expression, [params_struct])
32853        self.write_keyword("ML.PREDICT");
32854        self.write("(");
32855        self.write_keyword("MODEL");
32856        self.write_space();
32857        self.generate_expression(&e.this)?;
32858        self.write(", ");
32859        self.generate_expression(&e.expression)?;
32860        if let Some(params) = &e.params_struct {
32861            self.write(", ");
32862            self.generate_expression(params)?;
32863        }
32864        self.write(")");
32865        Ok(())
32866    }
32867
32868    fn generate_previous_day(&mut self, e: &PreviousDay) -> Result<()> {
32869        // PREVIOUS_DAY(this, expression)
32870        self.write_keyword("PREVIOUS_DAY");
32871        self.write("(");
32872        self.generate_expression(&e.this)?;
32873        self.write(", ");
32874        self.generate_expression(&e.expression)?;
32875        self.write(")");
32876        Ok(())
32877    }
32878
32879    fn generate_primary_key(&mut self, e: &PrimaryKey) -> Result<()> {
32880        // PRIMARY KEY [name] (columns) [INCLUDE (...)] [options]
32881        self.write_keyword("PRIMARY KEY");
32882        if let Some(name) = &e.this {
32883            self.write_space();
32884            self.generate_expression(name)?;
32885        }
32886        if !e.expressions.is_empty() {
32887            self.write(" (");
32888            for (i, expr) in e.expressions.iter().enumerate() {
32889                if i > 0 {
32890                    self.write(", ");
32891                }
32892                self.generate_expression(expr)?;
32893            }
32894            self.write(")");
32895        }
32896        if let Some(include) = &e.include {
32897            self.write_space();
32898            self.generate_expression(include)?;
32899        }
32900        if !e.options.is_empty() {
32901            self.write_space();
32902            for (i, opt) in e.options.iter().enumerate() {
32903                if i > 0 {
32904                    self.write_space();
32905                }
32906                self.generate_expression(opt)?;
32907            }
32908        }
32909        Ok(())
32910    }
32911
32912    fn generate_primary_key_column_constraint(
32913        &mut self,
32914        _e: &PrimaryKeyColumnConstraint,
32915    ) -> Result<()> {
32916        // PRIMARY KEY constraint at column level
32917        self.write_keyword("PRIMARY KEY");
32918        Ok(())
32919    }
32920
32921    fn generate_path_column_constraint(&mut self, e: &PathColumnConstraint) -> Result<()> {
32922        // PATH 'xpath' constraint for XMLTABLE/JSON_TABLE columns
32923        self.write_keyword("PATH");
32924        self.write_space();
32925        self.generate_expression(&e.this)?;
32926        Ok(())
32927    }
32928
32929    fn generate_projection_def(&mut self, e: &ProjectionDef) -> Result<()> {
32930        // PROJECTION this (expression)
32931        self.write_keyword("PROJECTION");
32932        self.write_space();
32933        self.generate_expression(&e.this)?;
32934        self.write(" (");
32935        self.generate_expression(&e.expression)?;
32936        self.write(")");
32937        Ok(())
32938    }
32939
32940    fn generate_properties(&mut self, e: &Properties) -> Result<()> {
32941        // Properties list
32942        for (i, prop) in e.expressions.iter().enumerate() {
32943            if i > 0 {
32944                self.write(", ");
32945            }
32946            self.generate_expression(prop)?;
32947        }
32948        Ok(())
32949    }
32950
32951    fn generate_property(&mut self, e: &Property) -> Result<()> {
32952        // name=value
32953        self.generate_expression(&e.this)?;
32954        if let Some(value) = &e.value {
32955            self.write("=");
32956            self.generate_expression(value)?;
32957        }
32958        Ok(())
32959    }
32960
32961    fn generate_options_property(&mut self, e: &OptionsProperty) -> Result<()> {
32962        self.write_keyword("OPTIONS");
32963        if e.entries.is_empty() {
32964            self.write(" ()");
32965            return Ok(());
32966        }
32967
32968        if self.config.pretty {
32969            self.write(" (");
32970            self.write_newline();
32971            self.indent_level += 1;
32972            for (i, entry) in e.entries.iter().enumerate() {
32973                if i > 0 {
32974                    self.write(",");
32975                    self.write_newline();
32976                }
32977                self.write_indent();
32978                self.generate_identifier(&entry.key)?;
32979                self.write("=");
32980                self.generate_expression(&entry.value)?;
32981            }
32982            self.indent_level -= 1;
32983            self.write_newline();
32984            self.write(")");
32985        } else {
32986            self.write(" (");
32987            for (i, entry) in e.entries.iter().enumerate() {
32988                if i > 0 {
32989                    self.write(", ");
32990                }
32991                self.generate_identifier(&entry.key)?;
32992                self.write("=");
32993                self.generate_expression(&entry.value)?;
32994            }
32995            self.write(")");
32996        }
32997        Ok(())
32998    }
32999
33000    /// Generate BigQuery-style OPTIONS clause: OPTIONS (key=value, key=value, ...)
33001    fn generate_options_clause(&mut self, options: &[Expression]) -> Result<()> {
33002        self.write_keyword("OPTIONS");
33003        self.write(" (");
33004        for (i, opt) in options.iter().enumerate() {
33005            if i > 0 {
33006                self.write(", ");
33007            }
33008            self.generate_option_expression(opt)?;
33009        }
33010        self.write(")");
33011        Ok(())
33012    }
33013
33014    /// Generate Doris/StarRocks-style PROPERTIES clause: PROPERTIES ('key'='value', 'key'='value', ...)
33015    fn generate_properties_clause(&mut self, properties: &[Expression]) -> Result<()> {
33016        self.write_keyword("PROPERTIES");
33017        self.write(" (");
33018        for (i, prop) in properties.iter().enumerate() {
33019            if i > 0 {
33020                self.write(", ");
33021            }
33022            self.generate_option_expression(prop)?;
33023        }
33024        self.write(")");
33025        Ok(())
33026    }
33027
33028    /// Generate Databricks-style ENVIRONMENT clause: ENVIRONMENT (key = 'value', key = 'value', ...)
33029    fn generate_environment_clause(&mut self, environment: &[Expression]) -> Result<()> {
33030        self.write_keyword("ENVIRONMENT");
33031        self.write(" (");
33032        for (i, env_item) in environment.iter().enumerate() {
33033            if i > 0 {
33034                self.write(", ");
33035            }
33036            self.generate_environment_expression(env_item)?;
33037        }
33038        self.write(")");
33039        Ok(())
33040    }
33041
33042    /// Generate an environment expression with spaces around =
33043    fn generate_environment_expression(&mut self, expr: &Expression) -> Result<()> {
33044        match expr {
33045            Expression::Eq(eq) => {
33046                // Generate key = value with spaces (Databricks ENVIRONMENT style)
33047                self.generate_expression(&eq.left)?;
33048                self.write(" = ");
33049                self.generate_expression(&eq.right)?;
33050                Ok(())
33051            }
33052            _ => self.generate_expression(expr),
33053        }
33054    }
33055
33056    /// Generate Hive-style TBLPROPERTIES clause: TBLPROPERTIES ('key'='value', ...)
33057    fn generate_tblproperties_clause(&mut self, options: &[Expression]) -> Result<()> {
33058        self.write_keyword("TBLPROPERTIES");
33059        if self.config.pretty {
33060            self.write(" (");
33061            self.write_newline();
33062            self.indent_level += 1;
33063            for (i, opt) in options.iter().enumerate() {
33064                if i > 0 {
33065                    self.write(",");
33066                    self.write_newline();
33067                }
33068                self.write_indent();
33069                self.generate_option_expression(opt)?;
33070            }
33071            self.indent_level -= 1;
33072            self.write_newline();
33073            self.write(")");
33074        } else {
33075            self.write(" (");
33076            for (i, opt) in options.iter().enumerate() {
33077                if i > 0 {
33078                    self.write(", ");
33079                }
33080                self.generate_option_expression(opt)?;
33081            }
33082            self.write(")");
33083        }
33084        Ok(())
33085    }
33086
33087    /// Generate an option expression without spaces around =
33088    fn generate_option_expression(&mut self, expr: &Expression) -> Result<()> {
33089        match expr {
33090            Expression::Eq(eq) => {
33091                // Generate key=value without spaces
33092                self.generate_expression(&eq.left)?;
33093                self.write("=");
33094                self.generate_expression(&eq.right)?;
33095                Ok(())
33096            }
33097            _ => self.generate_expression(expr),
33098        }
33099    }
33100
33101    fn generate_pseudo_type(&mut self, e: &PseudoType) -> Result<()> {
33102        // Just output the name
33103        self.generate_expression(&e.this)?;
33104        Ok(())
33105    }
33106
33107    fn generate_put(&mut self, e: &PutStmt) -> Result<()> {
33108        // PUT source_file @stage [options]
33109        self.write_keyword("PUT");
33110        self.write_space();
33111
33112        // Source file path - preserve original quoting
33113        if e.source_quoted {
33114            self.write("'");
33115            self.write(&e.source);
33116            self.write("'");
33117        } else {
33118            self.write(&e.source);
33119        }
33120
33121        self.write_space();
33122
33123        // Target stage reference - output the string directly (includes @)
33124        if let Expression::Literal(lit) = &e.target {
33125            if let Literal::String(s) = lit.as_ref() {
33126                self.write(s);
33127            }
33128        } else {
33129            self.generate_expression(&e.target)?;
33130        }
33131
33132        // Optional parameters: KEY=VALUE
33133        for param in &e.params {
33134            self.write_space();
33135            self.write(&param.name);
33136            if let Some(ref value) = param.value {
33137                self.write("=");
33138                self.generate_expression(value)?;
33139            }
33140        }
33141
33142        Ok(())
33143    }
33144
33145    fn generate_quantile(&mut self, e: &Quantile) -> Result<()> {
33146        // QUANTILE(this, quantile)
33147        self.write_keyword("QUANTILE");
33148        self.write("(");
33149        self.generate_expression(&e.this)?;
33150        if let Some(quantile) = &e.quantile {
33151            self.write(", ");
33152            self.generate_expression(quantile)?;
33153        }
33154        self.write(")");
33155        Ok(())
33156    }
33157
33158    fn generate_query_band(&mut self, e: &QueryBand) -> Result<()> {
33159        // QUERY_BAND = this [UPDATE] [FOR scope]
33160        if matches!(
33161            self.config.dialect,
33162            Some(crate::dialects::DialectType::Teradata)
33163        ) {
33164            self.write_keyword("SET");
33165            self.write_space();
33166        }
33167        self.write_keyword("QUERY_BAND");
33168        self.write(" = ");
33169        self.generate_expression(&e.this)?;
33170        if e.update.is_some() {
33171            self.write_space();
33172            self.write_keyword("UPDATE");
33173        }
33174        if let Some(scope) = &e.scope {
33175            self.write_space();
33176            self.write_keyword("FOR");
33177            self.write_space();
33178            self.generate_expression(scope)?;
33179        }
33180        Ok(())
33181    }
33182
33183    fn generate_query_option(&mut self, e: &QueryOption) -> Result<()> {
33184        // this = expression
33185        self.generate_expression(&e.this)?;
33186        if let Some(expression) = &e.expression {
33187            self.write(" = ");
33188            self.generate_expression(expression)?;
33189        }
33190        Ok(())
33191    }
33192
33193    fn generate_query_transform(&mut self, e: &QueryTransform) -> Result<()> {
33194        // TRANSFORM (expressions) [row_format_before] [RECORDWRITER record_writer] USING command_script [AS schema] [row_format_after] [RECORDREADER record_reader]
33195        self.write_keyword("TRANSFORM");
33196        self.write("(");
33197        for (i, expr) in e.expressions.iter().enumerate() {
33198            if i > 0 {
33199                self.write(", ");
33200            }
33201            self.generate_expression(expr)?;
33202        }
33203        self.write(")");
33204        if let Some(row_format_before) = &e.row_format_before {
33205            self.write_space();
33206            self.generate_expression(row_format_before)?;
33207        }
33208        if let Some(record_writer) = &e.record_writer {
33209            self.write_space();
33210            self.write_keyword("RECORDWRITER");
33211            self.write_space();
33212            self.generate_expression(record_writer)?;
33213        }
33214        if let Some(command_script) = &e.command_script {
33215            self.write_space();
33216            self.write_keyword("USING");
33217            self.write_space();
33218            self.generate_expression(command_script)?;
33219        }
33220        if let Some(schema) = &e.schema {
33221            self.write_space();
33222            self.write_keyword("AS");
33223            self.write_space();
33224            self.generate_expression(schema)?;
33225        }
33226        if let Some(row_format_after) = &e.row_format_after {
33227            self.write_space();
33228            self.generate_expression(row_format_after)?;
33229        }
33230        if let Some(record_reader) = &e.record_reader {
33231            self.write_space();
33232            self.write_keyword("RECORDREADER");
33233            self.write_space();
33234            self.generate_expression(record_reader)?;
33235        }
33236        Ok(())
33237    }
33238
33239    fn generate_randn(&mut self, e: &Randn) -> Result<()> {
33240        // RANDN([seed])
33241        self.write_keyword("RANDN");
33242        self.write("(");
33243        if let Some(this) = &e.this {
33244            self.generate_expression(this)?;
33245        }
33246        self.write(")");
33247        Ok(())
33248    }
33249
33250    fn generate_randstr(&mut self, e: &Randstr) -> Result<()> {
33251        // RANDSTR(this, [generator])
33252        self.write_keyword("RANDSTR");
33253        self.write("(");
33254        self.generate_expression(&e.this)?;
33255        if let Some(generator) = &e.generator {
33256            self.write(", ");
33257            self.generate_expression(generator)?;
33258        }
33259        self.write(")");
33260        Ok(())
33261    }
33262
33263    fn generate_range_bucket(&mut self, e: &RangeBucket) -> Result<()> {
33264        // RANGE_BUCKET(this, expression)
33265        self.write_keyword("RANGE_BUCKET");
33266        self.write("(");
33267        self.generate_expression(&e.this)?;
33268        self.write(", ");
33269        self.generate_expression(&e.expression)?;
33270        self.write(")");
33271        Ok(())
33272    }
33273
33274    fn generate_range_n(&mut self, e: &RangeN) -> Result<()> {
33275        // RANGE_N(this BETWEEN expressions [EACH each])
33276        self.write_keyword("RANGE_N");
33277        self.write("(");
33278        self.generate_expression(&e.this)?;
33279        self.write_space();
33280        self.write_keyword("BETWEEN");
33281        self.write_space();
33282        for (i, expr) in e.expressions.iter().enumerate() {
33283            if i > 0 {
33284                self.write(", ");
33285            }
33286            self.generate_expression(expr)?;
33287        }
33288        if let Some(each) = &e.each {
33289            self.write_space();
33290            self.write_keyword("EACH");
33291            self.write_space();
33292            self.generate_expression(each)?;
33293        }
33294        self.write(")");
33295        Ok(())
33296    }
33297
33298    fn generate_read_csv(&mut self, e: &ReadCSV) -> Result<()> {
33299        // READ_CSV(this, expressions...)
33300        self.write_keyword("READ_CSV");
33301        self.write("(");
33302        self.generate_expression(&e.this)?;
33303        for expr in &e.expressions {
33304            self.write(", ");
33305            self.generate_expression(expr)?;
33306        }
33307        self.write(")");
33308        Ok(())
33309    }
33310
33311    fn generate_read_parquet(&mut self, e: &ReadParquet) -> Result<()> {
33312        // READ_PARQUET(expressions...)
33313        self.write_keyword("READ_PARQUET");
33314        self.write("(");
33315        for (i, expr) in e.expressions.iter().enumerate() {
33316            if i > 0 {
33317                self.write(", ");
33318            }
33319            self.generate_expression(expr)?;
33320        }
33321        self.write(")");
33322        Ok(())
33323    }
33324
33325    fn generate_recursive_with_search(&mut self, e: &RecursiveWithSearch) -> Result<()> {
33326        // SEARCH kind FIRST BY this SET expression [USING using]
33327        // or CYCLE this SET expression [USING using]
33328        if e.kind == "CYCLE" {
33329            self.write_keyword("CYCLE");
33330        } else {
33331            self.write_keyword("SEARCH");
33332            self.write_space();
33333            self.write(&e.kind);
33334            self.write_space();
33335            self.write_keyword("FIRST BY");
33336        }
33337        self.write_space();
33338        self.generate_expression(&e.this)?;
33339        self.write_space();
33340        self.write_keyword("SET");
33341        self.write_space();
33342        self.generate_expression(&e.expression)?;
33343        if let Some(using) = &e.using {
33344            self.write_space();
33345            self.write_keyword("USING");
33346            self.write_space();
33347            self.generate_expression(using)?;
33348        }
33349        Ok(())
33350    }
33351
33352    fn generate_reduce(&mut self, e: &Reduce) -> Result<()> {
33353        // REDUCE(this, initial, merge, [finish])
33354        self.write_keyword("REDUCE");
33355        self.write("(");
33356        self.generate_expression(&e.this)?;
33357        if let Some(initial) = &e.initial {
33358            self.write(", ");
33359            self.generate_expression(initial)?;
33360        }
33361        if let Some(merge) = &e.merge {
33362            self.write(", ");
33363            self.generate_expression(merge)?;
33364        }
33365        if let Some(finish) = &e.finish {
33366            self.write(", ");
33367            self.generate_expression(finish)?;
33368        }
33369        self.write(")");
33370        Ok(())
33371    }
33372
33373    fn generate_reference(&mut self, e: &Reference) -> Result<()> {
33374        // REFERENCES this (expressions) [options]
33375        self.write_keyword("REFERENCES");
33376        self.write_space();
33377        self.generate_expression(&e.this)?;
33378        if !e.expressions.is_empty() {
33379            self.write(" (");
33380            for (i, expr) in e.expressions.iter().enumerate() {
33381                if i > 0 {
33382                    self.write(", ");
33383                }
33384                self.generate_expression(expr)?;
33385            }
33386            self.write(")");
33387        }
33388        for opt in &e.options {
33389            self.write_space();
33390            self.generate_expression(opt)?;
33391        }
33392        Ok(())
33393    }
33394
33395    fn generate_refresh(&mut self, e: &Refresh) -> Result<()> {
33396        // REFRESH [kind] this
33397        self.write_keyword("REFRESH");
33398        if !e.kind.is_empty() {
33399            self.write_space();
33400            self.write_keyword(&e.kind);
33401        }
33402        self.write_space();
33403        self.generate_expression(&e.this)?;
33404        Ok(())
33405    }
33406
33407    fn generate_refresh_trigger_property(&mut self, e: &RefreshTriggerProperty) -> Result<()> {
33408        // Doris REFRESH clause: REFRESH method ON kind [EVERY n UNIT] [STARTS 'datetime']
33409        self.write_keyword("REFRESH");
33410        self.write_space();
33411        self.write_keyword(&e.method);
33412
33413        if let Some(ref kind) = e.kind {
33414            self.write_space();
33415            self.write_keyword("ON");
33416            self.write_space();
33417            self.write_keyword(kind);
33418
33419            // EVERY n UNIT
33420            if let Some(ref every) = e.every {
33421                self.write_space();
33422                self.write_keyword("EVERY");
33423                self.write_space();
33424                self.generate_expression(every)?;
33425                if let Some(ref unit) = e.unit {
33426                    self.write_space();
33427                    self.write_keyword(unit);
33428                }
33429            }
33430
33431            // STARTS 'datetime'
33432            if let Some(ref starts) = e.starts {
33433                self.write_space();
33434                self.write_keyword("STARTS");
33435                self.write_space();
33436                self.generate_expression(starts)?;
33437            }
33438        }
33439        Ok(())
33440    }
33441
33442    fn generate_regexp_count(&mut self, e: &RegexpCount) -> Result<()> {
33443        // REGEXP_COUNT(this, expression, position, parameters)
33444        self.write_keyword("REGEXP_COUNT");
33445        self.write("(");
33446        self.generate_expression(&e.this)?;
33447        self.write(", ");
33448        self.generate_expression(&e.expression)?;
33449        if let Some(position) = &e.position {
33450            self.write(", ");
33451            self.generate_expression(position)?;
33452        }
33453        if let Some(parameters) = &e.parameters {
33454            self.write(", ");
33455            self.generate_expression(parameters)?;
33456        }
33457        self.write(")");
33458        Ok(())
33459    }
33460
33461    fn generate_regexp_extract_all(&mut self, e: &RegexpExtractAll) -> Result<()> {
33462        // REGEXP_EXTRACT_ALL(this, expression, group, parameters, position, occurrence)
33463        self.write_keyword("REGEXP_EXTRACT_ALL");
33464        self.write("(");
33465        self.generate_expression(&e.this)?;
33466        self.write(", ");
33467        self.generate_expression(&e.expression)?;
33468        if let Some(group) = &e.group {
33469            self.write(", ");
33470            self.generate_expression(group)?;
33471        }
33472        self.write(")");
33473        Ok(())
33474    }
33475
33476    fn generate_regexp_full_match(&mut self, e: &RegexpFullMatch) -> Result<()> {
33477        // REGEXP_FULL_MATCH(this, expression)
33478        self.write_keyword("REGEXP_FULL_MATCH");
33479        self.write("(");
33480        self.generate_expression(&e.this)?;
33481        self.write(", ");
33482        self.generate_expression(&e.expression)?;
33483        self.write(")");
33484        Ok(())
33485    }
33486
33487    fn generate_regexp_i_like(&mut self, e: &RegexpILike) -> Result<()> {
33488        use crate::dialects::DialectType;
33489        // PostgreSQL/Redshift uses ~* operator for case-insensitive regex matching
33490        if matches!(
33491            self.config.dialect,
33492            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
33493        ) && e.flag.is_none()
33494        {
33495            self.generate_expression(&e.this)?;
33496            self.write(" ~* ");
33497            self.generate_expression(&e.expression)?;
33498        } else if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
33499            // Snowflake uses REGEXP_LIKE(x, pattern, 'i')
33500            self.write_keyword("REGEXP_LIKE");
33501            self.write("(");
33502            self.generate_expression(&e.this)?;
33503            self.write(", ");
33504            self.generate_expression(&e.expression)?;
33505            self.write(", ");
33506            if let Some(flag) = &e.flag {
33507                self.generate_expression(flag)?;
33508            } else {
33509                self.write("'i'");
33510            }
33511            self.write(")");
33512        } else {
33513            // this REGEXP_ILIKE expression or REGEXP_ILIKE(this, expression, flag)
33514            self.generate_expression(&e.this)?;
33515            self.write_space();
33516            self.write_keyword("REGEXP_ILIKE");
33517            self.write_space();
33518            self.generate_expression(&e.expression)?;
33519            if let Some(flag) = &e.flag {
33520                self.write(", ");
33521                self.generate_expression(flag)?;
33522            }
33523        }
33524        Ok(())
33525    }
33526
33527    fn generate_regexp_instr(&mut self, e: &RegexpInstr) -> Result<()> {
33528        // REGEXP_INSTR(this, expression, position, occurrence, option, parameters, group)
33529        self.write_keyword("REGEXP_INSTR");
33530        self.write("(");
33531        self.generate_expression(&e.this)?;
33532        self.write(", ");
33533        self.generate_expression(&e.expression)?;
33534        if let Some(position) = &e.position {
33535            self.write(", ");
33536            self.generate_expression(position)?;
33537        }
33538        if let Some(occurrence) = &e.occurrence {
33539            self.write(", ");
33540            self.generate_expression(occurrence)?;
33541        }
33542        if let Some(option) = &e.option {
33543            self.write(", ");
33544            self.generate_expression(option)?;
33545        }
33546        if let Some(parameters) = &e.parameters {
33547            self.write(", ");
33548            self.generate_expression(parameters)?;
33549        }
33550        if let Some(group) = &e.group {
33551            self.write(", ");
33552            self.generate_expression(group)?;
33553        }
33554        self.write(")");
33555        Ok(())
33556    }
33557
33558    fn generate_regexp_split(&mut self, e: &RegexpSplit) -> Result<()> {
33559        // REGEXP_SPLIT(this, expression, limit)
33560        self.write_keyword("REGEXP_SPLIT");
33561        self.write("(");
33562        self.generate_expression(&e.this)?;
33563        self.write(", ");
33564        self.generate_expression(&e.expression)?;
33565        if let Some(limit) = &e.limit {
33566            self.write(", ");
33567            self.generate_expression(limit)?;
33568        }
33569        self.write(")");
33570        Ok(())
33571    }
33572
33573    fn generate_regr_avgx(&mut self, e: &RegrAvgx) -> Result<()> {
33574        // REGR_AVGX(this, expression)
33575        self.write_keyword("REGR_AVGX");
33576        self.write("(");
33577        self.generate_expression(&e.this)?;
33578        self.write(", ");
33579        self.generate_expression(&e.expression)?;
33580        self.write(")");
33581        Ok(())
33582    }
33583
33584    fn generate_regr_avgy(&mut self, e: &RegrAvgy) -> Result<()> {
33585        // REGR_AVGY(this, expression)
33586        self.write_keyword("REGR_AVGY");
33587        self.write("(");
33588        self.generate_expression(&e.this)?;
33589        self.write(", ");
33590        self.generate_expression(&e.expression)?;
33591        self.write(")");
33592        Ok(())
33593    }
33594
33595    fn generate_regr_count(&mut self, e: &RegrCount) -> Result<()> {
33596        // REGR_COUNT(this, expression)
33597        self.write_keyword("REGR_COUNT");
33598        self.write("(");
33599        self.generate_expression(&e.this)?;
33600        self.write(", ");
33601        self.generate_expression(&e.expression)?;
33602        self.write(")");
33603        Ok(())
33604    }
33605
33606    fn generate_regr_intercept(&mut self, e: &RegrIntercept) -> Result<()> {
33607        // REGR_INTERCEPT(this, expression)
33608        self.write_keyword("REGR_INTERCEPT");
33609        self.write("(");
33610        self.generate_expression(&e.this)?;
33611        self.write(", ");
33612        self.generate_expression(&e.expression)?;
33613        self.write(")");
33614        Ok(())
33615    }
33616
33617    fn generate_regr_r2(&mut self, e: &RegrR2) -> Result<()> {
33618        // REGR_R2(this, expression)
33619        self.write_keyword("REGR_R2");
33620        self.write("(");
33621        self.generate_expression(&e.this)?;
33622        self.write(", ");
33623        self.generate_expression(&e.expression)?;
33624        self.write(")");
33625        Ok(())
33626    }
33627
33628    fn generate_regr_slope(&mut self, e: &RegrSlope) -> Result<()> {
33629        // REGR_SLOPE(this, expression)
33630        self.write_keyword("REGR_SLOPE");
33631        self.write("(");
33632        self.generate_expression(&e.this)?;
33633        self.write(", ");
33634        self.generate_expression(&e.expression)?;
33635        self.write(")");
33636        Ok(())
33637    }
33638
33639    fn generate_regr_sxx(&mut self, e: &RegrSxx) -> Result<()> {
33640        // REGR_SXX(this, expression)
33641        self.write_keyword("REGR_SXX");
33642        self.write("(");
33643        self.generate_expression(&e.this)?;
33644        self.write(", ");
33645        self.generate_expression(&e.expression)?;
33646        self.write(")");
33647        Ok(())
33648    }
33649
33650    fn generate_regr_sxy(&mut self, e: &RegrSxy) -> Result<()> {
33651        // REGR_SXY(this, expression)
33652        self.write_keyword("REGR_SXY");
33653        self.write("(");
33654        self.generate_expression(&e.this)?;
33655        self.write(", ");
33656        self.generate_expression(&e.expression)?;
33657        self.write(")");
33658        Ok(())
33659    }
33660
33661    fn generate_regr_syy(&mut self, e: &RegrSyy) -> Result<()> {
33662        // REGR_SYY(this, expression)
33663        self.write_keyword("REGR_SYY");
33664        self.write("(");
33665        self.generate_expression(&e.this)?;
33666        self.write(", ");
33667        self.generate_expression(&e.expression)?;
33668        self.write(")");
33669        Ok(())
33670    }
33671
33672    fn generate_regr_valx(&mut self, e: &RegrValx) -> Result<()> {
33673        // REGR_VALX(this, expression)
33674        self.write_keyword("REGR_VALX");
33675        self.write("(");
33676        self.generate_expression(&e.this)?;
33677        self.write(", ");
33678        self.generate_expression(&e.expression)?;
33679        self.write(")");
33680        Ok(())
33681    }
33682
33683    fn generate_regr_valy(&mut self, e: &RegrValy) -> Result<()> {
33684        // REGR_VALY(this, expression)
33685        self.write_keyword("REGR_VALY");
33686        self.write("(");
33687        self.generate_expression(&e.this)?;
33688        self.write(", ");
33689        self.generate_expression(&e.expression)?;
33690        self.write(")");
33691        Ok(())
33692    }
33693
33694    fn generate_remote_with_connection_model_property(
33695        &mut self,
33696        e: &RemoteWithConnectionModelProperty,
33697    ) -> Result<()> {
33698        // REMOTE WITH CONNECTION this
33699        self.write_keyword("REMOTE WITH CONNECTION");
33700        self.write_space();
33701        self.generate_expression(&e.this)?;
33702        Ok(())
33703    }
33704
33705    fn generate_rename_column(&mut self, e: &RenameColumn) -> Result<()> {
33706        // RENAME COLUMN [IF EXISTS] this TO new_name
33707        self.write_keyword("RENAME COLUMN");
33708        if e.exists {
33709            self.write_space();
33710            self.write_keyword("IF EXISTS");
33711        }
33712        self.write_space();
33713        self.generate_expression(&e.this)?;
33714        if let Some(to) = &e.to {
33715            self.write_space();
33716            self.write_keyword("TO");
33717            self.write_space();
33718            self.generate_expression(to)?;
33719        }
33720        Ok(())
33721    }
33722
33723    fn generate_replace_partition(&mut self, e: &ReplacePartition) -> Result<()> {
33724        // REPLACE PARTITION expression [FROM source]
33725        self.write_keyword("REPLACE PARTITION");
33726        self.write_space();
33727        self.generate_expression(&e.expression)?;
33728        if let Some(source) = &e.source {
33729            self.write_space();
33730            self.write_keyword("FROM");
33731            self.write_space();
33732            self.generate_expression(source)?;
33733        }
33734        Ok(())
33735    }
33736
33737    fn generate_returning(&mut self, e: &Returning) -> Result<()> {
33738        // RETURNING expressions [INTO into]
33739        // TSQL and Fabric use OUTPUT instead of RETURNING
33740        let keyword = match self.config.dialect {
33741            Some(DialectType::TSQL) | Some(DialectType::Fabric) => "OUTPUT",
33742            _ => "RETURNING",
33743        };
33744        self.write_keyword(keyword);
33745        self.write_space();
33746        for (i, expr) in e.expressions.iter().enumerate() {
33747            if i > 0 {
33748                self.write(", ");
33749            }
33750            self.generate_expression(expr)?;
33751        }
33752        if let Some(into) = &e.into {
33753            self.write_space();
33754            self.write_keyword("INTO");
33755            self.write_space();
33756            self.generate_expression(into)?;
33757        }
33758        Ok(())
33759    }
33760
33761    fn generate_output_clause(&mut self, output: &OutputClause) -> Result<()> {
33762        // OUTPUT expressions [INTO into_table]
33763        self.write_space();
33764        self.write_keyword("OUTPUT");
33765        self.write_space();
33766        for (i, expr) in output.columns.iter().enumerate() {
33767            if i > 0 {
33768                self.write(", ");
33769            }
33770            self.generate_expression(expr)?;
33771        }
33772        if let Some(into_table) = &output.into_table {
33773            self.write_space();
33774            self.write_keyword("INTO");
33775            self.write_space();
33776            self.generate_expression(into_table)?;
33777        }
33778        Ok(())
33779    }
33780
33781    fn generate_returns_property(&mut self, e: &ReturnsProperty) -> Result<()> {
33782        // RETURNS [TABLE] this [NULL ON NULL INPUT | CALLED ON NULL INPUT]
33783        self.write_keyword("RETURNS");
33784        if e.is_table.is_some() {
33785            self.write_space();
33786            self.write_keyword("TABLE");
33787        }
33788        if let Some(table) = &e.table {
33789            self.write_space();
33790            self.generate_expression(table)?;
33791        } else if let Some(this) = &e.this {
33792            self.write_space();
33793            self.generate_expression(this)?;
33794        }
33795        if e.null.is_some() {
33796            self.write_space();
33797            self.write_keyword("NULL ON NULL INPUT");
33798        }
33799        Ok(())
33800    }
33801
33802    fn generate_rollback(&mut self, e: &Rollback) -> Result<()> {
33803        // ROLLBACK [TRANSACTION [transaction_name]] [TO savepoint]
33804        self.write_keyword("ROLLBACK");
33805
33806        // TSQL always uses ROLLBACK TRANSACTION
33807        if e.this.is_none()
33808            && matches!(
33809                self.config.dialect,
33810                Some(DialectType::TSQL) | Some(DialectType::Fabric)
33811            )
33812        {
33813            self.write_space();
33814            self.write_keyword("TRANSACTION");
33815        }
33816
33817        // Check if this has TRANSACTION keyword or transaction name
33818        if let Some(this) = &e.this {
33819            // Check if it's just the "TRANSACTION" marker or an actual transaction name
33820            let is_transaction_marker = matches!(
33821                this.as_ref(),
33822                Expression::Identifier(id) if id.name == "TRANSACTION"
33823            );
33824
33825            self.write_space();
33826            self.write_keyword("TRANSACTION");
33827
33828            // If it's a real transaction name, output it
33829            if !is_transaction_marker {
33830                self.write_space();
33831                self.generate_expression(this)?;
33832            }
33833        }
33834
33835        // Output TO savepoint
33836        if let Some(savepoint) = &e.savepoint {
33837            self.write_space();
33838            self.write_keyword("TO");
33839            self.write_space();
33840            self.generate_expression(savepoint)?;
33841        }
33842        Ok(())
33843    }
33844
33845    fn generate_rollup(&mut self, e: &Rollup) -> Result<()> {
33846        // Python: return f"ROLLUP {self.wrap(expressions)}" if expressions else "WITH ROLLUP"
33847        if e.expressions.is_empty() {
33848            self.write_keyword("WITH ROLLUP");
33849        } else {
33850            self.write_keyword("ROLLUP");
33851            self.write("(");
33852            for (i, expr) in e.expressions.iter().enumerate() {
33853                if i > 0 {
33854                    self.write(", ");
33855                }
33856                self.generate_expression(expr)?;
33857            }
33858            self.write(")");
33859        }
33860        Ok(())
33861    }
33862
33863    fn generate_row_format_delimited_property(
33864        &mut self,
33865        e: &RowFormatDelimitedProperty,
33866    ) -> Result<()> {
33867        // ROW FORMAT DELIMITED [FIELDS TERMINATED BY ...] [ESCAPED BY ...] [COLLECTION ITEMS TERMINATED BY ...] [MAP KEYS TERMINATED BY ...] [LINES TERMINATED BY ...] [NULL DEFINED AS ...]
33868        self.write_keyword("ROW FORMAT DELIMITED");
33869        if let Some(fields) = &e.fields {
33870            self.write_space();
33871            self.write_keyword("FIELDS TERMINATED BY");
33872            self.write_space();
33873            self.generate_expression(fields)?;
33874        }
33875        if let Some(escaped) = &e.escaped {
33876            self.write_space();
33877            self.write_keyword("ESCAPED BY");
33878            self.write_space();
33879            self.generate_expression(escaped)?;
33880        }
33881        if let Some(items) = &e.collection_items {
33882            self.write_space();
33883            self.write_keyword("COLLECTION ITEMS TERMINATED BY");
33884            self.write_space();
33885            self.generate_expression(items)?;
33886        }
33887        if let Some(keys) = &e.map_keys {
33888            self.write_space();
33889            self.write_keyword("MAP KEYS TERMINATED BY");
33890            self.write_space();
33891            self.generate_expression(keys)?;
33892        }
33893        if let Some(lines) = &e.lines {
33894            self.write_space();
33895            self.write_keyword("LINES TERMINATED BY");
33896            self.write_space();
33897            self.generate_expression(lines)?;
33898        }
33899        if let Some(null) = &e.null {
33900            self.write_space();
33901            self.write_keyword("NULL DEFINED AS");
33902            self.write_space();
33903            self.generate_expression(null)?;
33904        }
33905        if let Some(serde) = &e.serde {
33906            self.write_space();
33907            self.generate_expression(serde)?;
33908        }
33909        Ok(())
33910    }
33911
33912    fn generate_row_format_property(&mut self, e: &RowFormatProperty) -> Result<()> {
33913        // ROW FORMAT this
33914        self.write_keyword("ROW FORMAT");
33915        self.write_space();
33916        self.generate_expression(&e.this)?;
33917        Ok(())
33918    }
33919
33920    fn generate_row_format_serde_property(&mut self, e: &RowFormatSerdeProperty) -> Result<()> {
33921        // ROW FORMAT SERDE this [WITH SERDEPROPERTIES (...)]
33922        self.write_keyword("ROW FORMAT SERDE");
33923        self.write_space();
33924        self.generate_expression(&e.this)?;
33925        if let Some(props) = &e.serde_properties {
33926            self.write_space();
33927            // SerdeProperties generates its own "[WITH] SERDEPROPERTIES (...)"
33928            self.generate_expression(props)?;
33929        }
33930        Ok(())
33931    }
33932
33933    fn generate_sha2(&mut self, e: &SHA2) -> Result<()> {
33934        // SHA2(this, length)
33935        self.write_keyword("SHA2");
33936        self.write("(");
33937        self.generate_expression(&e.this)?;
33938        if let Some(length) = e.length {
33939            self.write(", ");
33940            self.write(&length.to_string());
33941        }
33942        self.write(")");
33943        Ok(())
33944    }
33945
33946    fn generate_sha2_digest(&mut self, e: &SHA2Digest) -> Result<()> {
33947        // SHA2_DIGEST(this, length)
33948        self.write_keyword("SHA2_DIGEST");
33949        self.write("(");
33950        self.generate_expression(&e.this)?;
33951        if let Some(length) = e.length {
33952            self.write(", ");
33953            self.write(&length.to_string());
33954        }
33955        self.write(")");
33956        Ok(())
33957    }
33958
33959    fn generate_safe_add(&mut self, e: &SafeAdd) -> Result<()> {
33960        let name = if matches!(
33961            self.config.dialect,
33962            Some(crate::dialects::DialectType::Spark)
33963                | Some(crate::dialects::DialectType::Databricks)
33964        ) {
33965            "TRY_ADD"
33966        } else {
33967            "SAFE_ADD"
33968        };
33969        self.write_keyword(name);
33970        self.write("(");
33971        self.generate_expression(&e.this)?;
33972        self.write(", ");
33973        self.generate_expression(&e.expression)?;
33974        self.write(")");
33975        Ok(())
33976    }
33977
33978    fn generate_safe_divide(&mut self, e: &SafeDivide) -> Result<()> {
33979        // SAFE_DIVIDE(this, expression)
33980        self.write_keyword("SAFE_DIVIDE");
33981        self.write("(");
33982        self.generate_expression(&e.this)?;
33983        self.write(", ");
33984        self.generate_expression(&e.expression)?;
33985        self.write(")");
33986        Ok(())
33987    }
33988
33989    fn generate_safe_multiply(&mut self, e: &SafeMultiply) -> Result<()> {
33990        let name = if matches!(
33991            self.config.dialect,
33992            Some(crate::dialects::DialectType::Spark)
33993                | Some(crate::dialects::DialectType::Databricks)
33994        ) {
33995            "TRY_MULTIPLY"
33996        } else {
33997            "SAFE_MULTIPLY"
33998        };
33999        self.write_keyword(name);
34000        self.write("(");
34001        self.generate_expression(&e.this)?;
34002        self.write(", ");
34003        self.generate_expression(&e.expression)?;
34004        self.write(")");
34005        Ok(())
34006    }
34007
34008    fn generate_safe_subtract(&mut self, e: &SafeSubtract) -> Result<()> {
34009        let name = if matches!(
34010            self.config.dialect,
34011            Some(crate::dialects::DialectType::Spark)
34012                | Some(crate::dialects::DialectType::Databricks)
34013        ) {
34014            "TRY_SUBTRACT"
34015        } else {
34016            "SAFE_SUBTRACT"
34017        };
34018        self.write_keyword(name);
34019        self.write("(");
34020        self.generate_expression(&e.this)?;
34021        self.write(", ");
34022        self.generate_expression(&e.expression)?;
34023        self.write(")");
34024        Ok(())
34025    }
34026
34027    /// Generate the body of a USING SAMPLE or TABLESAMPLE clause:
34028    /// METHOD (size UNIT) [REPEATABLE (seed)]
34029    fn generate_sample_body(&mut self, sample: &Sample) -> Result<()> {
34030        // Handle BUCKET sampling: TABLESAMPLE (BUCKET n OUT OF m [ON col])
34031        if matches!(sample.method, SampleMethod::Bucket) {
34032            self.write(" (");
34033            self.write_keyword("BUCKET");
34034            self.write_space();
34035            if let Some(ref num) = sample.bucket_numerator {
34036                self.generate_expression(num)?;
34037            }
34038            self.write_space();
34039            self.write_keyword("OUT OF");
34040            self.write_space();
34041            if let Some(ref denom) = sample.bucket_denominator {
34042                self.generate_expression(denom)?;
34043            }
34044            if let Some(ref field) = sample.bucket_field {
34045                self.write_space();
34046                self.write_keyword("ON");
34047                self.write_space();
34048                self.generate_expression(field)?;
34049            }
34050            self.write(")");
34051            return Ok(());
34052        }
34053
34054        // Output method name if explicitly specified, or for dialects that always require it
34055        let is_snowflake = matches!(
34056            self.config.dialect,
34057            Some(crate::dialects::DialectType::Snowflake)
34058        );
34059        let is_postgres = matches!(
34060            self.config.dialect,
34061            Some(crate::dialects::DialectType::PostgreSQL)
34062                | Some(crate::dialects::DialectType::Redshift)
34063        );
34064        // Databricks and Spark don't output method names
34065        let is_databricks = matches!(
34066            self.config.dialect,
34067            Some(crate::dialects::DialectType::Databricks)
34068        );
34069        let is_spark = matches!(
34070            self.config.dialect,
34071            Some(crate::dialects::DialectType::Spark)
34072        );
34073        let suppress_method = is_databricks || is_spark || sample.suppress_method_output;
34074        // PostgreSQL always outputs BERNOULLI for BERNOULLI samples
34075        let force_method = is_postgres && matches!(sample.method, SampleMethod::Bernoulli);
34076        if !suppress_method && (sample.explicit_method || is_snowflake || force_method) {
34077            self.write_space();
34078            if !sample.explicit_method && (is_snowflake || force_method) {
34079                // Snowflake/PostgreSQL defaults to BERNOULLI when no method is specified
34080                self.write_keyword("BERNOULLI");
34081            } else {
34082                match sample.method {
34083                    SampleMethod::Bernoulli => self.write_keyword("BERNOULLI"),
34084                    SampleMethod::System => self.write_keyword("SYSTEM"),
34085                    SampleMethod::Block => self.write_keyword("BLOCK"),
34086                    SampleMethod::Row => self.write_keyword("ROW"),
34087                    SampleMethod::Reservoir => self.write_keyword("RESERVOIR"),
34088                    SampleMethod::Percent => self.write_keyword("SYSTEM"),
34089                    SampleMethod::Bucket => {} // handled above
34090                }
34091            }
34092        }
34093
34094        // Output size, with or without parentheses depending on dialect
34095        let emit_size_no_parens = !self.config.tablesample_requires_parens;
34096        if emit_size_no_parens {
34097            self.write_space();
34098            match &sample.size {
34099                Expression::Tuple(tuple) => {
34100                    for (i, expr) in tuple.expressions.iter().enumerate() {
34101                        if i > 0 {
34102                            self.write(", ");
34103                        }
34104                        self.generate_expression(expr)?;
34105                    }
34106                }
34107                expr => self.generate_expression(expr)?,
34108            }
34109        } else {
34110            self.write(" (");
34111            self.generate_expression(&sample.size)?;
34112        }
34113
34114        // Determine unit
34115        let is_rows_method = matches!(
34116            sample.method,
34117            SampleMethod::Reservoir | SampleMethod::Row | SampleMethod::Bucket
34118        );
34119        let is_percent = matches!(
34120            sample.method,
34121            SampleMethod::Percent
34122                | SampleMethod::System
34123                | SampleMethod::Bernoulli
34124                | SampleMethod::Block
34125        );
34126
34127        // For Snowflake, PostgreSQL, and Presto/Trino, only output ROWS/PERCENT when the user explicitly wrote it (unit_after_size).
34128        // These dialects use bare numbers for percentage by default in TABLESAMPLE METHOD(size) syntax.
34129        // For Databricks and Spark, always output PERCENT for percentage samples.
34130        let is_presto = matches!(
34131            self.config.dialect,
34132            Some(crate::dialects::DialectType::Presto)
34133                | Some(crate::dialects::DialectType::Trino)
34134                | Some(crate::dialects::DialectType::Athena)
34135        );
34136        let should_output_unit = if is_databricks || is_spark {
34137            // Always output PERCENT for percentage-based methods, or ROWS for row-based methods
34138            is_percent || is_rows_method || sample.unit_after_size
34139        } else if is_snowflake || is_postgres || is_presto {
34140            sample.unit_after_size
34141        } else {
34142            sample.unit_after_size || (sample.explicit_method && (is_rows_method || is_percent))
34143        };
34144
34145        if should_output_unit {
34146            self.write_space();
34147            if sample.is_percent {
34148                self.write_keyword("PERCENT");
34149            } else if is_rows_method && !sample.unit_after_size {
34150                self.write_keyword("ROWS");
34151            } else if sample.unit_after_size {
34152                match sample.method {
34153                    SampleMethod::Percent
34154                    | SampleMethod::System
34155                    | SampleMethod::Bernoulli
34156                    | SampleMethod::Block => {
34157                        self.write_keyword("PERCENT");
34158                    }
34159                    SampleMethod::Row | SampleMethod::Reservoir => {
34160                        self.write_keyword("ROWS");
34161                    }
34162                    _ => self.write_keyword("ROWS"),
34163                }
34164            } else {
34165                self.write_keyword("PERCENT");
34166            }
34167        }
34168
34169        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
34170            if let Some(ref offset) = sample.offset {
34171                self.write_space();
34172                self.write_keyword("OFFSET");
34173                self.write_space();
34174                self.generate_expression(offset)?;
34175            }
34176        }
34177        if !emit_size_no_parens {
34178            self.write(")");
34179        }
34180
34181        Ok(())
34182    }
34183
34184    fn generate_sample_property(&mut self, e: &SampleProperty) -> Result<()> {
34185        // SAMPLE this (ClickHouse uses SAMPLE BY)
34186        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
34187            self.write_keyword("SAMPLE BY");
34188        } else {
34189            self.write_keyword("SAMPLE");
34190        }
34191        self.write_space();
34192        self.generate_expression(&e.this)?;
34193        Ok(())
34194    }
34195
34196    fn generate_schema(&mut self, e: &Schema) -> Result<()> {
34197        // this (expressions...)
34198        if let Some(this) = &e.this {
34199            self.generate_expression(this)?;
34200        }
34201        if !e.expressions.is_empty() {
34202            // Add space before column list if there's a preceding expression
34203            if e.this.is_some() {
34204                self.write_space();
34205            }
34206            self.write("(");
34207            for (i, expr) in e.expressions.iter().enumerate() {
34208                if i > 0 {
34209                    self.write(", ");
34210                }
34211                self.generate_expression(expr)?;
34212            }
34213            self.write(")");
34214        }
34215        Ok(())
34216    }
34217
34218    fn generate_schema_comment_property(&mut self, e: &SchemaCommentProperty) -> Result<()> {
34219        // COMMENT this
34220        self.write_keyword("COMMENT");
34221        self.write_space();
34222        self.generate_expression(&e.this)?;
34223        Ok(())
34224    }
34225
34226    fn generate_scope_resolution(&mut self, e: &ScopeResolution) -> Result<()> {
34227        // [this::]expression
34228        if let Some(this) = &e.this {
34229            self.generate_expression(this)?;
34230            self.write("::");
34231        }
34232        self.generate_expression(&e.expression)?;
34233        Ok(())
34234    }
34235
34236    fn generate_search(&mut self, e: &Search) -> Result<()> {
34237        // SEARCH(this, expression, [json_scope], [analyzer], [analyzer_options], [search_mode])
34238        self.write_keyword("SEARCH");
34239        self.write("(");
34240        self.generate_expression(&e.this)?;
34241        self.write(", ");
34242        self.generate_expression(&e.expression)?;
34243        if let Some(json_scope) = &e.json_scope {
34244            self.write(", ");
34245            self.generate_expression(json_scope)?;
34246        }
34247        if let Some(analyzer) = &e.analyzer {
34248            self.write(", ");
34249            self.generate_expression(analyzer)?;
34250        }
34251        if let Some(analyzer_options) = &e.analyzer_options {
34252            self.write(", ");
34253            self.generate_expression(analyzer_options)?;
34254        }
34255        if let Some(search_mode) = &e.search_mode {
34256            self.write(", ");
34257            self.generate_expression(search_mode)?;
34258        }
34259        self.write(")");
34260        Ok(())
34261    }
34262
34263    fn generate_search_ip(&mut self, e: &SearchIp) -> Result<()> {
34264        // SEARCH_IP(this, expression)
34265        self.write_keyword("SEARCH_IP");
34266        self.write("(");
34267        self.generate_expression(&e.this)?;
34268        self.write(", ");
34269        self.generate_expression(&e.expression)?;
34270        self.write(")");
34271        Ok(())
34272    }
34273
34274    fn generate_security_property(&mut self, e: &SecurityProperty) -> Result<()> {
34275        // SECURITY this
34276        self.write_keyword("SECURITY");
34277        self.write_space();
34278        self.generate_expression(&e.this)?;
34279        Ok(())
34280    }
34281
34282    fn generate_semantic_view(&mut self, e: &SemanticView) -> Result<()> {
34283        // SEMANTIC_VIEW(this [METRICS ...] [DIMENSIONS ...] [FACTS ...] [WHERE ...])
34284        self.write("SEMANTIC_VIEW(");
34285
34286        if self.config.pretty {
34287            // Pretty print: each clause on its own line
34288            self.write_newline();
34289            self.indent_level += 1;
34290            self.write_indent();
34291            self.generate_expression(&e.this)?;
34292
34293            if let Some(metrics) = &e.metrics {
34294                self.write_newline();
34295                self.write_indent();
34296                self.write_keyword("METRICS");
34297                self.write_space();
34298                self.generate_semantic_view_tuple(metrics)?;
34299            }
34300            if let Some(dimensions) = &e.dimensions {
34301                self.write_newline();
34302                self.write_indent();
34303                self.write_keyword("DIMENSIONS");
34304                self.write_space();
34305                self.generate_semantic_view_tuple(dimensions)?;
34306            }
34307            if let Some(facts) = &e.facts {
34308                self.write_newline();
34309                self.write_indent();
34310                self.write_keyword("FACTS");
34311                self.write_space();
34312                self.generate_semantic_view_tuple(facts)?;
34313            }
34314            if let Some(where_) = &e.where_ {
34315                self.write_newline();
34316                self.write_indent();
34317                self.write_keyword("WHERE");
34318                self.write_space();
34319                self.generate_expression(where_)?;
34320            }
34321            self.write_newline();
34322            self.indent_level -= 1;
34323            self.write_indent();
34324        } else {
34325            // Compact: all on one line
34326            self.generate_expression(&e.this)?;
34327            if let Some(metrics) = &e.metrics {
34328                self.write_space();
34329                self.write_keyword("METRICS");
34330                self.write_space();
34331                self.generate_semantic_view_tuple(metrics)?;
34332            }
34333            if let Some(dimensions) = &e.dimensions {
34334                self.write_space();
34335                self.write_keyword("DIMENSIONS");
34336                self.write_space();
34337                self.generate_semantic_view_tuple(dimensions)?;
34338            }
34339            if let Some(facts) = &e.facts {
34340                self.write_space();
34341                self.write_keyword("FACTS");
34342                self.write_space();
34343                self.generate_semantic_view_tuple(facts)?;
34344            }
34345            if let Some(where_) = &e.where_ {
34346                self.write_space();
34347                self.write_keyword("WHERE");
34348                self.write_space();
34349                self.generate_expression(where_)?;
34350            }
34351        }
34352        self.write(")");
34353        Ok(())
34354    }
34355
34356    /// Helper for SEMANTIC_VIEW tuple contents (without parentheses)
34357    fn generate_semantic_view_tuple(&mut self, expr: &Expression) -> Result<()> {
34358        if let Expression::Tuple(t) = expr {
34359            for (i, e) in t.expressions.iter().enumerate() {
34360                if i > 0 {
34361                    self.write(", ");
34362                }
34363                self.generate_expression(e)?;
34364            }
34365        } else {
34366            self.generate_expression(expr)?;
34367        }
34368        Ok(())
34369    }
34370
34371    fn generate_sequence_properties(&mut self, e: &SequenceProperties) -> Result<()> {
34372        // [START WITH start] [INCREMENT BY increment] [MINVALUE minvalue] [MAXVALUE maxvalue] [CACHE cache] [OWNED BY owned]
34373        if let Some(start) = &e.start {
34374            self.write_keyword("START WITH");
34375            self.write_space();
34376            self.generate_expression(start)?;
34377        }
34378        if let Some(increment) = &e.increment {
34379            self.write_space();
34380            self.write_keyword("INCREMENT BY");
34381            self.write_space();
34382            self.generate_expression(increment)?;
34383        }
34384        if let Some(minvalue) = &e.minvalue {
34385            self.write_space();
34386            self.write_keyword("MINVALUE");
34387            self.write_space();
34388            self.generate_expression(minvalue)?;
34389        }
34390        if let Some(maxvalue) = &e.maxvalue {
34391            self.write_space();
34392            self.write_keyword("MAXVALUE");
34393            self.write_space();
34394            self.generate_expression(maxvalue)?;
34395        }
34396        if let Some(cache) = &e.cache {
34397            self.write_space();
34398            self.write_keyword("CACHE");
34399            self.write_space();
34400            self.generate_expression(cache)?;
34401        }
34402        if let Some(owned) = &e.owned {
34403            self.write_space();
34404            self.write_keyword("OWNED BY");
34405            self.write_space();
34406            self.generate_expression(owned)?;
34407        }
34408        for opt in &e.options {
34409            self.write_space();
34410            self.generate_expression(opt)?;
34411        }
34412        Ok(())
34413    }
34414
34415    fn generate_serde_properties(&mut self, e: &SerdeProperties) -> Result<()> {
34416        // [WITH] SERDEPROPERTIES (expressions)
34417        if e.with_.is_some() {
34418            self.write_keyword("WITH");
34419            self.write_space();
34420        }
34421        self.write_keyword("SERDEPROPERTIES");
34422        self.write(" (");
34423        for (i, expr) in e.expressions.iter().enumerate() {
34424            if i > 0 {
34425                self.write(", ");
34426            }
34427            // Generate key=value without spaces around =
34428            match expr {
34429                Expression::Eq(eq) => {
34430                    self.generate_expression(&eq.left)?;
34431                    self.write("=");
34432                    self.generate_expression(&eq.right)?;
34433                }
34434                _ => self.generate_expression(expr)?,
34435            }
34436        }
34437        self.write(")");
34438        Ok(())
34439    }
34440
34441    fn generate_session_parameter(&mut self, e: &SessionParameter) -> Result<()> {
34442        // @@[kind.]this
34443        self.write("@@");
34444        if let Some(kind) = &e.kind {
34445            self.write(kind);
34446            self.write(".");
34447        }
34448        self.generate_expression(&e.this)?;
34449        Ok(())
34450    }
34451
34452    fn generate_set(&mut self, e: &Set) -> Result<()> {
34453        // SET/UNSET [TAG] expressions
34454        if e.unset.is_some() {
34455            self.write_keyword("UNSET");
34456        } else {
34457            self.write_keyword("SET");
34458        }
34459        if e.tag.is_some() {
34460            self.write_space();
34461            self.write_keyword("TAG");
34462        }
34463        if !e.expressions.is_empty() {
34464            self.write_space();
34465            for (i, expr) in e.expressions.iter().enumerate() {
34466                if i > 0 {
34467                    self.write(", ");
34468                }
34469                self.generate_expression(expr)?;
34470            }
34471        }
34472        Ok(())
34473    }
34474
34475    fn generate_set_config_property(&mut self, e: &SetConfigProperty) -> Result<()> {
34476        // SET this or SETCONFIG this
34477        self.write_keyword("SET");
34478        self.write_space();
34479        self.generate_expression(&e.this)?;
34480        Ok(())
34481    }
34482
34483    fn generate_set_item(&mut self, e: &SetItem) -> Result<()> {
34484        // [kind] name = value
34485        if let Some(kind) = &e.kind {
34486            self.write_keyword(kind);
34487            self.write_space();
34488        }
34489        self.generate_expression(&e.name)?;
34490        self.write(" = ");
34491        self.generate_expression(&e.value)?;
34492        Ok(())
34493    }
34494
34495    fn generate_set_operation(&mut self, e: &SetOperation) -> Result<()> {
34496        // [WITH ...] this UNION|INTERSECT|EXCEPT [ALL|DISTINCT] [BY NAME] expression
34497        if let Some(with_) = &e.with_ {
34498            self.generate_expression(with_)?;
34499            self.write_space();
34500        }
34501        self.generate_expression(&e.this)?;
34502        self.write_space();
34503        // kind should be UNION, INTERSECT, EXCEPT, etc.
34504        if let Some(kind) = &e.kind {
34505            self.write_keyword(kind);
34506        }
34507        if e.distinct {
34508            self.write_space();
34509            self.write_keyword("DISTINCT");
34510        } else {
34511            self.write_space();
34512            self.write_keyword("ALL");
34513        }
34514        if e.by_name.is_some() {
34515            self.write_space();
34516            self.write_keyword("BY NAME");
34517        }
34518        self.write_space();
34519        self.generate_expression(&e.expression)?;
34520        Ok(())
34521    }
34522
34523    fn generate_set_property(&mut self, e: &SetProperty) -> Result<()> {
34524        // SET or MULTISET
34525        if e.multi.is_some() {
34526            self.write_keyword("MULTISET");
34527        } else {
34528            self.write_keyword("SET");
34529        }
34530        Ok(())
34531    }
34532
34533    fn generate_settings_property(&mut self, e: &SettingsProperty) -> Result<()> {
34534        // SETTINGS expressions
34535        self.write_keyword("SETTINGS");
34536        if self.config.pretty && e.expressions.len() > 1 {
34537            // Pretty print: each setting on its own line, indented
34538            self.indent_level += 1;
34539            for (i, expr) in e.expressions.iter().enumerate() {
34540                if i > 0 {
34541                    self.write(",");
34542                }
34543                self.write_newline();
34544                self.write_indent();
34545                self.generate_expression(expr)?;
34546            }
34547            self.indent_level -= 1;
34548        } else {
34549            self.write_space();
34550            for (i, expr) in e.expressions.iter().enumerate() {
34551                if i > 0 {
34552                    self.write(", ");
34553                }
34554                self.generate_expression(expr)?;
34555            }
34556        }
34557        Ok(())
34558    }
34559
34560    fn generate_sharing_property(&mut self, e: &SharingProperty) -> Result<()> {
34561        // SHARING = this
34562        self.write_keyword("SHARING");
34563        if let Some(this) = &e.this {
34564            self.write(" = ");
34565            self.generate_expression(this)?;
34566        }
34567        Ok(())
34568    }
34569
34570    fn generate_slice(&mut self, e: &Slice) -> Result<()> {
34571        // Python array slicing: begin:end:step
34572        if let Some(begin) = &e.this {
34573            self.generate_expression(begin)?;
34574        }
34575        self.write(":");
34576        if let Some(end) = &e.expression {
34577            self.generate_expression(end)?;
34578        }
34579        if let Some(step) = &e.step {
34580            self.write(":");
34581            self.generate_expression(step)?;
34582        }
34583        Ok(())
34584    }
34585
34586    fn generate_sort_array(&mut self, e: &SortArray) -> Result<()> {
34587        // SORT_ARRAY(this, asc)
34588        self.write_keyword("SORT_ARRAY");
34589        self.write("(");
34590        self.generate_expression(&e.this)?;
34591        if let Some(asc) = &e.asc {
34592            self.write(", ");
34593            self.generate_expression(asc)?;
34594        }
34595        self.write(")");
34596        Ok(())
34597    }
34598
34599    fn generate_sort_by(&mut self, e: &SortBy) -> Result<()> {
34600        // SORT BY expressions
34601        self.write_keyword("SORT BY");
34602        self.write_space();
34603        for (i, expr) in e.expressions.iter().enumerate() {
34604            if i > 0 {
34605                self.write(", ");
34606            }
34607            self.generate_ordered(expr)?;
34608        }
34609        Ok(())
34610    }
34611
34612    fn generate_sort_key_property(&mut self, e: &SortKeyProperty) -> Result<()> {
34613        // [COMPOUND] SORTKEY(col1, col2, ...) - no space before paren
34614        if e.compound.is_some() {
34615            self.write_keyword("COMPOUND");
34616            self.write_space();
34617        }
34618        self.write_keyword("SORTKEY");
34619        self.write("(");
34620        // If this is a Tuple, unwrap its contents to avoid double parentheses
34621        if let Expression::Tuple(t) = e.this.as_ref() {
34622            for (i, expr) in t.expressions.iter().enumerate() {
34623                if i > 0 {
34624                    self.write(", ");
34625                }
34626                self.generate_expression(expr)?;
34627            }
34628        } else {
34629            self.generate_expression(&e.this)?;
34630        }
34631        self.write(")");
34632        Ok(())
34633    }
34634
34635    fn generate_split_part(&mut self, e: &SplitPart) -> Result<()> {
34636        // SPLIT_PART(this, delimiter, part_index)
34637        self.write_keyword("SPLIT_PART");
34638        self.write("(");
34639        self.generate_expression(&e.this)?;
34640        if let Some(delimiter) = &e.delimiter {
34641            self.write(", ");
34642            self.generate_expression(delimiter)?;
34643        }
34644        if let Some(part_index) = &e.part_index {
34645            self.write(", ");
34646            self.generate_expression(part_index)?;
34647        }
34648        self.write(")");
34649        Ok(())
34650    }
34651
34652    fn generate_sql_read_write_property(&mut self, e: &SqlReadWriteProperty) -> Result<()> {
34653        // READS SQL DATA or MODIFIES SQL DATA, etc.
34654        self.generate_expression(&e.this)?;
34655        Ok(())
34656    }
34657
34658    fn generate_sql_security_property(&mut self, e: &SqlSecurityProperty) -> Result<()> {
34659        // SQL SECURITY DEFINER or SQL SECURITY INVOKER
34660        self.write_keyword("SQL SECURITY");
34661        self.write_space();
34662        self.generate_expression(&e.this)?;
34663        Ok(())
34664    }
34665
34666    fn generate_st_distance(&mut self, e: &StDistance) -> Result<()> {
34667        // ST_DISTANCE(this, expression, [use_spheroid])
34668        self.write_keyword("ST_DISTANCE");
34669        self.write("(");
34670        self.generate_expression(&e.this)?;
34671        self.write(", ");
34672        self.generate_expression(&e.expression)?;
34673        if let Some(use_spheroid) = &e.use_spheroid {
34674            self.write(", ");
34675            self.generate_expression(use_spheroid)?;
34676        }
34677        self.write(")");
34678        Ok(())
34679    }
34680
34681    fn generate_st_point(&mut self, e: &StPoint) -> Result<()> {
34682        // ST_POINT(this, expression)
34683        self.write_keyword("ST_POINT");
34684        self.write("(");
34685        self.generate_expression(&e.this)?;
34686        self.write(", ");
34687        self.generate_expression(&e.expression)?;
34688        self.write(")");
34689        Ok(())
34690    }
34691
34692    fn generate_stability_property(&mut self, e: &StabilityProperty) -> Result<()> {
34693        // IMMUTABLE, STABLE, VOLATILE
34694        self.generate_expression(&e.this)?;
34695        Ok(())
34696    }
34697
34698    fn generate_standard_hash(&mut self, e: &StandardHash) -> Result<()> {
34699        // STANDARD_HASH(this, [expression])
34700        self.write_keyword("STANDARD_HASH");
34701        self.write("(");
34702        self.generate_expression(&e.this)?;
34703        if let Some(expression) = &e.expression {
34704            self.write(", ");
34705            self.generate_expression(expression)?;
34706        }
34707        self.write(")");
34708        Ok(())
34709    }
34710
34711    fn generate_storage_handler_property(&mut self, e: &StorageHandlerProperty) -> Result<()> {
34712        // STORED BY this
34713        self.write_keyword("STORED BY");
34714        self.write_space();
34715        self.generate_expression(&e.this)?;
34716        Ok(())
34717    }
34718
34719    fn generate_str_position(&mut self, e: &StrPosition) -> Result<()> {
34720        // STRPOS(this, substr) or STRPOS(this, substr, position)
34721        // Different dialects have different function names
34722        use crate::dialects::DialectType;
34723        if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
34724            // Snowflake: CHARINDEX(substr, str[, position])
34725            self.write_keyword("CHARINDEX");
34726            self.write("(");
34727            if let Some(substr) = &e.substr {
34728                self.generate_expression(substr)?;
34729                self.write(", ");
34730            }
34731            self.generate_expression(&e.this)?;
34732            if let Some(position) = &e.position {
34733                self.write(", ");
34734                self.generate_expression(position)?;
34735            }
34736            self.write(")");
34737        } else if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
34738            self.write_keyword("POSITION");
34739            self.write("(");
34740            self.generate_expression(&e.this)?;
34741            if let Some(substr) = &e.substr {
34742                self.write(", ");
34743                self.generate_expression(substr)?;
34744            }
34745            if let Some(position) = &e.position {
34746                self.write(", ");
34747                self.generate_expression(position)?;
34748            }
34749            if let Some(occurrence) = &e.occurrence {
34750                self.write(", ");
34751                self.generate_expression(occurrence)?;
34752            }
34753            self.write(")");
34754        } else if matches!(
34755            self.config.dialect,
34756            Some(DialectType::SQLite)
34757                | Some(DialectType::Oracle)
34758                | Some(DialectType::BigQuery)
34759                | Some(DialectType::Teradata)
34760        ) {
34761            self.write_keyword("INSTR");
34762            self.write("(");
34763            self.generate_expression(&e.this)?;
34764            if let Some(substr) = &e.substr {
34765                self.write(", ");
34766                self.generate_expression(substr)?;
34767            }
34768            if let Some(position) = &e.position {
34769                self.write(", ");
34770                self.generate_expression(position)?;
34771            } else if e.occurrence.is_some() {
34772                // INSTR requires a position arg before occurrence: INSTR(str, substr, start, nth)
34773                // Default start position is 1
34774                self.write(", 1");
34775            }
34776            if let Some(occurrence) = &e.occurrence {
34777                self.write(", ");
34778                self.generate_expression(occurrence)?;
34779            }
34780            self.write(")");
34781        } else if matches!(
34782            self.config.dialect,
34783            Some(DialectType::MySQL)
34784                | Some(DialectType::SingleStore)
34785                | Some(DialectType::Doris)
34786                | Some(DialectType::StarRocks)
34787                | Some(DialectType::Hive)
34788                | Some(DialectType::Spark)
34789                | Some(DialectType::Databricks)
34790        ) {
34791            // LOCATE(substr, str[, position]) - substr first
34792            self.write_keyword("LOCATE");
34793            self.write("(");
34794            if let Some(substr) = &e.substr {
34795                self.generate_expression(substr)?;
34796                self.write(", ");
34797            }
34798            self.generate_expression(&e.this)?;
34799            if let Some(position) = &e.position {
34800                self.write(", ");
34801                self.generate_expression(position)?;
34802            }
34803            self.write(")");
34804        } else if matches!(self.config.dialect, Some(DialectType::TSQL)) {
34805            // CHARINDEX(substr, str[, position])
34806            self.write_keyword("CHARINDEX");
34807            self.write("(");
34808            if let Some(substr) = &e.substr {
34809                self.generate_expression(substr)?;
34810                self.write(", ");
34811            }
34812            self.generate_expression(&e.this)?;
34813            if let Some(position) = &e.position {
34814                self.write(", ");
34815                self.generate_expression(position)?;
34816            }
34817            self.write(")");
34818        } else if matches!(
34819            self.config.dialect,
34820            Some(DialectType::PostgreSQL)
34821                | Some(DialectType::Materialize)
34822                | Some(DialectType::RisingWave)
34823                | Some(DialectType::Redshift)
34824        ) {
34825            // POSITION(substr IN str) syntax
34826            self.write_keyword("POSITION");
34827            self.write("(");
34828            if let Some(substr) = &e.substr {
34829                self.generate_expression(substr)?;
34830                self.write(" IN ");
34831            }
34832            self.generate_expression(&e.this)?;
34833            self.write(")");
34834        } else {
34835            self.write_keyword("STRPOS");
34836            self.write("(");
34837            self.generate_expression(&e.this)?;
34838            if let Some(substr) = &e.substr {
34839                self.write(", ");
34840                self.generate_expression(substr)?;
34841            }
34842            if let Some(position) = &e.position {
34843                self.write(", ");
34844                self.generate_expression(position)?;
34845            }
34846            if let Some(occurrence) = &e.occurrence {
34847                self.write(", ");
34848                self.generate_expression(occurrence)?;
34849            }
34850            self.write(")");
34851        }
34852        Ok(())
34853    }
34854
34855    fn generate_str_to_date(&mut self, e: &StrToDate) -> Result<()> {
34856        match self.config.dialect {
34857            Some(DialectType::Spark) | Some(DialectType::Databricks) | Some(DialectType::Hive) => {
34858                // TO_DATE(this, java_format)
34859                self.write_keyword("TO_DATE");
34860                self.write("(");
34861                self.generate_expression(&e.this)?;
34862                if let Some(format) = &e.format {
34863                    self.write(", '");
34864                    self.write(&Self::strftime_to_java_format(format));
34865                    self.write("'");
34866                }
34867                self.write(")");
34868            }
34869            Some(DialectType::DuckDB) => {
34870                // CAST(STRPTIME(this, format) AS DATE)
34871                self.write_keyword("CAST");
34872                self.write("(");
34873                self.write_keyword("STRPTIME");
34874                self.write("(");
34875                self.generate_expression(&e.this)?;
34876                if let Some(format) = &e.format {
34877                    self.write(", '");
34878                    self.write(format);
34879                    self.write("'");
34880                }
34881                self.write(")");
34882                self.write_keyword(" AS ");
34883                self.write_keyword("DATE");
34884                self.write(")");
34885            }
34886            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift) => {
34887                // TO_DATE(this, pg_format)
34888                self.write_keyword("TO_DATE");
34889                self.write("(");
34890                self.generate_expression(&e.this)?;
34891                if let Some(format) = &e.format {
34892                    self.write(", '");
34893                    self.write(&Self::strftime_to_postgres_format(format));
34894                    self.write("'");
34895                }
34896                self.write(")");
34897            }
34898            Some(DialectType::BigQuery) => {
34899                // PARSE_DATE(format, this) - note: format comes first for BigQuery
34900                self.write_keyword("PARSE_DATE");
34901                self.write("(");
34902                if let Some(format) = &e.format {
34903                    self.write("'");
34904                    self.write(format);
34905                    self.write("'");
34906                    self.write(", ");
34907                }
34908                self.generate_expression(&e.this)?;
34909                self.write(")");
34910            }
34911            Some(DialectType::Teradata) => {
34912                // CAST(this AS DATE FORMAT 'teradata_fmt')
34913                self.write_keyword("CAST");
34914                self.write("(");
34915                self.generate_expression(&e.this)?;
34916                self.write_keyword(" AS ");
34917                self.write_keyword("DATE");
34918                if let Some(format) = &e.format {
34919                    self.write_keyword(" FORMAT ");
34920                    self.write("'");
34921                    self.write(&Self::strftime_to_teradata_format(format));
34922                    self.write("'");
34923                }
34924                self.write(")");
34925            }
34926            _ => {
34927                // STR_TO_DATE(this, format) - MySQL default
34928                self.write_keyword("STR_TO_DATE");
34929                self.write("(");
34930                self.generate_expression(&e.this)?;
34931                if let Some(format) = &e.format {
34932                    self.write(", '");
34933                    self.write(format);
34934                    self.write("'");
34935                }
34936                self.write(")");
34937            }
34938        }
34939        Ok(())
34940    }
34941
34942    /// Convert strftime format to Teradata date format (YYYY, DD, MM, etc.)
34943    fn strftime_to_teradata_format(fmt: &str) -> String {
34944        let mut result = String::with_capacity(fmt.len() * 2);
34945        let bytes = fmt.as_bytes();
34946        let len = bytes.len();
34947        let mut i = 0;
34948        while i < len {
34949            if bytes[i] == b'%' && i + 1 < len {
34950                let replacement = match bytes[i + 1] {
34951                    b'Y' => "YYYY",
34952                    b'y' => "YY",
34953                    b'm' => "MM",
34954                    b'B' => "MMMM",
34955                    b'b' => "MMM",
34956                    b'd' => "DD",
34957                    b'j' => "DDD",
34958                    b'H' => "HH",
34959                    b'M' => "MI",
34960                    b'S' => "SS",
34961                    b'f' => "SSSSSS",
34962                    b'A' => "EEEE",
34963                    b'a' => "EEE",
34964                    _ => {
34965                        result.push('%');
34966                        i += 1;
34967                        continue;
34968                    }
34969                };
34970                result.push_str(replacement);
34971                i += 2;
34972            } else {
34973                result.push(bytes[i] as char);
34974                i += 1;
34975            }
34976        }
34977        result
34978    }
34979
34980    /// Convert strftime format (%Y, %m, %d, etc.) to Java date format (yyyy, MM, dd, etc.)
34981    /// Public static version for use by other modules
34982    pub fn strftime_to_java_format_static(fmt: &str) -> String {
34983        Self::strftime_to_java_format(fmt)
34984    }
34985
34986    /// Convert strftime format (%Y, %m, %d, etc.) to Java date format (yyyy, MM, dd, etc.)
34987    fn strftime_to_java_format(fmt: &str) -> String {
34988        let mut result = String::with_capacity(fmt.len() * 2);
34989        let bytes = fmt.as_bytes();
34990        let len = bytes.len();
34991        let mut i = 0;
34992        while i < len {
34993            if bytes[i] == b'%' && i + 1 < len {
34994                // Check for non-padded variants (%-X)
34995                if bytes[i + 1] == b'-' && i + 2 < len {
34996                    let replacement = match bytes[i + 2] {
34997                        b'd' => "d",
34998                        b'm' => "M",
34999                        b'H' => "H",
35000                        b'M' => "m",
35001                        b'S' => "s",
35002                        _ => {
35003                            result.push('%');
35004                            i += 1;
35005                            continue;
35006                        }
35007                    };
35008                    result.push_str(replacement);
35009                    i += 3;
35010                } else {
35011                    let replacement = match bytes[i + 1] {
35012                        b'Y' => "yyyy",
35013                        b'y' => "yy",
35014                        b'm' => "MM",
35015                        b'B' => "MMMM",
35016                        b'b' => "MMM",
35017                        b'd' => "dd",
35018                        b'j' => "DDD",
35019                        b'H' => "HH",
35020                        b'M' => "mm",
35021                        b'S' => "ss",
35022                        b'f' => "SSSSSS",
35023                        b'A' => "EEEE",
35024                        b'a' => "EEE",
35025                        _ => {
35026                            result.push('%');
35027                            i += 1;
35028                            continue;
35029                        }
35030                    };
35031                    result.push_str(replacement);
35032                    i += 2;
35033                }
35034            } else {
35035                result.push(bytes[i] as char);
35036                i += 1;
35037            }
35038        }
35039        result
35040    }
35041
35042    /// Convert strftime format (%Y, %m, %d, etc.) to .NET date format for TSQL FORMAT()
35043    /// Similar to Java but uses ffffff for microseconds instead of SSSSSS
35044    fn strftime_to_tsql_format(fmt: &str) -> String {
35045        let mut result = String::with_capacity(fmt.len() * 2);
35046        let bytes = fmt.as_bytes();
35047        let len = bytes.len();
35048        let mut i = 0;
35049        while i < len {
35050            if bytes[i] == b'%' && i + 1 < len {
35051                // Check for non-padded variants (%-X)
35052                if bytes[i + 1] == b'-' && i + 2 < len {
35053                    let replacement = match bytes[i + 2] {
35054                        b'd' => "d",
35055                        b'm' => "M",
35056                        b'H' => "H",
35057                        b'M' => "m",
35058                        b'S' => "s",
35059                        _ => {
35060                            result.push('%');
35061                            i += 1;
35062                            continue;
35063                        }
35064                    };
35065                    result.push_str(replacement);
35066                    i += 3;
35067                } else {
35068                    let replacement = match bytes[i + 1] {
35069                        b'Y' => "yyyy",
35070                        b'y' => "yy",
35071                        b'm' => "MM",
35072                        b'B' => "MMMM",
35073                        b'b' => "MMM",
35074                        b'd' => "dd",
35075                        b'j' => "DDD",
35076                        b'H' => "HH",
35077                        b'M' => "mm",
35078                        b'S' => "ss",
35079                        b'f' => "ffffff",
35080                        b'A' => "dddd",
35081                        b'a' => "ddd",
35082                        _ => {
35083                            result.push('%');
35084                            i += 1;
35085                            continue;
35086                        }
35087                    };
35088                    result.push_str(replacement);
35089                    i += 2;
35090                }
35091            } else {
35092                result.push(bytes[i] as char);
35093                i += 1;
35094            }
35095        }
35096        result
35097    }
35098
35099    /// Decompose a JSON path string like "$.y[0].z" into individual parts: ["y", "0", "z"]
35100    /// This is used for PostgreSQL/Redshift JSON_EXTRACT_PATH / JSON_EXTRACT_PATH_TEXT
35101    fn decompose_json_path(path: &str) -> Vec<String> {
35102        let mut parts = Vec::new();
35103        // Strip leading $ and optional .
35104        let path = if path.starts_with("$.") {
35105            &path[2..]
35106        } else if path.starts_with('$') {
35107            &path[1..]
35108        } else {
35109            path
35110        };
35111        if path.is_empty() {
35112            return parts;
35113        }
35114        let mut current = String::new();
35115        let chars: Vec<char> = path.chars().collect();
35116        let mut i = 0;
35117        while i < chars.len() {
35118            match chars[i] {
35119                '.' => {
35120                    if !current.is_empty() {
35121                        parts.push(current.clone());
35122                        current.clear();
35123                    }
35124                    i += 1;
35125                }
35126                '[' => {
35127                    if !current.is_empty() {
35128                        parts.push(current.clone());
35129                        current.clear();
35130                    }
35131                    i += 1;
35132                    // Read the content inside brackets
35133                    let mut bracket_content = String::new();
35134                    while i < chars.len() && chars[i] != ']' {
35135                        // Skip quotes inside brackets
35136                        if chars[i] == '"' || chars[i] == '\'' {
35137                            let quote = chars[i];
35138                            i += 1;
35139                            while i < chars.len() && chars[i] != quote {
35140                                bracket_content.push(chars[i]);
35141                                i += 1;
35142                            }
35143                            if i < chars.len() {
35144                                i += 1;
35145                            } // skip closing quote
35146                        } else {
35147                            bracket_content.push(chars[i]);
35148                            i += 1;
35149                        }
35150                    }
35151                    if i < chars.len() {
35152                        i += 1;
35153                    } // skip ]
35154                      // Skip wildcard [*] - don't add as a part
35155                    if bracket_content != "*" {
35156                        parts.push(bracket_content);
35157                    }
35158                }
35159                _ => {
35160                    current.push(chars[i]);
35161                    i += 1;
35162                }
35163            }
35164        }
35165        if !current.is_empty() {
35166            parts.push(current);
35167        }
35168        parts
35169    }
35170
35171    /// Convert strftime format to PostgreSQL date format (YYYY, MM, DD, etc.)
35172    fn strftime_to_postgres_format(fmt: &str) -> String {
35173        let mut result = String::with_capacity(fmt.len() * 2);
35174        let bytes = fmt.as_bytes();
35175        let len = bytes.len();
35176        let mut i = 0;
35177        while i < len {
35178            if bytes[i] == b'%' && i + 1 < len {
35179                // Check for non-padded variants (%-X)
35180                if bytes[i + 1] == b'-' && i + 2 < len {
35181                    let replacement = match bytes[i + 2] {
35182                        b'd' => "FMDD",
35183                        b'm' => "FMMM",
35184                        b'H' => "FMHH24",
35185                        b'M' => "FMMI",
35186                        b'S' => "FMSS",
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'B' => "Month",
35201                        b'b' => "Mon",
35202                        b'd' => "DD",
35203                        b'j' => "DDD",
35204                        b'H' => "HH24",
35205                        b'M' => "MI",
35206                        b'S' => "SS",
35207                        b'f' => "US",
35208                        b'A' => "Day",
35209                        b'a' => "Dy",
35210                        _ => {
35211                            result.push('%');
35212                            i += 1;
35213                            continue;
35214                        }
35215                    };
35216                    result.push_str(replacement);
35217                    i += 2;
35218                }
35219            } else {
35220                result.push(bytes[i] as char);
35221                i += 1;
35222            }
35223        }
35224        result
35225    }
35226
35227    /// Convert strftime format to Snowflake date format (yyyy, mm, DD, etc.)
35228    fn strftime_to_snowflake_format(fmt: &str) -> String {
35229        let mut result = String::with_capacity(fmt.len() * 2);
35230        let bytes = fmt.as_bytes();
35231        let len = bytes.len();
35232        let mut i = 0;
35233        while i < len {
35234            if bytes[i] == b'%' && i + 1 < len {
35235                // Check for non-padded variants (%-X)
35236                if bytes[i + 1] == b'-' && i + 2 < len {
35237                    let replacement = match bytes[i + 2] {
35238                        b'd' => "dd",
35239                        b'm' => "mm",
35240                        _ => {
35241                            result.push('%');
35242                            i += 1;
35243                            continue;
35244                        }
35245                    };
35246                    result.push_str(replacement);
35247                    i += 3;
35248                } else {
35249                    let replacement = match bytes[i + 1] {
35250                        b'Y' => "yyyy",
35251                        b'y' => "yy",
35252                        b'm' => "mm",
35253                        b'd' => "DD",
35254                        b'H' => "hh24",
35255                        b'M' => "mi",
35256                        b'S' => "ss",
35257                        b'f' => "ff",
35258                        _ => {
35259                            result.push('%');
35260                            i += 1;
35261                            continue;
35262                        }
35263                    };
35264                    result.push_str(replacement);
35265                    i += 2;
35266                }
35267            } else {
35268                result.push(bytes[i] as char);
35269                i += 1;
35270            }
35271        }
35272        result
35273    }
35274
35275    fn generate_str_to_map(&mut self, e: &StrToMap) -> Result<()> {
35276        // STR_TO_MAP(this, pair_delim, key_value_delim)
35277        self.write_keyword("STR_TO_MAP");
35278        self.write("(");
35279        self.generate_expression(&e.this)?;
35280        // Spark/Hive: STR_TO_MAP needs explicit default delimiters
35281        let needs_defaults = matches!(
35282            self.config.dialect,
35283            Some(DialectType::Spark) | Some(DialectType::Hive) | Some(DialectType::Databricks)
35284        );
35285        if let Some(pair_delim) = &e.pair_delim {
35286            self.write(", ");
35287            self.generate_expression(pair_delim)?;
35288        } else if needs_defaults {
35289            self.write(", ','");
35290        }
35291        if let Some(key_value_delim) = &e.key_value_delim {
35292            self.write(", ");
35293            self.generate_expression(key_value_delim)?;
35294        } else if needs_defaults {
35295            self.write(", ':'");
35296        }
35297        self.write(")");
35298        Ok(())
35299    }
35300
35301    fn generate_str_to_time(&mut self, e: &StrToTime) -> Result<()> {
35302        // Detect format style: strftime (starts with %) vs Snowflake/Java
35303        let is_strftime = e.format.contains('%');
35304        // Helper: get strftime format from whatever style is stored
35305        let to_strftime = |f: &str| -> String {
35306            if is_strftime {
35307                f.to_string()
35308            } else {
35309                Self::snowflake_format_to_strftime(f)
35310            }
35311        };
35312        // Helper: get Java format
35313        let to_java = |f: &str| -> String {
35314            if is_strftime {
35315                Self::strftime_to_java_format(f)
35316            } else {
35317                Self::snowflake_format_to_spark(f)
35318            }
35319        };
35320        // Helper: get PG format
35321        let to_pg = |f: &str| -> String {
35322            if is_strftime {
35323                Self::strftime_to_postgres_format(f)
35324            } else {
35325                Self::convert_strptime_to_postgres_format(f)
35326            }
35327        };
35328
35329        match self.config.dialect {
35330            Some(DialectType::Exasol) => {
35331                self.write_keyword("TO_DATE");
35332                self.write("(");
35333                self.generate_expression(&e.this)?;
35334                self.write(", '");
35335                self.write(&Self::convert_strptime_to_exasol_format(&e.format));
35336                self.write("'");
35337                self.write(")");
35338            }
35339            Some(DialectType::BigQuery) => {
35340                // BigQuery: PARSE_TIMESTAMP(format, value) - note swapped args
35341                let fmt = to_strftime(&e.format);
35342                // BigQuery normalizes: %Y-%m-%d -> %F, %H:%M:%S -> %T
35343                let fmt = fmt.replace("%Y-%m-%d", "%F").replace("%H:%M:%S", "%T");
35344                self.write_keyword("PARSE_TIMESTAMP");
35345                self.write("('");
35346                self.write(&fmt);
35347                self.write("', ");
35348                self.generate_expression(&e.this)?;
35349                self.write(")");
35350            }
35351            Some(DialectType::Hive) => {
35352                // Hive: CAST(x AS TIMESTAMP) for simple date formats
35353                // Check both the raw format and the converted format (in case it's already Java)
35354                let java_fmt = to_java(&e.format);
35355                if java_fmt == "yyyy-MM-dd HH:mm:ss"
35356                    || java_fmt == "yyyy-MM-dd"
35357                    || e.format == "yyyy-MM-dd HH:mm:ss"
35358                    || e.format == "yyyy-MM-dd"
35359                {
35360                    self.write_keyword("CAST");
35361                    self.write("(");
35362                    self.generate_expression(&e.this)?;
35363                    self.write(" ");
35364                    self.write_keyword("AS TIMESTAMP");
35365                    self.write(")");
35366                } else {
35367                    // CAST(FROM_UNIXTIME(UNIX_TIMESTAMP(x, java_fmt)) AS TIMESTAMP)
35368                    self.write_keyword("CAST");
35369                    self.write("(");
35370                    self.write_keyword("FROM_UNIXTIME");
35371                    self.write("(");
35372                    self.write_keyword("UNIX_TIMESTAMP");
35373                    self.write("(");
35374                    self.generate_expression(&e.this)?;
35375                    self.write(", '");
35376                    self.write(&java_fmt);
35377                    self.write("')");
35378                    self.write(") ");
35379                    self.write_keyword("AS TIMESTAMP");
35380                    self.write(")");
35381                }
35382            }
35383            Some(DialectType::Spark) | Some(DialectType::Databricks) => {
35384                // Spark: TO_TIMESTAMP(value, java_format)
35385                let java_fmt = to_java(&e.format);
35386                self.write_keyword("TO_TIMESTAMP");
35387                self.write("(");
35388                self.generate_expression(&e.this)?;
35389                self.write(", '");
35390                self.write(&java_fmt);
35391                self.write("')");
35392            }
35393            Some(DialectType::MySQL) => {
35394                // MySQL: STR_TO_DATE(value, format)
35395                let mut fmt = to_strftime(&e.format);
35396                // MySQL uses %e for non-padded day, %T for %H:%M:%S
35397                fmt = fmt.replace("%-d", "%e");
35398                fmt = fmt.replace("%-m", "%c");
35399                fmt = fmt.replace("%H:%M:%S", "%T");
35400                self.write_keyword("STR_TO_DATE");
35401                self.write("(");
35402                self.generate_expression(&e.this)?;
35403                self.write(", '");
35404                self.write(&fmt);
35405                self.write("')");
35406            }
35407            Some(DialectType::Drill) => {
35408                // Drill: TO_TIMESTAMP(value, java_format) with T quoted in single quotes
35409                let java_fmt = to_java(&e.format);
35410                // Drill quotes literal T character: T -> ''T'' (double-quoted within SQL string literal)
35411                let java_fmt = java_fmt.replace('T', "''T''");
35412                self.write_keyword("TO_TIMESTAMP");
35413                self.write("(");
35414                self.generate_expression(&e.this)?;
35415                self.write(", '");
35416                self.write(&java_fmt);
35417                self.write("')");
35418            }
35419            Some(DialectType::Presto) | Some(DialectType::Trino) | Some(DialectType::Athena) => {
35420                // Presto: DATE_PARSE(value, strftime_format)
35421                let mut fmt = to_strftime(&e.format);
35422                // Presto uses %e for non-padded day, %T for %H:%M:%S
35423                fmt = fmt.replace("%-d", "%e");
35424                fmt = fmt.replace("%-m", "%c");
35425                fmt = fmt.replace("%H:%M:%S", "%T");
35426                self.write_keyword("DATE_PARSE");
35427                self.write("(");
35428                self.generate_expression(&e.this)?;
35429                self.write(", '");
35430                self.write(&fmt);
35431                self.write("')");
35432            }
35433            Some(DialectType::DuckDB) => {
35434                // DuckDB: STRPTIME(value, strftime_format)
35435                let fmt = to_strftime(&e.format);
35436                self.write_keyword("STRPTIME");
35437                self.write("(");
35438                self.generate_expression(&e.this)?;
35439                self.write(", '");
35440                self.write(&fmt);
35441                self.write("')");
35442            }
35443            Some(DialectType::PostgreSQL)
35444            | Some(DialectType::Redshift)
35445            | Some(DialectType::Materialize) => {
35446                // PostgreSQL/Redshift/Materialize: TO_TIMESTAMP(value, pg_format)
35447                let pg_fmt = to_pg(&e.format);
35448                self.write_keyword("TO_TIMESTAMP");
35449                self.write("(");
35450                self.generate_expression(&e.this)?;
35451                self.write(", '");
35452                self.write(&pg_fmt);
35453                self.write("')");
35454            }
35455            Some(DialectType::Oracle) => {
35456                // Oracle: TO_TIMESTAMP(value, pg_format)
35457                let pg_fmt = to_pg(&e.format);
35458                self.write_keyword("TO_TIMESTAMP");
35459                self.write("(");
35460                self.generate_expression(&e.this)?;
35461                self.write(", '");
35462                self.write(&pg_fmt);
35463                self.write("')");
35464            }
35465            Some(DialectType::Snowflake) => {
35466                // Snowflake: TO_TIMESTAMP(value, format) - native format
35467                self.write_keyword("TO_TIMESTAMP");
35468                self.write("(");
35469                self.generate_expression(&e.this)?;
35470                self.write(", '");
35471                self.write(&e.format);
35472                self.write("')");
35473            }
35474            _ => {
35475                // Default: STR_TO_TIME(this, format)
35476                self.write_keyword("STR_TO_TIME");
35477                self.write("(");
35478                self.generate_expression(&e.this)?;
35479                self.write(", '");
35480                self.write(&e.format);
35481                self.write("'");
35482                self.write(")");
35483            }
35484        }
35485        Ok(())
35486    }
35487
35488    /// Convert Snowflake normalized format to strftime-style (%Y, %m, etc.)
35489    fn snowflake_format_to_strftime(format: &str) -> String {
35490        let mut result = String::new();
35491        let chars: Vec<char> = format.chars().collect();
35492        let mut i = 0;
35493        while i < chars.len() {
35494            let remaining = &format[i..];
35495            if remaining.starts_with("yyyy") {
35496                result.push_str("%Y");
35497                i += 4;
35498            } else if remaining.starts_with("yy") {
35499                result.push_str("%y");
35500                i += 2;
35501            } else if remaining.starts_with("mmmm") {
35502                result.push_str("%B"); // full month name
35503                i += 4;
35504            } else if remaining.starts_with("mon") {
35505                result.push_str("%b"); // abbreviated month
35506                i += 3;
35507            } else if remaining.starts_with("mm") {
35508                result.push_str("%m");
35509                i += 2;
35510            } else if remaining.starts_with("DD") {
35511                result.push_str("%d");
35512                i += 2;
35513            } else if remaining.starts_with("dy") {
35514                result.push_str("%a"); // abbreviated day name
35515                i += 2;
35516            } else if remaining.starts_with("hh24") {
35517                result.push_str("%H");
35518                i += 4;
35519            } else if remaining.starts_with("hh12") {
35520                result.push_str("%I");
35521                i += 4;
35522            } else if remaining.starts_with("hh") {
35523                result.push_str("%H");
35524                i += 2;
35525            } else if remaining.starts_with("mi") {
35526                result.push_str("%M");
35527                i += 2;
35528            } else if remaining.starts_with("ss") {
35529                result.push_str("%S");
35530                i += 2;
35531            } else if remaining.starts_with("ff") {
35532                // Fractional seconds
35533                result.push_str("%f");
35534                i += 2;
35535                // Skip digits after ff (ff3, ff6, ff9)
35536                while i < chars.len() && chars[i].is_ascii_digit() {
35537                    i += 1;
35538                }
35539            } else if remaining.starts_with("am") || remaining.starts_with("pm") {
35540                result.push_str("%p");
35541                i += 2;
35542            } else if remaining.starts_with("tz") {
35543                result.push_str("%Z");
35544                i += 2;
35545            } else {
35546                result.push(chars[i]);
35547                i += 1;
35548            }
35549        }
35550        result
35551    }
35552
35553    /// Convert Snowflake normalized format to Spark format (Java-style)
35554    fn snowflake_format_to_spark(format: &str) -> String {
35555        let mut result = String::new();
35556        let chars: Vec<char> = format.chars().collect();
35557        let mut i = 0;
35558        while i < chars.len() {
35559            let remaining = &format[i..];
35560            if remaining.starts_with("yyyy") {
35561                result.push_str("yyyy");
35562                i += 4;
35563            } else if remaining.starts_with("yy") {
35564                result.push_str("yy");
35565                i += 2;
35566            } else if remaining.starts_with("mmmm") {
35567                result.push_str("MMMM"); // full month name
35568                i += 4;
35569            } else if remaining.starts_with("mon") {
35570                result.push_str("MMM"); // abbreviated month
35571                i += 3;
35572            } else if remaining.starts_with("mm") {
35573                result.push_str("MM");
35574                i += 2;
35575            } else if remaining.starts_with("DD") {
35576                result.push_str("dd");
35577                i += 2;
35578            } else if remaining.starts_with("dy") {
35579                result.push_str("EEE"); // abbreviated day name
35580                i += 2;
35581            } else if remaining.starts_with("hh24") {
35582                result.push_str("HH");
35583                i += 4;
35584            } else if remaining.starts_with("hh12") {
35585                result.push_str("hh");
35586                i += 4;
35587            } else if remaining.starts_with("hh") {
35588                result.push_str("HH");
35589                i += 2;
35590            } else if remaining.starts_with("mi") {
35591                result.push_str("mm");
35592                i += 2;
35593            } else if remaining.starts_with("ss") {
35594                result.push_str("ss");
35595                i += 2;
35596            } else if remaining.starts_with("ff") {
35597                result.push_str("SSS"); // milliseconds
35598                i += 2;
35599                // Skip digits after ff
35600                while i < chars.len() && chars[i].is_ascii_digit() {
35601                    i += 1;
35602                }
35603            } else if remaining.starts_with("am") || remaining.starts_with("pm") {
35604                result.push_str("a");
35605                i += 2;
35606            } else if remaining.starts_with("tz") {
35607                result.push_str("z");
35608                i += 2;
35609            } else {
35610                result.push(chars[i]);
35611                i += 1;
35612            }
35613        }
35614        result
35615    }
35616
35617    fn generate_str_to_unix(&mut self, e: &StrToUnix) -> Result<()> {
35618        match self.config.dialect {
35619            Some(DialectType::DuckDB) => {
35620                // DuckDB: EPOCH(STRPTIME(value, format))
35621                self.write_keyword("EPOCH");
35622                self.write("(");
35623                self.write_keyword("STRPTIME");
35624                self.write("(");
35625                if let Some(this) = &e.this {
35626                    self.generate_expression(this)?;
35627                }
35628                if let Some(format) = &e.format {
35629                    self.write(", '");
35630                    self.write(format);
35631                    self.write("'");
35632                }
35633                self.write("))");
35634            }
35635            Some(DialectType::Hive) => {
35636                // Hive: UNIX_TIMESTAMP(value, java_format) - convert C fmt to Java
35637                self.write_keyword("UNIX_TIMESTAMP");
35638                self.write("(");
35639                if let Some(this) = &e.this {
35640                    self.generate_expression(this)?;
35641                }
35642                if let Some(format) = &e.format {
35643                    let java_fmt = Self::strftime_to_java_format(format);
35644                    if java_fmt != "yyyy-MM-dd HH:mm:ss" {
35645                        self.write(", '");
35646                        self.write(&java_fmt);
35647                        self.write("'");
35648                    }
35649                }
35650                self.write(")");
35651            }
35652            Some(DialectType::Doris) | Some(DialectType::StarRocks) => {
35653                // Doris/StarRocks: UNIX_TIMESTAMP(value, format) - C format
35654                self.write_keyword("UNIX_TIMESTAMP");
35655                self.write("(");
35656                if let Some(this) = &e.this {
35657                    self.generate_expression(this)?;
35658                }
35659                if let Some(format) = &e.format {
35660                    self.write(", '");
35661                    self.write(format);
35662                    self.write("'");
35663                }
35664                self.write(")");
35665            }
35666            Some(DialectType::Presto) | Some(DialectType::Trino) => {
35667                // Presto: TO_UNIXTIME(COALESCE(TRY(DATE_PARSE(CAST(value AS VARCHAR), c_format)),
35668                //   PARSE_DATETIME(DATE_FORMAT(CAST(value AS TIMESTAMP), c_format), java_format)))
35669                let c_fmt = e.format.as_deref().unwrap_or("%Y-%m-%d %T");
35670                let java_fmt = Self::strftime_to_java_format(c_fmt);
35671                self.write_keyword("TO_UNIXTIME");
35672                self.write("(");
35673                self.write_keyword("COALESCE");
35674                self.write("(");
35675                self.write_keyword("TRY");
35676                self.write("(");
35677                self.write_keyword("DATE_PARSE");
35678                self.write("(");
35679                self.write_keyword("CAST");
35680                self.write("(");
35681                if let Some(this) = &e.this {
35682                    self.generate_expression(this)?;
35683                }
35684                self.write(" ");
35685                self.write_keyword("AS VARCHAR");
35686                self.write("), '");
35687                self.write(c_fmt);
35688                self.write("')), ");
35689                self.write_keyword("PARSE_DATETIME");
35690                self.write("(");
35691                self.write_keyword("DATE_FORMAT");
35692                self.write("(");
35693                self.write_keyword("CAST");
35694                self.write("(");
35695                if let Some(this) = &e.this {
35696                    self.generate_expression(this)?;
35697                }
35698                self.write(" ");
35699                self.write_keyword("AS TIMESTAMP");
35700                self.write("), '");
35701                self.write(c_fmt);
35702                self.write("'), '");
35703                self.write(&java_fmt);
35704                self.write("')))");
35705            }
35706            Some(DialectType::Spark) | Some(DialectType::Databricks) => {
35707                // Spark: UNIX_TIMESTAMP(value, java_format)
35708                self.write_keyword("UNIX_TIMESTAMP");
35709                self.write("(");
35710                if let Some(this) = &e.this {
35711                    self.generate_expression(this)?;
35712                }
35713                if let Some(format) = &e.format {
35714                    let java_fmt = Self::strftime_to_java_format(format);
35715                    self.write(", '");
35716                    self.write(&java_fmt);
35717                    self.write("'");
35718                }
35719                self.write(")");
35720            }
35721            _ => {
35722                // Default: STR_TO_UNIX(this, format)
35723                self.write_keyword("STR_TO_UNIX");
35724                self.write("(");
35725                if let Some(this) = &e.this {
35726                    self.generate_expression(this)?;
35727                }
35728                if let Some(format) = &e.format {
35729                    self.write(", '");
35730                    self.write(format);
35731                    self.write("'");
35732                }
35733                self.write(")");
35734            }
35735        }
35736        Ok(())
35737    }
35738
35739    fn generate_string_to_array(&mut self, e: &StringToArray) -> Result<()> {
35740        // STRING_TO_ARRAY(this, delimiter, null_string)
35741        self.write_keyword("STRING_TO_ARRAY");
35742        self.write("(");
35743        self.generate_expression(&e.this)?;
35744        if let Some(expression) = &e.expression {
35745            self.write(", ");
35746            self.generate_expression(expression)?;
35747        }
35748        if let Some(null_val) = &e.null {
35749            self.write(", ");
35750            self.generate_expression(null_val)?;
35751        }
35752        self.write(")");
35753        Ok(())
35754    }
35755
35756    fn generate_struct(&mut self, e: &Struct) -> Result<()> {
35757        if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
35758            // Snowflake: OBJECT_CONSTRUCT('key', value, 'key', value, ...)
35759            self.write_keyword("OBJECT_CONSTRUCT");
35760            self.write("(");
35761            for (i, (name, expr)) in e.fields.iter().enumerate() {
35762                if i > 0 {
35763                    self.write(", ");
35764                }
35765                if let Some(name) = name {
35766                    self.write("'");
35767                    self.write(name);
35768                    self.write("'");
35769                    self.write(", ");
35770                } else {
35771                    self.write("'_");
35772                    self.write(&i.to_string());
35773                    self.write("'");
35774                    self.write(", ");
35775                }
35776                self.generate_expression(expr)?;
35777            }
35778            self.write(")");
35779        } else if self.config.struct_curly_brace_notation {
35780            // DuckDB-style: {'key': value, ...}
35781            self.write("{");
35782            for (i, (name, expr)) in e.fields.iter().enumerate() {
35783                if i > 0 {
35784                    self.write(", ");
35785                }
35786                if let Some(name) = name {
35787                    // Quote the key as a string literal
35788                    self.write("'");
35789                    self.write(name);
35790                    self.write("'");
35791                    self.write(": ");
35792                } else {
35793                    // Unnamed field: use positional key
35794                    self.write("'_");
35795                    self.write(&i.to_string());
35796                    self.write("'");
35797                    self.write(": ");
35798                }
35799                self.generate_expression(expr)?;
35800            }
35801            self.write("}");
35802        } else {
35803            // Standard SQL struct notation
35804            // BigQuery/Spark/Databricks use: STRUCT(value AS name, ...)
35805            // Others (Presto etc.) use: STRUCT(name AS value, ...) or ROW(value, ...)
35806            let value_as_name = matches!(
35807                self.config.dialect,
35808                Some(DialectType::BigQuery)
35809                    | Some(DialectType::Spark)
35810                    | Some(DialectType::Databricks)
35811                    | Some(DialectType::Hive)
35812            );
35813            self.write_keyword("STRUCT");
35814            self.write("(");
35815            for (i, (name, expr)) in e.fields.iter().enumerate() {
35816                if i > 0 {
35817                    self.write(", ");
35818                }
35819                if let Some(name) = name {
35820                    if value_as_name {
35821                        // STRUCT(value AS name)
35822                        self.generate_expression(expr)?;
35823                        self.write_space();
35824                        self.write_keyword("AS");
35825                        self.write_space();
35826                        // Quote name if it contains spaces or special chars
35827                        let needs_quoting = name.contains(' ') || name.contains('-');
35828                        if needs_quoting {
35829                            if matches!(
35830                                self.config.dialect,
35831                                Some(DialectType::Spark)
35832                                    | Some(DialectType::Databricks)
35833                                    | Some(DialectType::Hive)
35834                            ) {
35835                                self.write("`");
35836                                self.write(name);
35837                                self.write("`");
35838                            } else {
35839                                self.write(name);
35840                            }
35841                        } else {
35842                            self.write(name);
35843                        }
35844                    } else {
35845                        // STRUCT(name AS value)
35846                        self.write(name);
35847                        self.write_space();
35848                        self.write_keyword("AS");
35849                        self.write_space();
35850                        self.generate_expression(expr)?;
35851                    }
35852                } else {
35853                    self.generate_expression(expr)?;
35854                }
35855            }
35856            self.write(")");
35857        }
35858        Ok(())
35859    }
35860
35861    fn generate_stuff(&mut self, e: &Stuff) -> Result<()> {
35862        // STUFF(this, start, length, expression)
35863        self.write_keyword("STUFF");
35864        self.write("(");
35865        self.generate_expression(&e.this)?;
35866        if let Some(start) = &e.start {
35867            self.write(", ");
35868            self.generate_expression(start)?;
35869        }
35870        if let Some(length) = e.length {
35871            self.write(", ");
35872            self.write(&length.to_string());
35873        }
35874        self.write(", ");
35875        self.generate_expression(&e.expression)?;
35876        self.write(")");
35877        Ok(())
35878    }
35879
35880    fn generate_substring_index(&mut self, e: &SubstringIndex) -> Result<()> {
35881        // SUBSTRING_INDEX(this, delimiter, count)
35882        self.write_keyword("SUBSTRING_INDEX");
35883        self.write("(");
35884        self.generate_expression(&e.this)?;
35885        if let Some(delimiter) = &e.delimiter {
35886            self.write(", ");
35887            self.generate_expression(delimiter)?;
35888        }
35889        if let Some(count) = &e.count {
35890            self.write(", ");
35891            self.generate_expression(count)?;
35892        }
35893        self.write(")");
35894        Ok(())
35895    }
35896
35897    fn generate_summarize(&mut self, e: &Summarize) -> Result<()> {
35898        // SUMMARIZE [TABLE] this
35899        self.write_keyword("SUMMARIZE");
35900        if e.table.is_some() {
35901            self.write_space();
35902            self.write_keyword("TABLE");
35903        }
35904        self.write_space();
35905        self.generate_expression(&e.this)?;
35906        Ok(())
35907    }
35908
35909    fn generate_systimestamp(&mut self, _e: &Systimestamp) -> Result<()> {
35910        // SYSTIMESTAMP
35911        self.write_keyword("SYSTIMESTAMP");
35912        Ok(())
35913    }
35914
35915    fn generate_table_alias(&mut self, e: &TableAlias) -> Result<()> {
35916        // alias (columns...)
35917        if let Some(this) = &e.this {
35918            self.generate_expression(this)?;
35919        }
35920        if !e.columns.is_empty() {
35921            self.write("(");
35922            for (i, col) in e.columns.iter().enumerate() {
35923                if i > 0 {
35924                    self.write(", ");
35925                }
35926                self.generate_expression(col)?;
35927            }
35928            self.write(")");
35929        }
35930        Ok(())
35931    }
35932
35933    fn generate_table_from_rows(&mut self, e: &TableFromRows) -> Result<()> {
35934        // TABLE(this) [AS alias]
35935        self.write_keyword("TABLE");
35936        self.write("(");
35937        self.generate_expression(&e.this)?;
35938        self.write(")");
35939        if let Some(alias) = &e.alias {
35940            self.write_space();
35941            self.write_keyword("AS");
35942            self.write_space();
35943            self.write(alias);
35944        }
35945        Ok(())
35946    }
35947
35948    fn generate_rows_from(&mut self, e: &RowsFrom) -> Result<()> {
35949        // ROWS FROM (func1(...) AS alias1(...), func2(...) AS alias2(...)) [WITH ORDINALITY] [AS alias(...)]
35950        self.write_keyword("ROWS FROM");
35951        self.write(" (");
35952        for (i, expr) in e.expressions.iter().enumerate() {
35953            if i > 0 {
35954                self.write(", ");
35955            }
35956            // Each expression is either:
35957            // - A plain function (no alias)
35958            // - A Tuple(function, TableAlias) for: FUNC() AS alias(col type, ...)
35959            match expr {
35960                Expression::Tuple(tuple) if tuple.expressions.len() == 2 => {
35961                    // First element is the function, second is the TableAlias
35962                    self.generate_expression(&tuple.expressions[0])?;
35963                    self.write_space();
35964                    self.write_keyword("AS");
35965                    self.write_space();
35966                    self.generate_expression(&tuple.expressions[1])?;
35967                }
35968                _ => {
35969                    self.generate_expression(expr)?;
35970                }
35971            }
35972        }
35973        self.write(")");
35974        if e.ordinality {
35975            self.write_space();
35976            self.write_keyword("WITH ORDINALITY");
35977        }
35978        if let Some(alias) = &e.alias {
35979            self.write_space();
35980            self.write_keyword("AS");
35981            self.write_space();
35982            self.generate_expression(alias)?;
35983        }
35984        Ok(())
35985    }
35986
35987    fn generate_table_sample(&mut self, e: &TableSample) -> Result<()> {
35988        use crate::dialects::DialectType;
35989
35990        // New wrapper pattern: expression + Sample struct
35991        if let (Some(this), Some(sample)) = (&e.this, &e.sample) {
35992            // For alias_post_tablesample dialects (Spark, Hive, Oracle): output base expr, TABLESAMPLE, then alias
35993            if self.config.alias_post_tablesample {
35994                // Handle Subquery with alias and Alias wrapper
35995                if let Expression::Subquery(ref s) = **this {
35996                    if let Some(ref alias) = s.alias {
35997                        // Create a clone without alias for output
35998                        let mut subquery_no_alias = (**s).clone();
35999                        subquery_no_alias.alias = None;
36000                        subquery_no_alias.column_aliases = Vec::new();
36001                        self.generate_expression(&Expression::Subquery(Box::new(
36002                            subquery_no_alias,
36003                        )))?;
36004                        self.write_space();
36005                        self.write_keyword(self.config.tablesample_keywords);
36006                        self.generate_sample_body(sample)?;
36007                        if let Some(ref seed) = sample.seed {
36008                            self.write_space();
36009                            let use_seed = sample.use_seed_keyword
36010                                && !matches!(
36011                                    self.config.dialect,
36012                                    Some(crate::dialects::DialectType::Databricks)
36013                                        | Some(crate::dialects::DialectType::Spark)
36014                                );
36015                            if use_seed {
36016                                self.write_keyword("SEED");
36017                            } else {
36018                                self.write_keyword("REPEATABLE");
36019                            }
36020                            self.write(" (");
36021                            self.generate_expression(seed)?;
36022                            self.write(")");
36023                        }
36024                        self.write_space();
36025                        self.write_keyword("AS");
36026                        self.write_space();
36027                        self.generate_identifier(alias)?;
36028                        return Ok(());
36029                    }
36030                } else if let Expression::Alias(ref a) = **this {
36031                    // Output the base expression without alias
36032                    self.generate_expression(&a.this)?;
36033                    self.write_space();
36034                    self.write_keyword(self.config.tablesample_keywords);
36035                    self.generate_sample_body(sample)?;
36036                    if let Some(ref seed) = sample.seed {
36037                        self.write_space();
36038                        let use_seed = sample.use_seed_keyword
36039                            && !matches!(
36040                                self.config.dialect,
36041                                Some(crate::dialects::DialectType::Databricks)
36042                                    | Some(crate::dialects::DialectType::Spark)
36043                            );
36044                        if use_seed {
36045                            self.write_keyword("SEED");
36046                        } else {
36047                            self.write_keyword("REPEATABLE");
36048                        }
36049                        self.write(" (");
36050                        self.generate_expression(seed)?;
36051                        self.write(")");
36052                    }
36053                    // Output alias after TABLESAMPLE
36054                    self.write_space();
36055                    self.write_keyword("AS");
36056                    self.write_space();
36057                    self.generate_identifier(&a.alias)?;
36058                    return Ok(());
36059                }
36060            }
36061            // Default: generate wrapped expression first, then TABLESAMPLE
36062            self.generate_expression(this)?;
36063            self.write_space();
36064            self.write_keyword(self.config.tablesample_keywords);
36065            self.generate_sample_body(sample)?;
36066            // Seed for table-level sample
36067            if let Some(ref seed) = sample.seed {
36068                self.write_space();
36069                // Databricks uses REPEATABLE, not SEED
36070                let use_seed = sample.use_seed_keyword
36071                    && !matches!(
36072                        self.config.dialect,
36073                        Some(crate::dialects::DialectType::Databricks)
36074                            | Some(crate::dialects::DialectType::Spark)
36075                    );
36076                if use_seed {
36077                    self.write_keyword("SEED");
36078                } else {
36079                    self.write_keyword("REPEATABLE");
36080                }
36081                self.write(" (");
36082                self.generate_expression(seed)?;
36083                self.write(")");
36084            }
36085            return Ok(());
36086        }
36087
36088        // Legacy pattern: TABLESAMPLE [method] (expressions) or TABLESAMPLE method BUCKET numerator OUT OF denominator
36089        self.write_keyword(self.config.tablesample_keywords);
36090        if let Some(method) = &e.method {
36091            self.write_space();
36092            self.write_keyword(method);
36093        } else if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
36094            // Snowflake defaults to BERNOULLI when no method is specified
36095            self.write_space();
36096            self.write_keyword("BERNOULLI");
36097        }
36098        if let (Some(numerator), Some(denominator)) = (&e.bucket_numerator, &e.bucket_denominator) {
36099            self.write_space();
36100            self.write_keyword("BUCKET");
36101            self.write_space();
36102            self.generate_expression(numerator)?;
36103            self.write_space();
36104            self.write_keyword("OUT OF");
36105            self.write_space();
36106            self.generate_expression(denominator)?;
36107            if let Some(field) = &e.bucket_field {
36108                self.write_space();
36109                self.write_keyword("ON");
36110                self.write_space();
36111                self.generate_expression(field)?;
36112            }
36113        } else if !e.expressions.is_empty() {
36114            self.write(" (");
36115            for (i, expr) in e.expressions.iter().enumerate() {
36116                if i > 0 {
36117                    self.write(", ");
36118                }
36119                self.generate_expression(expr)?;
36120            }
36121            self.write(")");
36122        } else if let Some(percent) = &e.percent {
36123            self.write(" (");
36124            self.generate_expression(percent)?;
36125            self.write_space();
36126            self.write_keyword("PERCENT");
36127            self.write(")");
36128        }
36129        Ok(())
36130    }
36131
36132    fn generate_tag(&mut self, e: &Tag) -> Result<()> {
36133        // [prefix]this[postfix]
36134        if let Some(prefix) = &e.prefix {
36135            self.generate_expression(prefix)?;
36136        }
36137        if let Some(this) = &e.this {
36138            self.generate_expression(this)?;
36139        }
36140        if let Some(postfix) = &e.postfix {
36141            self.generate_expression(postfix)?;
36142        }
36143        Ok(())
36144    }
36145
36146    fn generate_tags(&mut self, e: &Tags) -> Result<()> {
36147        // TAG (expressions)
36148        self.write_keyword("TAG");
36149        self.write(" (");
36150        for (i, expr) in e.expressions.iter().enumerate() {
36151            if i > 0 {
36152                self.write(", ");
36153            }
36154            self.generate_expression(expr)?;
36155        }
36156        self.write(")");
36157        Ok(())
36158    }
36159
36160    fn generate_temporary_property(&mut self, e: &TemporaryProperty) -> Result<()> {
36161        // TEMPORARY or TEMP or [this] TEMPORARY
36162        if let Some(this) = &e.this {
36163            self.generate_expression(this)?;
36164            self.write_space();
36165        }
36166        self.write_keyword("TEMPORARY");
36167        Ok(())
36168    }
36169
36170    /// Generate a Time function expression
36171    /// For most dialects: TIME('value')
36172    fn generate_time_func(&mut self, e: &UnaryFunc) -> Result<()> {
36173        // Standard: TIME(value)
36174        self.write_keyword("TIME");
36175        self.write("(");
36176        self.generate_expression(&e.this)?;
36177        self.write(")");
36178        Ok(())
36179    }
36180
36181    fn generate_time_add(&mut self, e: &TimeAdd) -> Result<()> {
36182        // TIME_ADD(this, expression, unit)
36183        self.write_keyword("TIME_ADD");
36184        self.write("(");
36185        self.generate_expression(&e.this)?;
36186        self.write(", ");
36187        self.generate_expression(&e.expression)?;
36188        if let Some(unit) = &e.unit {
36189            self.write(", ");
36190            self.write_keyword(unit);
36191        }
36192        self.write(")");
36193        Ok(())
36194    }
36195
36196    fn generate_time_diff(&mut self, e: &TimeDiff) -> Result<()> {
36197        // TIME_DIFF(this, expression, unit)
36198        self.write_keyword("TIME_DIFF");
36199        self.write("(");
36200        self.generate_expression(&e.this)?;
36201        self.write(", ");
36202        self.generate_expression(&e.expression)?;
36203        if let Some(unit) = &e.unit {
36204            self.write(", ");
36205            self.write_keyword(unit);
36206        }
36207        self.write(")");
36208        Ok(())
36209    }
36210
36211    fn generate_time_from_parts(&mut self, e: &TimeFromParts) -> Result<()> {
36212        // TIME_FROM_PARTS(hour, minute, second, nanosecond)
36213        self.write_keyword("TIME_FROM_PARTS");
36214        self.write("(");
36215        let mut first = true;
36216        if let Some(hour) = &e.hour {
36217            self.generate_expression(hour)?;
36218            first = false;
36219        }
36220        if let Some(minute) = &e.min {
36221            if !first {
36222                self.write(", ");
36223            }
36224            self.generate_expression(minute)?;
36225            first = false;
36226        }
36227        if let Some(second) = &e.sec {
36228            if !first {
36229                self.write(", ");
36230            }
36231            self.generate_expression(second)?;
36232            first = false;
36233        }
36234        if let Some(ns) = &e.nano {
36235            if !first {
36236                self.write(", ");
36237            }
36238            self.generate_expression(ns)?;
36239        }
36240        self.write(")");
36241        Ok(())
36242    }
36243
36244    fn generate_time_slice(&mut self, e: &TimeSlice) -> Result<()> {
36245        // TIME_SLICE(this, expression, unit)
36246        self.write_keyword("TIME_SLICE");
36247        self.write("(");
36248        self.generate_expression(&e.this)?;
36249        self.write(", ");
36250        self.generate_expression(&e.expression)?;
36251        self.write(", ");
36252        self.write_keyword(&e.unit);
36253        self.write(")");
36254        Ok(())
36255    }
36256
36257    fn generate_time_str_to_time(&mut self, e: &TimeStrToTime) -> Result<()> {
36258        // TIME_STR_TO_TIME(this)
36259        self.write_keyword("TIME_STR_TO_TIME");
36260        self.write("(");
36261        self.generate_expression(&e.this)?;
36262        self.write(")");
36263        Ok(())
36264    }
36265
36266    fn generate_time_sub(&mut self, e: &TimeSub) -> Result<()> {
36267        // TIME_SUB(this, expression, unit)
36268        self.write_keyword("TIME_SUB");
36269        self.write("(");
36270        self.generate_expression(&e.this)?;
36271        self.write(", ");
36272        self.generate_expression(&e.expression)?;
36273        if let Some(unit) = &e.unit {
36274            self.write(", ");
36275            self.write_keyword(unit);
36276        }
36277        self.write(")");
36278        Ok(())
36279    }
36280
36281    fn generate_time_to_str(&mut self, e: &TimeToStr) -> Result<()> {
36282        match self.config.dialect {
36283            Some(DialectType::Exasol) => {
36284                // Exasol uses TO_CHAR with Exasol-specific format
36285                self.write_keyword("TO_CHAR");
36286                self.write("(");
36287                self.generate_expression(&e.this)?;
36288                self.write(", '");
36289                self.write(&Self::convert_strptime_to_exasol_format(&e.format));
36290                self.write("'");
36291                self.write(")");
36292            }
36293            Some(DialectType::PostgreSQL)
36294            | Some(DialectType::Redshift)
36295            | Some(DialectType::Materialize) => {
36296                // PostgreSQL/Redshift/Materialize uses TO_CHAR with PG-specific format
36297                self.write_keyword("TO_CHAR");
36298                self.write("(");
36299                self.generate_expression(&e.this)?;
36300                self.write(", '");
36301                self.write(&Self::convert_strptime_to_postgres_format(&e.format));
36302                self.write("'");
36303                self.write(")");
36304            }
36305            Some(DialectType::Oracle) => {
36306                // Oracle uses TO_CHAR with PG-like format
36307                self.write_keyword("TO_CHAR");
36308                self.write("(");
36309                self.generate_expression(&e.this)?;
36310                self.write(", '");
36311                self.write(&Self::convert_strptime_to_postgres_format(&e.format));
36312                self.write("'");
36313                self.write(")");
36314            }
36315            Some(DialectType::Drill) => {
36316                // Drill: TO_CHAR with Java format
36317                self.write_keyword("TO_CHAR");
36318                self.write("(");
36319                self.generate_expression(&e.this)?;
36320                self.write(", '");
36321                self.write(&Self::strftime_to_java_format(&e.format));
36322                self.write("'");
36323                self.write(")");
36324            }
36325            Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
36326                // TSQL: FORMAT(value, format) with .NET-style format
36327                self.write_keyword("FORMAT");
36328                self.write("(");
36329                self.generate_expression(&e.this)?;
36330                self.write(", '");
36331                self.write(&Self::strftime_to_tsql_format(&e.format));
36332                self.write("'");
36333                self.write(")");
36334            }
36335            Some(DialectType::DuckDB) => {
36336                // DuckDB: STRFTIME(value, format) - keeps C format
36337                self.write_keyword("STRFTIME");
36338                self.write("(");
36339                self.generate_expression(&e.this)?;
36340                self.write(", '");
36341                self.write(&e.format);
36342                self.write("'");
36343                self.write(")");
36344            }
36345            Some(DialectType::BigQuery) => {
36346                // BigQuery: FORMAT_DATE(format, value) - note swapped arg order
36347                // Normalize: %Y-%m-%d -> %F, %H:%M:%S -> %T
36348                let fmt = e.format.replace("%Y-%m-%d", "%F").replace("%H:%M:%S", "%T");
36349                self.write_keyword("FORMAT_DATE");
36350                self.write("('");
36351                self.write(&fmt);
36352                self.write("', ");
36353                self.generate_expression(&e.this)?;
36354                self.write(")");
36355            }
36356            Some(DialectType::Hive) | Some(DialectType::Spark) | Some(DialectType::Databricks) => {
36357                // Hive/Spark: DATE_FORMAT(value, java_format)
36358                self.write_keyword("DATE_FORMAT");
36359                self.write("(");
36360                self.generate_expression(&e.this)?;
36361                self.write(", '");
36362                self.write(&Self::strftime_to_java_format(&e.format));
36363                self.write("'");
36364                self.write(")");
36365            }
36366            Some(DialectType::Presto) | Some(DialectType::Trino) | Some(DialectType::Athena) => {
36367                // Presto/Trino: DATE_FORMAT(value, format) - keeps C format
36368                self.write_keyword("DATE_FORMAT");
36369                self.write("(");
36370                self.generate_expression(&e.this)?;
36371                self.write(", '");
36372                self.write(&e.format);
36373                self.write("'");
36374                self.write(")");
36375            }
36376            Some(DialectType::Doris) | Some(DialectType::StarRocks) => {
36377                // Doris/StarRocks: DATE_FORMAT(value, format) - keeps C format
36378                self.write_keyword("DATE_FORMAT");
36379                self.write("(");
36380                self.generate_expression(&e.this)?;
36381                self.write(", '");
36382                self.write(&e.format);
36383                self.write("'");
36384                self.write(")");
36385            }
36386            _ => {
36387                // Default: TIME_TO_STR(this, format)
36388                self.write_keyword("TIME_TO_STR");
36389                self.write("(");
36390                self.generate_expression(&e.this)?;
36391                self.write(", '");
36392                self.write(&e.format);
36393                self.write("'");
36394                self.write(")");
36395            }
36396        }
36397        Ok(())
36398    }
36399
36400    fn generate_time_to_unix(&mut self, e: &crate::expressions::UnaryFunc) -> Result<()> {
36401        match self.config.dialect {
36402            Some(DialectType::DuckDB) => {
36403                // DuckDB: EPOCH(x)
36404                self.write_keyword("EPOCH");
36405                self.write("(");
36406                self.generate_expression(&e.this)?;
36407                self.write(")");
36408            }
36409            Some(DialectType::Hive)
36410            | Some(DialectType::Spark)
36411            | Some(DialectType::Databricks)
36412            | Some(DialectType::Doris)
36413            | Some(DialectType::StarRocks)
36414            | Some(DialectType::Drill) => {
36415                // Hive/Spark/Doris/StarRocks/Drill: UNIX_TIMESTAMP(x)
36416                self.write_keyword("UNIX_TIMESTAMP");
36417                self.write("(");
36418                self.generate_expression(&e.this)?;
36419                self.write(")");
36420            }
36421            Some(DialectType::Presto) | Some(DialectType::Trino) => {
36422                // Presto: TO_UNIXTIME(x)
36423                self.write_keyword("TO_UNIXTIME");
36424                self.write("(");
36425                self.generate_expression(&e.this)?;
36426                self.write(")");
36427            }
36428            _ => {
36429                // Default: TIME_TO_UNIX(x)
36430                self.write_keyword("TIME_TO_UNIX");
36431                self.write("(");
36432                self.generate_expression(&e.this)?;
36433                self.write(")");
36434            }
36435        }
36436        Ok(())
36437    }
36438
36439    fn generate_time_str_to_date(&mut self, e: &crate::expressions::UnaryFunc) -> Result<()> {
36440        match self.config.dialect {
36441            Some(DialectType::Hive) => {
36442                // Hive: TO_DATE(x)
36443                self.write_keyword("TO_DATE");
36444                self.write("(");
36445                self.generate_expression(&e.this)?;
36446                self.write(")");
36447            }
36448            _ => {
36449                // Default: TIME_STR_TO_DATE(x)
36450                self.write_keyword("TIME_STR_TO_DATE");
36451                self.write("(");
36452                self.generate_expression(&e.this)?;
36453                self.write(")");
36454            }
36455        }
36456        Ok(())
36457    }
36458
36459    fn generate_time_trunc(&mut self, e: &TimeTrunc) -> Result<()> {
36460        // TIME_TRUNC(this, unit)
36461        self.write_keyword("TIME_TRUNC");
36462        self.write("(");
36463        self.generate_expression(&e.this)?;
36464        self.write(", ");
36465        self.write_keyword(&e.unit);
36466        self.write(")");
36467        Ok(())
36468    }
36469
36470    fn generate_time_unit(&mut self, e: &TimeUnit) -> Result<()> {
36471        // Just output the unit name
36472        if let Some(unit) = &e.unit {
36473            self.write_keyword(unit);
36474        }
36475        Ok(())
36476    }
36477
36478    /// Generate a Timestamp function expression
36479    /// For Exasol: {ts'value'} -> TO_TIMESTAMP('value')
36480    /// For other dialects: TIMESTAMP('value')
36481    fn generate_timestamp_func(&mut self, e: &TimestampFunc) -> Result<()> {
36482        use crate::dialects::DialectType;
36483        use crate::expressions::Literal;
36484
36485        match self.config.dialect {
36486            // Exasol uses TO_TIMESTAMP for Timestamp expressions
36487            Some(DialectType::Exasol) => {
36488                self.write_keyword("TO_TIMESTAMP");
36489                self.write("(");
36490                // Extract the string value from the expression if it's a string literal
36491                if let Some(this) = &e.this {
36492                    match this.as_ref() {
36493                        Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
36494                            let Literal::String(s) = lit.as_ref() else {
36495                                unreachable!()
36496                            };
36497                            self.write("'");
36498                            self.write(s);
36499                            self.write("'");
36500                        }
36501                        _ => {
36502                            self.generate_expression(this)?;
36503                        }
36504                    }
36505                }
36506                self.write(")");
36507            }
36508            // Standard: TIMESTAMP(value) or TIMESTAMP(value, zone)
36509            _ => {
36510                self.write_keyword("TIMESTAMP");
36511                self.write("(");
36512                if let Some(this) = &e.this {
36513                    self.generate_expression(this)?;
36514                }
36515                if let Some(zone) = &e.zone {
36516                    self.write(", ");
36517                    self.generate_expression(zone)?;
36518                }
36519                self.write(")");
36520            }
36521        }
36522        Ok(())
36523    }
36524
36525    fn generate_timestamp_add(&mut self, e: &TimestampAdd) -> Result<()> {
36526        // TIMESTAMP_ADD(this, expression, unit)
36527        self.write_keyword("TIMESTAMP_ADD");
36528        self.write("(");
36529        self.generate_expression(&e.this)?;
36530        self.write(", ");
36531        self.generate_expression(&e.expression)?;
36532        if let Some(unit) = &e.unit {
36533            self.write(", ");
36534            self.write_keyword(unit);
36535        }
36536        self.write(")");
36537        Ok(())
36538    }
36539
36540    fn generate_timestamp_diff(&mut self, e: &TimestampDiff) -> Result<()> {
36541        // TIMESTAMP_DIFF(this, expression, unit)
36542        self.write_keyword("TIMESTAMP_DIFF");
36543        self.write("(");
36544        self.generate_expression(&e.this)?;
36545        self.write(", ");
36546        self.generate_expression(&e.expression)?;
36547        if let Some(unit) = &e.unit {
36548            self.write(", ");
36549            self.write_keyword(unit);
36550        }
36551        self.write(")");
36552        Ok(())
36553    }
36554
36555    fn generate_timestamp_from_parts(&mut self, e: &TimestampFromParts) -> Result<()> {
36556        // TIMESTAMP_FROM_PARTS(this, expression)
36557        self.write_keyword("TIMESTAMP_FROM_PARTS");
36558        self.write("(");
36559        if let Some(this) = &e.this {
36560            self.generate_expression(this)?;
36561        }
36562        if let Some(expression) = &e.expression {
36563            self.write(", ");
36564            self.generate_expression(expression)?;
36565        }
36566        if let Some(zone) = &e.zone {
36567            self.write(", ");
36568            self.generate_expression(zone)?;
36569        }
36570        if let Some(milli) = &e.milli {
36571            self.write(", ");
36572            self.generate_expression(milli)?;
36573        }
36574        self.write(")");
36575        Ok(())
36576    }
36577
36578    fn generate_timestamp_sub(&mut self, e: &TimestampSub) -> Result<()> {
36579        // TIMESTAMP_SUB(this, INTERVAL expression unit)
36580        self.write_keyword("TIMESTAMP_SUB");
36581        self.write("(");
36582        self.generate_expression(&e.this)?;
36583        self.write(", ");
36584        self.write_keyword("INTERVAL");
36585        self.write_space();
36586        self.generate_expression(&e.expression)?;
36587        if let Some(unit) = &e.unit {
36588            self.write_space();
36589            self.write_keyword(unit);
36590        }
36591        self.write(")");
36592        Ok(())
36593    }
36594
36595    fn generate_timestamp_tz_from_parts(&mut self, e: &TimestampTzFromParts) -> Result<()> {
36596        // TIMESTAMP_TZ_FROM_PARTS(...)
36597        self.write_keyword("TIMESTAMP_TZ_FROM_PARTS");
36598        self.write("(");
36599        if let Some(zone) = &e.zone {
36600            self.generate_expression(zone)?;
36601        }
36602        self.write(")");
36603        Ok(())
36604    }
36605
36606    fn generate_to_binary(&mut self, e: &ToBinary) -> Result<()> {
36607        // TO_BINARY(this, [format])
36608        self.write_keyword("TO_BINARY");
36609        self.write("(");
36610        self.generate_expression(&e.this)?;
36611        if let Some(format) = &e.format {
36612            self.write(", '");
36613            self.write(format);
36614            self.write("'");
36615        }
36616        self.write(")");
36617        Ok(())
36618    }
36619
36620    fn generate_to_boolean(&mut self, e: &ToBoolean) -> Result<()> {
36621        // TO_BOOLEAN(this)
36622        self.write_keyword("TO_BOOLEAN");
36623        self.write("(");
36624        self.generate_expression(&e.this)?;
36625        self.write(")");
36626        Ok(())
36627    }
36628
36629    fn generate_to_char(&mut self, e: &ToChar) -> Result<()> {
36630        // TO_CHAR(this, [format], [nlsparam])
36631        self.write_keyword("TO_CHAR");
36632        self.write("(");
36633        self.generate_expression(&e.this)?;
36634        if let Some(format) = &e.format {
36635            self.write(", '");
36636            self.write(format);
36637            self.write("'");
36638        }
36639        if let Some(nlsparam) = &e.nlsparam {
36640            self.write(", ");
36641            self.generate_expression(nlsparam)?;
36642        }
36643        self.write(")");
36644        Ok(())
36645    }
36646
36647    fn generate_to_decfloat(&mut self, e: &ToDecfloat) -> Result<()> {
36648        // TO_DECFLOAT(this, [format])
36649        self.write_keyword("TO_DECFLOAT");
36650        self.write("(");
36651        self.generate_expression(&e.this)?;
36652        if let Some(format) = &e.format {
36653            self.write(", '");
36654            self.write(format);
36655            self.write("'");
36656        }
36657        self.write(")");
36658        Ok(())
36659    }
36660
36661    fn generate_to_double(&mut self, e: &ToDouble) -> Result<()> {
36662        // TO_DOUBLE(this, [format])
36663        self.write_keyword("TO_DOUBLE");
36664        self.write("(");
36665        self.generate_expression(&e.this)?;
36666        if let Some(format) = &e.format {
36667            self.write(", '");
36668            self.write(format);
36669            self.write("'");
36670        }
36671        self.write(")");
36672        Ok(())
36673    }
36674
36675    fn generate_to_file(&mut self, e: &ToFile) -> Result<()> {
36676        // TO_FILE(this, path)
36677        self.write_keyword("TO_FILE");
36678        self.write("(");
36679        self.generate_expression(&e.this)?;
36680        if let Some(path) = &e.path {
36681            self.write(", ");
36682            self.generate_expression(path)?;
36683        }
36684        self.write(")");
36685        Ok(())
36686    }
36687
36688    fn generate_to_number(&mut self, e: &ToNumber) -> Result<()> {
36689        // TO_NUMBER or TRY_TO_NUMBER (this, [format], [precision], [scale])
36690        // If safe flag is set, output TRY_TO_NUMBER
36691        let is_safe = e.safe.is_some();
36692        if is_safe {
36693            self.write_keyword("TRY_TO_NUMBER");
36694        } else {
36695            self.write_keyword("TO_NUMBER");
36696        }
36697        self.write("(");
36698        self.generate_expression(&e.this)?;
36699        let precision_is_snowflake_default = e.precision.is_none()
36700            || matches!(
36701                e.precision.as_deref(),
36702                Some(Expression::Literal(lit))
36703                    if matches!(lit.as_ref(), Literal::Number(n) if n == "0")
36704            );
36705        let is_snowflake_default_precision =
36706            matches!(self.config.dialect, Some(DialectType::Snowflake))
36707                && e.nlsparam.is_none()
36708                && e.scale.is_none()
36709                && matches!(
36710                    e.format.as_deref(),
36711                    Some(Expression::Literal(lit))
36712                        if matches!(lit.as_ref(), Literal::Number(n) if n == "38")
36713                )
36714                && precision_is_snowflake_default;
36715
36716        if !is_snowflake_default_precision {
36717            if let Some(format) = &e.format {
36718                self.write(", ");
36719                self.generate_expression(format)?;
36720            }
36721            if let Some(nlsparam) = &e.nlsparam {
36722                self.write(", ");
36723                self.generate_expression(nlsparam)?;
36724            }
36725            if let Some(precision) = &e.precision {
36726                self.write(", ");
36727                self.generate_expression(precision)?;
36728            }
36729            if let Some(scale) = &e.scale {
36730                self.write(", ");
36731                self.generate_expression(scale)?;
36732            }
36733        }
36734        self.write(")");
36735        Ok(())
36736    }
36737
36738    fn generate_to_table_property(&mut self, e: &ToTableProperty) -> Result<()> {
36739        // TO_TABLE this
36740        self.write_keyword("TO_TABLE");
36741        self.write_space();
36742        self.generate_expression(&e.this)?;
36743        Ok(())
36744    }
36745
36746    fn generate_transaction(&mut self, e: &Transaction) -> Result<()> {
36747        // Check mark to determine the format
36748        let mark_text = e.mark.as_ref().map(|m| match m.as_ref() {
36749            Expression::Identifier(id) => id.name.clone(),
36750            Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
36751                let Literal::String(s) = lit.as_ref() else {
36752                    unreachable!()
36753                };
36754                s.clone()
36755            }
36756            _ => String::new(),
36757        });
36758
36759        let is_start = mark_text.as_ref().map_or(false, |s| s == "START");
36760        let has_transaction_keyword = mark_text.as_ref().map_or(false, |s| s == "TRANSACTION");
36761        let has_with_mark = e.mark.as_ref().map_or(false, |m| {
36762            matches!(m.as_ref(), Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)))
36763        });
36764
36765        // For Presto/Trino: always use START TRANSACTION
36766        let use_start_transaction = matches!(
36767            self.config.dialect,
36768            Some(DialectType::Presto) | Some(DialectType::Trino) | Some(DialectType::Athena)
36769        );
36770        // For most dialects: strip TRANSACTION keyword
36771        let strip_transaction = matches!(
36772            self.config.dialect,
36773            Some(DialectType::Snowflake)
36774                | Some(DialectType::PostgreSQL)
36775                | Some(DialectType::Redshift)
36776                | Some(DialectType::MySQL)
36777                | Some(DialectType::Hive)
36778                | Some(DialectType::Spark)
36779                | Some(DialectType::Databricks)
36780                | Some(DialectType::DuckDB)
36781                | Some(DialectType::Oracle)
36782                | Some(DialectType::Doris)
36783                | Some(DialectType::StarRocks)
36784                | Some(DialectType::Materialize)
36785                | Some(DialectType::ClickHouse)
36786        );
36787
36788        if is_start || use_start_transaction {
36789            // START TRANSACTION [modes]
36790            self.write_keyword("START TRANSACTION");
36791            if let Some(modes) = &e.modes {
36792                self.write_space();
36793                self.generate_expression(modes)?;
36794            }
36795        } else {
36796            // BEGIN [DEFERRED|IMMEDIATE|EXCLUSIVE] [TRANSACTION] [transaction_name] [WITH MARK 'desc']
36797            self.write_keyword("BEGIN");
36798
36799            // Check if `this` is a transaction kind (DEFERRED/IMMEDIATE/EXCLUSIVE)
36800            let is_kind = e.this.as_ref().map_or(false, |t| {
36801                if let Expression::Identifier(id) = t.as_ref() {
36802                    id.name.eq_ignore_ascii_case("DEFERRED")
36803                        || id.name.eq_ignore_ascii_case("IMMEDIATE")
36804                        || id.name.eq_ignore_ascii_case("EXCLUSIVE")
36805                } else {
36806                    false
36807                }
36808            });
36809
36810            // Output kind before TRANSACTION keyword
36811            if is_kind {
36812                if let Some(this) = &e.this {
36813                    self.write_space();
36814                    if let Expression::Identifier(id) = this.as_ref() {
36815                        self.write_keyword(&id.name);
36816                    }
36817                }
36818            }
36819
36820            // Output TRANSACTION keyword if it was present and target supports it
36821            if (has_transaction_keyword || has_with_mark) && !strip_transaction {
36822                self.write_space();
36823                self.write_keyword("TRANSACTION");
36824            }
36825
36826            // Output transaction name (not kind)
36827            if !is_kind {
36828                if let Some(this) = &e.this {
36829                    self.write_space();
36830                    self.generate_expression(this)?;
36831                }
36832            }
36833
36834            // Output WITH MARK 'description' for TSQL
36835            if has_with_mark {
36836                self.write_space();
36837                self.write_keyword("WITH MARK");
36838                if let Some(Expression::Literal(lit)) = e.mark.as_deref() {
36839                    if let Literal::String(desc) = lit.as_ref() {
36840                        if !desc.is_empty() {
36841                            self.write_space();
36842                            self.write(&format!("'{}'", desc));
36843                        }
36844                    }
36845                }
36846            }
36847
36848            // Output modes (isolation levels, etc.)
36849            if let Some(modes) = &e.modes {
36850                self.write_space();
36851                self.generate_expression(modes)?;
36852            }
36853        }
36854        Ok(())
36855    }
36856
36857    fn generate_transform(&mut self, e: &Transform) -> Result<()> {
36858        // TRANSFORM(this, expression)
36859        self.write_keyword("TRANSFORM");
36860        self.write("(");
36861        self.generate_expression(&e.this)?;
36862        self.write(", ");
36863        self.generate_expression(&e.expression)?;
36864        self.write(")");
36865        Ok(())
36866    }
36867
36868    fn generate_transform_model_property(&mut self, e: &TransformModelProperty) -> Result<()> {
36869        // TRANSFORM(expressions)
36870        self.write_keyword("TRANSFORM");
36871        self.write("(");
36872        if self.config.pretty && !e.expressions.is_empty() {
36873            self.indent_level += 1;
36874            for (i, expr) in e.expressions.iter().enumerate() {
36875                if i > 0 {
36876                    self.write(",");
36877                }
36878                self.write_newline();
36879                self.write_indent();
36880                self.generate_expression(expr)?;
36881            }
36882            self.indent_level -= 1;
36883            self.write_newline();
36884            self.write(")");
36885        } else {
36886            for (i, expr) in e.expressions.iter().enumerate() {
36887                if i > 0 {
36888                    self.write(", ");
36889                }
36890                self.generate_expression(expr)?;
36891            }
36892            self.write(")");
36893        }
36894        Ok(())
36895    }
36896
36897    fn generate_transient_property(&mut self, e: &TransientProperty) -> Result<()> {
36898        use crate::dialects::DialectType;
36899        // TRANSIENT is Snowflake-specific; skip for other dialects
36900        if let Some(this) = &e.this {
36901            self.generate_expression(this)?;
36902            if matches!(self.config.dialect, Some(DialectType::Snowflake) | None) {
36903                self.write_space();
36904            }
36905        }
36906        if matches!(self.config.dialect, Some(DialectType::Snowflake) | None) {
36907            self.write_keyword("TRANSIENT");
36908        }
36909        Ok(())
36910    }
36911
36912    fn generate_translate(&mut self, e: &Translate) -> Result<()> {
36913        // TRANSLATE(this, from_, to)
36914        self.write_keyword("TRANSLATE");
36915        self.write("(");
36916        self.generate_expression(&e.this)?;
36917        if let Some(from) = &e.from_ {
36918            self.write(", ");
36919            self.generate_expression(from)?;
36920        }
36921        if let Some(to) = &e.to {
36922            self.write(", ");
36923            self.generate_expression(to)?;
36924        }
36925        self.write(")");
36926        Ok(())
36927    }
36928
36929    fn generate_translate_characters(&mut self, e: &TranslateCharacters) -> Result<()> {
36930        // TRANSLATE(this USING expression)
36931        self.write_keyword("TRANSLATE");
36932        self.write("(");
36933        self.generate_expression(&e.this)?;
36934        self.write_space();
36935        self.write_keyword("USING");
36936        self.write_space();
36937        self.generate_expression(&e.expression)?;
36938        if e.with_error.is_some() {
36939            self.write_space();
36940            self.write_keyword("WITH ERROR");
36941        }
36942        self.write(")");
36943        Ok(())
36944    }
36945
36946    fn generate_truncate_table(&mut self, e: &TruncateTable) -> Result<()> {
36947        // TRUNCATE TABLE table1, table2, ...
36948        self.write_keyword("TRUNCATE TABLE");
36949        self.write_space();
36950        for (i, expr) in e.expressions.iter().enumerate() {
36951            if i > 0 {
36952                self.write(", ");
36953            }
36954            self.generate_expression(expr)?;
36955        }
36956        Ok(())
36957    }
36958
36959    fn generate_try_base64_decode_binary(&mut self, e: &TryBase64DecodeBinary) -> Result<()> {
36960        // TRY_BASE64_DECODE_BINARY(this, [alphabet])
36961        self.write_keyword("TRY_BASE64_DECODE_BINARY");
36962        self.write("(");
36963        self.generate_expression(&e.this)?;
36964        if let Some(alphabet) = &e.alphabet {
36965            self.write(", ");
36966            self.generate_expression(alphabet)?;
36967        }
36968        self.write(")");
36969        Ok(())
36970    }
36971
36972    fn generate_try_base64_decode_string(&mut self, e: &TryBase64DecodeString) -> Result<()> {
36973        // TRY_BASE64_DECODE_STRING(this, [alphabet])
36974        self.write_keyword("TRY_BASE64_DECODE_STRING");
36975        self.write("(");
36976        self.generate_expression(&e.this)?;
36977        if let Some(alphabet) = &e.alphabet {
36978            self.write(", ");
36979            self.generate_expression(alphabet)?;
36980        }
36981        self.write(")");
36982        Ok(())
36983    }
36984
36985    fn generate_try_to_decfloat(&mut self, e: &TryToDecfloat) -> Result<()> {
36986        // TRY_TO_DECFLOAT(this, [format])
36987        self.write_keyword("TRY_TO_DECFLOAT");
36988        self.write("(");
36989        self.generate_expression(&e.this)?;
36990        if let Some(format) = &e.format {
36991            self.write(", '");
36992            self.write(format);
36993            self.write("'");
36994        }
36995        self.write(")");
36996        Ok(())
36997    }
36998
36999    fn generate_ts_or_ds_add(&mut self, e: &TsOrDsAdd) -> Result<()> {
37000        // TS_OR_DS_ADD(this, expression, [unit], [return_type])
37001        self.write_keyword("TS_OR_DS_ADD");
37002        self.write("(");
37003        self.generate_expression(&e.this)?;
37004        self.write(", ");
37005        self.generate_expression(&e.expression)?;
37006        if let Some(unit) = &e.unit {
37007            self.write(", ");
37008            self.write_keyword(unit);
37009        }
37010        if let Some(return_type) = &e.return_type {
37011            self.write(", ");
37012            self.generate_expression(return_type)?;
37013        }
37014        self.write(")");
37015        Ok(())
37016    }
37017
37018    fn generate_ts_or_ds_diff(&mut self, e: &TsOrDsDiff) -> Result<()> {
37019        // TS_OR_DS_DIFF(this, expression, [unit])
37020        self.write_keyword("TS_OR_DS_DIFF");
37021        self.write("(");
37022        self.generate_expression(&e.this)?;
37023        self.write(", ");
37024        self.generate_expression(&e.expression)?;
37025        if let Some(unit) = &e.unit {
37026            self.write(", ");
37027            self.write_keyword(unit);
37028        }
37029        self.write(")");
37030        Ok(())
37031    }
37032
37033    fn generate_ts_or_ds_to_date(&mut self, e: &TsOrDsToDate) -> Result<()> {
37034        let default_time_format = "%Y-%m-%d %H:%M:%S";
37035        let default_date_format = "%Y-%m-%d";
37036        let has_non_default_format = e.format.as_ref().map_or(false, |f| {
37037            f != default_time_format && f != default_date_format
37038        });
37039
37040        if has_non_default_format {
37041            // With non-default format: dialect-specific handling
37042            let fmt = e.format.as_ref().unwrap();
37043            match self.config.dialect {
37044                Some(DialectType::MySQL) | Some(DialectType::StarRocks) => {
37045                    // MySQL/StarRocks: STR_TO_DATE(x, fmt) - no CAST wrapper
37046                    // STR_TO_DATE is the MySQL-native form of StrToTime
37047                    let str_to_time = crate::expressions::StrToTime {
37048                        this: Box::new((*e.this).clone()),
37049                        format: fmt.clone(),
37050                        zone: None,
37051                        safe: None,
37052                        target_type: None,
37053                    };
37054                    self.generate_str_to_time(&str_to_time)?;
37055                }
37056                Some(DialectType::Hive)
37057                | Some(DialectType::Spark)
37058                | Some(DialectType::Databricks) => {
37059                    // Hive/Spark: TO_DATE(x, java_fmt)
37060                    self.write_keyword("TO_DATE");
37061                    self.write("(");
37062                    self.generate_expression(&e.this)?;
37063                    self.write(", '");
37064                    self.write(&Self::strftime_to_java_format(fmt));
37065                    self.write("')");
37066                }
37067                Some(DialectType::Snowflake) => {
37068                    // Snowflake: TO_DATE(x, snowflake_fmt)
37069                    self.write_keyword("TO_DATE");
37070                    self.write("(");
37071                    self.generate_expression(&e.this)?;
37072                    self.write(", '");
37073                    self.write(&Self::strftime_to_snowflake_format(fmt));
37074                    self.write("')");
37075                }
37076                Some(DialectType::Doris) => {
37077                    // Doris: TO_DATE(x) - ignores format
37078                    self.write_keyword("TO_DATE");
37079                    self.write("(");
37080                    self.generate_expression(&e.this)?;
37081                    self.write(")");
37082                }
37083                _ => {
37084                    // Default: CAST(STR_TO_TIME(x, fmt) AS DATE)
37085                    self.write_keyword("CAST");
37086                    self.write("(");
37087                    let str_to_time = crate::expressions::StrToTime {
37088                        this: Box::new((*e.this).clone()),
37089                        format: fmt.clone(),
37090                        zone: None,
37091                        safe: None,
37092                        target_type: None,
37093                    };
37094                    self.generate_str_to_time(&str_to_time)?;
37095                    self.write_keyword(" AS ");
37096                    self.write_keyword("DATE");
37097                    self.write(")");
37098                }
37099            }
37100        } else {
37101            // Without format (or default format): simple date conversion
37102            match self.config.dialect {
37103                Some(DialectType::MySQL)
37104                | Some(DialectType::SQLite)
37105                | Some(DialectType::StarRocks) => {
37106                    // MySQL/SQLite/StarRocks: DATE(x)
37107                    self.write_keyword("DATE");
37108                    self.write("(");
37109                    self.generate_expression(&e.this)?;
37110                    self.write(")");
37111                }
37112                Some(DialectType::Hive)
37113                | Some(DialectType::Spark)
37114                | Some(DialectType::Databricks)
37115                | Some(DialectType::Snowflake)
37116                | Some(DialectType::Doris) => {
37117                    // Hive/Spark/Databricks/Snowflake/Doris: TO_DATE(x)
37118                    self.write_keyword("TO_DATE");
37119                    self.write("(");
37120                    self.generate_expression(&e.this)?;
37121                    self.write(")");
37122                }
37123                Some(DialectType::Presto)
37124                | Some(DialectType::Trino)
37125                | Some(DialectType::Athena) => {
37126                    // Presto/Trino: CAST(CAST(x AS TIMESTAMP) AS DATE)
37127                    self.write_keyword("CAST");
37128                    self.write("(");
37129                    self.write_keyword("CAST");
37130                    self.write("(");
37131                    self.generate_expression(&e.this)?;
37132                    self.write_keyword(" AS ");
37133                    self.write_keyword("TIMESTAMP");
37134                    self.write(")");
37135                    self.write_keyword(" AS ");
37136                    self.write_keyword("DATE");
37137                    self.write(")");
37138                }
37139                Some(DialectType::ClickHouse) => {
37140                    // ClickHouse: CAST(x AS Nullable(DATE))
37141                    self.write_keyword("CAST");
37142                    self.write("(");
37143                    self.generate_expression(&e.this)?;
37144                    self.write_keyword(" AS ");
37145                    self.write("Nullable(DATE)");
37146                    self.write(")");
37147                }
37148                _ => {
37149                    // Default: CAST(x AS DATE)
37150                    self.write_keyword("CAST");
37151                    self.write("(");
37152                    self.generate_expression(&e.this)?;
37153                    self.write_keyword(" AS ");
37154                    self.write_keyword("DATE");
37155                    self.write(")");
37156                }
37157            }
37158        }
37159        Ok(())
37160    }
37161
37162    fn generate_ts_or_ds_to_time(&mut self, e: &TsOrDsToTime) -> Result<()> {
37163        // TS_OR_DS_TO_TIME(this, [format])
37164        self.write_keyword("TS_OR_DS_TO_TIME");
37165        self.write("(");
37166        self.generate_expression(&e.this)?;
37167        if let Some(format) = &e.format {
37168            self.write(", '");
37169            self.write(format);
37170            self.write("'");
37171        }
37172        self.write(")");
37173        Ok(())
37174    }
37175
37176    fn generate_unhex(&mut self, e: &Unhex) -> Result<()> {
37177        // UNHEX(this, [expression])
37178        self.write_keyword("UNHEX");
37179        self.write("(");
37180        self.generate_expression(&e.this)?;
37181        if let Some(expression) = &e.expression {
37182            self.write(", ");
37183            self.generate_expression(expression)?;
37184        }
37185        self.write(")");
37186        Ok(())
37187    }
37188
37189    fn generate_unicode_string(&mut self, e: &UnicodeString) -> Result<()> {
37190        // U&this [UESCAPE escape]
37191        self.write("U&");
37192        self.generate_expression(&e.this)?;
37193        if let Some(escape) = &e.escape {
37194            self.write_space();
37195            self.write_keyword("UESCAPE");
37196            self.write_space();
37197            self.generate_expression(escape)?;
37198        }
37199        Ok(())
37200    }
37201
37202    fn generate_uniform(&mut self, e: &Uniform) -> Result<()> {
37203        // UNIFORM(this, expression, [gen], [seed])
37204        self.write_keyword("UNIFORM");
37205        self.write("(");
37206        self.generate_expression(&e.this)?;
37207        self.write(", ");
37208        self.generate_expression(&e.expression)?;
37209        if let Some(gen) = &e.gen {
37210            self.write(", ");
37211            self.generate_expression(gen)?;
37212        }
37213        if let Some(seed) = &e.seed {
37214            self.write(", ");
37215            self.generate_expression(seed)?;
37216        }
37217        self.write(")");
37218        Ok(())
37219    }
37220
37221    fn generate_unique_column_constraint(&mut self, e: &UniqueColumnConstraint) -> Result<()> {
37222        // UNIQUE [NULLS NOT DISTINCT] [this] [index_type] [on_conflict] [options]
37223        self.write_keyword("UNIQUE");
37224        // Output NULLS NOT DISTINCT if nulls is set (PostgreSQL 15+ feature)
37225        if e.nulls.is_some() {
37226            self.write(" NULLS NOT DISTINCT");
37227        }
37228        if let Some(this) = &e.this {
37229            self.write_space();
37230            self.generate_expression(this)?;
37231        }
37232        if let Some(index_type) = &e.index_type {
37233            self.write(" USING ");
37234            self.generate_expression(index_type)?;
37235        }
37236        if let Some(on_conflict) = &e.on_conflict {
37237            self.write_space();
37238            self.generate_expression(on_conflict)?;
37239        }
37240        for opt in &e.options {
37241            self.write_space();
37242            self.generate_expression(opt)?;
37243        }
37244        Ok(())
37245    }
37246
37247    fn generate_unique_key_property(&mut self, e: &UniqueKeyProperty) -> Result<()> {
37248        // UNIQUE KEY (expressions)
37249        self.write_keyword("UNIQUE KEY");
37250        self.write(" (");
37251        for (i, expr) in e.expressions.iter().enumerate() {
37252            if i > 0 {
37253                self.write(", ");
37254            }
37255            self.generate_expression(expr)?;
37256        }
37257        self.write(")");
37258        Ok(())
37259    }
37260
37261    fn generate_rollup_property(&mut self, e: &RollupProperty) -> Result<()> {
37262        // ROLLUP (r1(col1, col2), r2(col1))
37263        self.write_keyword("ROLLUP");
37264        self.write(" (");
37265        for (i, index) in e.expressions.iter().enumerate() {
37266            if i > 0 {
37267                self.write(", ");
37268            }
37269            self.generate_identifier(&index.name)?;
37270            self.write("(");
37271            for (j, col) in index.expressions.iter().enumerate() {
37272                if j > 0 {
37273                    self.write(", ");
37274                }
37275                self.generate_identifier(col)?;
37276            }
37277            self.write(")");
37278        }
37279        self.write(")");
37280        Ok(())
37281    }
37282
37283    fn generate_unix_to_str(&mut self, e: &UnixToStr) -> Result<()> {
37284        match self.config.dialect {
37285            Some(DialectType::DuckDB) => {
37286                // DuckDB: STRFTIME(TO_TIMESTAMP(value), format)
37287                self.write_keyword("STRFTIME");
37288                self.write("(");
37289                self.write_keyword("TO_TIMESTAMP");
37290                self.write("(");
37291                self.generate_expression(&e.this)?;
37292                self.write("), '");
37293                if let Some(format) = &e.format {
37294                    self.write(format);
37295                }
37296                self.write("')");
37297            }
37298            Some(DialectType::Hive) => {
37299                // Hive: FROM_UNIXTIME(value, format) - elide format when it's the default
37300                self.write_keyword("FROM_UNIXTIME");
37301                self.write("(");
37302                self.generate_expression(&e.this)?;
37303                if let Some(format) = &e.format {
37304                    if format != "yyyy-MM-dd HH:mm:ss" {
37305                        self.write(", '");
37306                        self.write(format);
37307                        self.write("'");
37308                    }
37309                }
37310                self.write(")");
37311            }
37312            Some(DialectType::Presto) | Some(DialectType::Trino) => {
37313                // Presto: DATE_FORMAT(FROM_UNIXTIME(value), format)
37314                self.write_keyword("DATE_FORMAT");
37315                self.write("(");
37316                self.write_keyword("FROM_UNIXTIME");
37317                self.write("(");
37318                self.generate_expression(&e.this)?;
37319                self.write("), '");
37320                if let Some(format) = &e.format {
37321                    self.write(format);
37322                }
37323                self.write("')");
37324            }
37325            Some(DialectType::Spark) | Some(DialectType::Databricks) => {
37326                // Spark: FROM_UNIXTIME(value, format)
37327                self.write_keyword("FROM_UNIXTIME");
37328                self.write("(");
37329                self.generate_expression(&e.this)?;
37330                if let Some(format) = &e.format {
37331                    self.write(", '");
37332                    self.write(format);
37333                    self.write("'");
37334                }
37335                self.write(")");
37336            }
37337            _ => {
37338                // Default: UNIX_TO_STR(this, [format])
37339                self.write_keyword("UNIX_TO_STR");
37340                self.write("(");
37341                self.generate_expression(&e.this)?;
37342                if let Some(format) = &e.format {
37343                    self.write(", '");
37344                    self.write(format);
37345                    self.write("'");
37346                }
37347                self.write(")");
37348            }
37349        }
37350        Ok(())
37351    }
37352
37353    fn generate_unix_to_time(&mut self, e: &UnixToTime) -> Result<()> {
37354        use crate::dialects::DialectType;
37355        let scale = e.scale.unwrap_or(0); // 0 = seconds
37356
37357        match self.config.dialect {
37358            Some(DialectType::Snowflake) => {
37359                // Snowflake: TO_TIMESTAMP(value[, scale]) - skip scale for seconds (0)
37360                self.write_keyword("TO_TIMESTAMP");
37361                self.write("(");
37362                self.generate_expression(&e.this)?;
37363                if let Some(s) = e.scale {
37364                    if s > 0 {
37365                        self.write(", ");
37366                        self.write(&s.to_string());
37367                    }
37368                }
37369                self.write(")");
37370            }
37371            Some(DialectType::BigQuery) => {
37372                // BigQuery: TIMESTAMP_SECONDS(value) / TIMESTAMP_MILLIS(value)
37373                // or TIMESTAMP_SECONDS(CAST(value / POWER(10, scale) AS INT64)) for other scales
37374                match scale {
37375                    0 => {
37376                        self.write_keyword("TIMESTAMP_SECONDS");
37377                        self.write("(");
37378                        self.generate_expression(&e.this)?;
37379                        self.write(")");
37380                    }
37381                    3 => {
37382                        self.write_keyword("TIMESTAMP_MILLIS");
37383                        self.write("(");
37384                        self.generate_expression(&e.this)?;
37385                        self.write(")");
37386                    }
37387                    6 => {
37388                        self.write_keyword("TIMESTAMP_MICROS");
37389                        self.write("(");
37390                        self.generate_expression(&e.this)?;
37391                        self.write(")");
37392                    }
37393                    _ => {
37394                        // TIMESTAMP_SECONDS(CAST(value / POWER(10, scale) AS INT64))
37395                        self.write_keyword("TIMESTAMP_SECONDS");
37396                        self.write("(CAST(");
37397                        self.generate_expression(&e.this)?;
37398                        self.write(&format!(" / POWER(10, {}) AS INT64))", scale));
37399                    }
37400                }
37401            }
37402            Some(DialectType::Spark) => {
37403                // Spark: CAST(FROM_UNIXTIME(value) AS TIMESTAMP) for scale=0
37404                // TIMESTAMP_MILLIS(value) for scale=3
37405                // TIMESTAMP_MICROS(value) for scale=6
37406                // TIMESTAMP_SECONDS(value / POWER(10, scale)) for other scales
37407                match scale {
37408                    0 => {
37409                        self.write_keyword("CAST");
37410                        self.write("(");
37411                        self.write_keyword("FROM_UNIXTIME");
37412                        self.write("(");
37413                        self.generate_expression(&e.this)?;
37414                        self.write(") ");
37415                        self.write_keyword("AS TIMESTAMP");
37416                        self.write(")");
37417                    }
37418                    3 => {
37419                        self.write_keyword("TIMESTAMP_MILLIS");
37420                        self.write("(");
37421                        self.generate_expression(&e.this)?;
37422                        self.write(")");
37423                    }
37424                    6 => {
37425                        self.write_keyword("TIMESTAMP_MICROS");
37426                        self.write("(");
37427                        self.generate_expression(&e.this)?;
37428                        self.write(")");
37429                    }
37430                    _ => {
37431                        self.write_keyword("TIMESTAMP_SECONDS");
37432                        self.write("(");
37433                        self.generate_expression(&e.this)?;
37434                        self.write(&format!(" / POWER(10, {}))", scale));
37435                    }
37436                }
37437            }
37438            Some(DialectType::Databricks) => {
37439                // Databricks: CAST(FROM_UNIXTIME(value) AS TIMESTAMP) for scale=0
37440                // TIMESTAMP_MILLIS(value) for scale=3
37441                // TIMESTAMP_MICROS(value) for scale=6
37442                match scale {
37443                    0 => {
37444                        self.write_keyword("CAST");
37445                        self.write("(");
37446                        self.write_keyword("FROM_UNIXTIME");
37447                        self.write("(");
37448                        self.generate_expression(&e.this)?;
37449                        self.write(") ");
37450                        self.write_keyword("AS TIMESTAMP");
37451                        self.write(")");
37452                    }
37453                    3 => {
37454                        self.write_keyword("TIMESTAMP_MILLIS");
37455                        self.write("(");
37456                        self.generate_expression(&e.this)?;
37457                        self.write(")");
37458                    }
37459                    6 => {
37460                        self.write_keyword("TIMESTAMP_MICROS");
37461                        self.write("(");
37462                        self.generate_expression(&e.this)?;
37463                        self.write(")");
37464                    }
37465                    _ => {
37466                        self.write_keyword("TIMESTAMP_SECONDS");
37467                        self.write("(");
37468                        self.generate_expression(&e.this)?;
37469                        self.write(&format!(" / POWER(10, {}))", scale));
37470                    }
37471                }
37472            }
37473            Some(DialectType::Hive) => {
37474                // Hive: FROM_UNIXTIME(value)
37475                if scale == 0 {
37476                    self.write_keyword("FROM_UNIXTIME");
37477                    self.write("(");
37478                    self.generate_expression(&e.this)?;
37479                    self.write(")");
37480                } else {
37481                    self.write_keyword("FROM_UNIXTIME");
37482                    self.write("(");
37483                    self.generate_expression(&e.this)?;
37484                    self.write(&format!(" / POWER(10, {})", scale));
37485                    self.write(")");
37486                }
37487            }
37488            Some(DialectType::Presto) | Some(DialectType::Trino) => {
37489                // Presto: FROM_UNIXTIME(CAST(value AS DOUBLE) / POW(10, scale)) for scale > 0
37490                // FROM_UNIXTIME(value) for scale=0
37491                if scale == 0 {
37492                    self.write_keyword("FROM_UNIXTIME");
37493                    self.write("(");
37494                    self.generate_expression(&e.this)?;
37495                    self.write(")");
37496                } else {
37497                    self.write_keyword("FROM_UNIXTIME");
37498                    self.write("(CAST(");
37499                    self.generate_expression(&e.this)?;
37500                    self.write(&format!(" AS DOUBLE) / POW(10, {}))", scale));
37501                }
37502            }
37503            Some(DialectType::DuckDB) => {
37504                // DuckDB: TO_TIMESTAMP(value) for scale=0
37505                // EPOCH_MS(value) for scale=3
37506                // MAKE_TIMESTAMP(value) for scale=6
37507                match scale {
37508                    0 => {
37509                        self.write_keyword("TO_TIMESTAMP");
37510                        self.write("(");
37511                        self.generate_expression(&e.this)?;
37512                        self.write(")");
37513                    }
37514                    3 => {
37515                        self.write_keyword("EPOCH_MS");
37516                        self.write("(");
37517                        self.generate_expression(&e.this)?;
37518                        self.write(")");
37519                    }
37520                    6 => {
37521                        self.write_keyword("MAKE_TIMESTAMP");
37522                        self.write("(");
37523                        self.generate_expression(&e.this)?;
37524                        self.write(")");
37525                    }
37526                    _ => {
37527                        self.write_keyword("TO_TIMESTAMP");
37528                        self.write("(");
37529                        self.generate_expression(&e.this)?;
37530                        self.write(&format!(" / POWER(10, {}))", scale));
37531                        self.write_keyword(" AT TIME ZONE");
37532                        self.write(" 'UTC'");
37533                    }
37534                }
37535            }
37536            Some(DialectType::Doris) | Some(DialectType::StarRocks) => {
37537                // Doris/StarRocks: FROM_UNIXTIME(value)
37538                self.write_keyword("FROM_UNIXTIME");
37539                self.write("(");
37540                self.generate_expression(&e.this)?;
37541                self.write(")");
37542            }
37543            Some(DialectType::Oracle) => {
37544                // Oracle: TO_DATE('1970-01-01', 'YYYY-MM-DD') + (x / 86400)
37545                self.write("TO_DATE('1970-01-01', 'YYYY-MM-DD') + (");
37546                self.generate_expression(&e.this)?;
37547                self.write(" / 86400)");
37548            }
37549            Some(DialectType::Redshift) => {
37550                // Redshift: (TIMESTAMP 'epoch' + value * INTERVAL '1 SECOND') for scale=0
37551                // (TIMESTAMP 'epoch' + (value / POWER(10, scale)) * INTERVAL '1 SECOND') for scale > 0
37552                self.write("(TIMESTAMP 'epoch' + ");
37553                if scale == 0 {
37554                    self.generate_expression(&e.this)?;
37555                } else {
37556                    self.write("(");
37557                    self.generate_expression(&e.this)?;
37558                    self.write(&format!(" / POWER(10, {}))", scale));
37559                }
37560                self.write(" * INTERVAL '1 SECOND')");
37561            }
37562            Some(DialectType::Exasol) => {
37563                // Exasol: FROM_POSIX_TIME(value)
37564                self.write_keyword("FROM_POSIX_TIME");
37565                self.write("(");
37566                self.generate_expression(&e.this)?;
37567                self.write(")");
37568            }
37569            _ => {
37570                // Default: TO_TIMESTAMP(value[, scale])
37571                self.write_keyword("TO_TIMESTAMP");
37572                self.write("(");
37573                self.generate_expression(&e.this)?;
37574                if let Some(s) = e.scale {
37575                    self.write(", ");
37576                    self.write(&s.to_string());
37577                }
37578                self.write(")");
37579            }
37580        }
37581        Ok(())
37582    }
37583
37584    fn generate_unpivot_columns(&mut self, e: &UnpivotColumns) -> Result<()> {
37585        // NAME col VALUE col1, col2, ...
37586        if !matches!(&*e.this, Expression::Null(_)) {
37587            self.write_keyword("NAME");
37588            self.write_space();
37589            self.generate_expression(&e.this)?;
37590        }
37591        if !e.expressions.is_empty() {
37592            self.write_space();
37593            self.write_keyword("VALUE");
37594            self.write_space();
37595            for (i, expr) in e.expressions.iter().enumerate() {
37596                if i > 0 {
37597                    self.write(", ");
37598                }
37599                self.generate_expression(expr)?;
37600            }
37601        }
37602        Ok(())
37603    }
37604
37605    fn generate_user_defined_function(&mut self, e: &UserDefinedFunction) -> Result<()> {
37606        // this(expressions) or (this)(expressions)
37607        if e.wrapped.is_some() {
37608            self.write("(");
37609        }
37610        self.generate_expression(&e.this)?;
37611        if e.wrapped.is_some() {
37612            self.write(")");
37613        }
37614        self.write("(");
37615        for (i, expr) in e.expressions.iter().enumerate() {
37616            if i > 0 {
37617                self.write(", ");
37618            }
37619            self.generate_expression(expr)?;
37620        }
37621        self.write(")");
37622        Ok(())
37623    }
37624
37625    fn generate_using_template_property(&mut self, e: &UsingTemplateProperty) -> Result<()> {
37626        // USING TEMPLATE this
37627        self.write_keyword("USING TEMPLATE");
37628        self.write_space();
37629        self.generate_expression(&e.this)?;
37630        Ok(())
37631    }
37632
37633    fn generate_utc_time(&mut self, _e: &UtcTime) -> Result<()> {
37634        // UTC_TIME
37635        self.write_keyword("UTC_TIME");
37636        Ok(())
37637    }
37638
37639    fn generate_utc_timestamp(&mut self, _e: &UtcTimestamp) -> Result<()> {
37640        if matches!(
37641            self.config.dialect,
37642            Some(crate::dialects::DialectType::ClickHouse)
37643        ) {
37644            self.write_keyword("CURRENT_TIMESTAMP");
37645            self.write("('UTC')");
37646        } else {
37647            self.write_keyword("UTC_TIMESTAMP");
37648        }
37649        Ok(())
37650    }
37651
37652    fn generate_uuid(&mut self, e: &Uuid) -> Result<()> {
37653        use crate::dialects::DialectType;
37654        // Choose UUID function name based on target dialect
37655        let func_name = match self.config.dialect {
37656            Some(DialectType::Snowflake) => "UUID_STRING",
37657            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift) => "GEN_RANDOM_UUID",
37658            Some(DialectType::BigQuery) => "GENERATE_UUID",
37659            _ => {
37660                if let Some(name) = &e.name {
37661                    name.as_str()
37662                } else {
37663                    "UUID"
37664                }
37665            }
37666        };
37667        self.write_keyword(func_name);
37668        self.write("(");
37669        if let Some(this) = &e.this {
37670            self.generate_expression(this)?;
37671        }
37672        self.write(")");
37673        Ok(())
37674    }
37675
37676    fn generate_var_map(&mut self, e: &VarMap) -> Result<()> {
37677        // MAP(key1, value1, key2, value2, ...)
37678        self.write_keyword("MAP");
37679        self.write("(");
37680        let mut first = true;
37681        for (k, v) in e.keys.iter().zip(e.values.iter()) {
37682            if !first {
37683                self.write(", ");
37684            }
37685            self.generate_expression(k)?;
37686            self.write(", ");
37687            self.generate_expression(v)?;
37688            first = false;
37689        }
37690        self.write(")");
37691        Ok(())
37692    }
37693
37694    fn generate_vector_search(&mut self, e: &VectorSearch) -> Result<()> {
37695        // VECTOR_SEARCH(this, column_to_search, query_table, query_column_to_search, top_k, distance_type, ...)
37696        self.write_keyword("VECTOR_SEARCH");
37697        self.write("(");
37698        self.generate_expression(&e.this)?;
37699        if let Some(col) = &e.column_to_search {
37700            self.write(", ");
37701            self.generate_expression(col)?;
37702        }
37703        if let Some(query_table) = &e.query_table {
37704            self.write(", ");
37705            self.generate_expression(query_table)?;
37706        }
37707        if let Some(query_col) = &e.query_column_to_search {
37708            self.write(", ");
37709            self.generate_expression(query_col)?;
37710        }
37711        if let Some(top_k) = &e.top_k {
37712            self.write(", ");
37713            self.generate_expression(top_k)?;
37714        }
37715        if let Some(dist_type) = &e.distance_type {
37716            self.write(", ");
37717            self.generate_expression(dist_type)?;
37718        }
37719        self.write(")");
37720        Ok(())
37721    }
37722
37723    fn generate_version(&mut self, e: &Version) -> Result<()> {
37724        // Python: f"FOR {expression.name} {kind} {expr}"
37725        // e.this = Identifier("TIMESTAMP" or "VERSION")
37726        // e.kind = "AS OF" (or "BETWEEN", etc.)
37727        // e.expression = the value expression
37728        // Hive does NOT use the FOR prefix for time travel
37729        use crate::dialects::DialectType;
37730        let skip_for = matches!(
37731            self.config.dialect,
37732            Some(DialectType::Hive) | Some(DialectType::Spark) | Some(DialectType::Databricks)
37733        );
37734        if !skip_for {
37735            self.write_keyword("FOR");
37736            self.write_space();
37737        }
37738        // Extract the name from this (which is an Identifier expression)
37739        match e.this.as_ref() {
37740            Expression::Identifier(ident) => {
37741                self.write_keyword(&ident.name);
37742            }
37743            _ => {
37744                self.generate_expression(&e.this)?;
37745            }
37746        }
37747        self.write_space();
37748        self.write_keyword(&e.kind);
37749        if let Some(expression) = &e.expression {
37750            self.write_space();
37751            self.generate_expression(expression)?;
37752        }
37753        Ok(())
37754    }
37755
37756    fn generate_view_attribute_property(&mut self, e: &ViewAttributeProperty) -> Result<()> {
37757        // Python: return self.sql(expression, "this")
37758        self.generate_expression(&e.this)?;
37759        Ok(())
37760    }
37761
37762    fn generate_volatile_property(&mut self, e: &VolatileProperty) -> Result<()> {
37763        // Python: return "VOLATILE" if expression.args.get("this") is None else "NOT VOLATILE"
37764        if e.this.is_some() {
37765            self.write_keyword("NOT VOLATILE");
37766        } else {
37767            self.write_keyword("VOLATILE");
37768        }
37769        Ok(())
37770    }
37771
37772    fn generate_watermark_column_constraint(
37773        &mut self,
37774        e: &WatermarkColumnConstraint,
37775    ) -> Result<()> {
37776        // Python: f"WATERMARK FOR {self.sql(expression, 'this')} AS {self.sql(expression, 'expression')}"
37777        self.write_keyword("WATERMARK FOR");
37778        self.write_space();
37779        self.generate_expression(&e.this)?;
37780        self.write_space();
37781        self.write_keyword("AS");
37782        self.write_space();
37783        self.generate_expression(&e.expression)?;
37784        Ok(())
37785    }
37786
37787    fn generate_week(&mut self, e: &Week) -> Result<()> {
37788        // Python: return self.func("WEEK", expression.this, expression.args.get("mode"))
37789        self.write_keyword("WEEK");
37790        self.write("(");
37791        self.generate_expression(&e.this)?;
37792        if let Some(mode) = &e.mode {
37793            self.write(", ");
37794            self.generate_expression(mode)?;
37795        }
37796        self.write(")");
37797        Ok(())
37798    }
37799
37800    fn generate_when(&mut self, e: &When) -> Result<()> {
37801        // Python: WHEN {matched}{source}{condition} THEN {then}
37802        // matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED"
37803        // source = " BY SOURCE" if MATCHED_BY_SOURCE and expression.args.get("source") else ""
37804        self.write_keyword("WHEN");
37805        self.write_space();
37806
37807        // Check if matched
37808        if let Some(matched) = &e.matched {
37809            // Check the expression - if it's a boolean true, use MATCHED, otherwise NOT MATCHED
37810            match matched.as_ref() {
37811                Expression::Boolean(b) if b.value => {
37812                    self.write_keyword("MATCHED");
37813                }
37814                _ => {
37815                    self.write_keyword("NOT MATCHED");
37816                }
37817            }
37818        } else {
37819            self.write_keyword("NOT MATCHED");
37820        }
37821
37822        // BY SOURCE / BY TARGET
37823        // source = Boolean(true) means BY SOURCE, Boolean(false) means BY TARGET
37824        // BY TARGET is the default and typically omitted in output
37825        // Only emit if the dialect supports BY SOURCE syntax
37826        if self.config.matched_by_source {
37827            if let Some(source) = &e.source {
37828                if let Expression::Boolean(b) = source.as_ref() {
37829                    if b.value {
37830                        // BY SOURCE
37831                        self.write_space();
37832                        self.write_keyword("BY SOURCE");
37833                    }
37834                    // BY TARGET (b.value == false) is omitted as it's the default
37835                } else {
37836                    // For non-boolean source, output as BY SOURCE (legacy behavior)
37837                    self.write_space();
37838                    self.write_keyword("BY SOURCE");
37839                }
37840            }
37841        }
37842
37843        // Condition
37844        if let Some(condition) = &e.condition {
37845            self.write_space();
37846            self.write_keyword("AND");
37847            self.write_space();
37848            self.generate_expression(condition)?;
37849        }
37850
37851        self.write_space();
37852        self.write_keyword("THEN");
37853        self.write_space();
37854
37855        // Generate the then expression (could be INSERT, UPDATE, DELETE)
37856        // MERGE actions are stored as Tuples with the action keyword as first element
37857        self.generate_merge_action(&e.then)?;
37858
37859        Ok(())
37860    }
37861
37862    fn generate_merge_action(&mut self, action: &Expression) -> Result<()> {
37863        match action {
37864            Expression::Tuple(tuple) => {
37865                let elements = &tuple.expressions;
37866                if elements.is_empty() {
37867                    return self.generate_expression(action);
37868                }
37869                // Check if first element is a Var (INSERT, UPDATE, DELETE, etc.)
37870                match &elements[0] {
37871                    Expression::Var(v) if v.this == "INSERT" => {
37872                        self.write_keyword("INSERT");
37873                        // Spark: INSERT * (insert all columns)
37874                        if elements.len() > 1 && matches!(&elements[1], Expression::Star(_)) {
37875                            self.write(" *");
37876                            if let Some(Expression::Where(w)) = elements.get(2) {
37877                                self.write_space();
37878                                self.generate_where(w)?;
37879                            }
37880                        } else {
37881                            let mut values_idx = 1;
37882                            // Check if second element is column list (Tuple)
37883                            if elements.len() > 1 {
37884                                if let Expression::Tuple(cols) = &elements[1] {
37885                                    // Could be columns or values - if there's a third element, second is columns
37886                                    if elements.len() > 2 {
37887                                        // Second is columns, third is values
37888                                        self.write(" (");
37889                                        for (i, col) in cols.expressions.iter().enumerate() {
37890                                            if i > 0 {
37891                                                self.write(", ");
37892                                            }
37893                                            // Strip MERGE target qualifiers from INSERT column list
37894                                            if !self.merge_strip_qualifiers.is_empty() {
37895                                                let stripped = self.strip_merge_qualifier(col);
37896                                                self.generate_expression(&stripped)?;
37897                                            } else {
37898                                                self.generate_expression(col)?;
37899                                            }
37900                                        }
37901                                        self.write(")");
37902                                        values_idx = 2;
37903                                    } else {
37904                                        // Only two elements: INSERT + values (no explicit columns)
37905                                        values_idx = 1;
37906                                    }
37907                                }
37908                            }
37909                            let mut next_idx = values_idx;
37910                            // Generate VALUES clause
37911                            if values_idx < elements.len()
37912                                && !matches!(&elements[values_idx], Expression::Where(_))
37913                            {
37914                                // Check if it's INSERT ROW (BigQuery) — no VALUES keyword needed
37915                                let is_row = matches!(&elements[values_idx], Expression::Var(v) if v.this == "ROW");
37916                                if !is_row {
37917                                    self.write_space();
37918                                    self.write_keyword("VALUES");
37919                                }
37920                                self.write(" ");
37921                                if let Expression::Tuple(vals) = &elements[values_idx] {
37922                                    self.write("(");
37923                                    for (i, val) in vals.expressions.iter().enumerate() {
37924                                        if i > 0 {
37925                                            self.write(", ");
37926                                        }
37927                                        self.generate_expression(val)?;
37928                                    }
37929                                    self.write(")");
37930                                } else {
37931                                    self.generate_expression(&elements[values_idx])?;
37932                                }
37933                                next_idx += 1;
37934                            }
37935                            if let Some(Expression::Where(w)) = elements.get(next_idx) {
37936                                self.write_space();
37937                                self.generate_where(w)?;
37938                            }
37939                        } // close else for INSERT * check
37940                    }
37941                    Expression::Var(v) if v.this == "UPDATE" => {
37942                        self.write_keyword("UPDATE");
37943                        // Spark: UPDATE * (update all columns)
37944                        if elements.len() > 1 && matches!(&elements[1], Expression::Star(_)) {
37945                            self.write(" *");
37946                            if let Some(Expression::Where(w)) = elements.get(2) {
37947                                self.write_space();
37948                                self.generate_where(w)?;
37949                            }
37950                        } else if elements.len() > 1 {
37951                            self.write_space();
37952                            self.write_keyword("SET");
37953                            // In pretty mode, put assignments on next line with extra indent
37954                            if self.config.pretty {
37955                                self.write_newline();
37956                                self.indent_level += 1;
37957                                self.write_indent();
37958                            } else {
37959                                self.write_space();
37960                            }
37961                            if let Expression::Tuple(assignments) = &elements[1] {
37962                                for (i, assignment) in assignments.expressions.iter().enumerate() {
37963                                    if i > 0 {
37964                                        if self.config.pretty {
37965                                            self.write(",");
37966                                            self.write_newline();
37967                                            self.write_indent();
37968                                        } else {
37969                                            self.write(", ");
37970                                        }
37971                                    }
37972                                    // Strip MERGE target qualifiers from left side of UPDATE SET
37973                                    if !self.merge_strip_qualifiers.is_empty() {
37974                                        self.generate_merge_set_assignment(assignment)?;
37975                                    } else {
37976                                        self.generate_expression(assignment)?;
37977                                    }
37978                                }
37979                            } else {
37980                                self.generate_expression(&elements[1])?;
37981                            }
37982                            if self.config.pretty {
37983                                self.indent_level -= 1;
37984                            }
37985                            if let Some(Expression::Where(w)) = elements.get(2) {
37986                                self.write_space();
37987                                self.generate_where(w)?;
37988                            }
37989                        }
37990                    }
37991                    Expression::Var(v) if v.this == "DELETE" => {
37992                        self.write_keyword("DELETE");
37993                        if let Some(Expression::Where(w)) = elements.get(1) {
37994                            self.write_space();
37995                            self.generate_where(w)?;
37996                        }
37997                    }
37998                    _ => {
37999                        // Fallback: generic tuple generation
38000                        self.generate_expression(action)?;
38001                    }
38002                }
38003            }
38004            Expression::Var(v)
38005                if v.this == "INSERT"
38006                    || v.this == "UPDATE"
38007                    || v.this == "DELETE"
38008                    || v.this == "DO NOTHING" =>
38009            {
38010                self.write_keyword(&v.this);
38011            }
38012            _ => {
38013                self.generate_expression(action)?;
38014            }
38015        }
38016        Ok(())
38017    }
38018
38019    /// Generate a MERGE UPDATE SET assignment, stripping target table qualifier from left side
38020    fn generate_merge_set_assignment(&mut self, assignment: &Expression) -> Result<()> {
38021        match assignment {
38022            Expression::Eq(eq) => {
38023                // Strip qualifier from the left side if it matches a MERGE target name
38024                let stripped_left = self.strip_merge_qualifier(&eq.left);
38025                self.generate_expression(&stripped_left)?;
38026                self.write(" = ");
38027                self.generate_expression(&eq.right)?;
38028                Ok(())
38029            }
38030            other => self.generate_expression(other),
38031        }
38032    }
38033
38034    /// Strip table qualifier from a column reference if it matches a MERGE target name
38035    fn strip_merge_qualifier(&self, expr: &Expression) -> Expression {
38036        match expr {
38037            Expression::Column(col) => {
38038                if let Some(ref table_ident) = col.table {
38039                    if self
38040                        .merge_strip_qualifiers
38041                        .iter()
38042                        .any(|n| n.eq_ignore_ascii_case(&table_ident.name))
38043                    {
38044                        // Strip the table qualifier
38045                        let mut col = col.clone();
38046                        col.table = None;
38047                        return Expression::Column(col);
38048                    }
38049                }
38050                expr.clone()
38051            }
38052            Expression::Dot(dot) => {
38053                // table.column -> column (strip qualifier)
38054                if let Expression::Identifier(id) = &dot.this {
38055                    if self
38056                        .merge_strip_qualifiers
38057                        .iter()
38058                        .any(|n| n.eq_ignore_ascii_case(&id.name))
38059                    {
38060                        return Expression::Identifier(dot.field.clone());
38061                    }
38062                }
38063                expr.clone()
38064            }
38065            _ => expr.clone(),
38066        }
38067    }
38068
38069    fn generate_whens(&mut self, e: &Whens) -> Result<()> {
38070        // Python: return self.expressions(expression, sep=" ", indent=False)
38071        for (i, expr) in e.expressions.iter().enumerate() {
38072            if i > 0 {
38073                // In pretty mode, each WHEN clause on its own line
38074                if self.config.pretty {
38075                    self.write_newline();
38076                    self.write_indent();
38077                } else {
38078                    self.write_space();
38079                }
38080            }
38081            self.generate_expression(expr)?;
38082        }
38083        Ok(())
38084    }
38085
38086    fn generate_where(&mut self, e: &Where) -> Result<()> {
38087        // Python: return f"{self.seg('WHERE')}{self.sep()}{this}"
38088        self.write_keyword("WHERE");
38089        self.write_space();
38090        self.generate_expression(&e.this)?;
38091        Ok(())
38092    }
38093
38094    fn generate_width_bucket(&mut self, e: &WidthBucket) -> Result<()> {
38095        // Python: return self.func("WIDTH_BUCKET", expression.this, ...)
38096        self.write_keyword("WIDTH_BUCKET");
38097        self.write("(");
38098        self.generate_expression(&e.this)?;
38099        if let Some(min_value) = &e.min_value {
38100            self.write(", ");
38101            self.generate_expression(min_value)?;
38102        }
38103        if let Some(max_value) = &e.max_value {
38104            self.write(", ");
38105            self.generate_expression(max_value)?;
38106        }
38107        if let Some(num_buckets) = &e.num_buckets {
38108            self.write(", ");
38109            self.generate_expression(num_buckets)?;
38110        }
38111        self.write(")");
38112        Ok(())
38113    }
38114
38115    fn generate_window(&mut self, e: &WindowSpec) -> Result<()> {
38116        // Window specification: PARTITION BY ... ORDER BY ... frame
38117        self.generate_window_spec(e)
38118    }
38119
38120    fn generate_window_spec(&mut self, e: &WindowSpec) -> Result<()> {
38121        // Window specification: PARTITION BY ... ORDER BY ... frame
38122        let mut has_content = false;
38123
38124        // PARTITION BY
38125        if !e.partition_by.is_empty() {
38126            self.write_keyword("PARTITION BY");
38127            self.write_space();
38128            for (i, expr) in e.partition_by.iter().enumerate() {
38129                if i > 0 {
38130                    self.write(", ");
38131                }
38132                self.generate_expression(expr)?;
38133            }
38134            has_content = true;
38135        }
38136
38137        // ORDER BY
38138        if !e.order_by.is_empty() {
38139            if has_content {
38140                self.write_space();
38141            }
38142            self.write_keyword("ORDER BY");
38143            self.write_space();
38144            for (i, ordered) in e.order_by.iter().enumerate() {
38145                if i > 0 {
38146                    self.write(", ");
38147                }
38148                self.generate_expression(&ordered.this)?;
38149                if ordered.desc {
38150                    self.write_space();
38151                    self.write_keyword("DESC");
38152                } else if ordered.explicit_asc {
38153                    self.write_space();
38154                    self.write_keyword("ASC");
38155                }
38156                if let Some(nulls_first) = ordered.nulls_first {
38157                    self.write_space();
38158                    self.write_keyword("NULLS");
38159                    self.write_space();
38160                    if nulls_first {
38161                        self.write_keyword("FIRST");
38162                    } else {
38163                        self.write_keyword("LAST");
38164                    }
38165                }
38166            }
38167            has_content = true;
38168        }
38169
38170        // Frame specification
38171        if let Some(frame) = &e.frame {
38172            if has_content {
38173                self.write_space();
38174            }
38175            self.generate_window_frame(frame)?;
38176        }
38177
38178        Ok(())
38179    }
38180
38181    fn generate_with_data_property(&mut self, e: &WithDataProperty) -> Result<()> {
38182        // Python: f"WITH {'NO ' if expression.args.get('no') else ''}DATA"
38183        self.write_keyword("WITH");
38184        self.write_space();
38185        if e.no.is_some() {
38186            self.write_keyword("NO");
38187            self.write_space();
38188        }
38189        self.write_keyword("DATA");
38190
38191        // statistics
38192        if let Some(statistics) = &e.statistics {
38193            self.write_space();
38194            self.write_keyword("AND");
38195            self.write_space();
38196            // Check if statistics is true or false
38197            match statistics.as_ref() {
38198                Expression::Boolean(b) if !b.value => {
38199                    self.write_keyword("NO");
38200                    self.write_space();
38201                }
38202                _ => {}
38203            }
38204            self.write_keyword("STATISTICS");
38205        }
38206        Ok(())
38207    }
38208
38209    fn generate_with_fill(&mut self, e: &WithFill) -> Result<()> {
38210        // Python: f"WITH FILL{from_sql}{to_sql}{step_sql}{interpolate}"
38211        self.write_keyword("WITH FILL");
38212
38213        if let Some(from_) = &e.from_ {
38214            self.write_space();
38215            self.write_keyword("FROM");
38216            self.write_space();
38217            self.generate_expression(from_)?;
38218        }
38219
38220        if let Some(to) = &e.to {
38221            self.write_space();
38222            self.write_keyword("TO");
38223            self.write_space();
38224            self.generate_expression(to)?;
38225        }
38226
38227        if let Some(step) = &e.step {
38228            self.write_space();
38229            self.write_keyword("STEP");
38230            self.write_space();
38231            self.generate_expression(step)?;
38232        }
38233
38234        if let Some(staleness) = &e.staleness {
38235            self.write_space();
38236            self.write_keyword("STALENESS");
38237            self.write_space();
38238            self.generate_expression(staleness)?;
38239        }
38240
38241        if let Some(interpolate) = &e.interpolate {
38242            self.write_space();
38243            self.write_keyword("INTERPOLATE");
38244            self.write(" (");
38245            // INTERPOLATE items use reversed alias format: name AS expression
38246            self.generate_interpolate_item(interpolate)?;
38247            self.write(")");
38248        }
38249
38250        Ok(())
38251    }
38252
38253    /// Generate INTERPOLATE items with reversed alias format (name AS expression)
38254    fn generate_interpolate_item(&mut self, expr: &Expression) -> Result<()> {
38255        match expr {
38256            Expression::Alias(alias) => {
38257                // Output as: alias_name AS expression
38258                self.generate_identifier(&alias.alias)?;
38259                self.write_space();
38260                self.write_keyword("AS");
38261                self.write_space();
38262                self.generate_expression(&alias.this)?;
38263            }
38264            Expression::Tuple(tuple) => {
38265                for (i, item) in tuple.expressions.iter().enumerate() {
38266                    if i > 0 {
38267                        self.write(", ");
38268                    }
38269                    self.generate_interpolate_item(item)?;
38270                }
38271            }
38272            other => {
38273                self.generate_expression(other)?;
38274            }
38275        }
38276        Ok(())
38277    }
38278
38279    fn generate_with_journal_table_property(&mut self, e: &WithJournalTableProperty) -> Result<()> {
38280        // Python: return f"WITH JOURNAL TABLE={self.sql(expression, 'this')}"
38281        self.write_keyword("WITH JOURNAL TABLE");
38282        self.write("=");
38283        self.generate_expression(&e.this)?;
38284        Ok(())
38285    }
38286
38287    fn generate_with_operator(&mut self, e: &WithOperator) -> Result<()> {
38288        // Python: return f"{self.sql(expression, 'this')} WITH {self.sql(expression, 'op')}"
38289        self.generate_expression(&e.this)?;
38290        self.write_space();
38291        self.write_keyword("WITH");
38292        self.write_space();
38293        self.write_keyword(&e.op);
38294        Ok(())
38295    }
38296
38297    fn generate_with_procedure_options(&mut self, e: &WithProcedureOptions) -> Result<()> {
38298        // Python: return f"WITH {self.expressions(expression, flat=True)}"
38299        self.write_keyword("WITH");
38300        self.write_space();
38301        for (i, expr) in e.expressions.iter().enumerate() {
38302            if i > 0 {
38303                self.write(", ");
38304            }
38305            self.generate_expression(expr)?;
38306        }
38307        Ok(())
38308    }
38309
38310    fn generate_with_schema_binding_property(
38311        &mut self,
38312        e: &WithSchemaBindingProperty,
38313    ) -> Result<()> {
38314        // Python: return f"WITH {self.sql(expression, 'this')}"
38315        self.write_keyword("WITH");
38316        self.write_space();
38317        self.generate_expression(&e.this)?;
38318        Ok(())
38319    }
38320
38321    fn generate_with_system_versioning_property(
38322        &mut self,
38323        e: &WithSystemVersioningProperty,
38324    ) -> Result<()> {
38325        // Python: complex logic for SYSTEM_VERSIONING with options
38326        // SYSTEM_VERSIONING=ON(HISTORY_TABLE=..., DATA_CONSISTENCY_CHECK=..., HISTORY_RETENTION_PERIOD=...)
38327        // or SYSTEM_VERSIONING=ON/OFF
38328        // with WITH(...) wrapper if with_ is set
38329
38330        let mut parts = Vec::new();
38331
38332        if let Some(this) = &e.this {
38333            // HISTORY_TABLE=...
38334            let mut s = String::from("HISTORY_TABLE=");
38335            let mut gen = Generator::new();
38336            gen.generate_expression(this)?;
38337            s.push_str(&gen.output);
38338            parts.push(s);
38339        }
38340
38341        if let Some(data_consistency) = &e.data_consistency {
38342            let mut s = String::from("DATA_CONSISTENCY_CHECK=");
38343            let mut gen = Generator::new();
38344            gen.generate_expression(data_consistency)?;
38345            s.push_str(&gen.output);
38346            parts.push(s);
38347        }
38348
38349        if let Some(retention_period) = &e.retention_period {
38350            let mut s = String::from("HISTORY_RETENTION_PERIOD=");
38351            let mut gen = Generator::new();
38352            gen.generate_expression(retention_period)?;
38353            s.push_str(&gen.output);
38354            parts.push(s);
38355        }
38356
38357        self.write_keyword("SYSTEM_VERSIONING");
38358        self.write("=");
38359
38360        if !parts.is_empty() {
38361            self.write_keyword("ON");
38362            self.write("(");
38363            self.write(&parts.join(", "));
38364            self.write(")");
38365        } else if e.on.is_some() {
38366            self.write_keyword("ON");
38367        } else {
38368            self.write_keyword("OFF");
38369        }
38370
38371        // Wrap in WITH(...) if with_ is set
38372        if e.with_.is_some() {
38373            let inner = self.output.clone();
38374            self.output.clear();
38375            self.write("WITH(");
38376            self.write(&inner);
38377            self.write(")");
38378        }
38379
38380        Ok(())
38381    }
38382
38383    fn generate_with_table_hint(&mut self, e: &WithTableHint) -> Result<()> {
38384        // Python: f"WITH ({self.expressions(expression, flat=True)})"
38385        self.write_keyword("WITH");
38386        self.write(" (");
38387        for (i, expr) in e.expressions.iter().enumerate() {
38388            if i > 0 {
38389                self.write(", ");
38390            }
38391            self.generate_expression(expr)?;
38392        }
38393        self.write(")");
38394        Ok(())
38395    }
38396
38397    fn generate_xml_element(&mut self, e: &XMLElement) -> Result<()> {
38398        // Python: prefix = "EVALNAME" if expression.args.get("evalname") else "NAME"
38399        // return self.func("XMLELEMENT", name, *expression.expressions)
38400        self.write_keyword("XMLELEMENT");
38401        self.write("(");
38402
38403        if e.evalname.is_some() {
38404            self.write_keyword("EVALNAME");
38405        } else {
38406            self.write_keyword("NAME");
38407        }
38408        self.write_space();
38409        self.generate_expression(&e.this)?;
38410
38411        for expr in &e.expressions {
38412            self.write(", ");
38413            self.generate_expression(expr)?;
38414        }
38415        self.write(")");
38416        Ok(())
38417    }
38418
38419    fn generate_xml_get(&mut self, e: &XMLGet) -> Result<()> {
38420        // XMLGET(this, expression [, instance])
38421        self.write_keyword("XMLGET");
38422        self.write("(");
38423        self.generate_expression(&e.this)?;
38424        self.write(", ");
38425        self.generate_expression(&e.expression)?;
38426        if let Some(instance) = &e.instance {
38427            self.write(", ");
38428            self.generate_expression(instance)?;
38429        }
38430        self.write(")");
38431        Ok(())
38432    }
38433
38434    fn generate_xml_key_value_option(&mut self, e: &XMLKeyValueOption) -> Result<()> {
38435        // Python: this + optional (expr)
38436        self.generate_expression(&e.this)?;
38437        if let Some(expression) = &e.expression {
38438            self.write("(");
38439            self.generate_expression(expression)?;
38440            self.write(")");
38441        }
38442        Ok(())
38443    }
38444
38445    fn generate_xml_table(&mut self, e: &XMLTable) -> Result<()> {
38446        // Python: XMLTABLE(namespaces + this + passing + by_ref + columns)
38447        self.write_keyword("XMLTABLE");
38448        self.write("(");
38449
38450        if self.config.pretty {
38451            self.indent_level += 1;
38452            self.write_newline();
38453            self.write_indent();
38454            self.generate_expression(&e.this)?;
38455
38456            if let Some(passing) = &e.passing {
38457                self.write_newline();
38458                self.write_indent();
38459                self.write_keyword("PASSING");
38460                if let Expression::Tuple(tuple) = passing.as_ref() {
38461                    for expr in &tuple.expressions {
38462                        self.write_newline();
38463                        self.indent_level += 1;
38464                        self.write_indent();
38465                        self.generate_expression(expr)?;
38466                        self.indent_level -= 1;
38467                    }
38468                } else {
38469                    self.write_newline();
38470                    self.indent_level += 1;
38471                    self.write_indent();
38472                    self.generate_expression(passing)?;
38473                    self.indent_level -= 1;
38474                }
38475            }
38476
38477            if e.by_ref.is_some() {
38478                self.write_newline();
38479                self.write_indent();
38480                self.write_keyword("RETURNING SEQUENCE BY REF");
38481            }
38482
38483            if !e.columns.is_empty() {
38484                self.write_newline();
38485                self.write_indent();
38486                self.write_keyword("COLUMNS");
38487                for (i, col) in e.columns.iter().enumerate() {
38488                    self.write_newline();
38489                    self.indent_level += 1;
38490                    self.write_indent();
38491                    self.generate_expression(col)?;
38492                    self.indent_level -= 1;
38493                    if i < e.columns.len() - 1 {
38494                        self.write(",");
38495                    }
38496                }
38497            }
38498
38499            self.indent_level -= 1;
38500            self.write_newline();
38501            self.write_indent();
38502            self.write(")");
38503            return Ok(());
38504        }
38505
38506        // Namespaces - unwrap Tuple to generate comma-separated list without parentheses
38507        if let Some(namespaces) = &e.namespaces {
38508            self.write_keyword("XMLNAMESPACES");
38509            self.write("(");
38510            // Unwrap Tuple if present to avoid extra parentheses
38511            if let Expression::Tuple(tuple) = namespaces.as_ref() {
38512                for (i, expr) in tuple.expressions.iter().enumerate() {
38513                    if i > 0 {
38514                        self.write(", ");
38515                    }
38516                    // Python pattern: if it's an Alias, output as-is; otherwise prepend DEFAULT
38517                    // See xmlnamespace_sql in generator.py
38518                    if !matches!(expr, Expression::Alias(_)) {
38519                        self.write_keyword("DEFAULT");
38520                        self.write_space();
38521                    }
38522                    self.generate_expression(expr)?;
38523                }
38524            } else {
38525                // Single namespace - check if DEFAULT
38526                if !matches!(namespaces.as_ref(), Expression::Alias(_)) {
38527                    self.write_keyword("DEFAULT");
38528                    self.write_space();
38529                }
38530                self.generate_expression(namespaces)?;
38531            }
38532            self.write("), ");
38533        }
38534
38535        // XPath expression
38536        self.generate_expression(&e.this)?;
38537
38538        // PASSING clause - unwrap Tuple to generate comma-separated list without parentheses
38539        if let Some(passing) = &e.passing {
38540            self.write_space();
38541            self.write_keyword("PASSING");
38542            self.write_space();
38543            // Unwrap Tuple if present to avoid extra parentheses
38544            if let Expression::Tuple(tuple) = passing.as_ref() {
38545                for (i, expr) in tuple.expressions.iter().enumerate() {
38546                    if i > 0 {
38547                        self.write(", ");
38548                    }
38549                    self.generate_expression(expr)?;
38550                }
38551            } else {
38552                self.generate_expression(passing)?;
38553            }
38554        }
38555
38556        // RETURNING SEQUENCE BY REF
38557        if e.by_ref.is_some() {
38558            self.write_space();
38559            self.write_keyword("RETURNING SEQUENCE BY REF");
38560        }
38561
38562        // COLUMNS clause
38563        if !e.columns.is_empty() {
38564            self.write_space();
38565            self.write_keyword("COLUMNS");
38566            self.write_space();
38567            for (i, col) in e.columns.iter().enumerate() {
38568                if i > 0 {
38569                    self.write(", ");
38570                }
38571                self.generate_expression(col)?;
38572            }
38573        }
38574
38575        self.write(")");
38576        Ok(())
38577    }
38578
38579    fn generate_xor(&mut self, e: &Xor) -> Result<()> {
38580        // Python: return self.connector_sql(expression, "XOR", stack)
38581        // Handles: this XOR expression or expressions joined by XOR
38582        if let Some(this) = &e.this {
38583            self.generate_expression(this)?;
38584            if let Some(expression) = &e.expression {
38585                self.write_space();
38586                self.write_keyword("XOR");
38587                self.write_space();
38588                self.generate_expression(expression)?;
38589            }
38590        }
38591
38592        // Handle multiple expressions
38593        for (i, expr) in e.expressions.iter().enumerate() {
38594            if i > 0 || e.this.is_some() {
38595                self.write_space();
38596                self.write_keyword("XOR");
38597                self.write_space();
38598            }
38599            self.generate_expression(expr)?;
38600        }
38601        Ok(())
38602    }
38603
38604    fn generate_zipf(&mut self, e: &Zipf) -> Result<()> {
38605        // ZIPF(this, elementcount [, gen])
38606        self.write_keyword("ZIPF");
38607        self.write("(");
38608        self.generate_expression(&e.this)?;
38609        if let Some(elementcount) = &e.elementcount {
38610            self.write(", ");
38611            self.generate_expression(elementcount)?;
38612        }
38613        if let Some(gen) = &e.gen {
38614            self.write(", ");
38615            self.generate_expression(gen)?;
38616        }
38617        self.write(")");
38618        Ok(())
38619    }
38620}
38621
38622impl Default for Generator {
38623    fn default() -> Self {
38624        Self::new()
38625    }
38626}
38627
38628#[cfg(test)]
38629mod tests {
38630    use super::*;
38631    use crate::parser::Parser;
38632
38633    fn roundtrip(sql: &str) -> String {
38634        let ast = Parser::parse_sql(sql).unwrap();
38635        Generator::sql(&ast[0]).unwrap()
38636    }
38637
38638    #[test]
38639    fn test_simple_select() {
38640        let result = roundtrip("SELECT 1");
38641        assert_eq!(result, "SELECT 1");
38642    }
38643
38644    #[test]
38645    fn test_select_from() {
38646        let result = roundtrip("SELECT a, b FROM t");
38647        assert_eq!(result, "SELECT a, b FROM t");
38648    }
38649
38650    #[test]
38651    fn test_select_where() {
38652        let result = roundtrip("SELECT * FROM t WHERE x = 1");
38653        assert_eq!(result, "SELECT * FROM t WHERE x = 1");
38654    }
38655
38656    #[test]
38657    fn test_select_join() {
38658        let result = roundtrip("SELECT * FROM a JOIN b ON a.id = b.id");
38659        assert_eq!(result, "SELECT * FROM a JOIN b ON a.id = b.id");
38660    }
38661
38662    #[test]
38663    fn test_insert() {
38664        let result = roundtrip("INSERT INTO t (a, b) VALUES (1, 2)");
38665        assert_eq!(result, "INSERT INTO t (a, b) VALUES (1, 2)");
38666    }
38667
38668    #[test]
38669    fn test_pretty_print() {
38670        let ast = Parser::parse_sql("SELECT a, b FROM t WHERE x = 1").unwrap();
38671        let result = Generator::pretty_sql(&ast[0]).unwrap();
38672        assert!(result.contains('\n'));
38673    }
38674
38675    #[test]
38676    fn test_window_function() {
38677        let result = roundtrip("SELECT ROW_NUMBER() OVER (PARTITION BY category ORDER BY id)");
38678        assert_eq!(
38679            result,
38680            "SELECT ROW_NUMBER() OVER (PARTITION BY category ORDER BY id)"
38681        );
38682    }
38683
38684    #[test]
38685    fn test_window_function_with_frame() {
38686        let result = roundtrip("SELECT SUM(amount) OVER (ORDER BY order_date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)");
38687        assert_eq!(result, "SELECT SUM(amount) OVER (ORDER BY order_date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)");
38688    }
38689
38690    #[test]
38691    fn test_aggregate_with_filter() {
38692        let result = roundtrip("SELECT COUNT(*) FILTER (WHERE status = 1) FROM orders");
38693        assert_eq!(
38694            result,
38695            "SELECT COUNT(*) FILTER(WHERE status = 1) FROM orders"
38696        );
38697    }
38698
38699    #[test]
38700    fn test_subscript() {
38701        let result = roundtrip("SELECT arr[0]");
38702        assert_eq!(result, "SELECT arr[0]");
38703    }
38704
38705    // DDL tests
38706    #[test]
38707    fn test_create_table() {
38708        let result = roundtrip("CREATE TABLE users (id INT, name VARCHAR(100))");
38709        assert_eq!(result, "CREATE TABLE users (id INT, name VARCHAR(100))");
38710    }
38711
38712    #[test]
38713    fn test_create_table_with_constraints() {
38714        let result = roundtrip(
38715            "CREATE TABLE users (id INT PRIMARY KEY, email VARCHAR(255) UNIQUE NOT NULL)",
38716        );
38717        assert_eq!(
38718            result,
38719            "CREATE TABLE users (id INT PRIMARY KEY, email VARCHAR(255) UNIQUE NOT NULL)"
38720        );
38721    }
38722
38723    #[test]
38724    fn test_create_table_if_not_exists() {
38725        let result = roundtrip("CREATE TABLE IF NOT EXISTS t (id INT)");
38726        assert_eq!(result, "CREATE TABLE IF NOT EXISTS t (id INT)");
38727    }
38728
38729    #[test]
38730    fn test_drop_table() {
38731        let result = roundtrip("DROP TABLE users");
38732        assert_eq!(result, "DROP TABLE users");
38733    }
38734
38735    #[test]
38736    fn test_drop_table_if_exists_cascade() {
38737        let result = roundtrip("DROP TABLE IF EXISTS users CASCADE");
38738        assert_eq!(result, "DROP TABLE IF EXISTS users CASCADE");
38739    }
38740
38741    #[test]
38742    fn test_alter_table_add_column() {
38743        let result = roundtrip("ALTER TABLE users ADD COLUMN email VARCHAR(255)");
38744        assert_eq!(result, "ALTER TABLE users ADD COLUMN email VARCHAR(255)");
38745    }
38746
38747    #[test]
38748    fn test_alter_table_drop_column() {
38749        let result = roundtrip("ALTER TABLE users DROP COLUMN email");
38750        assert_eq!(result, "ALTER TABLE users DROP COLUMN email");
38751    }
38752
38753    #[test]
38754    fn test_create_index() {
38755        let result = roundtrip("CREATE INDEX idx_name ON users(name)");
38756        assert_eq!(result, "CREATE INDEX idx_name ON users(name)");
38757    }
38758
38759    #[test]
38760    fn test_create_unique_index() {
38761        let result = roundtrip("CREATE UNIQUE INDEX idx_email ON users(email)");
38762        assert_eq!(result, "CREATE UNIQUE INDEX idx_email ON users(email)");
38763    }
38764
38765    #[test]
38766    fn test_drop_index() {
38767        let result = roundtrip("DROP INDEX idx_name");
38768        assert_eq!(result, "DROP INDEX idx_name");
38769    }
38770
38771    #[test]
38772    fn test_create_view() {
38773        let result = roundtrip("CREATE VIEW active_users AS SELECT * FROM users WHERE active = 1");
38774        assert_eq!(
38775            result,
38776            "CREATE VIEW active_users AS SELECT * FROM users WHERE active = 1"
38777        );
38778    }
38779
38780    #[test]
38781    fn test_drop_view() {
38782        let result = roundtrip("DROP VIEW active_users");
38783        assert_eq!(result, "DROP VIEW active_users");
38784    }
38785
38786    #[test]
38787    fn test_truncate() {
38788        let result = roundtrip("TRUNCATE TABLE users");
38789        assert_eq!(result, "TRUNCATE TABLE users");
38790    }
38791
38792    #[test]
38793    fn test_string_literal_escaping_default() {
38794        // Default: double single quotes
38795        let result = roundtrip("SELECT 'hello'");
38796        assert_eq!(result, "SELECT 'hello'");
38797
38798        // Single quotes are doubled
38799        let result = roundtrip("SELECT 'it''s a test'");
38800        assert_eq!(result, "SELECT 'it''s a test'");
38801    }
38802
38803    #[test]
38804    fn test_not_in_style_prefix_default_generic() {
38805        let result = roundtrip("SELECT id FROM users WHERE status NOT IN ('deleted', 'banned')");
38806        assert_eq!(
38807            result,
38808            "SELECT id FROM users WHERE NOT status IN ('deleted', 'banned')"
38809        );
38810    }
38811
38812    #[test]
38813    fn test_not_in_style_infix_generic_override() {
38814        let ast =
38815            Parser::parse_sql("SELECT id FROM users WHERE status NOT IN ('deleted', 'banned')")
38816                .unwrap();
38817        let config = GeneratorConfig {
38818            not_in_style: NotInStyle::Infix,
38819            ..Default::default()
38820        };
38821        let mut gen = Generator::with_config(config);
38822        let result = gen.generate(&ast[0]).unwrap();
38823        assert_eq!(
38824            result,
38825            "SELECT id FROM users WHERE status NOT IN ('deleted', 'banned')"
38826        );
38827    }
38828
38829    #[test]
38830    fn test_string_literal_escaping_mysql() {
38831        use crate::dialects::DialectType;
38832
38833        let config = GeneratorConfig {
38834            dialect: Some(DialectType::MySQL),
38835            ..Default::default()
38836        };
38837
38838        let ast = Parser::parse_sql("SELECT 'hello'").unwrap();
38839        let mut gen = Generator::with_config(config.clone());
38840        let result = gen.generate(&ast[0]).unwrap();
38841        assert_eq!(result, "SELECT 'hello'");
38842
38843        // MySQL uses SQL standard quote doubling for escaping (matches Python sqlglot)
38844        let ast = Parser::parse_sql("SELECT 'it''s'").unwrap();
38845        let mut gen = Generator::with_config(config.clone());
38846        let result = gen.generate(&ast[0]).unwrap();
38847        assert_eq!(result, "SELECT 'it''s'");
38848    }
38849
38850    #[test]
38851    fn test_string_literal_escaping_postgres() {
38852        use crate::dialects::DialectType;
38853
38854        let config = GeneratorConfig {
38855            dialect: Some(DialectType::PostgreSQL),
38856            ..Default::default()
38857        };
38858
38859        let ast = Parser::parse_sql("SELECT 'hello'").unwrap();
38860        let mut gen = Generator::with_config(config.clone());
38861        let result = gen.generate(&ast[0]).unwrap();
38862        assert_eq!(result, "SELECT 'hello'");
38863
38864        // PostgreSQL uses doubled quotes for regular strings
38865        let ast = Parser::parse_sql("SELECT 'it''s'").unwrap();
38866        let mut gen = Generator::with_config(config.clone());
38867        let result = gen.generate(&ast[0]).unwrap();
38868        assert_eq!(result, "SELECT 'it''s'");
38869    }
38870
38871    #[test]
38872    fn test_string_literal_escaping_bigquery() {
38873        use crate::dialects::DialectType;
38874
38875        let config = GeneratorConfig {
38876            dialect: Some(DialectType::BigQuery),
38877            ..Default::default()
38878        };
38879
38880        let ast = Parser::parse_sql("SELECT 'hello'").unwrap();
38881        let mut gen = Generator::with_config(config.clone());
38882        let result = gen.generate(&ast[0]).unwrap();
38883        assert_eq!(result, "SELECT 'hello'");
38884
38885        // BigQuery escapes single quotes with backslash
38886        let ast = Parser::parse_sql("SELECT 'it''s'").unwrap();
38887        let mut gen = Generator::with_config(config.clone());
38888        let result = gen.generate(&ast[0]).unwrap();
38889        assert_eq!(result, "SELECT 'it\\'s'");
38890    }
38891
38892    #[test]
38893    fn test_generate_deep_and_chain_without_stack_growth() {
38894        let mut expr = Expression::Eq(Box::new(BinaryOp::new(
38895            Expression::column("c0"),
38896            Expression::number(0),
38897        )));
38898
38899        for i in 1..2500 {
38900            let predicate = Expression::Eq(Box::new(BinaryOp::new(
38901                Expression::column(format!("c{i}")),
38902                Expression::number(i as i64),
38903            )));
38904            expr = Expression::And(Box::new(BinaryOp::new(expr, predicate)));
38905        }
38906
38907        let sql = Generator::sql(&expr).expect("deep AND chain should generate");
38908        assert!(sql.contains("c2499 = 2499"), "{}", sql);
38909    }
38910
38911    #[test]
38912    fn test_generate_deep_or_chain_without_stack_growth() {
38913        let mut expr = Expression::Eq(Box::new(BinaryOp::new(
38914            Expression::column("c0"),
38915            Expression::number(0),
38916        )));
38917
38918        for i in 1..2500 {
38919            let predicate = Expression::Eq(Box::new(BinaryOp::new(
38920                Expression::column(format!("c{i}")),
38921                Expression::number(i as i64),
38922            )));
38923            expr = Expression::Or(Box::new(BinaryOp::new(expr, predicate)));
38924        }
38925
38926        let sql = Generator::sql(&expr).expect("deep OR chain should generate");
38927        assert!(sql.contains("c2499 = 2499"), "{}", sql);
38928    }
38929}