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!(
4807                    self.config.dialect,
4808                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
4809                ) {
4810                    // SQL Server 2012+ OFFSET ... FETCH syntax
4811                    self.write_keyword("OFFSET");
4812                    self.write_space();
4813                    self.write_limit_expr(&offset.this)?;
4814                    self.write_space();
4815                    self.write_keyword("ROWS");
4816                    // If there was a LIMIT, use FETCH NEXT ... ROWS ONLY
4817                    if let Some(limit) = &select.limit {
4818                        self.write_space();
4819                        self.write_keyword("FETCH NEXT");
4820                        self.write_space();
4821                        self.write_limit_expr(&limit.this)?;
4822                        self.write_space();
4823                        self.write_keyword("ROWS ONLY");
4824                    }
4825                } else {
4826                    self.write_keyword("OFFSET");
4827                    self.write_space();
4828                    self.write_limit_expr(&offset.this)?;
4829                    // Output ROWS keyword if it was in the original SQL
4830                    if offset.rows == Some(true) {
4831                        self.write_space();
4832                        self.write_keyword("ROWS");
4833                    }
4834                }
4835            }
4836        }
4837
4838        // ClickHouse LIMIT BY clause (after LIMIT/OFFSET)
4839        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
4840            if let Some(limit_by) = &select.limit_by {
4841                if !limit_by.is_empty() {
4842                    self.write_space();
4843                    self.write_keyword("BY");
4844                    self.write_space();
4845                    for (i, expr) in limit_by.iter().enumerate() {
4846                        if i > 0 {
4847                            self.write(", ");
4848                        }
4849                        self.generate_expression(expr)?;
4850                    }
4851                }
4852            }
4853        }
4854
4855        // ClickHouse SETTINGS and FORMAT modifiers (after LIMIT/OFFSET)
4856        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
4857            if let Some(settings) = &select.settings {
4858                if self.config.pretty {
4859                    self.write_newline();
4860                    self.write_indent();
4861                } else {
4862                    self.write_space();
4863                }
4864                self.write_keyword("SETTINGS");
4865                self.write_space();
4866                for (i, expr) in settings.iter().enumerate() {
4867                    if i > 0 {
4868                        self.write(", ");
4869                    }
4870                    self.generate_expression(expr)?;
4871                }
4872            }
4873
4874            if let Some(format_expr) = &select.format {
4875                if self.config.pretty {
4876                    self.write_newline();
4877                    self.write_indent();
4878                } else {
4879                    self.write_space();
4880                }
4881                self.write_keyword("FORMAT");
4882                self.write_space();
4883                self.generate_expression(format_expr)?;
4884            }
4885        }
4886
4887        // FETCH FIRST/NEXT
4888        if let Some(fetch) = &select.fetch {
4889            // Check if we already emitted LIMIT from FETCH before OFFSET
4890            let fetch_already_as_limit = select.offset.is_some()
4891                && !fetch.percent
4892                && !fetch.with_ties
4893                && fetch.count.is_some()
4894                && matches!(
4895                    self.config.dialect,
4896                    Some(DialectType::Spark)
4897                        | Some(DialectType::Hive)
4898                        | Some(DialectType::DuckDB)
4899                        | Some(DialectType::SQLite)
4900                        | Some(DialectType::MySQL)
4901                        | Some(DialectType::BigQuery)
4902                        | Some(DialectType::Databricks)
4903                        | Some(DialectType::StarRocks)
4904                        | Some(DialectType::Doris)
4905                        | Some(DialectType::Athena)
4906                        | Some(DialectType::ClickHouse)
4907                        | Some(DialectType::Redshift)
4908                );
4909
4910            if fetch_already_as_limit {
4911                // Already emitted as LIMIT before OFFSET, skip
4912            } else {
4913                if self.config.pretty {
4914                    self.write_newline();
4915                    self.write_indent();
4916                } else {
4917                    self.write_space();
4918                }
4919
4920                // Convert FETCH to LIMIT for dialects that prefer LIMIT syntax
4921                let use_limit = !fetch.percent
4922                    && !fetch.with_ties
4923                    && fetch.count.is_some()
4924                    && matches!(
4925                        self.config.dialect,
4926                        Some(DialectType::Spark)
4927                            | Some(DialectType::Hive)
4928                            | Some(DialectType::DuckDB)
4929                            | Some(DialectType::SQLite)
4930                            | Some(DialectType::MySQL)
4931                            | Some(DialectType::BigQuery)
4932                            | Some(DialectType::Databricks)
4933                            | Some(DialectType::StarRocks)
4934                            | Some(DialectType::Doris)
4935                            | Some(DialectType::Athena)
4936                            | Some(DialectType::ClickHouse)
4937                            | Some(DialectType::Redshift)
4938                    );
4939
4940                if use_limit {
4941                    self.write_keyword("LIMIT");
4942                    self.write_space();
4943                    self.generate_expression(fetch.count.as_ref().unwrap())?;
4944                } else {
4945                    self.write_keyword("FETCH");
4946                    self.write_space();
4947                    self.write_keyword(&fetch.direction);
4948                    if let Some(ref count) = fetch.count {
4949                        self.write_space();
4950                        self.generate_expression(count)?;
4951                    }
4952                    if fetch.percent {
4953                        self.write_space();
4954                        self.write_keyword("PERCENT");
4955                    }
4956                    if fetch.rows {
4957                        self.write_space();
4958                        self.write_keyword("ROWS");
4959                    }
4960                    if fetch.with_ties {
4961                        self.write_space();
4962                        self.write_keyword("WITH TIES");
4963                    } else {
4964                        self.write_space();
4965                        self.write_keyword("ONLY");
4966                    }
4967                }
4968            } // close fetch_already_as_limit else
4969        }
4970
4971        // SAMPLE / TABLESAMPLE
4972        if let Some(sample) = &select.sample {
4973            use crate::dialects::DialectType;
4974            if self.config.pretty {
4975                self.write_newline();
4976            } else {
4977                self.write_space();
4978            }
4979
4980            if sample.is_using_sample {
4981                // DuckDB USING SAMPLE: METHOD (size UNIT) [REPEATABLE (seed)]
4982                self.write_keyword("USING SAMPLE");
4983                self.generate_sample_body(sample)?;
4984            } else {
4985                self.write_keyword("TABLESAMPLE");
4986
4987                // Snowflake defaults to BERNOULLI when no explicit method is given
4988                let snowflake_bernoulli =
4989                    matches!(self.config.dialect, Some(DialectType::Snowflake))
4990                        && !sample.explicit_method;
4991                if snowflake_bernoulli {
4992                    self.write_space();
4993                    self.write_keyword("BERNOULLI");
4994                }
4995
4996                // Handle BUCKET sampling: TABLESAMPLE (BUCKET 1 OUT OF 5 ON x)
4997                if matches!(sample.method, SampleMethod::Bucket) {
4998                    self.write_space();
4999                    self.write("(");
5000                    self.write_keyword("BUCKET");
5001                    self.write_space();
5002                    if let Some(ref num) = sample.bucket_numerator {
5003                        self.generate_expression(num)?;
5004                    }
5005                    self.write_space();
5006                    self.write_keyword("OUT OF");
5007                    self.write_space();
5008                    if let Some(ref denom) = sample.bucket_denominator {
5009                        self.generate_expression(denom)?;
5010                    }
5011                    if let Some(ref field) = sample.bucket_field {
5012                        self.write_space();
5013                        self.write_keyword("ON");
5014                        self.write_space();
5015                        self.generate_expression(field)?;
5016                    }
5017                    self.write(")");
5018                } else if sample.unit_after_size {
5019                    // Syntax: TABLESAMPLE [METHOD] (size ROWS) or TABLESAMPLE [METHOD] (size PERCENT)
5020                    if sample.explicit_method && sample.method_before_size {
5021                        self.write_space();
5022                        match sample.method {
5023                            SampleMethod::Bernoulli => self.write_keyword("BERNOULLI"),
5024                            SampleMethod::System => self.write_keyword("SYSTEM"),
5025                            SampleMethod::Block => self.write_keyword("BLOCK"),
5026                            SampleMethod::Row => self.write_keyword("ROW"),
5027                            SampleMethod::Reservoir => self.write_keyword("RESERVOIR"),
5028                            _ => {}
5029                        }
5030                    }
5031                    self.write(" (");
5032                    self.generate_expression(&sample.size)?;
5033                    self.write_space();
5034                    match sample.method {
5035                        SampleMethod::Percent => self.write_keyword("PERCENT"),
5036                        SampleMethod::Row => self.write_keyword("ROWS"),
5037                        SampleMethod::Reservoir => self.write_keyword("ROWS"),
5038                        _ => {
5039                            self.write_keyword("PERCENT");
5040                        }
5041                    }
5042                    self.write(")");
5043                } else {
5044                    // Syntax: TABLESAMPLE METHOD (size)
5045                    self.write_space();
5046                    match sample.method {
5047                        SampleMethod::Bernoulli => self.write_keyword("BERNOULLI"),
5048                        SampleMethod::System => self.write_keyword("SYSTEM"),
5049                        SampleMethod::Block => self.write_keyword("BLOCK"),
5050                        SampleMethod::Row => self.write_keyword("ROW"),
5051                        SampleMethod::Percent => self.write_keyword("BERNOULLI"),
5052                        SampleMethod::Bucket => {}
5053                        SampleMethod::Reservoir => self.write_keyword("RESERVOIR"),
5054                    }
5055                    self.write(" (");
5056                    self.generate_expression(&sample.size)?;
5057                    if matches!(sample.method, SampleMethod::Percent) {
5058                        self.write_space();
5059                        self.write_keyword("PERCENT");
5060                    }
5061                    self.write(")");
5062                }
5063            }
5064
5065            if let Some(seed) = &sample.seed {
5066                self.write_space();
5067                // Databricks/Spark use REPEATABLE, not SEED
5068                let use_seed = sample.use_seed_keyword
5069                    && !matches!(
5070                        self.config.dialect,
5071                        Some(crate::dialects::DialectType::Databricks)
5072                            | Some(crate::dialects::DialectType::Spark)
5073                    );
5074                if use_seed {
5075                    self.write_keyword("SEED");
5076                } else {
5077                    self.write_keyword("REPEATABLE");
5078                }
5079                self.write(" (");
5080                self.generate_expression(seed)?;
5081                self.write(")");
5082            }
5083        }
5084
5085        // FOR UPDATE/SHARE locks
5086        // Skip locking clauses for dialects that don't support them
5087        if self.config.locking_reads_supported {
5088            for lock in &select.locks {
5089                if self.config.pretty {
5090                    self.write_newline();
5091                    self.write_indent();
5092                } else {
5093                    self.write_space();
5094                }
5095                self.generate_lock(lock)?;
5096            }
5097        }
5098
5099        // FOR XML clause (T-SQL)
5100        if !select.for_xml.is_empty() {
5101            if self.config.pretty {
5102                self.write_newline();
5103                self.write_indent();
5104            } else {
5105                self.write_space();
5106            }
5107            self.write_keyword("FOR XML");
5108            for (i, opt) in select.for_xml.iter().enumerate() {
5109                if self.config.pretty {
5110                    if i > 0 {
5111                        self.write(",");
5112                    }
5113                    self.write_newline();
5114                    self.write_indent();
5115                    self.write("  "); // extra indent for options
5116                } else {
5117                    if i > 0 {
5118                        self.write(",");
5119                    }
5120                    self.write_space();
5121                }
5122                self.generate_for_xml_option(opt)?;
5123            }
5124        }
5125
5126        // FOR JSON clause (T-SQL)
5127        if !select.for_json.is_empty() {
5128            if self.config.pretty {
5129                self.write_newline();
5130                self.write_indent();
5131            } else {
5132                self.write_space();
5133            }
5134            self.write_keyword("FOR JSON");
5135            for (i, opt) in select.for_json.iter().enumerate() {
5136                if self.config.pretty {
5137                    if i > 0 {
5138                        self.write(",");
5139                    }
5140                    self.write_newline();
5141                    self.write_indent();
5142                    self.write("  "); // extra indent for options
5143                } else {
5144                    if i > 0 {
5145                        self.write(",");
5146                    }
5147                    self.write_space();
5148                }
5149                self.generate_for_xml_option(opt)?;
5150            }
5151        }
5152
5153        // TSQL: OPTION clause
5154        if let Some(ref option) = select.option {
5155            if matches!(
5156                self.config.dialect,
5157                Some(crate::dialects::DialectType::TSQL)
5158                    | Some(crate::dialects::DialectType::Fabric)
5159            ) {
5160                self.write_space();
5161                self.write(option);
5162            }
5163        }
5164
5165        Ok(())
5166    }
5167
5168    /// Generate a single FOR XML option
5169    fn generate_for_xml_option(&mut self, opt: &Expression) -> Result<()> {
5170        match opt {
5171            Expression::QueryOption(qo) => {
5172                // Extract the option name from Var
5173                if let Expression::Var(var) = &*qo.this {
5174                    self.write(&var.this);
5175                } else {
5176                    self.generate_expression(&qo.this)?;
5177                }
5178                // If there's an expression (like PATH('element')), output it in parens
5179                if let Some(expr) = &qo.expression {
5180                    self.write("(");
5181                    self.generate_expression(expr)?;
5182                    self.write(")");
5183                }
5184            }
5185            _ => {
5186                self.generate_expression(opt)?;
5187            }
5188        }
5189        Ok(())
5190    }
5191
5192    fn generate_with(&mut self, with: &With) -> Result<()> {
5193        use crate::dialects::DialectType;
5194
5195        // Output leading comments before WITH
5196        for comment in &with.leading_comments {
5197            self.write_formatted_comment(comment);
5198            self.write(" ");
5199        }
5200        self.write_keyword("WITH");
5201        if with.recursive && self.config.cte_recursive_keyword_required {
5202            self.write_space();
5203            self.write_keyword("RECURSIVE");
5204        }
5205        self.write_space();
5206
5207        // BigQuery doesn't support column aliases in CTE definitions
5208        let skip_cte_columns = matches!(self.config.dialect, Some(DialectType::BigQuery));
5209
5210        for (i, cte) in with.ctes.iter().enumerate() {
5211            if i > 0 {
5212                self.write(",");
5213                if self.config.pretty {
5214                    self.write_space();
5215                } else {
5216                    self.write(" ");
5217                }
5218            }
5219            if matches!(self.config.dialect, Some(DialectType::ClickHouse)) && !cte.alias_first {
5220                self.generate_expression(&cte.this)?;
5221                self.write_space();
5222                self.write_keyword("AS");
5223                self.write_space();
5224                self.generate_identifier(&cte.alias)?;
5225                continue;
5226            }
5227            self.generate_identifier(&cte.alias)?;
5228            // Output CTE comments after alias name, before AS
5229            for comment in &cte.comments {
5230                self.write_space();
5231                self.write_formatted_comment(comment);
5232            }
5233            if !cte.columns.is_empty() && !skip_cte_columns {
5234                self.write("(");
5235                for (j, col) in cte.columns.iter().enumerate() {
5236                    if j > 0 {
5237                        self.write(", ");
5238                    }
5239                    self.generate_identifier(col)?;
5240                }
5241                self.write(")");
5242            }
5243            // USING KEY (columns) for DuckDB recursive CTEs
5244            if !cte.key_expressions.is_empty() {
5245                self.write_space();
5246                self.write_keyword("USING KEY");
5247                self.write(" (");
5248                for (i, key) in cte.key_expressions.iter().enumerate() {
5249                    if i > 0 {
5250                        self.write(", ");
5251                    }
5252                    self.generate_identifier(key)?;
5253                }
5254                self.write(")");
5255            }
5256            self.write_space();
5257            self.write_keyword("AS");
5258            // MATERIALIZED / NOT MATERIALIZED
5259            if let Some(materialized) = cte.materialized {
5260                self.write_space();
5261                if materialized {
5262                    self.write_keyword("MATERIALIZED");
5263                } else {
5264                    self.write_keyword("NOT MATERIALIZED");
5265                }
5266            }
5267            self.write(" (");
5268            if self.config.pretty {
5269                self.write_newline();
5270                self.indent_level += 1;
5271                self.write_indent();
5272            }
5273            // For Spark/Databricks, VALUES in a CTE must be wrapped with SELECT * FROM
5274            // e.g., WITH t AS (VALUES ('foo_val') AS t(foo1)) -> WITH t AS (SELECT * FROM VALUES ('foo_val') AS t(foo1))
5275            let wrap_values_in_select = matches!(
5276                self.config.dialect,
5277                Some(DialectType::Spark) | Some(DialectType::Databricks)
5278            ) && matches!(&cte.this, Expression::Values(_));
5279
5280            if wrap_values_in_select {
5281                self.write_keyword("SELECT");
5282                self.write(" * ");
5283                self.write_keyword("FROM");
5284                self.write_space();
5285            }
5286            self.generate_expression(&cte.this)?;
5287            if self.config.pretty {
5288                self.write_newline();
5289                self.indent_level -= 1;
5290                self.write_indent();
5291            }
5292            self.write(")");
5293        }
5294
5295        // Generate SEARCH/CYCLE clause if present
5296        if let Some(search) = &with.search {
5297            self.write_space();
5298            self.generate_expression(search)?;
5299        }
5300
5301        Ok(())
5302    }
5303
5304    /// Generate joins with proper nesting structure for pretty printing.
5305    /// Deferred-condition joins "own" the non-deferred joins that follow them
5306    /// within the same nesting_group.
5307    fn generate_joins_with_nesting(&mut self, joins: &[Join]) -> Result<()> {
5308        let mut i = 0;
5309        while i < joins.len() {
5310            if joins[i].deferred_condition {
5311                let parent_group = joins[i].nesting_group;
5312
5313                // This join owns the following non-deferred joins in the same nesting_group
5314                // First output the join keyword and table (without condition)
5315                self.generate_join_without_condition(&joins[i])?;
5316
5317                // Find the range of child joins: same nesting_group and not deferred
5318                let child_start = i + 1;
5319                let mut child_end = child_start;
5320                while child_end < joins.len()
5321                    && !joins[child_end].deferred_condition
5322                    && joins[child_end].nesting_group == parent_group
5323                {
5324                    child_end += 1;
5325                }
5326
5327                // Output child joins with extra indentation
5328                if child_start < child_end {
5329                    self.indent_level += 1;
5330                    for j in child_start..child_end {
5331                        self.generate_join(&joins[j])?;
5332                    }
5333                    self.indent_level -= 1;
5334                }
5335
5336                // Output the deferred condition at the parent level
5337                self.generate_join_condition(&joins[i])?;
5338
5339                i = child_end;
5340            } else {
5341                // Regular join (no nesting)
5342                self.generate_join(&joins[i])?;
5343                i += 1;
5344            }
5345        }
5346        Ok(())
5347    }
5348
5349    /// Generate a join's keyword and table reference, but not its ON/USING condition.
5350    /// Used for deferred-condition joins where the condition is output after child joins.
5351    fn generate_join_without_condition(&mut self, join: &Join) -> Result<()> {
5352        // Save and temporarily clear the condition to prevent generate_join from outputting it
5353        // We achieve this by creating a modified copy
5354        let mut join_copy = join.clone();
5355        join_copy.on = None;
5356        join_copy.using = Vec::new();
5357        join_copy.deferred_condition = false;
5358        self.generate_join(&join_copy)
5359    }
5360
5361    fn generate_join(&mut self, join: &Join) -> Result<()> {
5362        // Implicit (comma) joins: output as ", table" instead of "CROSS JOIN table"
5363        if join.kind == JoinKind::Implicit {
5364            self.write(",");
5365            if self.config.pretty {
5366                self.write_newline();
5367                self.write_indent();
5368            } else {
5369                self.write_space();
5370            }
5371            self.generate_expression(&join.this)?;
5372            return Ok(());
5373        }
5374
5375        if self.config.pretty {
5376            self.write_newline();
5377            self.write_indent();
5378        } else {
5379            self.write_space();
5380        }
5381
5382        // Helper: format hint suffix (e.g., " LOOP" or "")
5383        // Only include join hints for dialects that support them
5384        let hint_str = if self.config.join_hints {
5385            join.join_hint
5386                .as_ref()
5387                .map(|h| format!(" {}", h))
5388                .unwrap_or_default()
5389        } else {
5390            String::new()
5391        };
5392
5393        let clickhouse_join_keyword =
5394            if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
5395                if let Some(hint) = &join.join_hint {
5396                    let mut global = false;
5397                    let mut strictness: Option<&'static str> = None;
5398                    for part in hint.split_whitespace() {
5399                        if part.eq_ignore_ascii_case("GLOBAL") {
5400                            global = true;
5401                        } else if part.eq_ignore_ascii_case("ALL") {
5402                            strictness = Some("ALL");
5403                        } else if part.eq_ignore_ascii_case("ANY") {
5404                            strictness = Some("ANY");
5405                        } else if part.eq_ignore_ascii_case("ASOF") {
5406                            strictness = Some("ASOF");
5407                        } else if part.eq_ignore_ascii_case("SEMI") {
5408                            strictness = Some("SEMI");
5409                        } else if part.eq_ignore_ascii_case("ANTI") {
5410                            strictness = Some("ANTI");
5411                        }
5412                    }
5413
5414                    if global || strictness.is_some() {
5415                        let join_type = match join.kind {
5416                            JoinKind::Left => {
5417                                if join.use_outer_keyword {
5418                                    "LEFT OUTER"
5419                                } else if join.use_inner_keyword {
5420                                    "LEFT INNER"
5421                                } else {
5422                                    "LEFT"
5423                                }
5424                            }
5425                            JoinKind::Right => {
5426                                if join.use_outer_keyword {
5427                                    "RIGHT OUTER"
5428                                } else if join.use_inner_keyword {
5429                                    "RIGHT INNER"
5430                                } else {
5431                                    "RIGHT"
5432                                }
5433                            }
5434                            JoinKind::Full => {
5435                                if join.use_outer_keyword {
5436                                    "FULL OUTER"
5437                                } else {
5438                                    "FULL"
5439                                }
5440                            }
5441                            JoinKind::Inner => {
5442                                if join.use_inner_keyword {
5443                                    "INNER"
5444                                } else {
5445                                    ""
5446                                }
5447                            }
5448                            _ => "",
5449                        };
5450
5451                        let mut parts = Vec::new();
5452                        if global {
5453                            parts.push("GLOBAL");
5454                        }
5455                        if !join_type.is_empty() {
5456                            parts.push(join_type);
5457                        }
5458                        if let Some(strict) = strictness {
5459                            parts.push(strict);
5460                        }
5461                        parts.push("JOIN");
5462                        Some(parts.join(" "))
5463                    } else {
5464                        None
5465                    }
5466                } else {
5467                    None
5468                }
5469            } else {
5470                None
5471            };
5472
5473        // Output any comments associated with this join
5474        // In pretty mode, comments go on their own line before the join keyword
5475        // In non-pretty mode, comments go inline before the join keyword
5476        if !join.comments.is_empty() {
5477            if self.config.pretty {
5478                // In pretty mode, go back before the newline+indent we just wrote
5479                // and output comments on their own lines
5480                // We need to output comments BEFORE the join keyword on separate lines
5481                // Trim the trailing newline+indent we already wrote
5482                let trimmed = self.output.trim_end().len();
5483                self.output.truncate(trimmed);
5484                for comment in &join.comments {
5485                    self.write_newline();
5486                    self.write_indent();
5487                    self.write_formatted_comment(comment);
5488                }
5489                self.write_newline();
5490                self.write_indent();
5491            } else {
5492                for comment in &join.comments {
5493                    self.write_formatted_comment(comment);
5494                    self.write_space();
5495                }
5496            }
5497        }
5498
5499        let directed_str = if join.directed { " DIRECTED" } else { "" };
5500
5501        if let Some(keyword) = clickhouse_join_keyword {
5502            self.write_keyword(&keyword);
5503        } else {
5504            match join.kind {
5505                JoinKind::Inner => {
5506                    if join.use_inner_keyword {
5507                        if hint_str.is_empty() && directed_str.is_empty() {
5508                            self.write_keyword("INNER JOIN");
5509                        } else {
5510                            self.write_keyword("INNER");
5511                            if !hint_str.is_empty() {
5512                                self.write_keyword(&hint_str);
5513                            }
5514                            if !directed_str.is_empty() {
5515                                self.write_keyword(directed_str);
5516                            }
5517                            self.write_keyword(" JOIN");
5518                        }
5519                    } else {
5520                        if !hint_str.is_empty() {
5521                            self.write_keyword(hint_str.trim());
5522                            self.write_keyword(" ");
5523                        }
5524                        if !directed_str.is_empty() {
5525                            self.write_keyword("DIRECTED ");
5526                        }
5527                        self.write_keyword("JOIN");
5528                    }
5529                }
5530                JoinKind::Left => {
5531                    if join.use_outer_keyword {
5532                        if hint_str.is_empty() && directed_str.is_empty() {
5533                            self.write_keyword("LEFT OUTER JOIN");
5534                        } else {
5535                            self.write_keyword("LEFT OUTER");
5536                            if !hint_str.is_empty() {
5537                                self.write_keyword(&hint_str);
5538                            }
5539                            if !directed_str.is_empty() {
5540                                self.write_keyword(directed_str);
5541                            }
5542                            self.write_keyword(" JOIN");
5543                        }
5544                    } else if join.use_inner_keyword {
5545                        if hint_str.is_empty() && directed_str.is_empty() {
5546                            self.write_keyword("LEFT INNER JOIN");
5547                        } else {
5548                            self.write_keyword("LEFT INNER");
5549                            if !hint_str.is_empty() {
5550                                self.write_keyword(&hint_str);
5551                            }
5552                            if !directed_str.is_empty() {
5553                                self.write_keyword(directed_str);
5554                            }
5555                            self.write_keyword(" JOIN");
5556                        }
5557                    } else {
5558                        if hint_str.is_empty() && directed_str.is_empty() {
5559                            self.write_keyword("LEFT JOIN");
5560                        } else {
5561                            self.write_keyword("LEFT");
5562                            if !hint_str.is_empty() {
5563                                self.write_keyword(&hint_str);
5564                            }
5565                            if !directed_str.is_empty() {
5566                                self.write_keyword(directed_str);
5567                            }
5568                            self.write_keyword(" JOIN");
5569                        }
5570                    }
5571                }
5572                JoinKind::Right => {
5573                    if join.use_outer_keyword {
5574                        if hint_str.is_empty() && directed_str.is_empty() {
5575                            self.write_keyword("RIGHT OUTER JOIN");
5576                        } else {
5577                            self.write_keyword("RIGHT OUTER");
5578                            if !hint_str.is_empty() {
5579                                self.write_keyword(&hint_str);
5580                            }
5581                            if !directed_str.is_empty() {
5582                                self.write_keyword(directed_str);
5583                            }
5584                            self.write_keyword(" JOIN");
5585                        }
5586                    } else if join.use_inner_keyword {
5587                        if hint_str.is_empty() && directed_str.is_empty() {
5588                            self.write_keyword("RIGHT INNER JOIN");
5589                        } else {
5590                            self.write_keyword("RIGHT INNER");
5591                            if !hint_str.is_empty() {
5592                                self.write_keyword(&hint_str);
5593                            }
5594                            if !directed_str.is_empty() {
5595                                self.write_keyword(directed_str);
5596                            }
5597                            self.write_keyword(" JOIN");
5598                        }
5599                    } else {
5600                        if hint_str.is_empty() && directed_str.is_empty() {
5601                            self.write_keyword("RIGHT JOIN");
5602                        } else {
5603                            self.write_keyword("RIGHT");
5604                            if !hint_str.is_empty() {
5605                                self.write_keyword(&hint_str);
5606                            }
5607                            if !directed_str.is_empty() {
5608                                self.write_keyword(directed_str);
5609                            }
5610                            self.write_keyword(" JOIN");
5611                        }
5612                    }
5613                }
5614                JoinKind::Full => {
5615                    if join.use_outer_keyword {
5616                        if hint_str.is_empty() && directed_str.is_empty() {
5617                            self.write_keyword("FULL OUTER JOIN");
5618                        } else {
5619                            self.write_keyword("FULL OUTER");
5620                            if !hint_str.is_empty() {
5621                                self.write_keyword(&hint_str);
5622                            }
5623                            if !directed_str.is_empty() {
5624                                self.write_keyword(directed_str);
5625                            }
5626                            self.write_keyword(" JOIN");
5627                        }
5628                    } else {
5629                        if hint_str.is_empty() && directed_str.is_empty() {
5630                            self.write_keyword("FULL JOIN");
5631                        } else {
5632                            self.write_keyword("FULL");
5633                            if !hint_str.is_empty() {
5634                                self.write_keyword(&hint_str);
5635                            }
5636                            if !directed_str.is_empty() {
5637                                self.write_keyword(directed_str);
5638                            }
5639                            self.write_keyword(" JOIN");
5640                        }
5641                    }
5642                }
5643                JoinKind::Outer => {
5644                    if directed_str.is_empty() {
5645                        self.write_keyword("OUTER JOIN");
5646                    } else {
5647                        self.write_keyword("OUTER");
5648                        self.write_keyword(directed_str);
5649                        self.write_keyword(" JOIN");
5650                    }
5651                }
5652                JoinKind::Cross => {
5653                    if directed_str.is_empty() {
5654                        self.write_keyword("CROSS JOIN");
5655                    } else {
5656                        self.write_keyword("CROSS");
5657                        self.write_keyword(directed_str);
5658                        self.write_keyword(" JOIN");
5659                    }
5660                }
5661                JoinKind::Natural => {
5662                    if join.use_inner_keyword {
5663                        if directed_str.is_empty() {
5664                            self.write_keyword("NATURAL INNER JOIN");
5665                        } else {
5666                            self.write_keyword("NATURAL INNER");
5667                            self.write_keyword(directed_str);
5668                            self.write_keyword(" JOIN");
5669                        }
5670                    } else {
5671                        if directed_str.is_empty() {
5672                            self.write_keyword("NATURAL JOIN");
5673                        } else {
5674                            self.write_keyword("NATURAL");
5675                            self.write_keyword(directed_str);
5676                            self.write_keyword(" JOIN");
5677                        }
5678                    }
5679                }
5680                JoinKind::NaturalLeft => {
5681                    if join.use_outer_keyword {
5682                        if directed_str.is_empty() {
5683                            self.write_keyword("NATURAL LEFT OUTER JOIN");
5684                        } else {
5685                            self.write_keyword("NATURAL LEFT OUTER");
5686                            self.write_keyword(directed_str);
5687                            self.write_keyword(" JOIN");
5688                        }
5689                    } else {
5690                        if directed_str.is_empty() {
5691                            self.write_keyword("NATURAL LEFT JOIN");
5692                        } else {
5693                            self.write_keyword("NATURAL LEFT");
5694                            self.write_keyword(directed_str);
5695                            self.write_keyword(" JOIN");
5696                        }
5697                    }
5698                }
5699                JoinKind::NaturalRight => {
5700                    if join.use_outer_keyword {
5701                        if directed_str.is_empty() {
5702                            self.write_keyword("NATURAL RIGHT OUTER JOIN");
5703                        } else {
5704                            self.write_keyword("NATURAL RIGHT OUTER");
5705                            self.write_keyword(directed_str);
5706                            self.write_keyword(" JOIN");
5707                        }
5708                    } else {
5709                        if directed_str.is_empty() {
5710                            self.write_keyword("NATURAL RIGHT JOIN");
5711                        } else {
5712                            self.write_keyword("NATURAL RIGHT");
5713                            self.write_keyword(directed_str);
5714                            self.write_keyword(" JOIN");
5715                        }
5716                    }
5717                }
5718                JoinKind::NaturalFull => {
5719                    if join.use_outer_keyword {
5720                        if directed_str.is_empty() {
5721                            self.write_keyword("NATURAL FULL OUTER JOIN");
5722                        } else {
5723                            self.write_keyword("NATURAL FULL OUTER");
5724                            self.write_keyword(directed_str);
5725                            self.write_keyword(" JOIN");
5726                        }
5727                    } else {
5728                        if directed_str.is_empty() {
5729                            self.write_keyword("NATURAL FULL JOIN");
5730                        } else {
5731                            self.write_keyword("NATURAL FULL");
5732                            self.write_keyword(directed_str);
5733                            self.write_keyword(" JOIN");
5734                        }
5735                    }
5736                }
5737                JoinKind::Semi => self.write_keyword("SEMI JOIN"),
5738                JoinKind::Anti => self.write_keyword("ANTI JOIN"),
5739                JoinKind::LeftSemi => self.write_keyword("LEFT SEMI JOIN"),
5740                JoinKind::LeftAnti => self.write_keyword("LEFT ANTI JOIN"),
5741                JoinKind::RightSemi => self.write_keyword("RIGHT SEMI JOIN"),
5742                JoinKind::RightAnti => self.write_keyword("RIGHT ANTI JOIN"),
5743                JoinKind::CrossApply => {
5744                    // CROSS APPLY -> INNER JOIN LATERAL for non-TSQL dialects
5745                    if matches!(self.config.dialect, Some(DialectType::TSQL) | None) {
5746                        self.write_keyword("CROSS APPLY");
5747                    } else {
5748                        self.write_keyword("INNER JOIN LATERAL");
5749                    }
5750                }
5751                JoinKind::OuterApply => {
5752                    // OUTER APPLY -> LEFT JOIN LATERAL for non-TSQL dialects
5753                    if matches!(self.config.dialect, Some(DialectType::TSQL) | None) {
5754                        self.write_keyword("OUTER APPLY");
5755                    } else {
5756                        self.write_keyword("LEFT JOIN LATERAL");
5757                    }
5758                }
5759                JoinKind::AsOf => self.write_keyword("ASOF JOIN"),
5760                JoinKind::AsOfLeft => {
5761                    if join.use_outer_keyword {
5762                        self.write_keyword("ASOF LEFT OUTER JOIN");
5763                    } else {
5764                        self.write_keyword("ASOF LEFT JOIN");
5765                    }
5766                }
5767                JoinKind::AsOfRight => {
5768                    if join.use_outer_keyword {
5769                        self.write_keyword("ASOF RIGHT OUTER JOIN");
5770                    } else {
5771                        self.write_keyword("ASOF RIGHT JOIN");
5772                    }
5773                }
5774                JoinKind::Lateral => self.write_keyword("LATERAL JOIN"),
5775                JoinKind::LeftLateral => {
5776                    if join.use_outer_keyword {
5777                        self.write_keyword("LEFT OUTER LATERAL JOIN");
5778                    } else {
5779                        self.write_keyword("LEFT LATERAL JOIN");
5780                    }
5781                }
5782                JoinKind::Straight => self.write_keyword("STRAIGHT_JOIN"),
5783                JoinKind::Implicit => {
5784                    // BigQuery, Hive, Spark, and Databricks prefer explicit CROSS JOIN over comma syntax
5785                    // But only when source is the same dialect (identity) or source is another CROSS JOIN dialect
5786                    // When source is Generic, keep commas (Python sqlglot: parser marks joins, not generator)
5787                    use crate::dialects::DialectType;
5788                    let is_cj_dialect = matches!(
5789                        self.config.dialect,
5790                        Some(DialectType::BigQuery)
5791                            | Some(DialectType::Hive)
5792                            | Some(DialectType::Spark)
5793                            | Some(DialectType::Databricks)
5794                    );
5795                    let source_is_same = self.config.source_dialect.is_some()
5796                        && self.config.source_dialect == self.config.dialect;
5797                    let source_is_cj = matches!(
5798                        self.config.source_dialect,
5799                        Some(DialectType::BigQuery)
5800                            | Some(DialectType::Hive)
5801                            | Some(DialectType::Spark)
5802                            | Some(DialectType::Databricks)
5803                    );
5804                    if is_cj_dialect
5805                        && (source_is_same || source_is_cj || self.config.source_dialect.is_none())
5806                    {
5807                        self.write_keyword("CROSS JOIN");
5808                    } else {
5809                        // Implicit join uses comma: FROM a, b
5810                        // We already wrote a space before the match, so replace with comma
5811                        // by removing trailing space and writing ", "
5812                        self.output.truncate(self.output.trim_end().len());
5813                        self.write(",");
5814                    }
5815                }
5816                JoinKind::Array => self.write_keyword("ARRAY JOIN"),
5817                JoinKind::LeftArray => self.write_keyword("LEFT ARRAY JOIN"),
5818                JoinKind::Paste => self.write_keyword("PASTE JOIN"),
5819                JoinKind::Positional => self.write_keyword("POSITIONAL JOIN"),
5820            }
5821        }
5822
5823        // ARRAY JOIN items need comma-separated output (Tuple holds multiple items)
5824        if matches!(join.kind, JoinKind::Array | JoinKind::LeftArray) {
5825            match &join.this {
5826                Expression::Tuple(t) if t.expressions.is_empty() => {}
5827                Expression::Tuple(t) => {
5828                    self.write_space();
5829                    for (i, item) in t.expressions.iter().enumerate() {
5830                        if i > 0 {
5831                            self.write(", ");
5832                        }
5833                        self.generate_expression(item)?;
5834                    }
5835                }
5836                other => {
5837                    self.write_space();
5838                    self.generate_expression(other)?;
5839                }
5840            }
5841        } else {
5842            self.write_space();
5843            self.generate_expression(&join.this)?;
5844        }
5845
5846        // Only output MATCH_CONDITION/ON/USING inline if the condition wasn't deferred
5847        if !join.deferred_condition {
5848            // Output MATCH_CONDITION first (Snowflake ASOF JOIN)
5849            if let Some(match_cond) = &join.match_condition {
5850                self.write_space();
5851                self.write_keyword("MATCH_CONDITION");
5852                self.write(" (");
5853                self.generate_expression(match_cond)?;
5854                self.write(")");
5855            }
5856
5857            if let Some(on) = &join.on {
5858                if self.config.pretty {
5859                    self.write_newline();
5860                    self.indent_level += 1;
5861                    self.write_indent();
5862                    self.write_keyword("ON");
5863                    self.write_space();
5864                    self.generate_join_on_condition(on)?;
5865                    self.indent_level -= 1;
5866                } else {
5867                    self.write_space();
5868                    self.write_keyword("ON");
5869                    self.write_space();
5870                    self.generate_expression(on)?;
5871                }
5872            }
5873
5874            if !join.using.is_empty() {
5875                if self.config.pretty {
5876                    self.write_newline();
5877                    self.indent_level += 1;
5878                    self.write_indent();
5879                    self.write_keyword("USING");
5880                    self.write(" (");
5881                    for (i, col) in join.using.iter().enumerate() {
5882                        if i > 0 {
5883                            self.write(", ");
5884                        }
5885                        self.generate_identifier(col)?;
5886                    }
5887                    self.write(")");
5888                    self.indent_level -= 1;
5889                } else {
5890                    self.write_space();
5891                    self.write_keyword("USING");
5892                    self.write(" (");
5893                    for (i, col) in join.using.iter().enumerate() {
5894                        if i > 0 {
5895                            self.write(", ");
5896                        }
5897                        self.generate_identifier(col)?;
5898                    }
5899                    self.write(")");
5900                }
5901            }
5902        }
5903
5904        // Generate PIVOT/UNPIVOT expressions that follow this join
5905        for pivot in &join.pivots {
5906            self.write_space();
5907            self.generate_expression(pivot)?;
5908        }
5909
5910        Ok(())
5911    }
5912
5913    /// Generate just the ON/USING/MATCH_CONDITION for a join (used for deferred conditions)
5914    fn generate_join_condition(&mut self, join: &Join) -> Result<()> {
5915        // Generate MATCH_CONDITION first (Snowflake ASOF JOIN)
5916        if let Some(match_cond) = &join.match_condition {
5917            self.write_space();
5918            self.write_keyword("MATCH_CONDITION");
5919            self.write(" (");
5920            self.generate_expression(match_cond)?;
5921            self.write(")");
5922        }
5923
5924        if let Some(on) = &join.on {
5925            if self.config.pretty {
5926                self.write_newline();
5927                self.indent_level += 1;
5928                self.write_indent();
5929                self.write_keyword("ON");
5930                self.write_space();
5931                // In pretty mode, split AND conditions onto separate lines
5932                self.generate_join_on_condition(on)?;
5933                self.indent_level -= 1;
5934            } else {
5935                self.write_space();
5936                self.write_keyword("ON");
5937                self.write_space();
5938                self.generate_expression(on)?;
5939            }
5940        }
5941
5942        if !join.using.is_empty() {
5943            if self.config.pretty {
5944                self.write_newline();
5945                self.indent_level += 1;
5946                self.write_indent();
5947                self.write_keyword("USING");
5948                self.write(" (");
5949                for (i, col) in join.using.iter().enumerate() {
5950                    if i > 0 {
5951                        self.write(", ");
5952                    }
5953                    self.generate_identifier(col)?;
5954                }
5955                self.write(")");
5956                self.indent_level -= 1;
5957            } else {
5958                self.write_space();
5959                self.write_keyword("USING");
5960                self.write(" (");
5961                for (i, col) in join.using.iter().enumerate() {
5962                    if i > 0 {
5963                        self.write(", ");
5964                    }
5965                    self.generate_identifier(col)?;
5966                }
5967                self.write(")");
5968            }
5969        }
5970
5971        // Generate PIVOT/UNPIVOT expressions that follow this join (for deferred conditions)
5972        for pivot in &join.pivots {
5973            self.write_space();
5974            self.generate_expression(pivot)?;
5975        }
5976
5977        Ok(())
5978    }
5979
5980    /// Generate JOIN ON condition with AND clauses on separate lines in pretty mode
5981    fn generate_join_on_condition(&mut self, expr: &Expression) -> Result<()> {
5982        if let Expression::And(and_op) = expr {
5983            if let Some(conditions) = self.flatten_connector_terms(and_op, ConnectorOperator::And) {
5984                self.generate_expression(conditions[0])?;
5985                for condition in conditions.iter().skip(1) {
5986                    self.write_newline();
5987                    self.write_indent();
5988                    self.write_keyword("AND");
5989                    self.write_space();
5990                    self.generate_expression(condition)?;
5991                }
5992                return Ok(());
5993            }
5994        }
5995
5996        self.generate_expression(expr)
5997    }
5998
5999    fn generate_joined_table(&mut self, jt: &JoinedTable) -> Result<()> {
6000        // Parenthesized join: (tbl1 CROSS JOIN tbl2)
6001        self.write("(");
6002        self.generate_expression(&jt.left)?;
6003
6004        // Generate all joins
6005        for join in &jt.joins {
6006            self.generate_join(join)?;
6007        }
6008
6009        // Generate LATERAL VIEW clauses (Hive/Spark)
6010        for (lv_idx, lv) in jt.lateral_views.iter().enumerate() {
6011            self.generate_lateral_view(lv, lv_idx)?;
6012        }
6013
6014        self.write(")");
6015
6016        // Alias
6017        if let Some(alias) = &jt.alias {
6018            self.write_space();
6019            self.write_keyword("AS");
6020            self.write_space();
6021            self.generate_identifier(alias)?;
6022        }
6023
6024        Ok(())
6025    }
6026
6027    fn generate_lateral_view(&mut self, lv: &LateralView, lv_index: usize) -> Result<()> {
6028        use crate::dialects::DialectType;
6029
6030        if self.config.pretty {
6031            self.write_newline();
6032            self.write_indent();
6033        } else {
6034            self.write_space();
6035        }
6036
6037        // For Hive/Spark/Databricks (or no dialect specified), output native LATERAL VIEW syntax
6038        // For PostgreSQL and other specific dialects, convert to CROSS JOIN (LATERAL or UNNEST)
6039        let use_lateral_join = matches!(
6040            self.config.dialect,
6041            Some(DialectType::PostgreSQL)
6042                | Some(DialectType::DuckDB)
6043                | Some(DialectType::Snowflake)
6044                | Some(DialectType::TSQL)
6045                | Some(DialectType::Presto)
6046                | Some(DialectType::Trino)
6047                | Some(DialectType::Athena)
6048        );
6049
6050        // Check if target dialect should use UNNEST instead of EXPLODE
6051        let use_unnest = matches!(
6052            self.config.dialect,
6053            Some(DialectType::DuckDB)
6054                | Some(DialectType::Presto)
6055                | Some(DialectType::Trino)
6056                | Some(DialectType::Athena)
6057        );
6058
6059        // Check if we need POSEXPLODE -> UNNEST WITH ORDINALITY
6060        let (is_posexplode, is_inline, func_args) = match &lv.this {
6061            Expression::Explode(uf) => {
6062                // Expression::Explode is the dedicated EXPLODE expression type
6063                (false, false, vec![uf.this.clone()])
6064            }
6065            Expression::Unnest(uf) => {
6066                let mut args = vec![uf.this.clone()];
6067                args.extend(uf.expressions.clone());
6068                (false, false, args)
6069            }
6070            Expression::Function(func) => {
6071                if func.name.eq_ignore_ascii_case("POSEXPLODE")
6072                    || func.name.eq_ignore_ascii_case("POSEXPLODE_OUTER")
6073                {
6074                    (true, false, func.args.clone())
6075                } else if func.name.eq_ignore_ascii_case("INLINE") {
6076                    (false, true, func.args.clone())
6077                } else if func.name.eq_ignore_ascii_case("EXPLODE")
6078                    || func.name.eq_ignore_ascii_case("EXPLODE_OUTER")
6079                {
6080                    (false, false, func.args.clone())
6081                } else {
6082                    (false, false, vec![])
6083                }
6084            }
6085            _ => (false, false, vec![]),
6086        };
6087
6088        if use_lateral_join {
6089            // Convert to CROSS JOIN for PostgreSQL-like dialects
6090            if lv.outer {
6091                self.write_keyword("LEFT JOIN LATERAL");
6092            } else {
6093                self.write_keyword("CROSS JOIN");
6094            }
6095            self.write_space();
6096
6097            if use_unnest && !func_args.is_empty() {
6098                // Convert EXPLODE(y) -> UNNEST(y), POSEXPLODE(y) -> UNNEST(y)
6099                // For DuckDB, also convert ARRAY(y) -> [y]
6100                let unnest_args = if matches!(self.config.dialect, Some(DialectType::DuckDB)) {
6101                    // DuckDB: ARRAY(y) -> [y]
6102                    func_args
6103                        .iter()
6104                        .map(|a| {
6105                            if let Expression::Function(ref f) = a {
6106                                if f.name.eq_ignore_ascii_case("ARRAY") && f.args.len() == 1 {
6107                                    return Expression::ArrayFunc(Box::new(
6108                                        crate::expressions::ArrayConstructor {
6109                                            expressions: f.args.clone(),
6110                                            bracket_notation: true,
6111                                            use_list_keyword: false,
6112                                        },
6113                                    ));
6114                                }
6115                            }
6116                            a.clone()
6117                        })
6118                        .collect::<Vec<_>>()
6119                } else if matches!(
6120                    self.config.dialect,
6121                    Some(DialectType::Presto)
6122                        | Some(DialectType::Trino)
6123                        | Some(DialectType::Athena)
6124                ) {
6125                    // Presto: ARRAY(y) -> ARRAY[y]
6126                    func_args
6127                        .iter()
6128                        .map(|a| {
6129                            if let Expression::Function(ref f) = a {
6130                                if f.name.eq_ignore_ascii_case("ARRAY") && f.args.len() >= 1 {
6131                                    return Expression::ArrayFunc(Box::new(
6132                                        crate::expressions::ArrayConstructor {
6133                                            expressions: f.args.clone(),
6134                                            bracket_notation: true,
6135                                            use_list_keyword: false,
6136                                        },
6137                                    ));
6138                                }
6139                            }
6140                            a.clone()
6141                        })
6142                        .collect::<Vec<_>>()
6143                } else {
6144                    func_args
6145                };
6146
6147                // POSEXPLODE -> LATERAL (SELECT pos - 1 AS pos, col FROM UNNEST(y) WITH ORDINALITY AS t(col, pos))
6148                if is_posexplode {
6149                    self.write_keyword("LATERAL");
6150                    self.write(" (");
6151                    self.write_keyword("SELECT");
6152                    self.write_space();
6153
6154                    // Build the outer SELECT list: pos - 1 AS pos, then data columns
6155                    // column_aliases[0] is the position column, rest are data columns
6156                    let pos_alias = if !lv.column_aliases.is_empty() {
6157                        lv.column_aliases[0].clone()
6158                    } else {
6159                        Identifier::new("pos")
6160                    };
6161                    let data_aliases: Vec<Identifier> = if lv.column_aliases.len() > 1 {
6162                        lv.column_aliases[1..].to_vec()
6163                    } else {
6164                        vec![Identifier::new("col")]
6165                    };
6166
6167                    // pos - 1 AS pos
6168                    self.generate_identifier(&pos_alias)?;
6169                    self.write(" - 1");
6170                    self.write_space();
6171                    self.write_keyword("AS");
6172                    self.write_space();
6173                    self.generate_identifier(&pos_alias)?;
6174
6175                    // , col [, key, value ...]
6176                    for data_col in &data_aliases {
6177                        self.write(", ");
6178                        self.generate_identifier(data_col)?;
6179                    }
6180
6181                    self.write_space();
6182                    self.write_keyword("FROM");
6183                    self.write_space();
6184                    self.write_keyword("UNNEST");
6185                    self.write("(");
6186                    for (i, arg) in unnest_args.iter().enumerate() {
6187                        if i > 0 {
6188                            self.write(", ");
6189                        }
6190                        self.generate_expression(arg)?;
6191                    }
6192                    self.write(")");
6193                    self.write_space();
6194                    self.write_keyword("WITH ORDINALITY");
6195                    self.write_space();
6196                    self.write_keyword("AS");
6197                    self.write_space();
6198
6199                    // Inner alias: t(data_cols..., pos) - data columns first, pos last
6200                    let table_alias_ident = lv
6201                        .table_alias
6202                        .clone()
6203                        .unwrap_or_else(|| Identifier::new("t"));
6204                    self.generate_identifier(&table_alias_ident)?;
6205                    self.write("(");
6206                    for (i, data_col) in data_aliases.iter().enumerate() {
6207                        if i > 0 {
6208                            self.write(", ");
6209                        }
6210                        self.generate_identifier(data_col)?;
6211                    }
6212                    self.write(", ");
6213                    self.generate_identifier(&pos_alias)?;
6214                    self.write("))");
6215                } else if is_inline && matches!(self.config.dialect, Some(DialectType::DuckDB)) {
6216                    // INLINE -> LATERAL (SELECT UNNEST(arg, max_depth => 2)) AS alias
6217                    self.write_keyword("LATERAL");
6218                    self.write(" (");
6219                    self.write_keyword("SELECT");
6220                    self.write_space();
6221                    self.write_keyword("UNNEST");
6222                    self.write("(");
6223                    for (i, arg) in unnest_args.iter().enumerate() {
6224                        if i > 0 {
6225                            self.write(", ");
6226                        }
6227                        self.generate_expression(arg)?;
6228                    }
6229                    self.write(", ");
6230                    self.write_keyword("max_depth");
6231                    self.write(" => 2))");
6232
6233                    // Add table and column aliases
6234                    if let Some(alias) = &lv.table_alias {
6235                        self.write_space();
6236                        self.write_keyword("AS");
6237                        self.write_space();
6238                        self.generate_identifier(alias)?;
6239                        if !lv.column_aliases.is_empty() {
6240                            self.write("(");
6241                            for (i, col) in lv.column_aliases.iter().enumerate() {
6242                                if i > 0 {
6243                                    self.write(", ");
6244                                }
6245                                self.generate_identifier(col)?;
6246                            }
6247                            self.write(")");
6248                        }
6249                    } else if !lv.column_aliases.is_empty() {
6250                        // Auto-generate alias like _u_N
6251                        self.write_space();
6252                        self.write_keyword("AS");
6253                        self.write_space();
6254                        self.write(&format!("_u_{}", lv_index));
6255                        self.write("(");
6256                        for (i, col) in lv.column_aliases.iter().enumerate() {
6257                            if i > 0 {
6258                                self.write(", ");
6259                            }
6260                            self.generate_identifier(col)?;
6261                        }
6262                        self.write(")");
6263                    }
6264                } else {
6265                    self.write_keyword("UNNEST");
6266                    self.write("(");
6267                    for (i, arg) in unnest_args.iter().enumerate() {
6268                        if i > 0 {
6269                            self.write(", ");
6270                        }
6271                        self.generate_expression(arg)?;
6272                    }
6273                    self.write(")");
6274
6275                    // Add table and column aliases for non-POSEXPLODE
6276                    if let Some(alias) = &lv.table_alias {
6277                        self.write_space();
6278                        self.write_keyword("AS");
6279                        self.write_space();
6280                        self.generate_identifier(alias)?;
6281                        if !lv.column_aliases.is_empty() {
6282                            self.write("(");
6283                            for (i, col) in lv.column_aliases.iter().enumerate() {
6284                                if i > 0 {
6285                                    self.write(", ");
6286                                }
6287                                self.generate_identifier(col)?;
6288                            }
6289                            self.write(")");
6290                        }
6291                    } else if !lv.column_aliases.is_empty() {
6292                        self.write_space();
6293                        self.write_keyword("AS");
6294                        self.write(" t(");
6295                        for (i, col) in lv.column_aliases.iter().enumerate() {
6296                            if i > 0 {
6297                                self.write(", ");
6298                            }
6299                            self.generate_identifier(col)?;
6300                        }
6301                        self.write(")");
6302                    }
6303                }
6304            } else {
6305                // Not EXPLODE/POSEXPLODE or not using UNNEST, use LATERAL
6306                if !lv.outer {
6307                    self.write_keyword("LATERAL");
6308                    self.write_space();
6309                }
6310                self.generate_expression(&lv.this)?;
6311
6312                // Add table and column aliases
6313                if let Some(alias) = &lv.table_alias {
6314                    self.write_space();
6315                    self.write_keyword("AS");
6316                    self.write_space();
6317                    self.generate_identifier(alias)?;
6318                    if !lv.column_aliases.is_empty() {
6319                        self.write("(");
6320                        for (i, col) in lv.column_aliases.iter().enumerate() {
6321                            if i > 0 {
6322                                self.write(", ");
6323                            }
6324                            self.generate_identifier(col)?;
6325                        }
6326                        self.write(")");
6327                    }
6328                } else if !lv.column_aliases.is_empty() {
6329                    self.write_space();
6330                    self.write_keyword("AS");
6331                    self.write(" t(");
6332                    for (i, col) in lv.column_aliases.iter().enumerate() {
6333                        if i > 0 {
6334                            self.write(", ");
6335                        }
6336                        self.generate_identifier(col)?;
6337                    }
6338                    self.write(")");
6339                }
6340            }
6341
6342            // For LEFT JOIN LATERAL, need ON TRUE
6343            if lv.outer {
6344                self.write_space();
6345                self.write_keyword("ON TRUE");
6346            }
6347        } else {
6348            // Output native LATERAL VIEW syntax (Hive/Spark/Databricks or default)
6349            self.write_keyword("LATERAL VIEW");
6350            if lv.outer {
6351                self.write_space();
6352                self.write_keyword("OUTER");
6353            }
6354            if self.config.pretty {
6355                self.write_newline();
6356                self.write_indent();
6357            } else {
6358                self.write_space();
6359            }
6360            self.generate_expression(&lv.this)?;
6361
6362            // Table alias
6363            if let Some(alias) = &lv.table_alias {
6364                self.write_space();
6365                self.generate_identifier(alias)?;
6366            }
6367
6368            // Column aliases
6369            if !lv.column_aliases.is_empty() {
6370                self.write_space();
6371                self.write_keyword("AS");
6372                self.write_space();
6373                for (i, col) in lv.column_aliases.iter().enumerate() {
6374                    if i > 0 {
6375                        self.write(", ");
6376                    }
6377                    self.generate_identifier(col)?;
6378                }
6379            }
6380        }
6381
6382        Ok(())
6383    }
6384
6385    fn generate_union(&mut self, outermost: &Union) -> Result<()> {
6386        // Collect the left-recursive chain of Union nodes iteratively.
6387        // This avoids stack overflow for deeply nested chains like
6388        // SELECT 1 UNION ALL SELECT 2 UNION ALL ... UNION ALL SELECT N
6389        // where the parser builds: Union(Union(Union(A, B), C), D)
6390        let mut chain: Vec<&Union> = vec![outermost];
6391        let mut leftmost: &Expression = &outermost.left;
6392        while let Expression::Union(inner) = leftmost {
6393            chain.push(inner);
6394            leftmost = &inner.left;
6395        }
6396        // chain[0] = outermost, chain[last] = innermost
6397        // leftmost = innermost.left (a non-Union expression, typically Select)
6398
6399        // WITH clause (only on outermost)
6400        if let Some(with) = &outermost.with {
6401            self.generate_with(with)?;
6402            self.write_space();
6403        }
6404
6405        // Generate the base (leftmost) expression
6406        self.generate_expression(leftmost)?;
6407
6408        // Generate each union step from innermost to outermost
6409        for union in chain.iter().rev() {
6410            self.generate_union_step(union)?;
6411        }
6412        Ok(())
6413    }
6414
6415    /// Generate a single UNION step: keyword, right expression, and trailing modifiers.
6416    fn generate_union_step(&mut self, union: &Union) -> Result<()> {
6417        if self.config.pretty {
6418            self.write_newline();
6419            self.write_indent();
6420        } else {
6421            self.write_space();
6422        }
6423
6424        // BigQuery set operation modifiers: [side] [kind] UNION
6425        if let Some(side) = &union.side {
6426            self.write_keyword(side);
6427            self.write_space();
6428        }
6429        if let Some(kind) = &union.kind {
6430            self.write_keyword(kind);
6431            self.write_space();
6432        }
6433
6434        self.write_keyword("UNION");
6435        if union.all {
6436            self.write_space();
6437            self.write_keyword("ALL");
6438        } else if union.distinct {
6439            self.write_space();
6440            self.write_keyword("DISTINCT");
6441        }
6442
6443        // BigQuery: CORRESPONDING/STRICT CORRESPONDING -> BY NAME, BY (cols) -> ON (cols)
6444        // DuckDB: BY NAME
6445        if union.corresponding || union.by_name {
6446            self.write_space();
6447            self.write_keyword("BY NAME");
6448        }
6449        if !union.on_columns.is_empty() {
6450            self.write_space();
6451            self.write_keyword("ON");
6452            self.write(" (");
6453            for (i, col) in union.on_columns.iter().enumerate() {
6454                if i > 0 {
6455                    self.write(", ");
6456                }
6457                self.generate_expression(col)?;
6458            }
6459            self.write(")");
6460        }
6461
6462        if self.config.pretty {
6463            self.write_newline();
6464            self.write_indent();
6465        } else {
6466            self.write_space();
6467        }
6468        self.generate_expression(&union.right)?;
6469        // ORDER BY, LIMIT, OFFSET for the set operation
6470        if let Some(order_by) = &union.order_by {
6471            if self.config.pretty {
6472                self.write_newline();
6473            } else {
6474                self.write_space();
6475            }
6476            self.write_keyword("ORDER BY");
6477            self.write_space();
6478            for (i, ordered) in order_by.expressions.iter().enumerate() {
6479                if i > 0 {
6480                    self.write(", ");
6481                }
6482                self.generate_ordered(ordered)?;
6483            }
6484        }
6485        if let Some(limit) = &union.limit {
6486            if self.config.pretty {
6487                self.write_newline();
6488            } else {
6489                self.write_space();
6490            }
6491            self.write_keyword("LIMIT");
6492            self.write_space();
6493            self.generate_expression(limit)?;
6494        }
6495        if let Some(offset) = &union.offset {
6496            if self.config.pretty {
6497                self.write_newline();
6498            } else {
6499                self.write_space();
6500            }
6501            self.write_keyword("OFFSET");
6502            self.write_space();
6503            self.generate_expression(offset)?;
6504        }
6505        // DISTRIBUTE BY (Hive/Spark)
6506        if let Some(distribute_by) = &union.distribute_by {
6507            self.write_space();
6508            self.write_keyword("DISTRIBUTE BY");
6509            self.write_space();
6510            for (i, expr) in distribute_by.expressions.iter().enumerate() {
6511                if i > 0 {
6512                    self.write(", ");
6513                }
6514                self.generate_expression(expr)?;
6515            }
6516        }
6517        // SORT BY (Hive/Spark)
6518        if let Some(sort_by) = &union.sort_by {
6519            self.write_space();
6520            self.write_keyword("SORT BY");
6521            self.write_space();
6522            for (i, ord) in sort_by.expressions.iter().enumerate() {
6523                if i > 0 {
6524                    self.write(", ");
6525                }
6526                self.generate_ordered(ord)?;
6527            }
6528        }
6529        // CLUSTER BY (Hive/Spark)
6530        if let Some(cluster_by) = &union.cluster_by {
6531            self.write_space();
6532            self.write_keyword("CLUSTER BY");
6533            self.write_space();
6534            for (i, ord) in cluster_by.expressions.iter().enumerate() {
6535                if i > 0 {
6536                    self.write(", ");
6537                }
6538                self.generate_ordered(ord)?;
6539            }
6540        }
6541        Ok(())
6542    }
6543
6544    fn generate_intersect(&mut self, outermost: &Intersect) -> Result<()> {
6545        // Collect the left-recursive chain iteratively to avoid stack overflow
6546        let mut chain: Vec<&Intersect> = vec![outermost];
6547        let mut leftmost: &Expression = &outermost.left;
6548        while let Expression::Intersect(inner) = leftmost {
6549            chain.push(inner);
6550            leftmost = &inner.left;
6551        }
6552
6553        if let Some(with) = &outermost.with {
6554            self.generate_with(with)?;
6555            self.write_space();
6556        }
6557
6558        self.generate_expression(leftmost)?;
6559
6560        for intersect in chain.iter().rev() {
6561            self.generate_intersect_step(intersect)?;
6562        }
6563        Ok(())
6564    }
6565
6566    /// Generate a single INTERSECT step: keyword, right expression, and trailing modifiers.
6567    fn generate_intersect_step(&mut self, intersect: &Intersect) -> Result<()> {
6568        if self.config.pretty {
6569            self.write_newline();
6570            self.write_indent();
6571        } else {
6572            self.write_space();
6573        }
6574
6575        // BigQuery set operation modifiers: [side] [kind] INTERSECT
6576        if let Some(side) = &intersect.side {
6577            self.write_keyword(side);
6578            self.write_space();
6579        }
6580        if let Some(kind) = &intersect.kind {
6581            self.write_keyword(kind);
6582            self.write_space();
6583        }
6584
6585        self.write_keyword("INTERSECT");
6586        if intersect.all {
6587            self.write_space();
6588            self.write_keyword("ALL");
6589        } else if intersect.distinct {
6590            self.write_space();
6591            self.write_keyword("DISTINCT");
6592        }
6593
6594        // BigQuery: CORRESPONDING/STRICT CORRESPONDING -> BY NAME, BY (cols) -> ON (cols)
6595        // DuckDB: BY NAME
6596        if intersect.corresponding || intersect.by_name {
6597            self.write_space();
6598            self.write_keyword("BY NAME");
6599        }
6600        if !intersect.on_columns.is_empty() {
6601            self.write_space();
6602            self.write_keyword("ON");
6603            self.write(" (");
6604            for (i, col) in intersect.on_columns.iter().enumerate() {
6605                if i > 0 {
6606                    self.write(", ");
6607                }
6608                self.generate_expression(col)?;
6609            }
6610            self.write(")");
6611        }
6612
6613        if self.config.pretty {
6614            self.write_newline();
6615            self.write_indent();
6616        } else {
6617            self.write_space();
6618        }
6619        self.generate_expression(&intersect.right)?;
6620        // ORDER BY, LIMIT, OFFSET for the set operation
6621        if let Some(order_by) = &intersect.order_by {
6622            if self.config.pretty {
6623                self.write_newline();
6624            } else {
6625                self.write_space();
6626            }
6627            self.write_keyword("ORDER BY");
6628            self.write_space();
6629            for (i, ordered) in order_by.expressions.iter().enumerate() {
6630                if i > 0 {
6631                    self.write(", ");
6632                }
6633                self.generate_ordered(ordered)?;
6634            }
6635        }
6636        if let Some(limit) = &intersect.limit {
6637            if self.config.pretty {
6638                self.write_newline();
6639            } else {
6640                self.write_space();
6641            }
6642            self.write_keyword("LIMIT");
6643            self.write_space();
6644            self.generate_expression(limit)?;
6645        }
6646        if let Some(offset) = &intersect.offset {
6647            if self.config.pretty {
6648                self.write_newline();
6649            } else {
6650                self.write_space();
6651            }
6652            self.write_keyword("OFFSET");
6653            self.write_space();
6654            self.generate_expression(offset)?;
6655        }
6656        // DISTRIBUTE BY (Hive/Spark)
6657        if let Some(distribute_by) = &intersect.distribute_by {
6658            self.write_space();
6659            self.write_keyword("DISTRIBUTE BY");
6660            self.write_space();
6661            for (i, expr) in distribute_by.expressions.iter().enumerate() {
6662                if i > 0 {
6663                    self.write(", ");
6664                }
6665                self.generate_expression(expr)?;
6666            }
6667        }
6668        // SORT BY (Hive/Spark)
6669        if let Some(sort_by) = &intersect.sort_by {
6670            self.write_space();
6671            self.write_keyword("SORT BY");
6672            self.write_space();
6673            for (i, ord) in sort_by.expressions.iter().enumerate() {
6674                if i > 0 {
6675                    self.write(", ");
6676                }
6677                self.generate_ordered(ord)?;
6678            }
6679        }
6680        // CLUSTER BY (Hive/Spark)
6681        if let Some(cluster_by) = &intersect.cluster_by {
6682            self.write_space();
6683            self.write_keyword("CLUSTER BY");
6684            self.write_space();
6685            for (i, ord) in cluster_by.expressions.iter().enumerate() {
6686                if i > 0 {
6687                    self.write(", ");
6688                }
6689                self.generate_ordered(ord)?;
6690            }
6691        }
6692        Ok(())
6693    }
6694
6695    fn generate_except(&mut self, outermost: &Except) -> Result<()> {
6696        // Collect the left-recursive chain iteratively to avoid stack overflow
6697        let mut chain: Vec<&Except> = vec![outermost];
6698        let mut leftmost: &Expression = &outermost.left;
6699        while let Expression::Except(inner) = leftmost {
6700            chain.push(inner);
6701            leftmost = &inner.left;
6702        }
6703
6704        if let Some(with) = &outermost.with {
6705            self.generate_with(with)?;
6706            self.write_space();
6707        }
6708
6709        self.generate_expression(leftmost)?;
6710
6711        for except in chain.iter().rev() {
6712            self.generate_except_step(except)?;
6713        }
6714        Ok(())
6715    }
6716
6717    /// Generate a single EXCEPT step: keyword, right expression, and trailing modifiers.
6718    fn generate_except_step(&mut self, except: &Except) -> Result<()> {
6719        use crate::dialects::DialectType;
6720
6721        if self.config.pretty {
6722            self.write_newline();
6723            self.write_indent();
6724        } else {
6725            self.write_space();
6726        }
6727
6728        // BigQuery set operation modifiers: [side] [kind] EXCEPT
6729        if let Some(side) = &except.side {
6730            self.write_keyword(side);
6731            self.write_space();
6732        }
6733        if let Some(kind) = &except.kind {
6734            self.write_keyword(kind);
6735            self.write_space();
6736        }
6737
6738        // Oracle uses MINUS instead of EXCEPT (but not for EXCEPT ALL)
6739        match self.config.dialect {
6740            Some(DialectType::Oracle) if !except.all => {
6741                self.write_keyword("MINUS");
6742            }
6743            Some(DialectType::ClickHouse) => {
6744                self.write_keyword("EXCEPT");
6745                let preserve_all = self.config.source_dialect.is_none()
6746                    || matches!(self.config.source_dialect, Some(DialectType::ClickHouse));
6747                if except.all && preserve_all {
6748                    self.write_space();
6749                    self.write_keyword("ALL");
6750                }
6751                if except.distinct {
6752                    self.write_space();
6753                    self.write_keyword("DISTINCT");
6754                }
6755            }
6756            Some(DialectType::BigQuery) => {
6757                // BigQuery: bare EXCEPT defaults to EXCEPT DISTINCT
6758                self.write_keyword("EXCEPT");
6759                if except.all {
6760                    self.write_space();
6761                    self.write_keyword("ALL");
6762                } else {
6763                    self.write_space();
6764                    self.write_keyword("DISTINCT");
6765                }
6766            }
6767            _ => {
6768                self.write_keyword("EXCEPT");
6769                if except.all {
6770                    self.write_space();
6771                    self.write_keyword("ALL");
6772                } else if except.distinct {
6773                    self.write_space();
6774                    self.write_keyword("DISTINCT");
6775                }
6776            }
6777        }
6778
6779        // BigQuery: CORRESPONDING/STRICT CORRESPONDING -> BY NAME, BY (cols) -> ON (cols)
6780        // DuckDB: BY NAME
6781        if except.corresponding || except.by_name {
6782            self.write_space();
6783            self.write_keyword("BY NAME");
6784        }
6785        if !except.on_columns.is_empty() {
6786            self.write_space();
6787            self.write_keyword("ON");
6788            self.write(" (");
6789            for (i, col) in except.on_columns.iter().enumerate() {
6790                if i > 0 {
6791                    self.write(", ");
6792                }
6793                self.generate_expression(col)?;
6794            }
6795            self.write(")");
6796        }
6797
6798        if self.config.pretty {
6799            self.write_newline();
6800            self.write_indent();
6801        } else {
6802            self.write_space();
6803        }
6804        self.generate_expression(&except.right)?;
6805        // ORDER BY, LIMIT, OFFSET for the set operation
6806        if let Some(order_by) = &except.order_by {
6807            if self.config.pretty {
6808                self.write_newline();
6809            } else {
6810                self.write_space();
6811            }
6812            self.write_keyword("ORDER BY");
6813            self.write_space();
6814            for (i, ordered) in order_by.expressions.iter().enumerate() {
6815                if i > 0 {
6816                    self.write(", ");
6817                }
6818                self.generate_ordered(ordered)?;
6819            }
6820        }
6821        if let Some(limit) = &except.limit {
6822            if self.config.pretty {
6823                self.write_newline();
6824            } else {
6825                self.write_space();
6826            }
6827            self.write_keyword("LIMIT");
6828            self.write_space();
6829            self.generate_expression(limit)?;
6830        }
6831        if let Some(offset) = &except.offset {
6832            if self.config.pretty {
6833                self.write_newline();
6834            } else {
6835                self.write_space();
6836            }
6837            self.write_keyword("OFFSET");
6838            self.write_space();
6839            self.generate_expression(offset)?;
6840        }
6841        // DISTRIBUTE BY (Hive/Spark)
6842        if let Some(distribute_by) = &except.distribute_by {
6843            self.write_space();
6844            self.write_keyword("DISTRIBUTE BY");
6845            self.write_space();
6846            for (i, expr) in distribute_by.expressions.iter().enumerate() {
6847                if i > 0 {
6848                    self.write(", ");
6849                }
6850                self.generate_expression(expr)?;
6851            }
6852        }
6853        // SORT BY (Hive/Spark)
6854        if let Some(sort_by) = &except.sort_by {
6855            self.write_space();
6856            self.write_keyword("SORT BY");
6857            self.write_space();
6858            for (i, ord) in sort_by.expressions.iter().enumerate() {
6859                if i > 0 {
6860                    self.write(", ");
6861                }
6862                self.generate_ordered(ord)?;
6863            }
6864        }
6865        // CLUSTER BY (Hive/Spark)
6866        if let Some(cluster_by) = &except.cluster_by {
6867            self.write_space();
6868            self.write_keyword("CLUSTER BY");
6869            self.write_space();
6870            for (i, ord) in cluster_by.expressions.iter().enumerate() {
6871                if i > 0 {
6872                    self.write(", ");
6873                }
6874                self.generate_ordered(ord)?;
6875            }
6876        }
6877        Ok(())
6878    }
6879
6880    fn generate_insert(&mut self, insert: &Insert) -> Result<()> {
6881        // For TSQL/Fabric/Spark/Hive/Databricks, CTEs must be prepended before INSERT
6882        let prepend_query_cte = if insert.with.is_none() {
6883            use crate::dialects::DialectType;
6884            let should_prepend = matches!(
6885                self.config.dialect,
6886                Some(DialectType::TSQL)
6887                    | Some(DialectType::Fabric)
6888                    | Some(DialectType::Spark)
6889                    | Some(DialectType::Databricks)
6890                    | Some(DialectType::Hive)
6891            );
6892            if should_prepend {
6893                if let Some(Expression::Select(select)) = &insert.query {
6894                    select.with.clone()
6895                } else {
6896                    None
6897                }
6898            } else {
6899                None
6900            }
6901        } else {
6902            None
6903        };
6904
6905        // Output WITH clause if on INSERT (e.g., WITH ... INSERT INTO ...)
6906        if let Some(with) = &insert.with {
6907            self.generate_with(with)?;
6908            self.write_space();
6909        } else if let Some(with) = &prepend_query_cte {
6910            self.generate_with(with)?;
6911            self.write_space();
6912        }
6913
6914        // Output leading comments before INSERT
6915        for comment in &insert.leading_comments {
6916            self.write_formatted_comment(comment);
6917            self.write(" ");
6918        }
6919
6920        // Handle directory insert (INSERT OVERWRITE DIRECTORY)
6921        if let Some(dir) = &insert.directory {
6922            self.write_keyword("INSERT OVERWRITE");
6923            if dir.local {
6924                self.write_space();
6925                self.write_keyword("LOCAL");
6926            }
6927            self.write_space();
6928            self.write_keyword("DIRECTORY");
6929            self.write_space();
6930            self.write("'");
6931            self.write(&dir.path);
6932            self.write("'");
6933
6934            // ROW FORMAT clause
6935            if let Some(row_format) = &dir.row_format {
6936                self.write_space();
6937                self.write_keyword("ROW FORMAT");
6938                if row_format.delimited {
6939                    self.write_space();
6940                    self.write_keyword("DELIMITED");
6941                }
6942                if let Some(val) = &row_format.fields_terminated_by {
6943                    self.write_space();
6944                    self.write_keyword("FIELDS TERMINATED BY");
6945                    self.write_space();
6946                    self.generate_string_literal(val)?;
6947                }
6948                if let Some(val) = &row_format.collection_items_terminated_by {
6949                    self.write_space();
6950                    self.write_keyword("COLLECTION ITEMS TERMINATED BY");
6951                    self.write_space();
6952                    self.write("'");
6953                    self.write(val);
6954                    self.write("'");
6955                }
6956                if let Some(val) = &row_format.map_keys_terminated_by {
6957                    self.write_space();
6958                    self.write_keyword("MAP KEYS TERMINATED BY");
6959                    self.write_space();
6960                    self.write("'");
6961                    self.write(val);
6962                    self.write("'");
6963                }
6964                if let Some(val) = &row_format.lines_terminated_by {
6965                    self.write_space();
6966                    self.write_keyword("LINES TERMINATED BY");
6967                    self.write_space();
6968                    self.write("'");
6969                    self.write(val);
6970                    self.write("'");
6971                }
6972                if let Some(val) = &row_format.null_defined_as {
6973                    self.write_space();
6974                    self.write_keyword("NULL DEFINED AS");
6975                    self.write_space();
6976                    self.write("'");
6977                    self.write(val);
6978                    self.write("'");
6979                }
6980            }
6981
6982            // STORED AS clause
6983            if let Some(format) = &dir.stored_as {
6984                self.write_space();
6985                self.write_keyword("STORED AS");
6986                self.write_space();
6987                self.write_keyword(format);
6988            }
6989
6990            // Query (SELECT statement)
6991            if let Some(query) = &insert.query {
6992                self.write_space();
6993                self.generate_expression(query)?;
6994            }
6995
6996            return Ok(());
6997        }
6998
6999        if insert.is_replace {
7000            // MySQL/SQLite REPLACE INTO statement
7001            self.write_keyword("REPLACE INTO");
7002        } else if insert.overwrite {
7003            // Use dialect-specific INSERT OVERWRITE format
7004            self.write_keyword("INSERT");
7005            // Output hint if present (Oracle: INSERT /*+ APPEND */ INTO)
7006            if let Some(ref hint) = insert.hint {
7007                self.generate_hint(hint)?;
7008            }
7009            self.write(&self.config.insert_overwrite.to_ascii_uppercase());
7010        } else if let Some(ref action) = insert.conflict_action {
7011            // SQLite conflict action: INSERT OR ABORT|FAIL|IGNORE|REPLACE|ROLLBACK INTO
7012            self.write_keyword("INSERT OR");
7013            self.write_space();
7014            self.write_keyword(action);
7015            self.write_space();
7016            self.write_keyword("INTO");
7017        } else if insert.ignore {
7018            // MySQL INSERT IGNORE syntax
7019            self.write_keyword("INSERT IGNORE INTO");
7020        } else {
7021            self.write_keyword("INSERT");
7022            // Output hint if present (Oracle: INSERT /*+ APPEND */ INTO)
7023            if let Some(ref hint) = insert.hint {
7024                self.generate_hint(hint)?;
7025            }
7026            self.write_space();
7027            self.write_keyword("INTO");
7028        }
7029        // ClickHouse: INSERT INTO FUNCTION func_name(args...)
7030        if let Some(ref func) = insert.function_target {
7031            self.write_space();
7032            self.write_keyword("FUNCTION");
7033            self.write_space();
7034            self.generate_expression(func)?;
7035        } else {
7036            self.write_space();
7037            self.generate_table(&insert.table)?;
7038        }
7039
7040        // Table alias (PostgreSQL: INSERT INTO table AS t(...), Oracle: INSERT INTO table t ...)
7041        if let Some(ref alias) = insert.alias {
7042            self.write_space();
7043            if insert.alias_explicit_as {
7044                self.write_keyword("AS");
7045                self.write_space();
7046            }
7047            self.generate_identifier(alias)?;
7048        }
7049
7050        // IF EXISTS clause (Hive)
7051        if insert.if_exists {
7052            self.write_space();
7053            self.write_keyword("IF EXISTS");
7054        }
7055
7056        // REPLACE WHERE clause (Databricks)
7057        if let Some(ref replace_where) = insert.replace_where {
7058            if self.config.pretty {
7059                self.write_newline();
7060                self.write_indent();
7061            } else {
7062                self.write_space();
7063            }
7064            self.write_keyword("REPLACE WHERE");
7065            self.write_space();
7066            self.generate_expression(replace_where)?;
7067        }
7068
7069        // Generate PARTITION clause if present
7070        if !insert.partition.is_empty() {
7071            self.write_space();
7072            self.write_keyword("PARTITION");
7073            self.write("(");
7074            for (i, (col, val)) in insert.partition.iter().enumerate() {
7075                if i > 0 {
7076                    self.write(", ");
7077                }
7078                self.generate_identifier(col)?;
7079                if let Some(v) = val {
7080                    self.write(" = ");
7081                    self.generate_expression(v)?;
7082                }
7083            }
7084            self.write(")");
7085        }
7086
7087        // ClickHouse: PARTITION BY expr
7088        if let Some(ref partition_by) = insert.partition_by {
7089            self.write_space();
7090            self.write_keyword("PARTITION BY");
7091            self.write_space();
7092            self.generate_expression(partition_by)?;
7093        }
7094
7095        // ClickHouse: SETTINGS key = val, ...
7096        if !insert.settings.is_empty() {
7097            self.write_space();
7098            self.write_keyword("SETTINGS");
7099            self.write_space();
7100            for (i, setting) in insert.settings.iter().enumerate() {
7101                if i > 0 {
7102                    self.write(", ");
7103                }
7104                self.generate_expression(setting)?;
7105            }
7106        }
7107
7108        if !insert.columns.is_empty() {
7109            if insert.alias.is_some() && insert.alias_explicit_as {
7110                // No space when explicit AS alias is present: INSERT INTO table AS t(a, b, c)
7111                self.write("(");
7112            } else {
7113                // Space for implicit alias or no alias: INSERT INTO dest d (i, value)
7114                self.write(" (");
7115            }
7116            for (i, col) in insert.columns.iter().enumerate() {
7117                if i > 0 {
7118                    self.write(", ");
7119                }
7120                self.generate_identifier(col)?;
7121            }
7122            self.write(")");
7123        }
7124
7125        // OUTPUT clause (TSQL)
7126        if let Some(ref output) = insert.output {
7127            self.generate_output_clause(output)?;
7128        }
7129
7130        // BY NAME modifier (DuckDB)
7131        if insert.by_name {
7132            self.write_space();
7133            self.write_keyword("BY NAME");
7134        }
7135
7136        if insert.default_values {
7137            self.write_space();
7138            self.write_keyword("DEFAULT VALUES");
7139        } else if let Some(query) = &insert.query {
7140            if self.config.pretty {
7141                self.write_newline();
7142            } else {
7143                self.write_space();
7144            }
7145            // If we prepended CTEs from nested SELECT (TSQL), strip the WITH from SELECT
7146            if prepend_query_cte.is_some() {
7147                if let Expression::Select(select) = query {
7148                    let mut select_no_with = select.clone();
7149                    select_no_with.with = None;
7150                    self.generate_select(&select_no_with)?;
7151                } else {
7152                    self.generate_expression(query)?;
7153                }
7154            } else {
7155                self.generate_expression(query)?;
7156            }
7157        } else if !insert.values.is_empty() {
7158            if self.config.pretty {
7159                // Pretty printing: VALUES on new line, each tuple indented
7160                self.write_newline();
7161                self.write_keyword("VALUES");
7162                self.write_newline();
7163                self.indent_level += 1;
7164                for (i, row) in insert.values.iter().enumerate() {
7165                    if i > 0 {
7166                        self.write(",");
7167                        self.write_newline();
7168                    }
7169                    self.write_indent();
7170                    self.write("(");
7171                    for (j, val) in row.iter().enumerate() {
7172                        if j > 0 {
7173                            self.write(", ");
7174                        }
7175                        self.generate_expression(val)?;
7176                    }
7177                    self.write(")");
7178                }
7179                self.indent_level -= 1;
7180            } else {
7181                // Non-pretty: single line
7182                self.write_space();
7183                self.write_keyword("VALUES");
7184                for (i, row) in insert.values.iter().enumerate() {
7185                    if i > 0 {
7186                        self.write(",");
7187                    }
7188                    self.write(" (");
7189                    for (j, val) in row.iter().enumerate() {
7190                        if j > 0 {
7191                            self.write(", ");
7192                        }
7193                        self.generate_expression(val)?;
7194                    }
7195                    self.write(")");
7196                }
7197            }
7198        }
7199
7200        // Source table (Hive/Spark): INSERT OVERWRITE TABLE target TABLE source
7201        if let Some(ref source) = insert.source {
7202            self.write_space();
7203            self.write_keyword("TABLE");
7204            self.write_space();
7205            self.generate_expression(source)?;
7206        }
7207
7208        // Source alias (MySQL: VALUES (...) AS new_data)
7209        if let Some(alias) = &insert.source_alias {
7210            self.write_space();
7211            self.write_keyword("AS");
7212            self.write_space();
7213            self.generate_identifier(alias)?;
7214        }
7215
7216        // ON CONFLICT clause (Materialize doesn't support ON CONFLICT)
7217        if let Some(on_conflict) = &insert.on_conflict {
7218            if !matches!(self.config.dialect, Some(DialectType::Materialize)) {
7219                self.write_space();
7220                self.generate_expression(on_conflict)?;
7221            }
7222        }
7223
7224        // RETURNING clause
7225        if !insert.returning.is_empty() {
7226            self.write_space();
7227            self.write_keyword("RETURNING");
7228            self.write_space();
7229            for (i, expr) in insert.returning.iter().enumerate() {
7230                if i > 0 {
7231                    self.write(", ");
7232                }
7233                self.generate_expression(expr)?;
7234            }
7235        }
7236
7237        Ok(())
7238    }
7239
7240    fn generate_update(&mut self, update: &Update) -> Result<()> {
7241        // Output leading comments before UPDATE
7242        for comment in &update.leading_comments {
7243            self.write_formatted_comment(comment);
7244            self.write(" ");
7245        }
7246
7247        // WITH clause (CTEs)
7248        if let Some(ref with) = update.with {
7249            self.generate_with(with)?;
7250            self.write_space();
7251        }
7252
7253        self.write_keyword("UPDATE");
7254        if let Some(hint) = &update.hint {
7255            self.generate_hint(hint)?;
7256        }
7257        self.write_space();
7258        self.generate_table(&update.table)?;
7259
7260        let mysql_like_update_from = matches!(
7261            self.config.dialect,
7262            Some(DialectType::MySQL) | Some(DialectType::SingleStore)
7263        ) && update.from_clause.is_some();
7264
7265        let mut set_pairs = update.set.clone();
7266
7267        // MySQL-style UPDATE doesn't support FROM after SET. Convert FROM tables to JOIN ... ON TRUE.
7268        let mut pre_set_joins = update.table_joins.clone();
7269        if mysql_like_update_from {
7270            let target_name = update
7271                .table
7272                .alias
7273                .as_ref()
7274                .map(|a| a.name.clone())
7275                .unwrap_or_else(|| update.table.name.name.clone());
7276
7277            for (col, _) in &mut set_pairs {
7278                if !col.name.contains('.') {
7279                    col.name = format!("{}.{}", target_name, col.name);
7280                }
7281            }
7282
7283            if let Some(from_clause) = &update.from_clause {
7284                for table_expr in &from_clause.expressions {
7285                    pre_set_joins.push(crate::expressions::Join {
7286                        this: table_expr.clone(),
7287                        on: Some(Expression::Boolean(crate::expressions::BooleanLiteral {
7288                            value: true,
7289                        })),
7290                        using: Vec::new(),
7291                        kind: crate::expressions::JoinKind::Inner,
7292                        use_inner_keyword: false,
7293                        use_outer_keyword: false,
7294                        deferred_condition: false,
7295                        join_hint: None,
7296                        match_condition: None,
7297                        pivots: Vec::new(),
7298                        comments: Vec::new(),
7299                        nesting_group: 0,
7300                        directed: false,
7301                    });
7302                }
7303            }
7304            for join in &update.from_joins {
7305                let mut join = join.clone();
7306                if join.on.is_none() && join.using.is_empty() {
7307                    join.on = Some(Expression::Boolean(crate::expressions::BooleanLiteral {
7308                        value: true,
7309                    }));
7310                }
7311                pre_set_joins.push(join);
7312            }
7313        }
7314
7315        // Extra tables for multi-table UPDATE (MySQL syntax)
7316        for extra_table in &update.extra_tables {
7317            self.write(", ");
7318            self.generate_table(extra_table)?;
7319        }
7320
7321        // JOINs attached to the table list (MySQL multi-table syntax)
7322        for join in &pre_set_joins {
7323            // generate_join already adds a leading space
7324            self.generate_join(join)?;
7325        }
7326
7327        // Teradata: FROM clause comes before SET
7328        let teradata_from_before_set = matches!(self.config.dialect, Some(DialectType::Teradata));
7329        if teradata_from_before_set && !mysql_like_update_from {
7330            if let Some(ref from_clause) = update.from_clause {
7331                self.write_space();
7332                self.write_keyword("FROM");
7333                self.write_space();
7334                for (i, table_expr) in from_clause.expressions.iter().enumerate() {
7335                    if i > 0 {
7336                        self.write(", ");
7337                    }
7338                    self.generate_expression(table_expr)?;
7339                }
7340            }
7341            for join in &update.from_joins {
7342                self.generate_join(join)?;
7343            }
7344        }
7345
7346        self.write_space();
7347        self.write_keyword("SET");
7348        self.write_space();
7349
7350        for (i, (col, val)) in set_pairs.iter().enumerate() {
7351            if i > 0 {
7352                self.write(", ");
7353            }
7354            self.generate_identifier(col)?;
7355            self.write(" = ");
7356            self.generate_expression(val)?;
7357        }
7358
7359        // OUTPUT clause (TSQL)
7360        if let Some(ref output) = update.output {
7361            self.generate_output_clause(output)?;
7362        }
7363
7364        // FROM clause (after SET for non-Teradata, non-MySQL dialects)
7365        if !mysql_like_update_from && !teradata_from_before_set {
7366            if let Some(ref from_clause) = update.from_clause {
7367                self.write_space();
7368                self.write_keyword("FROM");
7369                self.write_space();
7370                // Generate each table in the FROM clause
7371                for (i, table_expr) in from_clause.expressions.iter().enumerate() {
7372                    if i > 0 {
7373                        self.write(", ");
7374                    }
7375                    self.generate_expression(table_expr)?;
7376                }
7377            }
7378        }
7379
7380        if !mysql_like_update_from && !teradata_from_before_set {
7381            // JOINs after FROM clause (PostgreSQL, Snowflake, SQL Server syntax)
7382            for join in &update.from_joins {
7383                self.generate_join(join)?;
7384            }
7385        }
7386
7387        if let Some(where_clause) = &update.where_clause {
7388            self.write_space();
7389            self.write_keyword("WHERE");
7390            self.write_space();
7391            self.generate_expression(&where_clause.this)?;
7392        }
7393
7394        // RETURNING clause
7395        if !update.returning.is_empty() {
7396            self.write_space();
7397            self.write_keyword("RETURNING");
7398            self.write_space();
7399            for (i, expr) in update.returning.iter().enumerate() {
7400                if i > 0 {
7401                    self.write(", ");
7402                }
7403                self.generate_expression(expr)?;
7404            }
7405        }
7406
7407        // ORDER BY clause (MySQL)
7408        if let Some(ref order_by) = update.order_by {
7409            self.write_space();
7410            self.generate_order_by(order_by)?;
7411        }
7412
7413        // LIMIT clause (MySQL)
7414        if let Some(ref limit) = update.limit {
7415            self.write_space();
7416            self.write_keyword("LIMIT");
7417            self.write_space();
7418            self.generate_expression(limit)?;
7419        }
7420
7421        Ok(())
7422    }
7423
7424    fn generate_delete(&mut self, delete: &Delete) -> Result<()> {
7425        // Output WITH clause if present
7426        if let Some(with) = &delete.with {
7427            self.generate_with(with)?;
7428            self.write_space();
7429        }
7430
7431        // Output leading comments before DELETE
7432        for comment in &delete.leading_comments {
7433            self.write_formatted_comment(comment);
7434            self.write(" ");
7435        }
7436
7437        // MySQL multi-table DELETE or TSQL DELETE with OUTPUT before FROM
7438        if !delete.tables.is_empty() && !delete.tables_from_using {
7439            // DELETE t1[, t2] [OUTPUT ...] FROM ... syntax (tables before FROM)
7440            self.write_keyword("DELETE");
7441            if let Some(hint) = &delete.hint {
7442                self.generate_hint(hint)?;
7443            }
7444            self.write_space();
7445            for (i, tbl) in delete.tables.iter().enumerate() {
7446                if i > 0 {
7447                    self.write(", ");
7448                }
7449                self.generate_table(tbl)?;
7450            }
7451            // TSQL: OUTPUT clause between target table and FROM
7452            if let Some(ref output) = delete.output {
7453                self.generate_output_clause(output)?;
7454            }
7455            self.write_space();
7456            self.write_keyword("FROM");
7457            self.write_space();
7458            self.generate_table(&delete.table)?;
7459        } else if !delete.tables.is_empty() && delete.tables_from_using {
7460            // DELETE FROM t1, t2 USING ... syntax (tables after FROM)
7461            self.write_keyword("DELETE");
7462            if let Some(hint) = &delete.hint {
7463                self.generate_hint(hint)?;
7464            }
7465            self.write_space();
7466            self.write_keyword("FROM");
7467            self.write_space();
7468            for (i, tbl) in delete.tables.iter().enumerate() {
7469                if i > 0 {
7470                    self.write(", ");
7471                }
7472                self.generate_table(tbl)?;
7473            }
7474        } else if delete.no_from && matches!(self.config.dialect, Some(DialectType::BigQuery)) {
7475            // BigQuery-style DELETE without FROM keyword
7476            self.write_keyword("DELETE");
7477            if let Some(hint) = &delete.hint {
7478                self.generate_hint(hint)?;
7479            }
7480            self.write_space();
7481            self.generate_table(&delete.table)?;
7482        } else {
7483            self.write_keyword("DELETE");
7484            if let Some(hint) = &delete.hint {
7485                self.generate_hint(hint)?;
7486            }
7487            self.write_space();
7488            self.write_keyword("FROM");
7489            self.write_space();
7490            self.generate_table(&delete.table)?;
7491        }
7492
7493        // ClickHouse: ON CLUSTER clause
7494        if let Some(ref on_cluster) = delete.on_cluster {
7495            self.write_space();
7496            self.generate_on_cluster(on_cluster)?;
7497        }
7498
7499        // FORCE INDEX hint (MySQL)
7500        if let Some(ref idx) = delete.force_index {
7501            self.write_space();
7502            self.write_keyword("FORCE INDEX");
7503            self.write(" (");
7504            self.write(idx);
7505            self.write(")");
7506        }
7507
7508        // Optional alias
7509        if let Some(ref alias) = delete.alias {
7510            self.write_space();
7511            if delete.alias_explicit_as
7512                || matches!(self.config.dialect, Some(DialectType::BigQuery))
7513            {
7514                self.write_keyword("AS");
7515                self.write_space();
7516            }
7517            self.generate_identifier(alias)?;
7518        }
7519
7520        // JOINs (MySQL multi-table) - when NOT tables_from_using, JOINs come before USING
7521        if !delete.tables_from_using {
7522            for join in &delete.joins {
7523                self.generate_join(join)?;
7524            }
7525        }
7526
7527        // USING clause (PostgreSQL/DuckDB/MySQL)
7528        if !delete.using.is_empty() {
7529            self.write_space();
7530            self.write_keyword("USING");
7531            for (i, table) in delete.using.iter().enumerate() {
7532                if i > 0 {
7533                    self.write(",");
7534                }
7535                self.write_space();
7536                // Check if the table has subquery hints (DuckDB USING with subquery)
7537                if !table.hints.is_empty() && table.name.is_empty() {
7538                    // Subquery in USING: (VALUES ...) AS alias(cols)
7539                    self.generate_expression(&table.hints[0])?;
7540                    if let Some(ref alias) = table.alias {
7541                        self.write_space();
7542                        if table.alias_explicit_as {
7543                            self.write_keyword("AS");
7544                            self.write_space();
7545                        }
7546                        self.generate_identifier(alias)?;
7547                        if !table.column_aliases.is_empty() {
7548                            self.write("(");
7549                            for (j, col_alias) in table.column_aliases.iter().enumerate() {
7550                                if j > 0 {
7551                                    self.write(", ");
7552                                }
7553                                self.generate_identifier(col_alias)?;
7554                            }
7555                            self.write(")");
7556                        }
7557                    }
7558                } else {
7559                    self.generate_table(table)?;
7560                }
7561            }
7562        }
7563
7564        // JOINs (MySQL multi-table) - when tables_from_using, JOINs come after USING
7565        if delete.tables_from_using {
7566            for join in &delete.joins {
7567                self.generate_join(join)?;
7568            }
7569        }
7570
7571        // OUTPUT clause (TSQL) - only if not already emitted in the early position
7572        let output_already_emitted =
7573            !delete.tables.is_empty() && !delete.tables_from_using && delete.output.is_some();
7574        if !output_already_emitted {
7575            if let Some(ref output) = delete.output {
7576                self.generate_output_clause(output)?;
7577            }
7578        }
7579
7580        if let Some(where_clause) = &delete.where_clause {
7581            self.write_space();
7582            self.write_keyword("WHERE");
7583            self.write_space();
7584            self.generate_expression(&where_clause.this)?;
7585        }
7586
7587        // ORDER BY clause (MySQL)
7588        if let Some(ref order_by) = delete.order_by {
7589            self.write_space();
7590            self.generate_order_by(order_by)?;
7591        }
7592
7593        // LIMIT clause (MySQL)
7594        if let Some(ref limit) = delete.limit {
7595            self.write_space();
7596            self.write_keyword("LIMIT");
7597            self.write_space();
7598            self.generate_expression(limit)?;
7599        }
7600
7601        // RETURNING clause (PostgreSQL)
7602        if !delete.returning.is_empty() {
7603            self.write_space();
7604            self.write_keyword("RETURNING");
7605            self.write_space();
7606            for (i, expr) in delete.returning.iter().enumerate() {
7607                if i > 0 {
7608                    self.write(", ");
7609                }
7610                self.generate_expression(expr)?;
7611            }
7612        }
7613
7614        Ok(())
7615    }
7616
7617    // ==================== DDL Generation ====================
7618
7619    fn generate_create_table(&mut self, ct: &CreateTable) -> Result<()> {
7620        // Athena: Determine if this is Hive-style DDL or Trino-style DML
7621        // CREATE TABLE AS SELECT uses Trino (double quotes)
7622        // CREATE TABLE (without AS SELECT) and CREATE EXTERNAL TABLE use Hive (backticks)
7623        let saved_athena_hive_context = self.athena_hive_context;
7624        let is_clickhouse = matches!(self.config.dialect, Some(DialectType::ClickHouse));
7625        if matches!(
7626            self.config.dialect,
7627            Some(crate::dialects::DialectType::Athena)
7628        ) {
7629            // Use Hive context if:
7630            // 1. It's an EXTERNAL table, OR
7631            // 2. There's no AS SELECT clause
7632            let is_external = ct
7633                .table_modifier
7634                .as_ref()
7635                .map(|m| m.eq_ignore_ascii_case("EXTERNAL"))
7636                .unwrap_or(false);
7637            let has_as_select = ct.as_select.is_some();
7638            self.athena_hive_context = is_external || !has_as_select;
7639        }
7640
7641        // TSQL: Convert CREATE TABLE AS SELECT to SELECT * INTO table FROM (subquery) AS temp
7642        if matches!(
7643            self.config.dialect,
7644            Some(crate::dialects::DialectType::TSQL)
7645        ) {
7646            if let Some(ref query) = ct.as_select {
7647                // Output WITH CTE clause if present
7648                if let Some(with_cte) = &ct.with_cte {
7649                    self.generate_with(with_cte)?;
7650                    self.write_space();
7651                }
7652
7653                // Generate: SELECT * INTO [table] FROM (subquery) AS temp
7654                self.write_keyword("SELECT");
7655                self.write(" * ");
7656                self.write_keyword("INTO");
7657                self.write_space();
7658
7659                // If temporary, prefix with # for TSQL temp table
7660                if ct.temporary {
7661                    self.write("#");
7662                }
7663                self.generate_table(&ct.name)?;
7664
7665                self.write_space();
7666                self.write_keyword("FROM");
7667                self.write(" (");
7668                // For TSQL, add aliases to select columns to preserve column names
7669                let aliased_query = Self::add_column_aliases_to_query(query.clone());
7670                self.generate_expression(&aliased_query)?;
7671                self.write(") ");
7672                self.write_keyword("AS");
7673                self.write(" temp");
7674                return Ok(());
7675            }
7676        }
7677
7678        // Output WITH CTE clause if present
7679        if let Some(with_cte) = &ct.with_cte {
7680            self.generate_with(with_cte)?;
7681            self.write_space();
7682        }
7683
7684        // Output leading comments before CREATE
7685        for comment in &ct.leading_comments {
7686            self.write_formatted_comment(comment);
7687            self.write(" ");
7688        }
7689        self.write_keyword("CREATE");
7690
7691        if ct.or_replace {
7692            self.write_space();
7693            self.write_keyword("OR REPLACE");
7694        }
7695
7696        if ct.temporary {
7697            self.write_space();
7698            // Oracle uses GLOBAL TEMPORARY TABLE syntax
7699            if matches!(self.config.dialect, Some(DialectType::Oracle)) {
7700                self.write_keyword("GLOBAL TEMPORARY");
7701            } else {
7702                self.write_keyword("TEMPORARY");
7703            }
7704        }
7705
7706        // Table modifier: DYNAMIC, ICEBERG, EXTERNAL, HYBRID, TRANSIENT
7707        let is_dictionary = ct
7708            .table_modifier
7709            .as_ref()
7710            .map(|m| m.eq_ignore_ascii_case("DICTIONARY"))
7711            .unwrap_or(false);
7712        if let Some(ref modifier) = ct.table_modifier {
7713            // TRANSIENT is Snowflake-specific - skip for other dialects
7714            let skip_transient = modifier.eq_ignore_ascii_case("TRANSIENT")
7715                && !matches!(self.config.dialect, Some(DialectType::Snowflake) | None);
7716            // Teradata-specific modifiers: VOLATILE, SET, MULTISET, SET TABLE combinations
7717            let is_teradata_modifier = modifier.eq_ignore_ascii_case("VOLATILE")
7718                || modifier.eq_ignore_ascii_case("SET")
7719                || modifier.eq_ignore_ascii_case("MULTISET")
7720                || modifier.to_ascii_uppercase().contains("VOLATILE")
7721                || modifier.to_ascii_uppercase().starts_with("SET ")
7722                || modifier.to_ascii_uppercase().starts_with("MULTISET ");
7723            let skip_teradata =
7724                is_teradata_modifier && !matches!(self.config.dialect, Some(DialectType::Teradata));
7725            if !skip_transient && !skip_teradata {
7726                self.write_space();
7727                self.write_keyword(modifier);
7728            }
7729        }
7730
7731        if !is_dictionary {
7732            self.write_space();
7733            self.write_keyword("TABLE");
7734        }
7735
7736        if ct.if_not_exists {
7737            self.write_space();
7738            self.write_keyword("IF NOT EXISTS");
7739        }
7740
7741        self.write_space();
7742        self.generate_table(&ct.name)?;
7743
7744        // ClickHouse: UUID 'xxx' clause after table name
7745        if let Some(ref uuid) = ct.uuid {
7746            self.write_space();
7747            self.write_keyword("UUID");
7748            self.write(" '");
7749            self.write(uuid);
7750            self.write("'");
7751        }
7752
7753        // ClickHouse: ON CLUSTER clause
7754        if let Some(ref on_cluster) = ct.on_cluster {
7755            self.write_space();
7756            self.generate_on_cluster(on_cluster)?;
7757        }
7758
7759        // Teradata: options after table name before column list (comma-separated)
7760        if matches!(
7761            self.config.dialect,
7762            Some(crate::dialects::DialectType::Teradata)
7763        ) && !ct.teradata_post_name_options.is_empty()
7764        {
7765            for opt in &ct.teradata_post_name_options {
7766                self.write(", ");
7767                self.write(opt);
7768            }
7769        }
7770
7771        // Snowflake: COPY GRANTS clause
7772        if ct.copy_grants {
7773            self.write_space();
7774            self.write_keyword("COPY GRANTS");
7775        }
7776
7777        // Snowflake: USING TEMPLATE clause (before columns or AS SELECT)
7778        if let Some(ref using_template) = ct.using_template {
7779            self.write_space();
7780            self.write_keyword("USING TEMPLATE");
7781            self.write_space();
7782            self.generate_expression(using_template)?;
7783            return Ok(());
7784        }
7785
7786        // ClickHouse uses CREATE TABLE target AS source [ENGINE ...] for table-structure copies.
7787        if is_clickhouse {
7788            if let Some(ref clone_source) = ct.clone_source {
7789                self.write_space();
7790                self.write_keyword("AS");
7791                self.write_space();
7792                self.generate_table(clone_source)?;
7793            }
7794        }
7795
7796        // Handle [SHALLOW | DEEP] CLONE/COPY source_table [AT(...) | BEFORE(...)]
7797        if !is_clickhouse {
7798            if let Some(ref clone_source) = ct.clone_source {
7799                self.write_space();
7800                if ct.is_copy && self.config.supports_table_copy {
7801                    // BigQuery uses COPY
7802                    self.write_keyword("COPY");
7803                } else if ct.shallow_clone {
7804                    self.write_keyword("SHALLOW CLONE");
7805                } else if ct.deep_clone {
7806                    self.write_keyword("DEEP CLONE");
7807                } else {
7808                    self.write_keyword("CLONE");
7809                }
7810                self.write_space();
7811                self.generate_table(clone_source)?;
7812                // Generate AT/BEFORE time travel clause (stored as Raw expression)
7813                if let Some(ref at_clause) = ct.clone_at_clause {
7814                    self.write_space();
7815                    self.generate_expression(at_clause)?;
7816                }
7817                return Ok(());
7818            }
7819        }
7820
7821        // Handle PARTITION OF property
7822        // Output order: PARTITION OF <table> (<columns/constraints>) FOR VALUES ...
7823        // Columns/constraints must appear BETWEEN the table name and the partition bound spec
7824        if let Some(ref partition_of) = ct.partition_of {
7825            self.write_space();
7826
7827            // Extract the PartitionedOfProperty parts to generate them separately
7828            if let Expression::PartitionedOfProperty(ref pop) = partition_of {
7829                // Output: PARTITION OF <table>
7830                self.write_keyword("PARTITION OF");
7831                self.write_space();
7832                self.generate_expression(&pop.this)?;
7833
7834                // Output columns/constraints if present (e.g., (unitsales DEFAULT 0) or (CONSTRAINT ...))
7835                if !ct.columns.is_empty() || !ct.constraints.is_empty() {
7836                    self.write(" (");
7837                    let mut first = true;
7838                    for col in &ct.columns {
7839                        if !first {
7840                            self.write(", ");
7841                        }
7842                        first = false;
7843                        self.generate_column_def(col)?;
7844                    }
7845                    for constraint in &ct.constraints {
7846                        if !first {
7847                            self.write(", ");
7848                        }
7849                        first = false;
7850                        self.generate_table_constraint(constraint)?;
7851                    }
7852                    self.write(")");
7853                }
7854
7855                // Output partition bound spec: FOR VALUES ... or DEFAULT
7856                if let Expression::PartitionBoundSpec(_) = pop.expression.as_ref() {
7857                    self.write_space();
7858                    self.write_keyword("FOR VALUES");
7859                    self.write_space();
7860                    self.generate_expression(&pop.expression)?;
7861                } else {
7862                    self.write_space();
7863                    self.write_keyword("DEFAULT");
7864                }
7865            } else {
7866                // Fallback: generate the whole expression if it's not a PartitionedOfProperty
7867                self.generate_expression(partition_of)?;
7868
7869                // Output columns/constraints if present
7870                if !ct.columns.is_empty() || !ct.constraints.is_empty() {
7871                    self.write(" (");
7872                    let mut first = true;
7873                    for col in &ct.columns {
7874                        if !first {
7875                            self.write(", ");
7876                        }
7877                        first = false;
7878                        self.generate_column_def(col)?;
7879                    }
7880                    for constraint in &ct.constraints {
7881                        if !first {
7882                            self.write(", ");
7883                        }
7884                        first = false;
7885                        self.generate_table_constraint(constraint)?;
7886                    }
7887                    self.write(")");
7888                }
7889            }
7890
7891            // Output table properties (e.g., PARTITION BY RANGE(population))
7892            for prop in &ct.properties {
7893                self.write_space();
7894                self.generate_expression(prop)?;
7895            }
7896
7897            return Ok(());
7898        }
7899
7900        // SQLite: Inline single-column PRIMARY KEY constraints into column definition
7901        // This matches Python sqlglot's behavior for SQLite dialect
7902        self.sqlite_inline_pk_columns.clear();
7903        if matches!(
7904            self.config.dialect,
7905            Some(crate::dialects::DialectType::SQLite)
7906        ) {
7907            for constraint in &ct.constraints {
7908                if let TableConstraint::PrimaryKey { columns, name, .. } = constraint {
7909                    // Only inline if: single column, no constraint name, and column exists in table
7910                    if columns.len() == 1 && name.is_none() {
7911                        let pk_col_name = columns[0].name.to_ascii_lowercase();
7912                        // Check if this column exists in the table
7913                        if ct
7914                            .columns
7915                            .iter()
7916                            .any(|c| c.name.name.to_ascii_lowercase() == pk_col_name)
7917                        {
7918                            self.sqlite_inline_pk_columns.insert(pk_col_name);
7919                        }
7920                    }
7921                }
7922            }
7923        }
7924
7925        // Output columns if present (even for CTAS with columns)
7926        if !ct.columns.is_empty() {
7927            if self.config.pretty {
7928                // Pretty print: each column on new line
7929                self.write(" (");
7930                self.write_newline();
7931                self.indent_level += 1;
7932                for (i, col) in ct.columns.iter().enumerate() {
7933                    if i > 0 {
7934                        self.write(",");
7935                        self.write_newline();
7936                    }
7937                    self.write_indent();
7938                    self.generate_column_def(col)?;
7939                }
7940                // Table constraints (skip inlined PRIMARY KEY for SQLite)
7941                for constraint in &ct.constraints {
7942                    // Skip single-column PRIMARY KEY that was inlined for SQLite
7943                    if let TableConstraint::PrimaryKey { columns, name, .. } = constraint {
7944                        if columns.len() == 1
7945                            && name.is_none()
7946                            && self
7947                                .sqlite_inline_pk_columns
7948                                .contains(&columns[0].name.to_ascii_lowercase())
7949                        {
7950                            continue;
7951                        }
7952                    }
7953                    self.write(",");
7954                    self.write_newline();
7955                    self.write_indent();
7956                    self.generate_table_constraint(constraint)?;
7957                }
7958                self.indent_level -= 1;
7959                self.write_newline();
7960                self.write(")");
7961            } else {
7962                self.write(" (");
7963                for (i, col) in ct.columns.iter().enumerate() {
7964                    if i > 0 {
7965                        self.write(", ");
7966                    }
7967                    self.generate_column_def(col)?;
7968                }
7969                // Table constraints (skip inlined PRIMARY KEY for SQLite)
7970                let mut first_constraint = true;
7971                for constraint in &ct.constraints {
7972                    // Skip single-column PRIMARY KEY that was inlined for SQLite
7973                    if let TableConstraint::PrimaryKey { columns, name, .. } = constraint {
7974                        if columns.len() == 1
7975                            && name.is_none()
7976                            && self
7977                                .sqlite_inline_pk_columns
7978                                .contains(&columns[0].name.to_ascii_lowercase())
7979                        {
7980                            continue;
7981                        }
7982                    }
7983                    if first_constraint {
7984                        self.write(", ");
7985                        first_constraint = false;
7986                    } else {
7987                        self.write(", ");
7988                    }
7989                    self.generate_table_constraint(constraint)?;
7990                }
7991                self.write(")");
7992            }
7993        } else if !ct.constraints.is_empty() {
7994            // No columns but constraints exist (e.g., CREATE TABLE A LIKE B or CREATE TABLE A TAG (...))
7995            let has_like_only = ct
7996                .constraints
7997                .iter()
7998                .all(|c| matches!(c, TableConstraint::Like { .. }));
7999            let has_tags_only = ct
8000                .constraints
8001                .iter()
8002                .all(|c| matches!(c, TableConstraint::Tags(_)));
8003            // PostgreSQL: CREATE TABLE A (LIKE B INCLUDING ALL) (with parens)
8004            // Most dialects: CREATE TABLE A LIKE B (no parens)
8005            // Snowflake: CREATE TABLE A TAG (...) (no outer parens, but TAG has its own)
8006            let is_pg_like = matches!(
8007                self.config.dialect,
8008                Some(crate::dialects::DialectType::PostgreSQL)
8009                    | Some(crate::dialects::DialectType::CockroachDB)
8010                    | Some(crate::dialects::DialectType::Materialize)
8011                    | Some(crate::dialects::DialectType::RisingWave)
8012                    | Some(crate::dialects::DialectType::Redshift)
8013                    | Some(crate::dialects::DialectType::Presto)
8014                    | Some(crate::dialects::DialectType::Trino)
8015                    | Some(crate::dialects::DialectType::Athena)
8016            );
8017            let use_parens = if has_like_only {
8018                is_pg_like
8019            } else {
8020                !has_tags_only
8021            };
8022            if self.config.pretty && use_parens {
8023                self.write(" (");
8024                self.write_newline();
8025                self.indent_level += 1;
8026                for (i, constraint) in ct.constraints.iter().enumerate() {
8027                    if i > 0 {
8028                        self.write(",");
8029                        self.write_newline();
8030                    }
8031                    self.write_indent();
8032                    self.generate_table_constraint(constraint)?;
8033                }
8034                self.indent_level -= 1;
8035                self.write_newline();
8036                self.write(")");
8037            } else {
8038                if use_parens {
8039                    self.write(" (");
8040                } else {
8041                    self.write_space();
8042                }
8043                for (i, constraint) in ct.constraints.iter().enumerate() {
8044                    if i > 0 {
8045                        self.write(", ");
8046                    }
8047                    self.generate_table_constraint(constraint)?;
8048                }
8049                if use_parens {
8050                    self.write(")");
8051                }
8052            }
8053        }
8054
8055        // TSQL ON filegroup or ON filegroup (partition_column) clause
8056        if let Some(ref on_prop) = ct.on_property {
8057            self.write(" ");
8058            self.write_keyword("ON");
8059            self.write(" ");
8060            self.generate_expression(&on_prop.this)?;
8061        }
8062
8063        // BigQuery: WITH PARTITION COLUMNS (col_name col_type, ...)
8064        if !ct.with_partition_columns.is_empty() {
8065            if self.config.pretty {
8066                self.write_newline();
8067            } else {
8068                self.write_space();
8069            }
8070            self.write_keyword("WITH PARTITION COLUMNS");
8071            self.write(" (");
8072            if self.config.pretty {
8073                self.write_newline();
8074                self.indent_level += 1;
8075                for (i, col) in ct.with_partition_columns.iter().enumerate() {
8076                    if i > 0 {
8077                        self.write(",");
8078                        self.write_newline();
8079                    }
8080                    self.write_indent();
8081                    self.generate_column_def(col)?;
8082                }
8083                self.indent_level -= 1;
8084                self.write_newline();
8085            } else {
8086                for (i, col) in ct.with_partition_columns.iter().enumerate() {
8087                    if i > 0 {
8088                        self.write(", ");
8089                    }
8090                    self.generate_column_def(col)?;
8091                }
8092            }
8093            self.write(")");
8094        }
8095
8096        // BigQuery: WITH CONNECTION `project.region.connection`
8097        if let Some(ref conn) = ct.with_connection {
8098            if self.config.pretty {
8099                self.write_newline();
8100            } else {
8101                self.write_space();
8102            }
8103            self.write_keyword("WITH CONNECTION");
8104            self.write_space();
8105            self.generate_table(conn)?;
8106        }
8107
8108        // Output SchemaCommentProperty BEFORE WITH properties (Presto/Hive/Spark style)
8109        // For ClickHouse, SchemaCommentProperty goes after AS SELECT, handled later
8110        if !is_clickhouse {
8111            for prop in &ct.properties {
8112                if let Expression::SchemaCommentProperty(_) = prop {
8113                    if self.config.pretty {
8114                        self.write_newline();
8115                    } else {
8116                        self.write_space();
8117                    }
8118                    self.generate_expression(prop)?;
8119                }
8120            }
8121        }
8122
8123        // WITH properties (output after columns if columns exist, otherwise before AS)
8124        if !ct.with_properties.is_empty() {
8125            // Snowflake ICEBERG/DYNAMIC TABLE: output properties inline (space-separated, no WITH wrapper)
8126            let is_snowflake_special_table = matches!(
8127                self.config.dialect,
8128                Some(crate::dialects::DialectType::Snowflake)
8129            ) && (ct.table_modifier.as_deref() == Some("ICEBERG")
8130                || ct.table_modifier.as_deref() == Some("DYNAMIC"));
8131            if is_snowflake_special_table {
8132                for (key, value) in &ct.with_properties {
8133                    self.write_space();
8134                    self.write(key);
8135                    self.write("=");
8136                    self.write(value);
8137                }
8138            } else if self.config.pretty {
8139                self.write_newline();
8140                self.write_keyword("WITH");
8141                self.write(" (");
8142                self.write_newline();
8143                self.indent_level += 1;
8144                for (i, (key, value)) in ct.with_properties.iter().enumerate() {
8145                    if i > 0 {
8146                        self.write(",");
8147                        self.write_newline();
8148                    }
8149                    self.write_indent();
8150                    self.write(key);
8151                    self.write("=");
8152                    self.write(value);
8153                }
8154                self.indent_level -= 1;
8155                self.write_newline();
8156                self.write(")");
8157            } else {
8158                self.write_space();
8159                self.write_keyword("WITH");
8160                self.write(" (");
8161                for (i, (key, value)) in ct.with_properties.iter().enumerate() {
8162                    if i > 0 {
8163                        self.write(", ");
8164                    }
8165                    self.write(key);
8166                    self.write("=");
8167                    self.write(value);
8168                }
8169                self.write(")");
8170            }
8171        }
8172
8173        let (pre_as_properties, post_as_properties): (Vec<&Expression>, Vec<&Expression>) =
8174            if is_clickhouse && ct.as_select.is_some() {
8175                let mut pre = Vec::new();
8176                let mut post = Vec::new();
8177                for prop in &ct.properties {
8178                    if matches!(prop, Expression::SchemaCommentProperty(_)) {
8179                        post.push(prop);
8180                    } else {
8181                        pre.push(prop);
8182                    }
8183                }
8184                (pre, post)
8185            } else {
8186                (ct.properties.iter().collect(), Vec::new())
8187            };
8188
8189        // Table properties like DEFAULT COLLATE (BigQuery), OPTIONS (...), TBLPROPERTIES (...), or PROPERTIES (...)
8190        for prop in pre_as_properties {
8191            // SchemaCommentProperty was already output before WITH properties (except for ClickHouse)
8192            if !is_clickhouse && matches!(prop, Expression::SchemaCommentProperty(_)) {
8193                continue;
8194            }
8195            if self.config.pretty {
8196                self.write_newline();
8197            } else {
8198                self.write_space();
8199            }
8200            // BigQuery: Properties containing OPTIONS should be wrapped with OPTIONS (...)
8201            // Hive: Properties should be wrapped with TBLPROPERTIES (...)
8202            // Doris/StarRocks: Properties should be wrapped with PROPERTIES (...)
8203            if let Expression::Properties(props) = prop {
8204                let is_hive_dialect = matches!(
8205                    self.config.dialect,
8206                    Some(crate::dialects::DialectType::Hive)
8207                        | Some(crate::dialects::DialectType::Spark)
8208                        | Some(crate::dialects::DialectType::Databricks)
8209                        | Some(crate::dialects::DialectType::Athena)
8210                );
8211                let is_doris_starrocks = matches!(
8212                    self.config.dialect,
8213                    Some(crate::dialects::DialectType::Doris)
8214                        | Some(crate::dialects::DialectType::StarRocks)
8215                );
8216                if is_hive_dialect {
8217                    self.generate_tblproperties_clause(&props.expressions)?;
8218                } else if is_doris_starrocks {
8219                    self.generate_properties_clause(&props.expressions)?;
8220                } else {
8221                    self.generate_options_clause(&props.expressions)?;
8222                }
8223            } else {
8224                self.generate_expression(prop)?;
8225            }
8226        }
8227
8228        // Post-table properties like TSQL WITH(SYSTEM_VERSIONING=ON(...)) or Doris PROPERTIES
8229        for prop in &ct.post_table_properties {
8230            if let Expression::WithSystemVersioningProperty(ref svp) = prop {
8231                self.write(" WITH(");
8232                self.generate_system_versioning_content(svp)?;
8233                self.write(")");
8234            } else if let Expression::Properties(props) = prop {
8235                // Doris/StarRocks: PROPERTIES ('key'='value', ...) in post_table_properties
8236                let is_doris_starrocks = matches!(
8237                    self.config.dialect,
8238                    Some(crate::dialects::DialectType::Doris)
8239                        | Some(crate::dialects::DialectType::StarRocks)
8240                );
8241                self.write_space();
8242                if is_doris_starrocks {
8243                    self.generate_properties_clause(&props.expressions)?;
8244                } else {
8245                    self.generate_options_clause(&props.expressions)?;
8246                }
8247            } else {
8248                self.write_space();
8249                self.generate_expression(prop)?;
8250            }
8251        }
8252
8253        // StarRocks ROLLUP property: ROLLUP (r1(col1, col2), r2(col1))
8254        // Only output for StarRocks target
8255        if let Some(ref rollup) = ct.rollup {
8256            if matches!(self.config.dialect, Some(DialectType::StarRocks)) {
8257                self.write_space();
8258                self.generate_rollup_property(rollup)?;
8259            }
8260        }
8261
8262        // MySQL table options (ENGINE=val, AUTO_INCREMENT=val, etc.)
8263        // Only output for MySQL-compatible dialects; strip for others during transpilation
8264        // COMMENT is also used by Hive/Spark so we selectively preserve it
8265        let is_mysql_compatible = matches!(
8266            self.config.dialect,
8267            Some(DialectType::MySQL)
8268                | Some(DialectType::SingleStore)
8269                | Some(DialectType::Doris)
8270                | Some(DialectType::StarRocks)
8271                | None
8272        );
8273        let is_hive_compatible = matches!(
8274            self.config.dialect,
8275            Some(DialectType::Hive)
8276                | Some(DialectType::Spark)
8277                | Some(DialectType::Databricks)
8278                | Some(DialectType::Athena)
8279        );
8280        let mysql_pretty_options =
8281            self.config.pretty && matches!(self.config.dialect, Some(DialectType::MySQL));
8282        for (key, value) in &ct.mysql_table_options {
8283            // Skip non-MySQL-specific options for non-MySQL targets
8284            let should_output = if is_mysql_compatible {
8285                true
8286            } else if is_hive_compatible && key == "COMMENT" {
8287                true // COMMENT is valid in Hive/Spark table definitions
8288            } else {
8289                false
8290            };
8291            if should_output {
8292                if mysql_pretty_options {
8293                    self.write_newline();
8294                    self.write_indent();
8295                } else {
8296                    self.write_space();
8297                }
8298                self.write_keyword(key);
8299                // StarRocks/Doris: COMMENT 'value' (no =), others: COMMENT='value'
8300                if key == "COMMENT" && !self.config.schema_comment_with_eq {
8301                    self.write_space();
8302                } else {
8303                    self.write("=");
8304                }
8305                self.write(value);
8306            }
8307        }
8308
8309        // Spark/Databricks: USING PARQUET for temporary tables that don't already have a storage format
8310        if ct.temporary
8311            && matches!(
8312                self.config.dialect,
8313                Some(DialectType::Spark) | Some(DialectType::Databricks)
8314            )
8315            && ct.as_select.is_none()
8316        {
8317            self.write_space();
8318            self.write_keyword("USING PARQUET");
8319        }
8320
8321        // PostgreSQL INHERITS clause
8322        if !ct.inherits.is_empty() {
8323            self.write_space();
8324            self.write_keyword("INHERITS");
8325            self.write(" (");
8326            for (i, parent) in ct.inherits.iter().enumerate() {
8327                if i > 0 {
8328                    self.write(", ");
8329                }
8330                self.generate_table(parent)?;
8331            }
8332            self.write(")");
8333        }
8334
8335        // CREATE TABLE AS SELECT
8336        if let Some(ref query) = ct.as_select {
8337            self.write_space();
8338            self.write_keyword("AS");
8339            self.write_space();
8340            let source_is_clickhouse =
8341                matches!(self.config.source_dialect, Some(DialectType::ClickHouse));
8342            let wrap_as_select =
8343                ct.as_select_parenthesized && !(is_clickhouse && source_is_clickhouse);
8344            if wrap_as_select {
8345                self.write("(");
8346            }
8347            self.generate_expression(query)?;
8348            if wrap_as_select {
8349                self.write(")");
8350            }
8351
8352            // Teradata: WITH DATA / WITH NO DATA
8353            if let Some(with_data) = ct.with_data {
8354                self.write_space();
8355                self.write_keyword("WITH");
8356                if !with_data {
8357                    self.write_space();
8358                    self.write_keyword("NO");
8359                }
8360                self.write_space();
8361                self.write_keyword("DATA");
8362            }
8363
8364            // Teradata: AND STATISTICS / AND NO STATISTICS
8365            if let Some(with_statistics) = ct.with_statistics {
8366                self.write_space();
8367                self.write_keyword("AND");
8368                if !with_statistics {
8369                    self.write_space();
8370                    self.write_keyword("NO");
8371                }
8372                self.write_space();
8373                self.write_keyword("STATISTICS");
8374            }
8375
8376            // Teradata: Index specifications
8377            for index in &ct.teradata_indexes {
8378                self.write_space();
8379                match index.kind {
8380                    TeradataIndexKind::NoPrimary => {
8381                        self.write_keyword("NO PRIMARY INDEX");
8382                    }
8383                    TeradataIndexKind::Primary => {
8384                        self.write_keyword("PRIMARY INDEX");
8385                    }
8386                    TeradataIndexKind::PrimaryAmp => {
8387                        self.write_keyword("PRIMARY AMP INDEX");
8388                    }
8389                    TeradataIndexKind::Unique => {
8390                        self.write_keyword("UNIQUE INDEX");
8391                    }
8392                    TeradataIndexKind::UniquePrimary => {
8393                        self.write_keyword("UNIQUE PRIMARY INDEX");
8394                    }
8395                    TeradataIndexKind::Secondary => {
8396                        self.write_keyword("INDEX");
8397                    }
8398                }
8399                // Output index name if present
8400                if let Some(ref name) = index.name {
8401                    self.write_space();
8402                    self.write(name);
8403                }
8404                // Output columns if present
8405                if !index.columns.is_empty() {
8406                    self.write(" (");
8407                    for (i, col) in index.columns.iter().enumerate() {
8408                        if i > 0 {
8409                            self.write(", ");
8410                        }
8411                        self.write(col);
8412                    }
8413                    self.write(")");
8414                }
8415            }
8416
8417            // Teradata: ON COMMIT behavior for volatile tables
8418            if let Some(ref on_commit) = ct.on_commit {
8419                self.write_space();
8420                self.write_keyword("ON COMMIT");
8421                self.write_space();
8422                match on_commit {
8423                    OnCommit::PreserveRows => self.write_keyword("PRESERVE ROWS"),
8424                    OnCommit::DeleteRows => self.write_keyword("DELETE ROWS"),
8425                }
8426            }
8427
8428            if !post_as_properties.is_empty() {
8429                for prop in post_as_properties {
8430                    self.write_space();
8431                    self.generate_expression(prop)?;
8432                }
8433            }
8434
8435            // Restore Athena Hive context before early return
8436            self.athena_hive_context = saved_athena_hive_context;
8437            return Ok(());
8438        }
8439
8440        // ON COMMIT behavior (for non-CTAS tables)
8441        if let Some(ref on_commit) = ct.on_commit {
8442            self.write_space();
8443            self.write_keyword("ON COMMIT");
8444            self.write_space();
8445            match on_commit {
8446                OnCommit::PreserveRows => self.write_keyword("PRESERVE ROWS"),
8447                OnCommit::DeleteRows => self.write_keyword("DELETE ROWS"),
8448            }
8449        }
8450
8451        // Restore Athena Hive context
8452        self.athena_hive_context = saved_athena_hive_context;
8453
8454        Ok(())
8455    }
8456
8457    /// Generate column definition as an expression (for ROWS FROM alias columns, XMLTABLE/JSON_TABLE)
8458    /// Outputs: "col_name" TYPE [PATH 'xpath'] (not the full CREATE TABLE column definition)
8459    fn generate_column_def_expr(&mut self, col: &ColumnDef) -> Result<()> {
8460        // Output column name
8461        self.generate_identifier(&col.name)?;
8462        // Output data type if known
8463        if !matches!(col.data_type, DataType::Unknown) {
8464            self.write_space();
8465            self.generate_data_type(&col.data_type)?;
8466        }
8467        // Output PATH constraint if present (for XMLTABLE/JSON_TABLE columns)
8468        for constraint in &col.constraints {
8469            if let ColumnConstraint::Path(path_expr) = constraint {
8470                self.write_space();
8471                self.write_keyword("PATH");
8472                self.write_space();
8473                self.generate_expression(path_expr)?;
8474            }
8475        }
8476        Ok(())
8477    }
8478
8479    fn generate_column_def(&mut self, col: &ColumnDef) -> Result<()> {
8480        // Check if this is a TSQL computed column (no data type)
8481        let has_computed_no_type = matches!(&col.data_type, DataType::Custom { name } if name.is_empty())
8482            && col
8483                .constraints
8484                .iter()
8485                .any(|c| matches!(c, ColumnConstraint::ComputedColumn(_)));
8486        // Some dialects (notably TSQL/Fabric) do not include an explicit type for computed columns.
8487        let omit_computed_type = !self.config.computed_column_with_type
8488            && col
8489                .constraints
8490                .iter()
8491                .any(|c| matches!(c, ColumnConstraint::ComputedColumn(_)));
8492
8493        // Check if this is a partition column spec (no data type, type is Unknown)
8494        // This is used in PostgreSQL PARTITION OF syntax where columns only have constraints
8495        let is_partition_column_spec = matches!(col.data_type, DataType::Unknown);
8496
8497        // Check if this is a DYNAMIC TABLE column (no data type, empty Custom name, no constraints)
8498        // Also check the no_type flag for SQLite columns without types
8499        let has_no_type = col.no_type
8500            || (matches!(&col.data_type, DataType::Custom { name } if name.is_empty())
8501                && col.constraints.is_empty());
8502
8503        self.generate_identifier(&col.name)?;
8504
8505        // Check for SERIAL/BIGSERIAL/SMALLSERIAL expansion for Materialize and PostgreSQL
8506        let serial_expansion = if matches!(
8507            self.config.dialect,
8508            Some(DialectType::Materialize) | Some(DialectType::PostgreSQL)
8509        ) {
8510            if let DataType::Custom { ref name } = col.data_type {
8511                if name.eq_ignore_ascii_case("SERIAL") {
8512                    Some("INT")
8513                } else if name.eq_ignore_ascii_case("BIGSERIAL") {
8514                    Some("BIGINT")
8515                } else if name.eq_ignore_ascii_case("SMALLSERIAL") {
8516                    Some("SMALLINT")
8517                } else {
8518                    None
8519                }
8520            } else {
8521                None
8522            }
8523        } else {
8524            None
8525        };
8526
8527        if !has_computed_no_type && !omit_computed_type && !is_partition_column_spec && !has_no_type
8528        {
8529            self.write_space();
8530            // ClickHouse CREATE TABLE column types: suppress automatic Nullable wrapping
8531            // since ClickHouse uses explicit Nullable() in its type system.
8532            let saved_nullable_depth = self.clickhouse_nullable_depth;
8533            if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
8534                self.clickhouse_nullable_depth = -1;
8535            }
8536            if let Some(int_type) = serial_expansion {
8537                // SERIAL -> INT (+ constraints added below)
8538                self.write_keyword(int_type);
8539            } else if col.unsigned && matches!(self.config.dialect, Some(DialectType::DuckDB)) {
8540                // For DuckDB: convert unsigned integer types to their unsigned equivalents
8541                let unsigned_type = match &col.data_type {
8542                    DataType::Int { .. } => Some("UINTEGER"),
8543                    DataType::BigInt { .. } => Some("UBIGINT"),
8544                    DataType::SmallInt { .. } => Some("USMALLINT"),
8545                    DataType::TinyInt { .. } => Some("UTINYINT"),
8546                    _ => None,
8547                };
8548                if let Some(utype) = unsigned_type {
8549                    self.write_keyword(utype);
8550                } else {
8551                    self.generate_data_type(&col.data_type)?;
8552                }
8553            } else {
8554                self.generate_data_type(&col.data_type)?;
8555            }
8556            self.clickhouse_nullable_depth = saved_nullable_depth;
8557        }
8558
8559        // MySQL type modifiers (must come right after data type)
8560        // Skip UNSIGNED for DuckDB (already mapped to unsigned type above)
8561        if col.unsigned && !matches!(self.config.dialect, Some(DialectType::DuckDB)) {
8562            self.write_space();
8563            self.write_keyword("UNSIGNED");
8564        }
8565        if col.zerofill {
8566            self.write_space();
8567            self.write_keyword("ZEROFILL");
8568        }
8569
8570        // Teradata column attributes (must come right after data type, in specific order)
8571        // ORDER: CHARACTER SET, UPPERCASE, CASESPECIFIC, FORMAT, TITLE, INLINE LENGTH, COMPRESS
8572
8573        if let Some(ref charset) = col.character_set {
8574            self.write_space();
8575            self.write_keyword("CHARACTER SET");
8576            self.write_space();
8577            self.write(charset);
8578        }
8579
8580        if col.uppercase {
8581            self.write_space();
8582            self.write_keyword("UPPERCASE");
8583        }
8584
8585        if let Some(casespecific) = col.casespecific {
8586            self.write_space();
8587            if casespecific {
8588                self.write_keyword("CASESPECIFIC");
8589            } else {
8590                self.write_keyword("NOT CASESPECIFIC");
8591            }
8592        }
8593
8594        if let Some(ref format) = col.format {
8595            self.write_space();
8596            self.write_keyword("FORMAT");
8597            self.write(" '");
8598            self.write(format);
8599            self.write("'");
8600        }
8601
8602        if let Some(ref title) = col.title {
8603            self.write_space();
8604            self.write_keyword("TITLE");
8605            self.write(" '");
8606            self.write(title);
8607            self.write("'");
8608        }
8609
8610        if let Some(length) = col.inline_length {
8611            self.write_space();
8612            self.write_keyword("INLINE LENGTH");
8613            self.write(" ");
8614            self.write(&length.to_string());
8615        }
8616
8617        if let Some(ref compress) = col.compress {
8618            self.write_space();
8619            self.write_keyword("COMPRESS");
8620            if !compress.is_empty() {
8621                // Single string literal: output without parentheses (Teradata syntax)
8622                if compress.len() == 1 {
8623                    if let Expression::Literal(lit) = &compress[0] {
8624                        if let Literal::String(_) = lit.as_ref() {
8625                            self.write_space();
8626                            self.generate_expression(&compress[0])?;
8627                        }
8628                    } else {
8629                        self.write(" (");
8630                        self.generate_expression(&compress[0])?;
8631                        self.write(")");
8632                    }
8633                } else {
8634                    self.write(" (");
8635                    for (i, val) in compress.iter().enumerate() {
8636                        if i > 0 {
8637                            self.write(", ");
8638                        }
8639                        self.generate_expression(val)?;
8640                    }
8641                    self.write(")");
8642                }
8643            }
8644        }
8645
8646        // Column constraints - output in original order if constraint_order is populated
8647        // Otherwise fall back to legacy fixed order for backward compatibility
8648        if !col.constraint_order.is_empty() {
8649            // Use constraint_order for original ordering
8650            // Track indices for constraints stored in the constraints Vec
8651            let mut references_idx = 0;
8652            let mut check_idx = 0;
8653            let mut generated_idx = 0;
8654            let mut collate_idx = 0;
8655            let mut comment_idx = 0;
8656            // The preprocessing in dialects/mod.rs now handles the correct ordering of
8657            // NOT NULL relative to IDENTITY for PostgreSQL, so no deferral needed here.
8658            let defer_not_null_after_identity = false;
8659            let mut pending_not_null_after_identity = false;
8660
8661            for constraint_type in &col.constraint_order {
8662                match constraint_type {
8663                    ConstraintType::PrimaryKey => {
8664                        // Materialize doesn't support PRIMARY KEY column constraints
8665                        if col.primary_key
8666                            && !matches!(self.config.dialect, Some(DialectType::Materialize))
8667                        {
8668                            if let Some(ref cname) = col.primary_key_constraint_name {
8669                                self.write_space();
8670                                self.write_keyword("CONSTRAINT");
8671                                self.write_space();
8672                                self.write(cname);
8673                            }
8674                            self.write_space();
8675                            self.write_keyword("PRIMARY KEY");
8676                            if let Some(ref order) = col.primary_key_order {
8677                                self.write_space();
8678                                match order {
8679                                    SortOrder::Asc => self.write_keyword("ASC"),
8680                                    SortOrder::Desc => self.write_keyword("DESC"),
8681                                }
8682                            }
8683                        }
8684                    }
8685                    ConstraintType::Unique => {
8686                        if col.unique {
8687                            if let Some(ref cname) = col.unique_constraint_name {
8688                                self.write_space();
8689                                self.write_keyword("CONSTRAINT");
8690                                self.write_space();
8691                                self.write(cname);
8692                            }
8693                            self.write_space();
8694                            self.write_keyword("UNIQUE");
8695                            // PostgreSQL 15+: NULLS NOT DISTINCT
8696                            if col.unique_nulls_not_distinct {
8697                                self.write(" NULLS NOT DISTINCT");
8698                            }
8699                        }
8700                    }
8701                    ConstraintType::NotNull => {
8702                        if col.nullable == Some(false) {
8703                            if defer_not_null_after_identity {
8704                                pending_not_null_after_identity = true;
8705                                continue;
8706                            }
8707                            if let Some(ref cname) = col.not_null_constraint_name {
8708                                self.write_space();
8709                                self.write_keyword("CONSTRAINT");
8710                                self.write_space();
8711                                self.write(cname);
8712                            }
8713                            self.write_space();
8714                            self.write_keyword("NOT NULL");
8715                        }
8716                    }
8717                    ConstraintType::Null => {
8718                        if col.nullable == Some(true) {
8719                            self.write_space();
8720                            self.write_keyword("NULL");
8721                        }
8722                    }
8723                    ConstraintType::Default => {
8724                        if let Some(ref default) = col.default {
8725                            self.write_space();
8726                            self.write_keyword("DEFAULT");
8727                            self.write_space();
8728                            self.generate_expression(default)?;
8729                        }
8730                    }
8731                    ConstraintType::AutoIncrement => {
8732                        if col.auto_increment {
8733                            // DuckDB doesn't support AUTO_INCREMENT - skip entirely
8734                            if matches!(
8735                                self.config.dialect,
8736                                Some(crate::dialects::DialectType::DuckDB)
8737                            ) {
8738                                // Skip - DuckDB uses sequences or rowid instead
8739                            } else if matches!(
8740                                self.config.dialect,
8741                                Some(crate::dialects::DialectType::Materialize)
8742                            ) {
8743                                // Materialize strips AUTO_INCREMENT but adds NOT NULL
8744                                if !matches!(col.nullable, Some(false)) {
8745                                    self.write_space();
8746                                    self.write_keyword("NOT NULL");
8747                                }
8748                            } else if matches!(
8749                                self.config.dialect,
8750                                Some(crate::dialects::DialectType::PostgreSQL)
8751                            ) {
8752                                // PostgreSQL: AUTO_INCREMENT -> GENERATED BY DEFAULT AS IDENTITY
8753                                self.write_space();
8754                                self.generate_auto_increment_keyword(col)?;
8755                            } else {
8756                                self.write_space();
8757                                self.generate_auto_increment_keyword(col)?;
8758                                if pending_not_null_after_identity {
8759                                    self.write_space();
8760                                    self.write_keyword("NOT NULL");
8761                                    pending_not_null_after_identity = false;
8762                                }
8763                            }
8764                        } // close else for DuckDB skip
8765                    }
8766                    ConstraintType::References => {
8767                        // Find next References constraint
8768                        while references_idx < col.constraints.len() {
8769                            if let ColumnConstraint::References(fk_ref) =
8770                                &col.constraints[references_idx]
8771                            {
8772                                // CONSTRAINT name if present
8773                                if let Some(ref name) = fk_ref.constraint_name {
8774                                    self.write_space();
8775                                    self.write_keyword("CONSTRAINT");
8776                                    self.write_space();
8777                                    self.write(name);
8778                                }
8779                                self.write_space();
8780                                if fk_ref.has_foreign_key_keywords {
8781                                    self.write_keyword("FOREIGN KEY");
8782                                    self.write_space();
8783                                }
8784                                self.write_keyword("REFERENCES");
8785                                self.write_space();
8786                                self.generate_table(&fk_ref.table)?;
8787                                if !fk_ref.columns.is_empty() {
8788                                    self.write(" (");
8789                                    for (i, c) in fk_ref.columns.iter().enumerate() {
8790                                        if i > 0 {
8791                                            self.write(", ");
8792                                        }
8793                                        self.generate_identifier(c)?;
8794                                    }
8795                                    self.write(")");
8796                                }
8797                                self.generate_referential_actions(fk_ref)?;
8798                                references_idx += 1;
8799                                break;
8800                            }
8801                            references_idx += 1;
8802                        }
8803                    }
8804                    ConstraintType::Check => {
8805                        // Find next Check constraint
8806                        while check_idx < col.constraints.len() {
8807                            if let ColumnConstraint::Check(expr) = &col.constraints[check_idx] {
8808                                // Output CONSTRAINT name if present (only for first CHECK)
8809                                if check_idx == 0 {
8810                                    if let Some(ref cname) = col.check_constraint_name {
8811                                        self.write_space();
8812                                        self.write_keyword("CONSTRAINT");
8813                                        self.write_space();
8814                                        self.write(cname);
8815                                    }
8816                                }
8817                                self.write_space();
8818                                self.write_keyword("CHECK");
8819                                self.write(" (");
8820                                self.generate_expression(expr)?;
8821                                self.write(")");
8822                                check_idx += 1;
8823                                break;
8824                            }
8825                            check_idx += 1;
8826                        }
8827                    }
8828                    ConstraintType::GeneratedAsIdentity => {
8829                        // Find next GeneratedAsIdentity constraint
8830                        while generated_idx < col.constraints.len() {
8831                            if let ColumnConstraint::GeneratedAsIdentity(gen) =
8832                                &col.constraints[generated_idx]
8833                            {
8834                                self.write_space();
8835                                // Redshift uses IDENTITY(start, increment) syntax
8836                                if matches!(
8837                                    self.config.dialect,
8838                                    Some(crate::dialects::DialectType::Redshift)
8839                                ) {
8840                                    self.write_keyword("IDENTITY");
8841                                    self.write("(");
8842                                    if let Some(ref start) = gen.start {
8843                                        self.generate_expression(start)?;
8844                                    } else {
8845                                        self.write("0");
8846                                    }
8847                                    self.write(", ");
8848                                    if let Some(ref incr) = gen.increment {
8849                                        self.generate_expression(incr)?;
8850                                    } else {
8851                                        self.write("1");
8852                                    }
8853                                    self.write(")");
8854                                } else {
8855                                    self.write_keyword("GENERATED");
8856                                    if gen.always {
8857                                        self.write_space();
8858                                        self.write_keyword("ALWAYS");
8859                                    } else {
8860                                        self.write_space();
8861                                        self.write_keyword("BY DEFAULT");
8862                                        if gen.on_null {
8863                                            self.write_space();
8864                                            self.write_keyword("ON NULL");
8865                                        }
8866                                    }
8867                                    self.write_space();
8868                                    self.write_keyword("AS IDENTITY");
8869
8870                                    let has_options = gen.start.is_some()
8871                                        || gen.increment.is_some()
8872                                        || gen.minvalue.is_some()
8873                                        || gen.maxvalue.is_some()
8874                                        || gen.cycle.is_some();
8875                                    if has_options {
8876                                        self.write(" (");
8877                                        let mut first = true;
8878                                        if let Some(ref start) = gen.start {
8879                                            if !first {
8880                                                self.write(" ");
8881                                            }
8882                                            first = false;
8883                                            self.write_keyword("START WITH");
8884                                            self.write_space();
8885                                            self.generate_expression(start)?;
8886                                        }
8887                                        if let Some(ref incr) = gen.increment {
8888                                            if !first {
8889                                                self.write(" ");
8890                                            }
8891                                            first = false;
8892                                            self.write_keyword("INCREMENT BY");
8893                                            self.write_space();
8894                                            self.generate_expression(incr)?;
8895                                        }
8896                                        if let Some(ref minv) = gen.minvalue {
8897                                            if !first {
8898                                                self.write(" ");
8899                                            }
8900                                            first = false;
8901                                            self.write_keyword("MINVALUE");
8902                                            self.write_space();
8903                                            self.generate_expression(minv)?;
8904                                        }
8905                                        if let Some(ref maxv) = gen.maxvalue {
8906                                            if !first {
8907                                                self.write(" ");
8908                                            }
8909                                            first = false;
8910                                            self.write_keyword("MAXVALUE");
8911                                            self.write_space();
8912                                            self.generate_expression(maxv)?;
8913                                        }
8914                                        if let Some(cycle) = gen.cycle {
8915                                            if !first {
8916                                                self.write(" ");
8917                                            }
8918                                            if cycle {
8919                                                self.write_keyword("CYCLE");
8920                                            } else {
8921                                                self.write_keyword("NO CYCLE");
8922                                            }
8923                                        }
8924                                        self.write(")");
8925                                    }
8926                                }
8927                                generated_idx += 1;
8928                                break;
8929                            }
8930                            generated_idx += 1;
8931                        }
8932                    }
8933                    ConstraintType::Collate => {
8934                        // Find next Collate constraint
8935                        while collate_idx < col.constraints.len() {
8936                            if let ColumnConstraint::Collate(collation) =
8937                                &col.constraints[collate_idx]
8938                            {
8939                                self.write_space();
8940                                self.write_keyword("COLLATE");
8941                                self.write_space();
8942                                self.generate_identifier(collation)?;
8943                                collate_idx += 1;
8944                                break;
8945                            }
8946                            collate_idx += 1;
8947                        }
8948                    }
8949                    ConstraintType::Comment => {
8950                        // Find next Comment constraint
8951                        while comment_idx < col.constraints.len() {
8952                            if let ColumnConstraint::Comment(comment) =
8953                                &col.constraints[comment_idx]
8954                            {
8955                                self.write_space();
8956                                self.write_keyword("COMMENT");
8957                                self.write_space();
8958                                self.generate_string_literal(comment)?;
8959                                comment_idx += 1;
8960                                break;
8961                            }
8962                            comment_idx += 1;
8963                        }
8964                    }
8965                    ConstraintType::Tags => {
8966                        // Find next Tags constraint (Snowflake)
8967                        for constraint in &col.constraints {
8968                            if let ColumnConstraint::Tags(tags) = constraint {
8969                                self.write_space();
8970                                self.write_keyword("TAG");
8971                                self.write(" (");
8972                                for (i, expr) in tags.expressions.iter().enumerate() {
8973                                    if i > 0 {
8974                                        self.write(", ");
8975                                    }
8976                                    self.generate_expression(expr)?;
8977                                }
8978                                self.write(")");
8979                                break;
8980                            }
8981                        }
8982                    }
8983                    ConstraintType::ComputedColumn => {
8984                        // Find next ComputedColumn constraint
8985                        for constraint in &col.constraints {
8986                            if let ColumnConstraint::ComputedColumn(cc) = constraint {
8987                                self.write_space();
8988                                self.generate_computed_column_inline(cc)?;
8989                                break;
8990                            }
8991                        }
8992                    }
8993                    ConstraintType::GeneratedAsRow => {
8994                        // Find next GeneratedAsRow constraint
8995                        for constraint in &col.constraints {
8996                            if let ColumnConstraint::GeneratedAsRow(gar) = constraint {
8997                                self.write_space();
8998                                self.generate_generated_as_row_inline(gar)?;
8999                                break;
9000                            }
9001                        }
9002                    }
9003                    ConstraintType::OnUpdate => {
9004                        if let Some(ref expr) = col.on_update {
9005                            self.write_space();
9006                            self.write_keyword("ON UPDATE");
9007                            self.write_space();
9008                            self.generate_expression(expr)?;
9009                        }
9010                    }
9011                    ConstraintType::Encode => {
9012                        if let Some(ref encoding) = col.encoding {
9013                            self.write_space();
9014                            self.write_keyword("ENCODE");
9015                            self.write_space();
9016                            self.write(encoding);
9017                        }
9018                    }
9019                    ConstraintType::Path => {
9020                        // Find next Path constraint
9021                        for constraint in &col.constraints {
9022                            if let ColumnConstraint::Path(path_expr) = constraint {
9023                                self.write_space();
9024                                self.write_keyword("PATH");
9025                                self.write_space();
9026                                self.generate_expression(path_expr)?;
9027                                break;
9028                            }
9029                        }
9030                    }
9031                }
9032            }
9033            if pending_not_null_after_identity {
9034                self.write_space();
9035                self.write_keyword("NOT NULL");
9036            }
9037        } else {
9038            // Legacy fixed order for backward compatibility
9039            if col.primary_key {
9040                self.write_space();
9041                self.write_keyword("PRIMARY KEY");
9042                if let Some(ref order) = col.primary_key_order {
9043                    self.write_space();
9044                    match order {
9045                        SortOrder::Asc => self.write_keyword("ASC"),
9046                        SortOrder::Desc => self.write_keyword("DESC"),
9047                    }
9048                }
9049            }
9050
9051            if col.unique {
9052                self.write_space();
9053                self.write_keyword("UNIQUE");
9054                // PostgreSQL 15+: NULLS NOT DISTINCT
9055                if col.unique_nulls_not_distinct {
9056                    self.write(" NULLS NOT DISTINCT");
9057                }
9058            }
9059
9060            match col.nullable {
9061                Some(false) => {
9062                    self.write_space();
9063                    self.write_keyword("NOT NULL");
9064                }
9065                Some(true) => {
9066                    self.write_space();
9067                    self.write_keyword("NULL");
9068                }
9069                None => {}
9070            }
9071
9072            if let Some(ref default) = col.default {
9073                self.write_space();
9074                self.write_keyword("DEFAULT");
9075                self.write_space();
9076                self.generate_expression(default)?;
9077            }
9078
9079            if col.auto_increment {
9080                self.write_space();
9081                self.generate_auto_increment_keyword(col)?;
9082            }
9083
9084            // Column-level constraints from Vec
9085            for constraint in &col.constraints {
9086                match constraint {
9087                    ColumnConstraint::References(fk_ref) => {
9088                        self.write_space();
9089                        if fk_ref.has_foreign_key_keywords {
9090                            self.write_keyword("FOREIGN KEY");
9091                            self.write_space();
9092                        }
9093                        self.write_keyword("REFERENCES");
9094                        self.write_space();
9095                        self.generate_table(&fk_ref.table)?;
9096                        if !fk_ref.columns.is_empty() {
9097                            self.write(" (");
9098                            for (i, c) in fk_ref.columns.iter().enumerate() {
9099                                if i > 0 {
9100                                    self.write(", ");
9101                                }
9102                                self.generate_identifier(c)?;
9103                            }
9104                            self.write(")");
9105                        }
9106                        self.generate_referential_actions(fk_ref)?;
9107                    }
9108                    ColumnConstraint::Check(expr) => {
9109                        self.write_space();
9110                        self.write_keyword("CHECK");
9111                        self.write(" (");
9112                        self.generate_expression(expr)?;
9113                        self.write(")");
9114                    }
9115                    ColumnConstraint::GeneratedAsIdentity(gen) => {
9116                        self.write_space();
9117                        // Redshift uses IDENTITY(start, increment) syntax
9118                        if matches!(
9119                            self.config.dialect,
9120                            Some(crate::dialects::DialectType::Redshift)
9121                        ) {
9122                            self.write_keyword("IDENTITY");
9123                            self.write("(");
9124                            if let Some(ref start) = gen.start {
9125                                self.generate_expression(start)?;
9126                            } else {
9127                                self.write("0");
9128                            }
9129                            self.write(", ");
9130                            if let Some(ref incr) = gen.increment {
9131                                self.generate_expression(incr)?;
9132                            } else {
9133                                self.write("1");
9134                            }
9135                            self.write(")");
9136                        } else {
9137                            self.write_keyword("GENERATED");
9138                            if gen.always {
9139                                self.write_space();
9140                                self.write_keyword("ALWAYS");
9141                            } else {
9142                                self.write_space();
9143                                self.write_keyword("BY DEFAULT");
9144                                if gen.on_null {
9145                                    self.write_space();
9146                                    self.write_keyword("ON NULL");
9147                                }
9148                            }
9149                            self.write_space();
9150                            self.write_keyword("AS IDENTITY");
9151
9152                            let has_options = gen.start.is_some()
9153                                || gen.increment.is_some()
9154                                || gen.minvalue.is_some()
9155                                || gen.maxvalue.is_some()
9156                                || gen.cycle.is_some();
9157                            if has_options {
9158                                self.write(" (");
9159                                let mut first = true;
9160                                if let Some(ref start) = gen.start {
9161                                    if !first {
9162                                        self.write(" ");
9163                                    }
9164                                    first = false;
9165                                    self.write_keyword("START WITH");
9166                                    self.write_space();
9167                                    self.generate_expression(start)?;
9168                                }
9169                                if let Some(ref incr) = gen.increment {
9170                                    if !first {
9171                                        self.write(" ");
9172                                    }
9173                                    first = false;
9174                                    self.write_keyword("INCREMENT BY");
9175                                    self.write_space();
9176                                    self.generate_expression(incr)?;
9177                                }
9178                                if let Some(ref minv) = gen.minvalue {
9179                                    if !first {
9180                                        self.write(" ");
9181                                    }
9182                                    first = false;
9183                                    self.write_keyword("MINVALUE");
9184                                    self.write_space();
9185                                    self.generate_expression(minv)?;
9186                                }
9187                                if let Some(ref maxv) = gen.maxvalue {
9188                                    if !first {
9189                                        self.write(" ");
9190                                    }
9191                                    first = false;
9192                                    self.write_keyword("MAXVALUE");
9193                                    self.write_space();
9194                                    self.generate_expression(maxv)?;
9195                                }
9196                                if let Some(cycle) = gen.cycle {
9197                                    if !first {
9198                                        self.write(" ");
9199                                    }
9200                                    if cycle {
9201                                        self.write_keyword("CYCLE");
9202                                    } else {
9203                                        self.write_keyword("NO CYCLE");
9204                                    }
9205                                }
9206                                self.write(")");
9207                            }
9208                        }
9209                    }
9210                    ColumnConstraint::Collate(collation) => {
9211                        self.write_space();
9212                        self.write_keyword("COLLATE");
9213                        self.write_space();
9214                        self.generate_identifier(collation)?;
9215                    }
9216                    ColumnConstraint::Comment(comment) => {
9217                        self.write_space();
9218                        self.write_keyword("COMMENT");
9219                        self.write_space();
9220                        self.generate_string_literal(comment)?;
9221                    }
9222                    ColumnConstraint::Path(path_expr) => {
9223                        self.write_space();
9224                        self.write_keyword("PATH");
9225                        self.write_space();
9226                        self.generate_expression(path_expr)?;
9227                    }
9228                    _ => {} // Other constraints handled above
9229                }
9230            }
9231
9232            // Redshift: ENCODE encoding_type (legacy path)
9233            if let Some(ref encoding) = col.encoding {
9234                self.write_space();
9235                self.write_keyword("ENCODE");
9236                self.write_space();
9237                self.write(encoding);
9238            }
9239        }
9240
9241        // ClickHouse: CODEC(...)
9242        if let Some(ref codec) = col.codec {
9243            self.write_space();
9244            self.write_keyword("CODEC");
9245            self.write("(");
9246            self.write(codec);
9247            self.write(")");
9248        }
9249
9250        if let Some(visible) = col.visible {
9251            self.write_space();
9252            if visible {
9253                self.write_keyword("VISIBLE");
9254            } else {
9255                self.write_keyword("INVISIBLE");
9256            }
9257        }
9258
9259        // ClickHouse: EPHEMERAL [expr]
9260        if let Some(ref ephemeral) = col.ephemeral {
9261            self.write_space();
9262            self.write_keyword("EPHEMERAL");
9263            if let Some(ref expr) = ephemeral {
9264                self.write_space();
9265                self.generate_expression(expr)?;
9266            }
9267        }
9268
9269        // ClickHouse: MATERIALIZED expr
9270        if let Some(ref mat_expr) = col.materialized_expr {
9271            self.write_space();
9272            self.write_keyword("MATERIALIZED");
9273            self.write_space();
9274            self.generate_expression(mat_expr)?;
9275        }
9276
9277        // ClickHouse: ALIAS expr
9278        if let Some(ref alias_expr) = col.alias_expr {
9279            self.write_space();
9280            self.write_keyword("ALIAS");
9281            self.write_space();
9282            self.generate_expression(alias_expr)?;
9283        }
9284
9285        // ClickHouse: TTL expr
9286        if let Some(ref ttl_expr) = col.ttl_expr {
9287            self.write_space();
9288            self.write_keyword("TTL");
9289            self.write_space();
9290            self.generate_expression(ttl_expr)?;
9291        }
9292
9293        // TSQL: NOT FOR REPLICATION
9294        if col.not_for_replication
9295            && matches!(
9296                self.config.dialect,
9297                Some(crate::dialects::DialectType::TSQL)
9298                    | Some(crate::dialects::DialectType::Fabric)
9299            )
9300        {
9301            self.write_space();
9302            self.write_keyword("NOT FOR REPLICATION");
9303        }
9304
9305        // BigQuery: OPTIONS (key=value, ...) on column - comes after all constraints
9306        if !col.options.is_empty() {
9307            self.write_space();
9308            self.generate_options_clause(&col.options)?;
9309        }
9310
9311        // SQLite: Inline PRIMARY KEY from table constraint
9312        // This comes at the end, after all existing column constraints
9313        if !col.primary_key
9314            && self
9315                .sqlite_inline_pk_columns
9316                .contains(&col.name.name.to_ascii_lowercase())
9317        {
9318            self.write_space();
9319            self.write_keyword("PRIMARY KEY");
9320        }
9321
9322        // SERIAL expansion: add GENERATED BY DEFAULT AS IDENTITY NOT NULL for PostgreSQL,
9323        // just NOT NULL for Materialize (which strips GENERATED AS IDENTITY)
9324        if serial_expansion.is_some() {
9325            if matches!(self.config.dialect, Some(DialectType::PostgreSQL)) {
9326                self.write_space();
9327                self.write_keyword("GENERATED BY DEFAULT AS IDENTITY NOT NULL");
9328            } else if matches!(self.config.dialect, Some(DialectType::Materialize)) {
9329                self.write_space();
9330                self.write_keyword("NOT NULL");
9331            }
9332        }
9333
9334        Ok(())
9335    }
9336
9337    fn generate_table_constraint(&mut self, constraint: &TableConstraint) -> Result<()> {
9338        match constraint {
9339            TableConstraint::PrimaryKey {
9340                name,
9341                columns,
9342                include_columns,
9343                modifiers,
9344                has_constraint_keyword,
9345            } => {
9346                if let Some(ref n) = name {
9347                    if *has_constraint_keyword {
9348                        self.write_keyword("CONSTRAINT");
9349                        self.write_space();
9350                        self.generate_identifier(n)?;
9351                        self.write_space();
9352                    }
9353                }
9354                self.write_keyword("PRIMARY KEY");
9355                // TSQL CLUSTERED/NONCLUSTERED modifier (before columns)
9356                if let Some(ref clustered) = modifiers.clustered {
9357                    self.write_space();
9358                    self.write_keyword(clustered);
9359                }
9360                // MySQL format: PRIMARY KEY name (cols) when no CONSTRAINT keyword
9361                if let Some(ref n) = name {
9362                    if !*has_constraint_keyword {
9363                        self.write_space();
9364                        self.generate_identifier(n)?;
9365                    }
9366                }
9367                self.write(" (");
9368                for (i, col) in columns.iter().enumerate() {
9369                    if i > 0 {
9370                        self.write(", ");
9371                    }
9372                    self.generate_identifier(col)?;
9373                }
9374                self.write(")");
9375                if !include_columns.is_empty() {
9376                    self.write_space();
9377                    self.write_keyword("INCLUDE");
9378                    self.write(" (");
9379                    for (i, col) in include_columns.iter().enumerate() {
9380                        if i > 0 {
9381                            self.write(", ");
9382                        }
9383                        self.generate_identifier(col)?;
9384                    }
9385                    self.write(")");
9386                }
9387                self.generate_constraint_modifiers(modifiers);
9388            }
9389            TableConstraint::Unique {
9390                name,
9391                columns,
9392                columns_parenthesized,
9393                modifiers,
9394                has_constraint_keyword,
9395                nulls_not_distinct,
9396            } => {
9397                if let Some(ref n) = name {
9398                    if *has_constraint_keyword {
9399                        self.write_keyword("CONSTRAINT");
9400                        self.write_space();
9401                        self.generate_identifier(n)?;
9402                        self.write_space();
9403                    }
9404                }
9405                self.write_keyword("UNIQUE");
9406                // TSQL CLUSTERED/NONCLUSTERED modifier (before columns)
9407                if let Some(ref clustered) = modifiers.clustered {
9408                    self.write_space();
9409                    self.write_keyword(clustered);
9410                }
9411                // PostgreSQL 15+: NULLS NOT DISTINCT
9412                if *nulls_not_distinct {
9413                    self.write(" NULLS NOT DISTINCT");
9414                }
9415                // MySQL format: UNIQUE name (cols) when no CONSTRAINT keyword
9416                if let Some(ref n) = name {
9417                    if !*has_constraint_keyword {
9418                        self.write_space();
9419                        self.generate_identifier(n)?;
9420                    }
9421                }
9422                if *columns_parenthesized {
9423                    self.write(" (");
9424                    for (i, col) in columns.iter().enumerate() {
9425                        if i > 0 {
9426                            self.write(", ");
9427                        }
9428                        self.generate_identifier(col)?;
9429                    }
9430                    self.write(")");
9431                } else {
9432                    // UNIQUE without parentheses (e.g., UNIQUE idx_name)
9433                    for col in columns.iter() {
9434                        self.write_space();
9435                        self.generate_identifier(col)?;
9436                    }
9437                }
9438                self.generate_constraint_modifiers(modifiers);
9439            }
9440            TableConstraint::ForeignKey {
9441                name,
9442                columns,
9443                references,
9444                on_delete,
9445                on_update,
9446                modifiers,
9447            } => {
9448                if let Some(ref n) = name {
9449                    self.write_keyword("CONSTRAINT");
9450                    self.write_space();
9451                    self.generate_identifier(n)?;
9452                    self.write_space();
9453                }
9454                self.write_keyword("FOREIGN KEY");
9455                self.write(" (");
9456                for (i, col) in columns.iter().enumerate() {
9457                    if i > 0 {
9458                        self.write(", ");
9459                    }
9460                    self.generate_identifier(col)?;
9461                }
9462                self.write(")");
9463                if let Some(ref refs) = references {
9464                    self.write(" ");
9465                    self.write_keyword("REFERENCES");
9466                    self.write_space();
9467                    self.generate_table(&refs.table)?;
9468                    if !refs.columns.is_empty() {
9469                        if self.config.pretty {
9470                            self.write(" (");
9471                            self.write_newline();
9472                            self.indent_level += 1;
9473                            for (i, col) in refs.columns.iter().enumerate() {
9474                                if i > 0 {
9475                                    self.write(",");
9476                                    self.write_newline();
9477                                }
9478                                self.write_indent();
9479                                self.generate_identifier(col)?;
9480                            }
9481                            self.indent_level -= 1;
9482                            self.write_newline();
9483                            self.write_indent();
9484                            self.write(")");
9485                        } else {
9486                            self.write(" (");
9487                            for (i, col) in refs.columns.iter().enumerate() {
9488                                if i > 0 {
9489                                    self.write(", ");
9490                                }
9491                                self.generate_identifier(col)?;
9492                            }
9493                            self.write(")");
9494                        }
9495                    }
9496                    self.generate_referential_actions(refs)?;
9497                } else {
9498                    // No REFERENCES - output ON DELETE/ON UPDATE directly
9499                    if let Some(ref action) = on_delete {
9500                        self.write_space();
9501                        self.write_keyword("ON DELETE");
9502                        self.write_space();
9503                        self.generate_referential_action(action);
9504                    }
9505                    if let Some(ref action) = on_update {
9506                        self.write_space();
9507                        self.write_keyword("ON UPDATE");
9508                        self.write_space();
9509                        self.generate_referential_action(action);
9510                    }
9511                }
9512                self.generate_constraint_modifiers(modifiers);
9513            }
9514            TableConstraint::Check {
9515                name,
9516                expression,
9517                modifiers,
9518            } => {
9519                if let Some(ref n) = name {
9520                    self.write_keyword("CONSTRAINT");
9521                    self.write_space();
9522                    self.generate_identifier(n)?;
9523                    self.write_space();
9524                }
9525                self.write_keyword("CHECK");
9526                self.write(" (");
9527                self.generate_expression(expression)?;
9528                self.write(")");
9529                self.generate_constraint_modifiers(modifiers);
9530            }
9531            TableConstraint::Assume { name, expression } => {
9532                if let Some(ref n) = name {
9533                    self.write_keyword("CONSTRAINT");
9534                    self.write_space();
9535                    self.generate_identifier(n)?;
9536                    self.write_space();
9537                }
9538                self.write_keyword("ASSUME");
9539                self.write(" (");
9540                self.generate_expression(expression)?;
9541                self.write(")");
9542            }
9543            TableConstraint::Default {
9544                name,
9545                expression,
9546                column,
9547            } => {
9548                if let Some(ref n) = name {
9549                    self.write_keyword("CONSTRAINT");
9550                    self.write_space();
9551                    self.generate_identifier(n)?;
9552                    self.write_space();
9553                }
9554                self.write_keyword("DEFAULT");
9555                self.write_space();
9556                self.generate_expression(expression)?;
9557                self.write_space();
9558                self.write_keyword("FOR");
9559                self.write_space();
9560                self.generate_identifier(column)?;
9561            }
9562            TableConstraint::Index {
9563                name,
9564                columns,
9565                kind,
9566                modifiers,
9567                use_key_keyword,
9568                expression,
9569                index_type,
9570                granularity,
9571            } => {
9572                // ClickHouse-style INDEX: INDEX name expr TYPE type_func GRANULARITY n
9573                if expression.is_some() {
9574                    self.write_keyword("INDEX");
9575                    if let Some(ref n) = name {
9576                        self.write_space();
9577                        self.generate_identifier(n)?;
9578                    }
9579                    if let Some(ref expr) = expression {
9580                        self.write_space();
9581                        self.generate_expression(expr)?;
9582                    }
9583                    if let Some(ref idx_type) = index_type {
9584                        self.write_space();
9585                        self.write_keyword("TYPE");
9586                        self.write_space();
9587                        self.generate_expression(idx_type)?;
9588                    }
9589                    if let Some(ref gran) = granularity {
9590                        self.write_space();
9591                        self.write_keyword("GRANULARITY");
9592                        self.write_space();
9593                        self.generate_expression(gran)?;
9594                    }
9595                } else {
9596                    // Standard INDEX syntax
9597                    // Determine the index keyword to use
9598                    // MySQL normalizes KEY to INDEX
9599                    use crate::dialects::DialectType;
9600                    let index_keyword = if *use_key_keyword
9601                        && !matches!(self.config.dialect, Some(DialectType::MySQL))
9602                    {
9603                        "KEY"
9604                    } else {
9605                        "INDEX"
9606                    };
9607
9608                    // Output kind (UNIQUE, FULLTEXT, SPATIAL) if present
9609                    if let Some(ref k) = kind {
9610                        self.write_keyword(k);
9611                        // For UNIQUE, don't add INDEX/KEY keyword
9612                        if k != "UNIQUE" {
9613                            self.write_space();
9614                            self.write_keyword(index_keyword);
9615                        }
9616                    } else {
9617                        self.write_keyword(index_keyword);
9618                    }
9619
9620                    // Output USING before name if using_before_columns is true and there's no name
9621                    if modifiers.using_before_columns && name.is_none() {
9622                        if let Some(ref using) = modifiers.using {
9623                            self.write_space();
9624                            self.write_keyword("USING");
9625                            self.write_space();
9626                            self.write_keyword(using);
9627                        }
9628                    }
9629
9630                    // Output index name if present
9631                    if let Some(ref n) = name {
9632                        self.write_space();
9633                        self.generate_identifier(n)?;
9634                    }
9635
9636                    // Output USING after name but before columns if using_before_columns and there's a name
9637                    if modifiers.using_before_columns && name.is_some() {
9638                        if let Some(ref using) = modifiers.using {
9639                            self.write_space();
9640                            self.write_keyword("USING");
9641                            self.write_space();
9642                            self.write_keyword(using);
9643                        }
9644                    }
9645
9646                    // Output columns
9647                    self.write(" (");
9648                    for (i, col) in columns.iter().enumerate() {
9649                        if i > 0 {
9650                            self.write(", ");
9651                        }
9652                        self.generate_identifier(col)?;
9653                    }
9654                    self.write(")");
9655
9656                    // Output USING after columns if not using_before_columns
9657                    if !modifiers.using_before_columns {
9658                        if let Some(ref using) = modifiers.using {
9659                            self.write_space();
9660                            self.write_keyword("USING");
9661                            self.write_space();
9662                            self.write_keyword(using);
9663                        }
9664                    }
9665
9666                    // Output other constraint modifiers (but skip USING since we already handled it)
9667                    self.generate_constraint_modifiers_without_using(modifiers);
9668                }
9669            }
9670            TableConstraint::Projection { name, expression } => {
9671                // ClickHouse: PROJECTION name (SELECT ...)
9672                self.write_keyword("PROJECTION");
9673                self.write_space();
9674                self.generate_identifier(name)?;
9675                self.write(" (");
9676                self.generate_expression(expression)?;
9677                self.write(")");
9678            }
9679            TableConstraint::Like { source, options } => {
9680                self.write_keyword("LIKE");
9681                self.write_space();
9682                self.generate_table(source)?;
9683                for (action, prop) in options {
9684                    self.write_space();
9685                    match action {
9686                        LikeOptionAction::Including => self.write_keyword("INCLUDING"),
9687                        LikeOptionAction::Excluding => self.write_keyword("EXCLUDING"),
9688                    }
9689                    self.write_space();
9690                    self.write_keyword(prop);
9691                }
9692            }
9693            TableConstraint::PeriodForSystemTime { start_col, end_col } => {
9694                self.write_keyword("PERIOD FOR SYSTEM_TIME");
9695                self.write(" (");
9696                self.generate_identifier(start_col)?;
9697                self.write(", ");
9698                self.generate_identifier(end_col)?;
9699                self.write(")");
9700            }
9701            TableConstraint::Exclude {
9702                name,
9703                using,
9704                elements,
9705                include_columns,
9706                where_clause,
9707                with_params,
9708                using_index_tablespace,
9709                modifiers: _,
9710            } => {
9711                if let Some(ref n) = name {
9712                    self.write_keyword("CONSTRAINT");
9713                    self.write_space();
9714                    self.generate_identifier(n)?;
9715                    self.write_space();
9716                }
9717                self.write_keyword("EXCLUDE");
9718                if let Some(ref method) = using {
9719                    self.write_space();
9720                    self.write_keyword("USING");
9721                    self.write_space();
9722                    self.write(method);
9723                    self.write("(");
9724                } else {
9725                    self.write(" (");
9726                }
9727                for (i, elem) in elements.iter().enumerate() {
9728                    if i > 0 {
9729                        self.write(", ");
9730                    }
9731                    self.write(&elem.expression);
9732                    self.write_space();
9733                    self.write_keyword("WITH");
9734                    self.write_space();
9735                    self.write(&elem.operator);
9736                }
9737                self.write(")");
9738                if !include_columns.is_empty() {
9739                    self.write_space();
9740                    self.write_keyword("INCLUDE");
9741                    self.write(" (");
9742                    for (i, col) in include_columns.iter().enumerate() {
9743                        if i > 0 {
9744                            self.write(", ");
9745                        }
9746                        self.generate_identifier(col)?;
9747                    }
9748                    self.write(")");
9749                }
9750                if !with_params.is_empty() {
9751                    self.write_space();
9752                    self.write_keyword("WITH");
9753                    self.write(" (");
9754                    for (i, (key, val)) in with_params.iter().enumerate() {
9755                        if i > 0 {
9756                            self.write(", ");
9757                        }
9758                        self.write(key);
9759                        self.write("=");
9760                        self.write(val);
9761                    }
9762                    self.write(")");
9763                }
9764                if let Some(ref tablespace) = using_index_tablespace {
9765                    self.write_space();
9766                    self.write_keyword("USING INDEX TABLESPACE");
9767                    self.write_space();
9768                    self.write(tablespace);
9769                }
9770                if let Some(ref where_expr) = where_clause {
9771                    self.write_space();
9772                    self.write_keyword("WHERE");
9773                    self.write(" (");
9774                    self.generate_expression(where_expr)?;
9775                    self.write(")");
9776                }
9777            }
9778            TableConstraint::Tags(tags) => {
9779                self.write_keyword("TAG");
9780                self.write(" (");
9781                for (i, expr) in tags.expressions.iter().enumerate() {
9782                    if i > 0 {
9783                        self.write(", ");
9784                    }
9785                    self.generate_expression(expr)?;
9786                }
9787                self.write(")");
9788            }
9789            TableConstraint::InitiallyDeferred { deferred } => {
9790                self.write_keyword("INITIALLY");
9791                self.write_space();
9792                if *deferred {
9793                    self.write_keyword("DEFERRED");
9794                } else {
9795                    self.write_keyword("IMMEDIATE");
9796                }
9797            }
9798        }
9799        Ok(())
9800    }
9801
9802    fn generate_constraint_modifiers(&mut self, modifiers: &ConstraintModifiers) {
9803        // Output USING BTREE/HASH (MySQL) - comes first
9804        if let Some(using) = &modifiers.using {
9805            self.write_space();
9806            self.write_keyword("USING");
9807            self.write_space();
9808            self.write_keyword(using);
9809        }
9810        // Output ENFORCED/NOT ENFORCED
9811        if let Some(enforced) = modifiers.enforced {
9812            self.write_space();
9813            if enforced {
9814                self.write_keyword("ENFORCED");
9815            } else {
9816                self.write_keyword("NOT ENFORCED");
9817            }
9818        }
9819        // Output DEFERRABLE/NOT DEFERRABLE
9820        if let Some(deferrable) = modifiers.deferrable {
9821            self.write_space();
9822            if deferrable {
9823                self.write_keyword("DEFERRABLE");
9824            } else {
9825                self.write_keyword("NOT DEFERRABLE");
9826            }
9827        }
9828        // Output INITIALLY DEFERRED/INITIALLY IMMEDIATE
9829        if let Some(initially_deferred) = modifiers.initially_deferred {
9830            self.write_space();
9831            if initially_deferred {
9832                self.write_keyword("INITIALLY DEFERRED");
9833            } else {
9834                self.write_keyword("INITIALLY IMMEDIATE");
9835            }
9836        }
9837        // Output NORELY
9838        if modifiers.norely {
9839            self.write_space();
9840            self.write_keyword("NORELY");
9841        }
9842        // Output RELY
9843        if modifiers.rely {
9844            self.write_space();
9845            self.write_keyword("RELY");
9846        }
9847        // Output NOT VALID (PostgreSQL)
9848        if modifiers.not_valid {
9849            self.write_space();
9850            self.write_keyword("NOT VALID");
9851        }
9852        // Output ON CONFLICT (SQLite)
9853        if let Some(on_conflict) = &modifiers.on_conflict {
9854            self.write_space();
9855            self.write_keyword("ON CONFLICT");
9856            self.write_space();
9857            self.write_keyword(on_conflict);
9858        }
9859        // Output TSQL WITH options (PAD_INDEX=ON, STATISTICS_NORECOMPUTE=OFF, ...)
9860        if !modifiers.with_options.is_empty() {
9861            self.write_space();
9862            self.write_keyword("WITH");
9863            self.write(" (");
9864            for (i, (key, value)) in modifiers.with_options.iter().enumerate() {
9865                if i > 0 {
9866                    self.write(", ");
9867                }
9868                self.write(key);
9869                self.write("=");
9870                self.write(value);
9871            }
9872            self.write(")");
9873        }
9874        // Output TSQL ON filegroup
9875        if let Some(ref fg) = modifiers.on_filegroup {
9876            self.write_space();
9877            self.write_keyword("ON");
9878            self.write_space();
9879            let _ = self.generate_identifier(fg);
9880        }
9881    }
9882
9883    /// Generate constraint modifiers without USING (for Index constraints where USING is handled separately)
9884    fn generate_constraint_modifiers_without_using(&mut self, modifiers: &ConstraintModifiers) {
9885        // Output ENFORCED/NOT ENFORCED
9886        if let Some(enforced) = modifiers.enforced {
9887            self.write_space();
9888            if enforced {
9889                self.write_keyword("ENFORCED");
9890            } else {
9891                self.write_keyword("NOT ENFORCED");
9892            }
9893        }
9894        // Output DEFERRABLE/NOT DEFERRABLE
9895        if let Some(deferrable) = modifiers.deferrable {
9896            self.write_space();
9897            if deferrable {
9898                self.write_keyword("DEFERRABLE");
9899            } else {
9900                self.write_keyword("NOT DEFERRABLE");
9901            }
9902        }
9903        // Output INITIALLY DEFERRED/INITIALLY IMMEDIATE
9904        if let Some(initially_deferred) = modifiers.initially_deferred {
9905            self.write_space();
9906            if initially_deferred {
9907                self.write_keyword("INITIALLY DEFERRED");
9908            } else {
9909                self.write_keyword("INITIALLY IMMEDIATE");
9910            }
9911        }
9912        // Output NORELY
9913        if modifiers.norely {
9914            self.write_space();
9915            self.write_keyword("NORELY");
9916        }
9917        // Output RELY
9918        if modifiers.rely {
9919            self.write_space();
9920            self.write_keyword("RELY");
9921        }
9922        // Output NOT VALID (PostgreSQL)
9923        if modifiers.not_valid {
9924            self.write_space();
9925            self.write_keyword("NOT VALID");
9926        }
9927        // Output ON CONFLICT (SQLite)
9928        if let Some(on_conflict) = &modifiers.on_conflict {
9929            self.write_space();
9930            self.write_keyword("ON CONFLICT");
9931            self.write_space();
9932            self.write_keyword(on_conflict);
9933        }
9934        // Output MySQL index-specific modifiers
9935        self.generate_index_specific_modifiers(modifiers);
9936    }
9937
9938    /// Generate MySQL index-specific modifiers (COMMENT, VISIBLE, ENGINE_ATTRIBUTE, WITH PARSER)
9939    fn generate_index_specific_modifiers(&mut self, modifiers: &ConstraintModifiers) {
9940        if let Some(ref comment) = modifiers.comment {
9941            self.write_space();
9942            self.write_keyword("COMMENT");
9943            self.write(" '");
9944            self.write(comment);
9945            self.write("'");
9946        }
9947        if let Some(visible) = modifiers.visible {
9948            self.write_space();
9949            if visible {
9950                self.write_keyword("VISIBLE");
9951            } else {
9952                self.write_keyword("INVISIBLE");
9953            }
9954        }
9955        if let Some(ref attr) = modifiers.engine_attribute {
9956            self.write_space();
9957            self.write_keyword("ENGINE_ATTRIBUTE");
9958            self.write(" = '");
9959            self.write(attr);
9960            self.write("'");
9961        }
9962        if let Some(ref parser) = modifiers.with_parser {
9963            self.write_space();
9964            self.write_keyword("WITH PARSER");
9965            self.write_space();
9966            self.write(parser);
9967        }
9968    }
9969
9970    fn generate_referential_actions(&mut self, fk_ref: &ForeignKeyRef) -> Result<()> {
9971        // MATCH clause before ON DELETE/ON UPDATE (default position, e.g. PostgreSQL)
9972        if !fk_ref.match_after_actions {
9973            if let Some(ref match_type) = fk_ref.match_type {
9974                self.write_space();
9975                self.write_keyword("MATCH");
9976                self.write_space();
9977                match match_type {
9978                    MatchType::Full => self.write_keyword("FULL"),
9979                    MatchType::Partial => self.write_keyword("PARTIAL"),
9980                    MatchType::Simple => self.write_keyword("SIMPLE"),
9981                }
9982            }
9983        }
9984
9985        // Output ON UPDATE and ON DELETE in the original order
9986        if fk_ref.on_update_first {
9987            if let Some(ref action) = fk_ref.on_update {
9988                self.write_space();
9989                self.write_keyword("ON UPDATE");
9990                self.write_space();
9991                self.generate_referential_action(action);
9992            }
9993            if let Some(ref action) = fk_ref.on_delete {
9994                self.write_space();
9995                self.write_keyword("ON DELETE");
9996                self.write_space();
9997                self.generate_referential_action(action);
9998            }
9999        } else {
10000            if let Some(ref action) = fk_ref.on_delete {
10001                self.write_space();
10002                self.write_keyword("ON DELETE");
10003                self.write_space();
10004                self.generate_referential_action(action);
10005            }
10006            if let Some(ref action) = fk_ref.on_update {
10007                self.write_space();
10008                self.write_keyword("ON UPDATE");
10009                self.write_space();
10010                self.generate_referential_action(action);
10011            }
10012        }
10013
10014        // MATCH clause after ON DELETE/ON UPDATE (when original SQL had it after)
10015        if fk_ref.match_after_actions {
10016            if let Some(ref match_type) = fk_ref.match_type {
10017                self.write_space();
10018                self.write_keyword("MATCH");
10019                self.write_space();
10020                match match_type {
10021                    MatchType::Full => self.write_keyword("FULL"),
10022                    MatchType::Partial => self.write_keyword("PARTIAL"),
10023                    MatchType::Simple => self.write_keyword("SIMPLE"),
10024                }
10025            }
10026        }
10027
10028        // DEFERRABLE / NOT DEFERRABLE
10029        if let Some(deferrable) = fk_ref.deferrable {
10030            self.write_space();
10031            if deferrable {
10032                self.write_keyword("DEFERRABLE");
10033            } else {
10034                self.write_keyword("NOT DEFERRABLE");
10035            }
10036        }
10037
10038        Ok(())
10039    }
10040
10041    fn generate_referential_action(&mut self, action: &ReferentialAction) {
10042        match action {
10043            ReferentialAction::Cascade => self.write_keyword("CASCADE"),
10044            ReferentialAction::SetNull => self.write_keyword("SET NULL"),
10045            ReferentialAction::SetDefault => self.write_keyword("SET DEFAULT"),
10046            ReferentialAction::Restrict => self.write_keyword("RESTRICT"),
10047            ReferentialAction::NoAction => self.write_keyword("NO ACTION"),
10048        }
10049    }
10050
10051    fn generate_drop_table(&mut self, dt: &DropTable) -> Result<()> {
10052        // TSQL: IF NOT OBJECT_ID(...) IS NULL BEGIN DROP TABLE ...; END
10053        if let Some(ref object_id_args) = dt.object_id_args {
10054            if matches!(
10055                self.config.dialect,
10056                Some(crate::dialects::DialectType::TSQL)
10057                    | Some(crate::dialects::DialectType::Fabric)
10058            ) {
10059                self.write_keyword("IF NOT OBJECT_ID");
10060                self.write("(");
10061                self.write(object_id_args);
10062                self.write(")");
10063                self.write_space();
10064                self.write_keyword("IS NULL BEGIN DROP TABLE");
10065                self.write_space();
10066                for (i, table) in dt.names.iter().enumerate() {
10067                    if i > 0 {
10068                        self.write(", ");
10069                    }
10070                    self.generate_table(table)?;
10071                }
10072                self.write("; ");
10073                self.write_keyword("END");
10074                return Ok(());
10075            }
10076        }
10077
10078        // Athena: DROP TABLE uses Hive engine (backticks)
10079        let saved_athena_hive_context = self.athena_hive_context;
10080        if matches!(
10081            self.config.dialect,
10082            Some(crate::dialects::DialectType::Athena)
10083        ) {
10084            self.athena_hive_context = true;
10085        }
10086
10087        // Output leading comments (e.g., "-- comment\nDROP TABLE ...")
10088        for comment in &dt.leading_comments {
10089            self.write_formatted_comment(comment);
10090            self.write_space();
10091        }
10092        if dt.iceberg {
10093            self.write_keyword("DROP ICEBERG TABLE");
10094        } else {
10095            self.write_keyword("DROP TABLE");
10096        }
10097
10098        if dt.if_exists {
10099            self.write_space();
10100            self.write_keyword("IF EXISTS");
10101        }
10102
10103        self.write_space();
10104        for (i, table) in dt.names.iter().enumerate() {
10105            if i > 0 {
10106                self.write(", ");
10107            }
10108            self.generate_table(table)?;
10109        }
10110
10111        if dt.cascade_constraints {
10112            self.write_space();
10113            self.write_keyword("CASCADE CONSTRAINTS");
10114        } else if dt.cascade {
10115            self.write_space();
10116            self.write_keyword("CASCADE");
10117        }
10118
10119        if dt.restrict {
10120            self.write_space();
10121            self.write_keyword("RESTRICT");
10122        }
10123
10124        if dt.purge {
10125            self.write_space();
10126            self.write_keyword("PURGE");
10127        }
10128
10129        if dt.sync {
10130            self.write_space();
10131            self.write_keyword("SYNC");
10132        }
10133
10134        // Restore Athena Hive context
10135        self.athena_hive_context = saved_athena_hive_context;
10136
10137        Ok(())
10138    }
10139
10140    fn generate_undrop(&mut self, u: &Undrop) -> Result<()> {
10141        self.write_keyword("UNDROP");
10142        self.write_space();
10143        self.write_keyword(&u.kind);
10144        if u.if_exists {
10145            self.write_space();
10146            self.write_keyword("IF EXISTS");
10147        }
10148        self.write_space();
10149        self.generate_table(&u.name)?;
10150        Ok(())
10151    }
10152
10153    fn generate_alter_table(&mut self, at: &AlterTable) -> Result<()> {
10154        // Athena: ALTER TABLE uses Hive engine (backticks)
10155        let saved_athena_hive_context = self.athena_hive_context;
10156        if matches!(
10157            self.config.dialect,
10158            Some(crate::dialects::DialectType::Athena)
10159        ) {
10160            self.athena_hive_context = true;
10161        }
10162
10163        self.write_keyword("ALTER");
10164        // Write table modifier (e.g., ICEBERG) unless target is DuckDB
10165        if let Some(ref modifier) = at.table_modifier {
10166            if !matches!(
10167                self.config.dialect,
10168                Some(crate::dialects::DialectType::DuckDB)
10169            ) {
10170                self.write_space();
10171                self.write_keyword(modifier);
10172            }
10173        }
10174        self.write(" ");
10175        self.write_keyword("TABLE");
10176        if at.if_exists {
10177            self.write_space();
10178            self.write_keyword("IF EXISTS");
10179        }
10180        self.write_space();
10181        self.generate_table(&at.name)?;
10182
10183        // ClickHouse: ON CLUSTER clause
10184        if let Some(ref on_cluster) = at.on_cluster {
10185            self.write_space();
10186            self.generate_on_cluster(on_cluster)?;
10187        }
10188
10189        // Hive: PARTITION(key=value, ...) clause
10190        if let Some(ref partition) = at.partition {
10191            self.write_space();
10192            self.write_keyword("PARTITION");
10193            self.write("(");
10194            for (i, (key, value)) in partition.iter().enumerate() {
10195                if i > 0 {
10196                    self.write(", ");
10197                }
10198                self.generate_identifier(key)?;
10199                self.write(" = ");
10200                self.generate_expression(value)?;
10201            }
10202            self.write(")");
10203        }
10204
10205        // TSQL: WITH CHECK / WITH NOCHECK modifier
10206        if let Some(ref with_check) = at.with_check {
10207            self.write_space();
10208            self.write_keyword(with_check);
10209        }
10210
10211        if self.config.pretty {
10212            // In pretty mode, format actions with newlines and indentation
10213            self.write_newline();
10214            self.indent_level += 1;
10215            for (i, action) in at.actions.iter().enumerate() {
10216                // Check if this is a continuation of previous ADD COLUMN or ADD CONSTRAINT
10217                let is_continuation = i > 0
10218                    && matches!(
10219                        (&at.actions[i - 1], action),
10220                        (
10221                            AlterTableAction::AddColumn { .. },
10222                            AlterTableAction::AddColumn { .. }
10223                        ) | (
10224                            AlterTableAction::AddConstraint(_),
10225                            AlterTableAction::AddConstraint(_)
10226                        )
10227                    );
10228                if i > 0 {
10229                    self.write(",");
10230                    self.write_newline();
10231                }
10232                self.write_indent();
10233                self.generate_alter_action_with_continuation(action, is_continuation)?;
10234            }
10235            self.indent_level -= 1;
10236        } else {
10237            for (i, action) in at.actions.iter().enumerate() {
10238                // Check if this is a continuation of previous ADD COLUMN or ADD CONSTRAINT
10239                let is_continuation = i > 0
10240                    && matches!(
10241                        (&at.actions[i - 1], action),
10242                        (
10243                            AlterTableAction::AddColumn { .. },
10244                            AlterTableAction::AddColumn { .. }
10245                        ) | (
10246                            AlterTableAction::AddConstraint(_),
10247                            AlterTableAction::AddConstraint(_)
10248                        )
10249                    );
10250                if i > 0 {
10251                    self.write(",");
10252                }
10253                self.write_space();
10254                self.generate_alter_action_with_continuation(action, is_continuation)?;
10255            }
10256        }
10257
10258        // MySQL ALTER TABLE trailing options
10259        if let Some(ref algorithm) = at.algorithm {
10260            self.write(", ");
10261            self.write_keyword("ALGORITHM");
10262            self.write("=");
10263            self.write_keyword(algorithm);
10264        }
10265        if let Some(ref lock) = at.lock {
10266            self.write(", ");
10267            self.write_keyword("LOCK");
10268            self.write("=");
10269            self.write_keyword(lock);
10270        }
10271
10272        // Restore Athena Hive context
10273        self.athena_hive_context = saved_athena_hive_context;
10274
10275        Ok(())
10276    }
10277
10278    fn generate_alter_action_with_continuation(
10279        &mut self,
10280        action: &AlterTableAction,
10281        is_continuation: bool,
10282    ) -> Result<()> {
10283        match action {
10284            AlterTableAction::AddColumn {
10285                column,
10286                if_not_exists,
10287                position,
10288            } => {
10289                use crate::dialects::DialectType;
10290                // For Snowflake: consecutive ADD COLUMN actions are combined with commas
10291                // e.g., "ADD col1, col2" instead of "ADD col1, ADD col2"
10292                // For other dialects, repeat ADD COLUMN for each
10293                let is_snowflake = matches!(self.config.dialect, Some(DialectType::Snowflake));
10294                let is_tsql_like = matches!(
10295                    self.config.dialect,
10296                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
10297                );
10298                // Athena uses "ADD COLUMNS (col_def)" instead of "ADD COLUMN col_def"
10299                let is_athena = matches!(self.config.dialect, Some(DialectType::Athena));
10300
10301                if is_continuation && (is_snowflake || is_tsql_like) {
10302                    // Don't write ADD keyword for continuation in Snowflake/TSQL
10303                } else if is_snowflake {
10304                    self.write_keyword("ADD");
10305                    self.write_space();
10306                } else if is_athena {
10307                    // Athena uses ADD COLUMNS (col_def) syntax
10308                    self.write_keyword("ADD COLUMNS");
10309                    self.write(" (");
10310                } else if self.config.alter_table_include_column_keyword {
10311                    self.write_keyword("ADD COLUMN");
10312                    self.write_space();
10313                } else {
10314                    // Dialects like Oracle and TSQL don't use COLUMN keyword
10315                    self.write_keyword("ADD");
10316                    self.write_space();
10317                }
10318
10319                if *if_not_exists {
10320                    self.write_keyword("IF NOT EXISTS");
10321                    self.write_space();
10322                }
10323                self.generate_column_def(column)?;
10324
10325                // Close parenthesis for Athena
10326                if is_athena {
10327                    self.write(")");
10328                }
10329
10330                // Column position (FIRST or AFTER)
10331                if let Some(pos) = position {
10332                    self.write_space();
10333                    match pos {
10334                        ColumnPosition::First => self.write_keyword("FIRST"),
10335                        ColumnPosition::After(col_name) => {
10336                            self.write_keyword("AFTER");
10337                            self.write_space();
10338                            self.generate_identifier(col_name)?;
10339                        }
10340                    }
10341                }
10342            }
10343            AlterTableAction::DropColumn {
10344                name,
10345                if_exists,
10346                cascade,
10347            } => {
10348                self.write_keyword("DROP COLUMN");
10349                if *if_exists {
10350                    self.write_space();
10351                    self.write_keyword("IF EXISTS");
10352                }
10353                self.write_space();
10354                self.generate_identifier(name)?;
10355                if *cascade {
10356                    self.write_space();
10357                    self.write_keyword("CASCADE");
10358                }
10359            }
10360            AlterTableAction::DropColumns { names } => {
10361                self.write_keyword("DROP COLUMNS");
10362                self.write(" (");
10363                for (i, name) in names.iter().enumerate() {
10364                    if i > 0 {
10365                        self.write(", ");
10366                    }
10367                    self.generate_identifier(name)?;
10368                }
10369                self.write(")");
10370            }
10371            AlterTableAction::RenameColumn {
10372                old_name,
10373                new_name,
10374                if_exists,
10375            } => {
10376                self.write_keyword("RENAME COLUMN");
10377                if *if_exists {
10378                    self.write_space();
10379                    self.write_keyword("IF EXISTS");
10380                }
10381                self.write_space();
10382                self.generate_identifier(old_name)?;
10383                self.write_space();
10384                self.write_keyword("TO");
10385                self.write_space();
10386                self.generate_identifier(new_name)?;
10387            }
10388            AlterTableAction::AlterColumn {
10389                name,
10390                action,
10391                use_modify_keyword,
10392            } => {
10393                use crate::dialects::DialectType;
10394                // MySQL uses MODIFY COLUMN for type changes (SetDataType)
10395                // but ALTER COLUMN for SET DEFAULT, DROP DEFAULT, etc.
10396                let use_modify = *use_modify_keyword
10397                    || (matches!(self.config.dialect, Some(DialectType::MySQL))
10398                        && matches!(action, AlterColumnAction::SetDataType { .. }));
10399                if use_modify {
10400                    self.write_keyword("MODIFY COLUMN");
10401                    self.write_space();
10402                    self.generate_identifier(name)?;
10403                    // For MODIFY COLUMN, output the type directly
10404                    if let AlterColumnAction::SetDataType {
10405                        data_type,
10406                        using: _,
10407                        collate,
10408                    } = action
10409                    {
10410                        self.write_space();
10411                        self.generate_data_type(data_type)?;
10412                        // Output COLLATE clause if present
10413                        if let Some(collate_name) = collate {
10414                            self.write_space();
10415                            self.write_keyword("COLLATE");
10416                            self.write_space();
10417                            // Output as single-quoted string
10418                            self.write(&format!("'{}'", collate_name));
10419                        }
10420                    } else {
10421                        self.write_space();
10422                        self.generate_alter_column_action(action)?;
10423                    }
10424                } else if matches!(self.config.dialect, Some(DialectType::Hive))
10425                    && matches!(action, AlterColumnAction::SetDataType { .. })
10426                {
10427                    // Hive uses CHANGE COLUMN col_name col_name NEW_TYPE
10428                    self.write_keyword("CHANGE COLUMN");
10429                    self.write_space();
10430                    self.generate_identifier(name)?;
10431                    self.write_space();
10432                    self.generate_identifier(name)?;
10433                    if let AlterColumnAction::SetDataType { data_type, .. } = action {
10434                        self.write_space();
10435                        self.generate_data_type(data_type)?;
10436                    }
10437                } else {
10438                    self.write_keyword("ALTER COLUMN");
10439                    self.write_space();
10440                    self.generate_identifier(name)?;
10441                    self.write_space();
10442                    self.generate_alter_column_action(action)?;
10443                }
10444            }
10445            AlterTableAction::RenameTable(new_name) => {
10446                // MySQL-like dialects (MySQL, Doris, StarRocks) use RENAME without TO
10447                let mysql_like = matches!(
10448                    self.config.dialect,
10449                    Some(DialectType::MySQL)
10450                        | Some(DialectType::Doris)
10451                        | Some(DialectType::StarRocks)
10452                        | Some(DialectType::SingleStore)
10453                );
10454                if mysql_like {
10455                    self.write_keyword("RENAME");
10456                } else {
10457                    self.write_keyword("RENAME TO");
10458                }
10459                self.write_space();
10460                // Doris, DuckDB, BigQuery, PostgreSQL strip schema/catalog from target table
10461                let rename_table_with_db = !matches!(
10462                    self.config.dialect,
10463                    Some(DialectType::Doris)
10464                        | Some(DialectType::DuckDB)
10465                        | Some(DialectType::BigQuery)
10466                        | Some(DialectType::PostgreSQL)
10467                );
10468                if !rename_table_with_db {
10469                    let mut stripped = new_name.clone();
10470                    stripped.schema = None;
10471                    stripped.catalog = None;
10472                    self.generate_table(&stripped)?;
10473                } else {
10474                    self.generate_table(new_name)?;
10475                }
10476            }
10477            AlterTableAction::AddConstraint(constraint) => {
10478                // For consecutive ADD CONSTRAINT actions (is_continuation=true), skip ADD keyword
10479                // to produce: ADD CONSTRAINT c1 ..., CONSTRAINT c2 ...
10480                if !is_continuation {
10481                    self.write_keyword("ADD");
10482                    self.write_space();
10483                }
10484                self.generate_table_constraint(constraint)?;
10485            }
10486            AlterTableAction::DropConstraint { name, if_exists } => {
10487                self.write_keyword("DROP CONSTRAINT");
10488                if *if_exists {
10489                    self.write_space();
10490                    self.write_keyword("IF EXISTS");
10491                }
10492                self.write_space();
10493                self.generate_identifier(name)?;
10494            }
10495            AlterTableAction::DropForeignKey { name } => {
10496                self.write_keyword("DROP FOREIGN KEY");
10497                self.write_space();
10498                self.generate_identifier(name)?;
10499            }
10500            AlterTableAction::DropPartition {
10501                partitions,
10502                if_exists,
10503            } => {
10504                self.write_keyword("DROP");
10505                if *if_exists {
10506                    self.write_space();
10507                    self.write_keyword("IF EXISTS");
10508                }
10509                for (i, partition) in partitions.iter().enumerate() {
10510                    if i > 0 {
10511                        self.write(",");
10512                    }
10513                    self.write_space();
10514                    self.write_keyword("PARTITION");
10515                    // Check for special ClickHouse partition formats
10516                    if partition.len() == 1 && partition[0].0.name == "__expr__" {
10517                        // ClickHouse: PARTITION <expression>
10518                        self.write_space();
10519                        self.generate_expression(&partition[0].1)?;
10520                    } else if partition.len() == 1 && partition[0].0.name == "ALL" {
10521                        // ClickHouse: PARTITION ALL
10522                        self.write_space();
10523                        self.write_keyword("ALL");
10524                    } else if partition.len() == 1 && partition[0].0.name == "ID" {
10525                        // ClickHouse: PARTITION ID 'string'
10526                        self.write_space();
10527                        self.write_keyword("ID");
10528                        self.write_space();
10529                        self.generate_expression(&partition[0].1)?;
10530                    } else {
10531                        // Standard SQL: PARTITION(key=value, ...)
10532                        self.write("(");
10533                        for (j, (key, value)) in partition.iter().enumerate() {
10534                            if j > 0 {
10535                                self.write(", ");
10536                            }
10537                            self.generate_identifier(key)?;
10538                            self.write(" = ");
10539                            self.generate_expression(value)?;
10540                        }
10541                        self.write(")");
10542                    }
10543                }
10544            }
10545            AlterTableAction::Delete { where_clause } => {
10546                self.write_keyword("DELETE");
10547                self.write_space();
10548                self.write_keyword("WHERE");
10549                self.write_space();
10550                self.generate_expression(where_clause)?;
10551            }
10552            AlterTableAction::SwapWith(target) => {
10553                self.write_keyword("SWAP WITH");
10554                self.write_space();
10555                self.generate_table(target)?;
10556            }
10557            AlterTableAction::SetProperty { properties } => {
10558                use crate::dialects::DialectType;
10559                self.write_keyword("SET");
10560                // Trino/Presto use SET PROPERTIES syntax with spaces around =
10561                let is_trino_presto = matches!(
10562                    self.config.dialect,
10563                    Some(DialectType::Trino) | Some(DialectType::Presto)
10564                );
10565                if is_trino_presto {
10566                    self.write_space();
10567                    self.write_keyword("PROPERTIES");
10568                }
10569                let eq = if is_trino_presto { " = " } else { "=" };
10570                for (i, (key, value)) in properties.iter().enumerate() {
10571                    if i > 0 {
10572                        self.write(",");
10573                    }
10574                    self.write_space();
10575                    // Handle quoted property names for Trino
10576                    if key.contains(' ') {
10577                        self.generate_string_literal(key)?;
10578                    } else {
10579                        self.write(key);
10580                    }
10581                    self.write(eq);
10582                    self.generate_expression(value)?;
10583                }
10584            }
10585            AlterTableAction::UnsetProperty { properties } => {
10586                self.write_keyword("UNSET");
10587                for (i, name) in properties.iter().enumerate() {
10588                    if i > 0 {
10589                        self.write(",");
10590                    }
10591                    self.write_space();
10592                    self.write(name);
10593                }
10594            }
10595            AlterTableAction::ClusterBy { expressions } => {
10596                self.write_keyword("CLUSTER BY");
10597                self.write(" (");
10598                for (i, expr) in expressions.iter().enumerate() {
10599                    if i > 0 {
10600                        self.write(", ");
10601                    }
10602                    self.generate_expression(expr)?;
10603                }
10604                self.write(")");
10605            }
10606            AlterTableAction::SetTag { expressions } => {
10607                self.write_keyword("SET TAG");
10608                for (i, (key, value)) in expressions.iter().enumerate() {
10609                    if i > 0 {
10610                        self.write(",");
10611                    }
10612                    self.write_space();
10613                    self.write(key);
10614                    self.write(" = ");
10615                    self.generate_expression(value)?;
10616                }
10617            }
10618            AlterTableAction::UnsetTag { names } => {
10619                self.write_keyword("UNSET TAG");
10620                for (i, name) in names.iter().enumerate() {
10621                    if i > 0 {
10622                        self.write(",");
10623                    }
10624                    self.write_space();
10625                    self.write(name);
10626                }
10627            }
10628            AlterTableAction::SetOptions { expressions } => {
10629                self.write_keyword("SET");
10630                self.write(" (");
10631                for (i, expr) in expressions.iter().enumerate() {
10632                    if i > 0 {
10633                        self.write(", ");
10634                    }
10635                    self.generate_expression(expr)?;
10636                }
10637                self.write(")");
10638            }
10639            AlterTableAction::AlterIndex { name, visible } => {
10640                self.write_keyword("ALTER INDEX");
10641                self.write_space();
10642                self.generate_identifier(name)?;
10643                self.write_space();
10644                if *visible {
10645                    self.write_keyword("VISIBLE");
10646                } else {
10647                    self.write_keyword("INVISIBLE");
10648                }
10649            }
10650            AlterTableAction::SetAttribute { attribute } => {
10651                self.write_keyword("SET");
10652                self.write_space();
10653                self.write_keyword(attribute);
10654            }
10655            AlterTableAction::SetStageFileFormat { options } => {
10656                self.write_keyword("SET");
10657                self.write_space();
10658                self.write_keyword("STAGE_FILE_FORMAT");
10659                self.write(" = (");
10660                if let Some(opts) = options {
10661                    self.generate_space_separated_properties(opts)?;
10662                }
10663                self.write(")");
10664            }
10665            AlterTableAction::SetStageCopyOptions { options } => {
10666                self.write_keyword("SET");
10667                self.write_space();
10668                self.write_keyword("STAGE_COPY_OPTIONS");
10669                self.write(" = (");
10670                if let Some(opts) = options {
10671                    self.generate_space_separated_properties(opts)?;
10672                }
10673                self.write(")");
10674            }
10675            AlterTableAction::AddColumns { columns, cascade } => {
10676                // Oracle uses ADD (...) without COLUMNS keyword
10677                // Hive/Spark uses ADD COLUMNS (...)
10678                let is_oracle = matches!(self.config.dialect, Some(DialectType::Oracle));
10679                if is_oracle {
10680                    self.write_keyword("ADD");
10681                } else {
10682                    self.write_keyword("ADD COLUMNS");
10683                }
10684                self.write(" (");
10685                for (i, col) in columns.iter().enumerate() {
10686                    if i > 0 {
10687                        self.write(", ");
10688                    }
10689                    self.generate_column_def(col)?;
10690                }
10691                self.write(")");
10692                if *cascade {
10693                    self.write_space();
10694                    self.write_keyword("CASCADE");
10695                }
10696            }
10697            AlterTableAction::ChangeColumn {
10698                old_name,
10699                new_name,
10700                data_type,
10701                comment,
10702                cascade,
10703            } => {
10704                use crate::dialects::DialectType;
10705                let is_spark = matches!(
10706                    self.config.dialect,
10707                    Some(DialectType::Spark) | Some(DialectType::Databricks)
10708                );
10709                let is_rename = old_name.name != new_name.name;
10710
10711                if is_spark {
10712                    if is_rename {
10713                        // Spark: RENAME COLUMN old TO new
10714                        self.write_keyword("RENAME COLUMN");
10715                        self.write_space();
10716                        self.generate_identifier(old_name)?;
10717                        self.write_space();
10718                        self.write_keyword("TO");
10719                        self.write_space();
10720                        self.generate_identifier(new_name)?;
10721                    } else if comment.is_some() {
10722                        // Spark: ALTER COLUMN old COMMENT 'comment'
10723                        self.write_keyword("ALTER COLUMN");
10724                        self.write_space();
10725                        self.generate_identifier(old_name)?;
10726                        self.write_space();
10727                        self.write_keyword("COMMENT");
10728                        self.write_space();
10729                        self.write("'");
10730                        self.write(comment.as_ref().unwrap());
10731                        self.write("'");
10732                    } else if data_type.is_some() {
10733                        // Spark: ALTER COLUMN old TYPE data_type
10734                        self.write_keyword("ALTER COLUMN");
10735                        self.write_space();
10736                        self.generate_identifier(old_name)?;
10737                        self.write_space();
10738                        self.write_keyword("TYPE");
10739                        self.write_space();
10740                        self.generate_data_type(data_type.as_ref().unwrap())?;
10741                    } else {
10742                        // Fallback to CHANGE COLUMN
10743                        self.write_keyword("CHANGE COLUMN");
10744                        self.write_space();
10745                        self.generate_identifier(old_name)?;
10746                        self.write_space();
10747                        self.generate_identifier(new_name)?;
10748                    }
10749                } else {
10750                    // Hive/MySQL/default: CHANGE [COLUMN] old new [type] [COMMENT '...'] [CASCADE]
10751                    if data_type.is_some() {
10752                        self.write_keyword("CHANGE COLUMN");
10753                    } else {
10754                        self.write_keyword("CHANGE");
10755                    }
10756                    self.write_space();
10757                    self.generate_identifier(old_name)?;
10758                    self.write_space();
10759                    self.generate_identifier(new_name)?;
10760                    if let Some(ref dt) = data_type {
10761                        self.write_space();
10762                        self.generate_data_type(dt)?;
10763                    }
10764                    if let Some(ref c) = comment {
10765                        self.write_space();
10766                        self.write_keyword("COMMENT");
10767                        self.write_space();
10768                        self.write("'");
10769                        self.write(c);
10770                        self.write("'");
10771                    }
10772                    if *cascade {
10773                        self.write_space();
10774                        self.write_keyword("CASCADE");
10775                    }
10776                }
10777            }
10778            AlterTableAction::AddPartition {
10779                partition,
10780                if_not_exists,
10781                location,
10782            } => {
10783                self.write_keyword("ADD");
10784                self.write_space();
10785                if *if_not_exists {
10786                    self.write_keyword("IF NOT EXISTS");
10787                    self.write_space();
10788                }
10789                self.generate_expression(partition)?;
10790                if let Some(ref loc) = location {
10791                    self.write_space();
10792                    self.write_keyword("LOCATION");
10793                    self.write_space();
10794                    self.generate_expression(loc)?;
10795                }
10796            }
10797            AlterTableAction::AlterSortKey {
10798                this,
10799                expressions,
10800                compound,
10801            } => {
10802                // Redshift: ALTER [COMPOUND] SORTKEY AUTO|NONE|(col1, col2)
10803                self.write_keyword("ALTER");
10804                if *compound {
10805                    self.write_space();
10806                    self.write_keyword("COMPOUND");
10807                }
10808                self.write_space();
10809                self.write_keyword("SORTKEY");
10810                self.write_space();
10811                if let Some(style) = this {
10812                    self.write_keyword(style);
10813                } else if !expressions.is_empty() {
10814                    self.write("(");
10815                    for (i, expr) in expressions.iter().enumerate() {
10816                        if i > 0 {
10817                            self.write(", ");
10818                        }
10819                        self.generate_expression(expr)?;
10820                    }
10821                    self.write(")");
10822                }
10823            }
10824            AlterTableAction::AlterDistStyle { style, distkey } => {
10825                // Redshift: ALTER DISTSTYLE ALL|EVEN|AUTO|KEY [DISTKEY col]
10826                self.write_keyword("ALTER");
10827                self.write_space();
10828                self.write_keyword("DISTSTYLE");
10829                self.write_space();
10830                self.write_keyword(style);
10831                if let Some(col) = distkey {
10832                    self.write_space();
10833                    self.write_keyword("DISTKEY");
10834                    self.write_space();
10835                    self.generate_identifier(col)?;
10836                }
10837            }
10838            AlterTableAction::SetTableProperties { properties } => {
10839                // Redshift: SET TABLE PROPERTIES ('a' = '5', 'b' = 'c')
10840                self.write_keyword("SET TABLE PROPERTIES");
10841                self.write(" (");
10842                for (i, (key, value)) in properties.iter().enumerate() {
10843                    if i > 0 {
10844                        self.write(", ");
10845                    }
10846                    self.generate_expression(key)?;
10847                    self.write(" = ");
10848                    self.generate_expression(value)?;
10849                }
10850                self.write(")");
10851            }
10852            AlterTableAction::SetLocation { location } => {
10853                // Redshift: SET LOCATION 's3://bucket/folder/'
10854                self.write_keyword("SET LOCATION");
10855                self.write_space();
10856                self.write("'");
10857                self.write(location);
10858                self.write("'");
10859            }
10860            AlterTableAction::SetFileFormat { format } => {
10861                // Redshift: SET FILE FORMAT AVRO
10862                self.write_keyword("SET FILE FORMAT");
10863                self.write_space();
10864                self.write_keyword(format);
10865            }
10866            AlterTableAction::ReplacePartition { partition, source } => {
10867                // ClickHouse: REPLACE PARTITION expr FROM source
10868                self.write_keyword("REPLACE PARTITION");
10869                self.write_space();
10870                self.generate_expression(partition)?;
10871                if let Some(src) = source {
10872                    self.write_space();
10873                    self.write_keyword("FROM");
10874                    self.write_space();
10875                    self.generate_expression(src)?;
10876                }
10877            }
10878            AlterTableAction::Raw { sql } => {
10879                self.write(sql);
10880            }
10881        }
10882        Ok(())
10883    }
10884
10885    fn generate_alter_column_action(&mut self, action: &AlterColumnAction) -> Result<()> {
10886        match action {
10887            AlterColumnAction::SetDataType {
10888                data_type,
10889                using,
10890                collate,
10891            } => {
10892                use crate::dialects::DialectType;
10893                // Dialect-specific type change syntax:
10894                // - TSQL/Fabric/Hive: no prefix (ALTER COLUMN col datatype)
10895                // - Redshift/Spark: TYPE (ALTER COLUMN col TYPE datatype)
10896                // - Default: SET DATA TYPE (ALTER COLUMN col SET DATA TYPE datatype)
10897                let is_no_prefix = matches!(
10898                    self.config.dialect,
10899                    Some(DialectType::TSQL) | Some(DialectType::Fabric) | Some(DialectType::Hive)
10900                );
10901                let is_type_only = matches!(
10902                    self.config.dialect,
10903                    Some(DialectType::Redshift)
10904                        | Some(DialectType::Spark)
10905                        | Some(DialectType::Databricks)
10906                );
10907                if is_type_only {
10908                    self.write_keyword("TYPE");
10909                    self.write_space();
10910                } else if !is_no_prefix {
10911                    self.write_keyword("SET DATA TYPE");
10912                    self.write_space();
10913                }
10914                self.generate_data_type(data_type)?;
10915                if let Some(ref collation) = collate {
10916                    self.write_space();
10917                    self.write_keyword("COLLATE");
10918                    self.write_space();
10919                    self.write(collation);
10920                }
10921                if let Some(ref using_expr) = using {
10922                    self.write_space();
10923                    self.write_keyword("USING");
10924                    self.write_space();
10925                    self.generate_expression(using_expr)?;
10926                }
10927            }
10928            AlterColumnAction::SetDefault(expr) => {
10929                self.write_keyword("SET DEFAULT");
10930                self.write_space();
10931                self.generate_expression(expr)?;
10932            }
10933            AlterColumnAction::DropDefault => {
10934                self.write_keyword("DROP DEFAULT");
10935            }
10936            AlterColumnAction::SetNotNull => {
10937                self.write_keyword("SET NOT NULL");
10938            }
10939            AlterColumnAction::DropNotNull => {
10940                self.write_keyword("DROP NOT NULL");
10941            }
10942            AlterColumnAction::Comment(comment) => {
10943                self.write_keyword("COMMENT");
10944                self.write_space();
10945                self.generate_string_literal(comment)?;
10946            }
10947            AlterColumnAction::SetVisible => {
10948                self.write_keyword("SET VISIBLE");
10949            }
10950            AlterColumnAction::SetInvisible => {
10951                self.write_keyword("SET INVISIBLE");
10952            }
10953        }
10954        Ok(())
10955    }
10956
10957    fn generate_create_index(&mut self, ci: &CreateIndex) -> Result<()> {
10958        self.write_keyword("CREATE");
10959
10960        if ci.unique {
10961            self.write_space();
10962            self.write_keyword("UNIQUE");
10963        }
10964
10965        // TSQL CLUSTERED/NONCLUSTERED modifier
10966        if let Some(ref clustered) = ci.clustered {
10967            self.write_space();
10968            self.write_keyword(clustered);
10969        }
10970
10971        self.write_space();
10972        self.write_keyword("INDEX");
10973
10974        // PostgreSQL CONCURRENTLY modifier
10975        if ci.concurrently {
10976            self.write_space();
10977            self.write_keyword("CONCURRENTLY");
10978        }
10979
10980        if ci.if_not_exists {
10981            self.write_space();
10982            self.write_keyword("IF NOT EXISTS");
10983        }
10984
10985        // Index name is optional in PostgreSQL when IF NOT EXISTS is specified
10986        if !ci.name.name.is_empty() {
10987            self.write_space();
10988            self.generate_identifier(&ci.name)?;
10989        }
10990        self.write_space();
10991        self.write_keyword("ON");
10992        // Hive uses ON TABLE
10993        if matches!(self.config.dialect, Some(DialectType::Hive)) {
10994            self.write_space();
10995            self.write_keyword("TABLE");
10996        }
10997        self.write_space();
10998        self.generate_table(&ci.table)?;
10999
11000        // Column list (optional for COLUMNSTORE indexes)
11001        // Standard SQL convention: ON t(a) without space before paren
11002        if !ci.columns.is_empty() || ci.using.is_some() {
11003            let space_before_paren = false;
11004
11005            if let Some(ref using) = ci.using {
11006                self.write_space();
11007                self.write_keyword("USING");
11008                self.write_space();
11009                self.write(using);
11010                if space_before_paren {
11011                    self.write(" (");
11012                } else {
11013                    self.write("(");
11014                }
11015            } else {
11016                if space_before_paren {
11017                    self.write(" (");
11018                } else {
11019                    self.write("(");
11020                }
11021            }
11022            for (i, col) in ci.columns.iter().enumerate() {
11023                if i > 0 {
11024                    self.write(", ");
11025                }
11026                self.generate_identifier(&col.column)?;
11027                if let Some(ref opclass) = col.opclass {
11028                    self.write_space();
11029                    self.write(opclass);
11030                }
11031                if col.desc {
11032                    self.write_space();
11033                    self.write_keyword("DESC");
11034                } else if col.asc {
11035                    self.write_space();
11036                    self.write_keyword("ASC");
11037                }
11038                if let Some(nulls_first) = col.nulls_first {
11039                    self.write_space();
11040                    self.write_keyword("NULLS");
11041                    self.write_space();
11042                    self.write_keyword(if nulls_first { "FIRST" } else { "LAST" });
11043                }
11044            }
11045            self.write(")");
11046        }
11047
11048        // PostgreSQL INCLUDE (col1, col2) clause
11049        if !ci.include_columns.is_empty() {
11050            self.write_space();
11051            self.write_keyword("INCLUDE");
11052            self.write(" (");
11053            for (i, col) in ci.include_columns.iter().enumerate() {
11054                if i > 0 {
11055                    self.write(", ");
11056                }
11057                self.generate_identifier(col)?;
11058            }
11059            self.write(")");
11060        }
11061
11062        // TSQL: WITH (option=value, ...) clause
11063        if !ci.with_options.is_empty() {
11064            self.write_space();
11065            self.write_keyword("WITH");
11066            self.write(" (");
11067            for (i, (key, value)) in ci.with_options.iter().enumerate() {
11068                if i > 0 {
11069                    self.write(", ");
11070                }
11071                self.write(key);
11072                self.write("=");
11073                self.write(value);
11074            }
11075            self.write(")");
11076        }
11077
11078        // PostgreSQL WHERE clause for partial indexes
11079        if let Some(ref where_clause) = ci.where_clause {
11080            self.write_space();
11081            self.write_keyword("WHERE");
11082            self.write_space();
11083            self.generate_expression(where_clause)?;
11084        }
11085
11086        // TSQL: ON filegroup or partition scheme clause
11087        if let Some(ref on_fg) = ci.on_filegroup {
11088            self.write_space();
11089            self.write_keyword("ON");
11090            self.write_space();
11091            self.write(on_fg);
11092        }
11093
11094        Ok(())
11095    }
11096
11097    fn generate_drop_index(&mut self, di: &DropIndex) -> Result<()> {
11098        self.write_keyword("DROP INDEX");
11099
11100        if di.concurrently {
11101            self.write_space();
11102            self.write_keyword("CONCURRENTLY");
11103        }
11104
11105        if di.if_exists {
11106            self.write_space();
11107            self.write_keyword("IF EXISTS");
11108        }
11109
11110        self.write_space();
11111        self.generate_identifier(&di.name)?;
11112
11113        if let Some(ref table) = di.table {
11114            self.write_space();
11115            self.write_keyword("ON");
11116            self.write_space();
11117            self.generate_table(table)?;
11118        }
11119
11120        Ok(())
11121    }
11122
11123    fn generate_create_view(&mut self, cv: &CreateView) -> Result<()> {
11124        self.write_keyword("CREATE");
11125
11126        // MySQL: ALGORITHM=...
11127        if let Some(ref algorithm) = cv.algorithm {
11128            self.write_space();
11129            self.write_keyword("ALGORITHM");
11130            self.write("=");
11131            self.write_keyword(algorithm);
11132        }
11133
11134        // MySQL: DEFINER=...
11135        if let Some(ref definer) = cv.definer {
11136            self.write_space();
11137            self.write_keyword("DEFINER");
11138            self.write("=");
11139            self.write(definer);
11140        }
11141
11142        // MySQL: SQL SECURITY DEFINER/INVOKER (before VIEW keyword, unless it appeared after view name)
11143        if cv.security_sql_style && !cv.security_after_name {
11144            if let Some(ref security) = cv.security {
11145                self.write_space();
11146                self.write_keyword("SQL SECURITY");
11147                self.write_space();
11148                match security {
11149                    FunctionSecurity::Definer => self.write_keyword("DEFINER"),
11150                    FunctionSecurity::Invoker => self.write_keyword("INVOKER"),
11151                    FunctionSecurity::None => self.write_keyword("NONE"),
11152                }
11153            }
11154        }
11155
11156        if cv.or_alter {
11157            self.write_space();
11158            self.write_keyword("OR ALTER");
11159        } else if cv.or_replace {
11160            self.write_space();
11161            self.write_keyword("OR REPLACE");
11162        }
11163
11164        if cv.temporary {
11165            self.write_space();
11166            self.write_keyword("TEMPORARY");
11167        }
11168
11169        if cv.materialized {
11170            self.write_space();
11171            self.write_keyword("MATERIALIZED");
11172        }
11173
11174        // Snowflake: SECURE VIEW
11175        if cv.secure {
11176            self.write_space();
11177            self.write_keyword("SECURE");
11178        }
11179
11180        self.write_space();
11181        self.write_keyword("VIEW");
11182
11183        if cv.if_not_exists {
11184            self.write_space();
11185            self.write_keyword("IF NOT EXISTS");
11186        }
11187
11188        self.write_space();
11189        self.generate_table(&cv.name)?;
11190
11191        // ClickHouse: ON CLUSTER clause
11192        if let Some(ref on_cluster) = cv.on_cluster {
11193            self.write_space();
11194            self.generate_on_cluster(on_cluster)?;
11195        }
11196
11197        // ClickHouse: TO destination_table
11198        if let Some(ref to_table) = cv.to_table {
11199            self.write_space();
11200            self.write_keyword("TO");
11201            self.write_space();
11202            self.generate_table(to_table)?;
11203        }
11204
11205        // For regular VIEW: columns come before COPY GRANTS
11206        // For MATERIALIZED VIEW: COPY GRANTS comes before columns
11207        if !cv.materialized {
11208            // Regular VIEW: columns first
11209            if !cv.columns.is_empty() {
11210                self.write(" (");
11211                for (i, col) in cv.columns.iter().enumerate() {
11212                    if i > 0 {
11213                        self.write(", ");
11214                    }
11215                    self.generate_identifier(&col.name)?;
11216                    // BigQuery: OPTIONS (key=value, ...) on view column
11217                    if !col.options.is_empty() {
11218                        self.write_space();
11219                        self.generate_options_clause(&col.options)?;
11220                    }
11221                    if let Some(ref comment) = col.comment {
11222                        self.write_space();
11223                        self.write_keyword("COMMENT");
11224                        self.write_space();
11225                        self.generate_string_literal(comment)?;
11226                    }
11227                }
11228                self.write(")");
11229            }
11230
11231            // Presto/Trino/StarRocks: SECURITY DEFINER/INVOKER/NONE (after columns)
11232            // Also handles SQL SECURITY after view name (security_after_name)
11233            if !cv.security_sql_style || cv.security_after_name {
11234                if let Some(ref security) = cv.security {
11235                    self.write_space();
11236                    if cv.security_sql_style {
11237                        self.write_keyword("SQL SECURITY");
11238                    } else {
11239                        self.write_keyword("SECURITY");
11240                    }
11241                    self.write_space();
11242                    match security {
11243                        FunctionSecurity::Definer => self.write_keyword("DEFINER"),
11244                        FunctionSecurity::Invoker => self.write_keyword("INVOKER"),
11245                        FunctionSecurity::None => self.write_keyword("NONE"),
11246                    }
11247                }
11248            }
11249
11250            // Snowflake: COPY GRANTS
11251            if cv.copy_grants {
11252                self.write_space();
11253                self.write_keyword("COPY GRANTS");
11254            }
11255        } else {
11256            // MATERIALIZED VIEW: COPY GRANTS first
11257            if cv.copy_grants {
11258                self.write_space();
11259                self.write_keyword("COPY GRANTS");
11260            }
11261
11262            // Doris: If we have a schema (typed columns), generate that instead
11263            if let Some(ref schema) = cv.schema {
11264                self.write(" (");
11265                for (i, expr) in schema.expressions.iter().enumerate() {
11266                    if i > 0 {
11267                        self.write(", ");
11268                    }
11269                    self.generate_expression(expr)?;
11270                }
11271                self.write(")");
11272            } else if !cv.columns.is_empty() {
11273                // Then columns (simple column names without types)
11274                self.write(" (");
11275                for (i, col) in cv.columns.iter().enumerate() {
11276                    if i > 0 {
11277                        self.write(", ");
11278                    }
11279                    self.generate_identifier(&col.name)?;
11280                    // BigQuery: OPTIONS (key=value, ...) on view column
11281                    if !col.options.is_empty() {
11282                        self.write_space();
11283                        self.generate_options_clause(&col.options)?;
11284                    }
11285                    if let Some(ref comment) = col.comment {
11286                        self.write_space();
11287                        self.write_keyword("COMMENT");
11288                        self.write_space();
11289                        self.generate_string_literal(comment)?;
11290                    }
11291                }
11292                self.write(")");
11293            }
11294
11295            // Doris: KEY (columns) for materialized views
11296            if let Some(ref unique_key) = cv.unique_key {
11297                self.write_space();
11298                self.write_keyword("KEY");
11299                self.write(" (");
11300                for (i, expr) in unique_key.expressions.iter().enumerate() {
11301                    if i > 0 {
11302                        self.write(", ");
11303                    }
11304                    self.generate_expression(expr)?;
11305                }
11306                self.write(")");
11307            }
11308        }
11309
11310        if let Some(ref row_access_policy) = cv.row_access_policy {
11311            self.write_space();
11312            self.write_keyword("WITH");
11313            self.write_space();
11314            self.write(row_access_policy);
11315        }
11316
11317        // Snowflake: COMMENT = 'text'
11318        if let Some(ref comment) = cv.comment {
11319            self.write_space();
11320            self.write_keyword("COMMENT");
11321            self.write("=");
11322            self.generate_string_literal(comment)?;
11323        }
11324
11325        // Snowflake: TAG (name='value', ...)
11326        if !cv.tags.is_empty() {
11327            self.write_space();
11328            self.write_keyword("TAG");
11329            self.write(" (");
11330            for (i, (name, value)) in cv.tags.iter().enumerate() {
11331                if i > 0 {
11332                    self.write(", ");
11333                }
11334                self.write(name);
11335                self.write("='");
11336                self.write(value);
11337                self.write("'");
11338            }
11339            self.write(")");
11340        }
11341
11342        // BigQuery: OPTIONS (key=value, ...)
11343        if !cv.options.is_empty() {
11344            self.write_space();
11345            self.generate_options_clause(&cv.options)?;
11346        }
11347
11348        // Doris: BUILD IMMEDIATE/DEFERRED for materialized views
11349        if let Some(ref build) = cv.build {
11350            self.write_space();
11351            self.write_keyword("BUILD");
11352            self.write_space();
11353            self.write_keyword(build);
11354        }
11355
11356        // Doris: REFRESH clause for materialized views
11357        if let Some(ref refresh) = cv.refresh {
11358            self.write_space();
11359            self.generate_refresh_trigger_property(refresh)?;
11360        }
11361
11362        // Redshift: AUTO REFRESH YES|NO for materialized views
11363        if let Some(auto_refresh) = cv.auto_refresh {
11364            self.write_space();
11365            self.write_keyword("AUTO REFRESH");
11366            self.write_space();
11367            if auto_refresh {
11368                self.write_keyword("YES");
11369            } else {
11370                self.write_keyword("NO");
11371            }
11372        }
11373
11374        // ClickHouse: Table properties (ENGINE, ORDER BY, SAMPLE, SETTINGS, TTL, etc.)
11375        for prop in &cv.table_properties {
11376            self.write_space();
11377            self.generate_expression(prop)?;
11378        }
11379
11380        // ClickHouse: POPULATE / EMPTY before AS
11381        if let Some(ref population) = cv.clickhouse_population {
11382            self.write_space();
11383            self.write_keyword(population);
11384        }
11385
11386        // Only output AS clause if there's a real query (not just NULL placeholder)
11387        if !matches!(&cv.query, Expression::Null(_)) {
11388            self.write_space();
11389            self.write_keyword("AS");
11390            self.write_space();
11391
11392            // Teradata: LOCKING clause (between AS and query)
11393            if let Some(ref mode) = cv.locking_mode {
11394                self.write_keyword("LOCKING");
11395                self.write_space();
11396                self.write_keyword(mode);
11397                if let Some(ref access) = cv.locking_access {
11398                    self.write_space();
11399                    self.write_keyword("FOR");
11400                    self.write_space();
11401                    self.write_keyword(access);
11402                }
11403                self.write_space();
11404            }
11405
11406            if cv.query_parenthesized {
11407                self.write("(");
11408            }
11409            self.generate_expression(&cv.query)?;
11410            if cv.query_parenthesized {
11411                self.write(")");
11412            }
11413        }
11414
11415        // Redshift: WITH NO SCHEMA BINDING (after query)
11416        if cv.no_schema_binding {
11417            self.write_space();
11418            self.write_keyword("WITH NO SCHEMA BINDING");
11419        }
11420
11421        Ok(())
11422    }
11423
11424    fn generate_drop_view(&mut self, dv: &DropView) -> Result<()> {
11425        self.write_keyword("DROP");
11426
11427        if dv.materialized {
11428            self.write_space();
11429            self.write_keyword("MATERIALIZED");
11430        }
11431
11432        self.write_space();
11433        self.write_keyword("VIEW");
11434
11435        if dv.if_exists {
11436            self.write_space();
11437            self.write_keyword("IF EXISTS");
11438        }
11439
11440        self.write_space();
11441        self.generate_table(&dv.name)?;
11442
11443        Ok(())
11444    }
11445
11446    fn generate_truncate(&mut self, tr: &Truncate) -> Result<()> {
11447        match tr.target {
11448            TruncateTarget::Database => self.write_keyword("TRUNCATE DATABASE"),
11449            TruncateTarget::Table => self.write_keyword("TRUNCATE TABLE"),
11450        }
11451        if tr.if_exists {
11452            self.write_space();
11453            self.write_keyword("IF EXISTS");
11454        }
11455        self.write_space();
11456        self.generate_table(&tr.table)?;
11457
11458        // ClickHouse: ON CLUSTER clause
11459        if let Some(ref on_cluster) = tr.on_cluster {
11460            self.write_space();
11461            self.generate_on_cluster(on_cluster)?;
11462        }
11463
11464        // Check if first table has a * (multi-table with star)
11465        if !tr.extra_tables.is_empty() {
11466            // Check if the first entry matches the main table (star case)
11467            let skip_first = if let Some(first) = tr.extra_tables.first() {
11468                first.table.name == tr.table.name && first.star
11469            } else {
11470                false
11471            };
11472
11473            // PostgreSQL normalizes away the * suffix (it's the default behavior)
11474            let strip_star = matches!(
11475                self.config.dialect,
11476                Some(crate::dialects::DialectType::PostgreSQL)
11477                    | Some(crate::dialects::DialectType::Redshift)
11478            );
11479            if skip_first && !strip_star {
11480                self.write("*");
11481            }
11482
11483            // Generate additional tables
11484            for (i, entry) in tr.extra_tables.iter().enumerate() {
11485                if i == 0 && skip_first {
11486                    continue; // Already handled the star for first table
11487                }
11488                self.write(", ");
11489                self.generate_table(&entry.table)?;
11490                if entry.star && !strip_star {
11491                    self.write("*");
11492                }
11493            }
11494        }
11495
11496        // RESTART/CONTINUE IDENTITY
11497        if let Some(identity) = &tr.identity {
11498            self.write_space();
11499            match identity {
11500                TruncateIdentity::Restart => self.write_keyword("RESTART IDENTITY"),
11501                TruncateIdentity::Continue => self.write_keyword("CONTINUE IDENTITY"),
11502            }
11503        }
11504
11505        if tr.cascade {
11506            self.write_space();
11507            self.write_keyword("CASCADE");
11508        }
11509
11510        if tr.restrict {
11511            self.write_space();
11512            self.write_keyword("RESTRICT");
11513        }
11514
11515        // Output Hive PARTITION clause
11516        if let Some(ref partition) = tr.partition {
11517            self.write_space();
11518            self.generate_expression(partition)?;
11519        }
11520
11521        Ok(())
11522    }
11523
11524    fn generate_use(&mut self, u: &Use) -> Result<()> {
11525        // Teradata uses "DATABASE <name>" instead of "USE <name>"
11526        if matches!(self.config.dialect, Some(DialectType::Teradata)) {
11527            self.write_keyword("DATABASE");
11528            self.write_space();
11529            self.generate_identifier(&u.this)?;
11530            return Ok(());
11531        }
11532
11533        self.write_keyword("USE");
11534
11535        if let Some(kind) = &u.kind {
11536            self.write_space();
11537            match kind {
11538                UseKind::Database => self.write_keyword("DATABASE"),
11539                UseKind::Schema => self.write_keyword("SCHEMA"),
11540                UseKind::Role => self.write_keyword("ROLE"),
11541                UseKind::Warehouse => self.write_keyword("WAREHOUSE"),
11542                UseKind::Catalog => self.write_keyword("CATALOG"),
11543                UseKind::SecondaryRoles => self.write_keyword("SECONDARY ROLES"),
11544            }
11545        }
11546
11547        self.write_space();
11548        // For SECONDARY ROLES, write the value as-is (ALL, NONE, or role names)
11549        // without quoting, since these are keywords not identifiers
11550        if matches!(&u.kind, Some(UseKind::SecondaryRoles)) {
11551            self.write(&u.this.name);
11552        } else {
11553            self.generate_identifier(&u.this)?;
11554        }
11555        Ok(())
11556    }
11557
11558    fn generate_cache(&mut self, c: &Cache) -> Result<()> {
11559        self.write_keyword("CACHE");
11560        if c.lazy {
11561            self.write_space();
11562            self.write_keyword("LAZY");
11563        }
11564        self.write_space();
11565        self.write_keyword("TABLE");
11566        self.write_space();
11567        self.generate_identifier(&c.table)?;
11568
11569        // OPTIONS clause
11570        if !c.options.is_empty() {
11571            self.write_space();
11572            self.write_keyword("OPTIONS");
11573            self.write("(");
11574            for (i, (key, value)) in c.options.iter().enumerate() {
11575                if i > 0 {
11576                    self.write(", ");
11577                }
11578                self.generate_expression(key)?;
11579                self.write(" = ");
11580                self.generate_expression(value)?;
11581            }
11582            self.write(")");
11583        }
11584
11585        // AS query
11586        if let Some(query) = &c.query {
11587            self.write_space();
11588            self.write_keyword("AS");
11589            self.write_space();
11590            self.generate_expression(query)?;
11591        }
11592
11593        Ok(())
11594    }
11595
11596    fn generate_uncache(&mut self, u: &Uncache) -> Result<()> {
11597        self.write_keyword("UNCACHE TABLE");
11598        if u.if_exists {
11599            self.write_space();
11600            self.write_keyword("IF EXISTS");
11601        }
11602        self.write_space();
11603        self.generate_identifier(&u.table)?;
11604        Ok(())
11605    }
11606
11607    fn generate_load_data(&mut self, l: &LoadData) -> Result<()> {
11608        self.write_keyword("LOAD DATA");
11609        if l.local {
11610            self.write_space();
11611            self.write_keyword("LOCAL");
11612        }
11613        self.write_space();
11614        self.write_keyword("INPATH");
11615        self.write_space();
11616        self.write("'");
11617        self.write(&l.inpath);
11618        self.write("'");
11619
11620        if l.overwrite {
11621            self.write_space();
11622            self.write_keyword("OVERWRITE");
11623        }
11624
11625        self.write_space();
11626        self.write_keyword("INTO TABLE");
11627        self.write_space();
11628        self.generate_expression(&l.table)?;
11629
11630        // PARTITION clause
11631        if !l.partition.is_empty() {
11632            self.write_space();
11633            self.write_keyword("PARTITION");
11634            self.write("(");
11635            for (i, (col, val)) in l.partition.iter().enumerate() {
11636                if i > 0 {
11637                    self.write(", ");
11638                }
11639                self.generate_identifier(col)?;
11640                self.write(" = ");
11641                self.generate_expression(val)?;
11642            }
11643            self.write(")");
11644        }
11645
11646        // INPUTFORMAT clause
11647        if let Some(fmt) = &l.input_format {
11648            self.write_space();
11649            self.write_keyword("INPUTFORMAT");
11650            self.write_space();
11651            self.write("'");
11652            self.write(fmt);
11653            self.write("'");
11654        }
11655
11656        // SERDE clause
11657        if let Some(serde) = &l.serde {
11658            self.write_space();
11659            self.write_keyword("SERDE");
11660            self.write_space();
11661            self.write("'");
11662            self.write(serde);
11663            self.write("'");
11664        }
11665
11666        Ok(())
11667    }
11668
11669    fn generate_pragma(&mut self, p: &Pragma) -> Result<()> {
11670        self.write_keyword("PRAGMA");
11671        self.write_space();
11672
11673        // Schema prefix if present
11674        if let Some(schema) = &p.schema {
11675            self.generate_identifier(schema)?;
11676            self.write(".");
11677        }
11678
11679        // Pragma name
11680        self.generate_identifier(&p.name)?;
11681
11682        // Value assignment or function call
11683        if p.use_assignment_syntax {
11684            self.write(" = ");
11685            if let Some(value) = &p.value {
11686                self.generate_expression(value)?;
11687            } else if let Some(arg) = p.args.first() {
11688                self.generate_expression(arg)?;
11689            }
11690        } else if !p.args.is_empty() {
11691            self.write("(");
11692            for (i, arg) in p.args.iter().enumerate() {
11693                if i > 0 {
11694                    self.write(", ");
11695                }
11696                self.generate_expression(arg)?;
11697            }
11698            self.write(")");
11699        }
11700
11701        Ok(())
11702    }
11703
11704    fn generate_grant(&mut self, g: &Grant) -> Result<()> {
11705        self.write_keyword("GRANT");
11706        self.write_space();
11707
11708        // Privileges (with optional column lists)
11709        for (i, privilege) in g.privileges.iter().enumerate() {
11710            if i > 0 {
11711                self.write(", ");
11712            }
11713            self.write_keyword(&privilege.name);
11714            // Output column list if present: SELECT(col1, col2)
11715            if !privilege.columns.is_empty() {
11716                self.write("(");
11717                for (j, col) in privilege.columns.iter().enumerate() {
11718                    if j > 0 {
11719                        self.write(", ");
11720                    }
11721                    self.write(col);
11722                }
11723                self.write(")");
11724            }
11725        }
11726
11727        self.write_space();
11728        self.write_keyword("ON");
11729        self.write_space();
11730
11731        // Object kind (TABLE, SCHEMA, etc.)
11732        if let Some(kind) = &g.kind {
11733            self.write_keyword(kind);
11734            self.write_space();
11735        }
11736
11737        // Securable - normalize function/procedure names to uppercase for PostgreSQL family
11738        {
11739            use crate::dialects::DialectType;
11740            let should_upper = matches!(
11741                self.config.dialect,
11742                Some(DialectType::PostgreSQL)
11743                    | Some(DialectType::CockroachDB)
11744                    | Some(DialectType::Materialize)
11745                    | Some(DialectType::RisingWave)
11746            ) && (g.kind.as_deref() == Some("FUNCTION")
11747                || g.kind.as_deref() == Some("PROCEDURE"));
11748            if should_upper {
11749                use crate::expressions::Identifier;
11750                let upper_id = Identifier {
11751                    name: g.securable.name.to_ascii_uppercase(),
11752                    quoted: g.securable.quoted,
11753                    ..g.securable.clone()
11754                };
11755                self.generate_identifier(&upper_id)?;
11756            } else {
11757                self.generate_identifier(&g.securable)?;
11758            }
11759        }
11760
11761        // Function parameter types (if present)
11762        if !g.function_params.is_empty() {
11763            self.write("(");
11764            for (i, param) in g.function_params.iter().enumerate() {
11765                if i > 0 {
11766                    self.write(", ");
11767                }
11768                self.write(param);
11769            }
11770            self.write(")");
11771        }
11772
11773        self.write_space();
11774        self.write_keyword("TO");
11775        self.write_space();
11776
11777        // Principals
11778        for (i, principal) in g.principals.iter().enumerate() {
11779            if i > 0 {
11780                self.write(", ");
11781            }
11782            if principal.is_role {
11783                self.write_keyword("ROLE");
11784                self.write_space();
11785            } else if principal.is_group {
11786                self.write_keyword("GROUP");
11787                self.write_space();
11788            } else if principal.is_share {
11789                self.write_keyword("SHARE");
11790                self.write_space();
11791            }
11792            self.generate_identifier(&principal.name)?;
11793        }
11794
11795        // WITH GRANT OPTION
11796        if g.grant_option {
11797            self.write_space();
11798            self.write_keyword("WITH GRANT OPTION");
11799        }
11800
11801        // TSQL: AS principal
11802        if let Some(ref principal) = g.as_principal {
11803            self.write_space();
11804            self.write_keyword("AS");
11805            self.write_space();
11806            self.generate_identifier(principal)?;
11807        }
11808
11809        Ok(())
11810    }
11811
11812    fn generate_revoke(&mut self, r: &Revoke) -> Result<()> {
11813        self.write_keyword("REVOKE");
11814        self.write_space();
11815
11816        // GRANT OPTION FOR
11817        if r.grant_option {
11818            self.write_keyword("GRANT OPTION FOR");
11819            self.write_space();
11820        }
11821
11822        // Privileges (with optional column lists)
11823        for (i, privilege) in r.privileges.iter().enumerate() {
11824            if i > 0 {
11825                self.write(", ");
11826            }
11827            self.write_keyword(&privilege.name);
11828            // Output column list if present: SELECT(col1, col2)
11829            if !privilege.columns.is_empty() {
11830                self.write("(");
11831                for (j, col) in privilege.columns.iter().enumerate() {
11832                    if j > 0 {
11833                        self.write(", ");
11834                    }
11835                    self.write(col);
11836                }
11837                self.write(")");
11838            }
11839        }
11840
11841        self.write_space();
11842        self.write_keyword("ON");
11843        self.write_space();
11844
11845        // Object kind
11846        if let Some(kind) = &r.kind {
11847            self.write_keyword(kind);
11848            self.write_space();
11849        }
11850
11851        // Securable - normalize function/procedure names to uppercase for PostgreSQL family
11852        {
11853            use crate::dialects::DialectType;
11854            let should_upper = matches!(
11855                self.config.dialect,
11856                Some(DialectType::PostgreSQL)
11857                    | Some(DialectType::CockroachDB)
11858                    | Some(DialectType::Materialize)
11859                    | Some(DialectType::RisingWave)
11860            ) && (r.kind.as_deref() == Some("FUNCTION")
11861                || r.kind.as_deref() == Some("PROCEDURE"));
11862            if should_upper {
11863                use crate::expressions::Identifier;
11864                let upper_id = Identifier {
11865                    name: r.securable.name.to_ascii_uppercase(),
11866                    quoted: r.securable.quoted,
11867                    ..r.securable.clone()
11868                };
11869                self.generate_identifier(&upper_id)?;
11870            } else {
11871                self.generate_identifier(&r.securable)?;
11872            }
11873        }
11874
11875        // Function parameter types (if present)
11876        if !r.function_params.is_empty() {
11877            self.write("(");
11878            for (i, param) in r.function_params.iter().enumerate() {
11879                if i > 0 {
11880                    self.write(", ");
11881                }
11882                self.write(param);
11883            }
11884            self.write(")");
11885        }
11886
11887        self.write_space();
11888        self.write_keyword("FROM");
11889        self.write_space();
11890
11891        // Principals
11892        for (i, principal) in r.principals.iter().enumerate() {
11893            if i > 0 {
11894                self.write(", ");
11895            }
11896            if principal.is_role {
11897                self.write_keyword("ROLE");
11898                self.write_space();
11899            } else if principal.is_group {
11900                self.write_keyword("GROUP");
11901                self.write_space();
11902            } else if principal.is_share {
11903                self.write_keyword("SHARE");
11904                self.write_space();
11905            }
11906            self.generate_identifier(&principal.name)?;
11907        }
11908
11909        // CASCADE or RESTRICT
11910        if r.cascade {
11911            self.write_space();
11912            self.write_keyword("CASCADE");
11913        } else if r.restrict {
11914            self.write_space();
11915            self.write_keyword("RESTRICT");
11916        }
11917
11918        Ok(())
11919    }
11920
11921    fn generate_comment(&mut self, c: &Comment) -> Result<()> {
11922        self.write_keyword("COMMENT");
11923
11924        // IF EXISTS
11925        if c.exists {
11926            self.write_space();
11927            self.write_keyword("IF EXISTS");
11928        }
11929
11930        self.write_space();
11931        self.write_keyword("ON");
11932
11933        // MATERIALIZED
11934        if c.materialized {
11935            self.write_space();
11936            self.write_keyword("MATERIALIZED");
11937        }
11938
11939        self.write_space();
11940        self.write_keyword(&c.kind);
11941        self.write_space();
11942
11943        // Object name
11944        self.generate_expression(&c.this)?;
11945
11946        self.write_space();
11947        self.write_keyword("IS");
11948        self.write_space();
11949
11950        // Comment expression
11951        self.generate_expression(&c.expression)?;
11952
11953        Ok(())
11954    }
11955
11956    fn generate_set_statement(&mut self, s: &SetStatement) -> Result<()> {
11957        self.write_keyword("SET");
11958
11959        for (i, item) in s.items.iter().enumerate() {
11960            if i > 0 {
11961                self.write(",");
11962            }
11963            self.write_space();
11964
11965            // Kind modifier (GLOBAL, LOCAL, SESSION, PERSIST, PERSIST_ONLY, VARIABLE)
11966            let has_variable_kind = item.kind.as_deref() == Some("VARIABLE");
11967            if let Some(ref kind) = item.kind {
11968                // For VARIABLE kind, only output the keyword for dialects that require it
11969                // (Spark, Databricks, DuckDB) - matching Python sqlglot's
11970                // SET_ASSIGNMENT_REQUIRES_VARIABLE_KEYWORD flag
11971                if has_variable_kind {
11972                    if matches!(
11973                        self.config.dialect,
11974                        Some(DialectType::Spark | DialectType::Databricks | DialectType::DuckDB)
11975                    ) {
11976                        self.write_keyword("VARIABLE");
11977                        self.write_space();
11978                    }
11979                } else {
11980                    self.write_keyword(kind);
11981                    self.write_space();
11982                }
11983            }
11984
11985            // Check for special SET forms by name
11986            let name_str = match &item.name {
11987                Expression::Identifier(id) => Some(id.name.as_str()),
11988                _ => None,
11989            };
11990
11991            let is_transaction = name_str == Some("TRANSACTION");
11992            let is_character_set = name_str == Some("CHARACTER SET");
11993            let is_names = name_str == Some("NAMES");
11994            let is_collate = name_str == Some("COLLATE");
11995            let is_value_only =
11996                matches!(&item.value, Expression::Identifier(id) if id.name.is_empty());
11997
11998            if is_transaction {
11999                // Output: SET [GLOBAL|SESSION] TRANSACTION <characteristics>
12000                self.write_keyword("TRANSACTION");
12001                if let Expression::Identifier(id) = &item.value {
12002                    if !id.name.is_empty() {
12003                        self.write_space();
12004                        self.write(&id.name);
12005                    }
12006                }
12007            } else if is_character_set {
12008                // Output: SET CHARACTER SET <charset>
12009                self.write_keyword("CHARACTER SET");
12010                self.write_space();
12011                self.generate_set_value(&item.value)?;
12012            } else if is_names {
12013                // Output: SET NAMES <charset>
12014                self.write_keyword("NAMES");
12015                self.write_space();
12016                self.generate_set_value(&item.value)?;
12017            } else if is_collate {
12018                // Output: COLLATE <collation> (part of SET NAMES ... COLLATE ...)
12019                self.write_keyword("COLLATE");
12020                self.write_space();
12021                self.generate_set_value(&item.value)?;
12022            } else if has_variable_kind {
12023                // Output: SET [VARIABLE] <name> = <value>
12024                // VARIABLE keyword already written above if dialect requires it
12025                if let Some(ns) = name_str {
12026                    self.write(ns);
12027                } else {
12028                    self.generate_expression(&item.name)?;
12029                }
12030                self.write(" = ");
12031                self.generate_set_value(&item.value)?;
12032            } else if is_value_only {
12033                // SET <name> ON/OFF without = (TSQL: SET XACT_ABORT ON)
12034                self.generate_expression(&item.name)?;
12035            } else if item.no_equals && matches!(self.config.dialect, Some(DialectType::TSQL)) {
12036                // SET key value without = (TSQL style)
12037                self.generate_expression(&item.name)?;
12038                self.write_space();
12039                self.generate_set_value(&item.value)?;
12040            } else {
12041                // Standard: variable = value
12042                // SET item names should not be quoted (they are config parameter names, not column refs)
12043                match &item.name {
12044                    Expression::Identifier(id) => {
12045                        self.write(&id.name);
12046                    }
12047                    _ => {
12048                        self.generate_expression(&item.name)?;
12049                    }
12050                }
12051                self.write(" = ");
12052                self.generate_set_value(&item.value)?;
12053            }
12054        }
12055
12056        Ok(())
12057    }
12058
12059    /// Generate a SET statement value, writing keyword values (DEFAULT, ON, OFF)
12060    /// directly to avoid reserved keyword quoting.
12061    fn generate_set_value(&mut self, value: &Expression) -> Result<()> {
12062        if let Expression::Identifier(id) = value {
12063            match id.name.as_str() {
12064                "DEFAULT" | "ON" | "OFF" => {
12065                    self.write_keyword(&id.name);
12066                    return Ok(());
12067                }
12068                _ => {}
12069            }
12070        }
12071        self.generate_expression(value)
12072    }
12073
12074    // ==================== Phase 4: Additional DDL Generation ====================
12075
12076    fn generate_alter_view(&mut self, av: &AlterView) -> Result<()> {
12077        self.write_keyword("ALTER");
12078        // MySQL modifiers before VIEW
12079        if let Some(ref algorithm) = av.algorithm {
12080            self.write_space();
12081            self.write_keyword("ALGORITHM");
12082            self.write(" = ");
12083            self.write_keyword(algorithm);
12084        }
12085        if let Some(ref definer) = av.definer {
12086            self.write_space();
12087            self.write_keyword("DEFINER");
12088            self.write(" = ");
12089            self.write(definer);
12090        }
12091        if let Some(ref sql_security) = av.sql_security {
12092            self.write_space();
12093            self.write_keyword("SQL SECURITY");
12094            self.write(" = ");
12095            self.write_keyword(sql_security);
12096        }
12097        self.write_space();
12098        self.write_keyword("VIEW");
12099        self.write_space();
12100        self.generate_table(&av.name)?;
12101
12102        // Hive: Column aliases with optional COMMENT
12103        if !av.columns.is_empty() {
12104            self.write(" (");
12105            for (i, col) in av.columns.iter().enumerate() {
12106                if i > 0 {
12107                    self.write(", ");
12108                }
12109                self.generate_identifier(&col.name)?;
12110                if let Some(ref comment) = col.comment {
12111                    self.write_space();
12112                    self.write_keyword("COMMENT");
12113                    self.write(" ");
12114                    self.generate_string_literal(comment)?;
12115                }
12116            }
12117            self.write(")");
12118        }
12119
12120        // TSQL: WITH option before actions
12121        if let Some(ref opt) = av.with_option {
12122            self.write_space();
12123            self.write_keyword("WITH");
12124            self.write_space();
12125            self.write_keyword(opt);
12126        }
12127
12128        for action in &av.actions {
12129            self.write_space();
12130            match action {
12131                AlterViewAction::Rename(new_name) => {
12132                    self.write_keyword("RENAME TO");
12133                    self.write_space();
12134                    self.generate_table(new_name)?;
12135                }
12136                AlterViewAction::OwnerTo(owner) => {
12137                    self.write_keyword("OWNER TO");
12138                    self.write_space();
12139                    self.generate_identifier(owner)?;
12140                }
12141                AlterViewAction::SetSchema(schema) => {
12142                    self.write_keyword("SET SCHEMA");
12143                    self.write_space();
12144                    self.generate_identifier(schema)?;
12145                }
12146                AlterViewAction::SetAuthorization(auth) => {
12147                    self.write_keyword("SET AUTHORIZATION");
12148                    self.write_space();
12149                    self.write(auth);
12150                }
12151                AlterViewAction::AlterColumn { name, action } => {
12152                    self.write_keyword("ALTER COLUMN");
12153                    self.write_space();
12154                    self.generate_identifier(name)?;
12155                    self.write_space();
12156                    self.generate_alter_column_action(action)?;
12157                }
12158                AlterViewAction::AsSelect(query) => {
12159                    self.write_keyword("AS");
12160                    self.write_space();
12161                    self.generate_expression(query)?;
12162                }
12163                AlterViewAction::SetTblproperties(props) => {
12164                    self.write_keyword("SET TBLPROPERTIES");
12165                    self.write(" (");
12166                    for (i, (key, value)) in props.iter().enumerate() {
12167                        if i > 0 {
12168                            self.write(", ");
12169                        }
12170                        self.generate_string_literal(key)?;
12171                        self.write("=");
12172                        self.generate_string_literal(value)?;
12173                    }
12174                    self.write(")");
12175                }
12176                AlterViewAction::UnsetTblproperties(keys) => {
12177                    self.write_keyword("UNSET TBLPROPERTIES");
12178                    self.write(" (");
12179                    for (i, key) in keys.iter().enumerate() {
12180                        if i > 0 {
12181                            self.write(", ");
12182                        }
12183                        self.generate_string_literal(key)?;
12184                    }
12185                    self.write(")");
12186                }
12187            }
12188        }
12189
12190        Ok(())
12191    }
12192
12193    fn generate_alter_index(&mut self, ai: &AlterIndex) -> Result<()> {
12194        self.write_keyword("ALTER INDEX");
12195        self.write_space();
12196        self.generate_identifier(&ai.name)?;
12197
12198        if let Some(table) = &ai.table {
12199            self.write_space();
12200            self.write_keyword("ON");
12201            self.write_space();
12202            self.generate_table(table)?;
12203        }
12204
12205        for action in &ai.actions {
12206            self.write_space();
12207            match action {
12208                AlterIndexAction::Rename(new_name) => {
12209                    self.write_keyword("RENAME TO");
12210                    self.write_space();
12211                    self.generate_identifier(new_name)?;
12212                }
12213                AlterIndexAction::SetTablespace(tablespace) => {
12214                    self.write_keyword("SET TABLESPACE");
12215                    self.write_space();
12216                    self.generate_identifier(tablespace)?;
12217                }
12218                AlterIndexAction::Visible(visible) => {
12219                    if *visible {
12220                        self.write_keyword("VISIBLE");
12221                    } else {
12222                        self.write_keyword("INVISIBLE");
12223                    }
12224                }
12225            }
12226        }
12227
12228        Ok(())
12229    }
12230
12231    fn generate_create_schema(&mut self, cs: &CreateSchema) -> Result<()> {
12232        // Output leading comments
12233        for comment in &cs.leading_comments {
12234            self.write_formatted_comment(comment);
12235            self.write_space();
12236        }
12237
12238        // Athena: CREATE SCHEMA uses Hive engine (backticks)
12239        let saved_athena_hive_context = self.athena_hive_context;
12240        if matches!(
12241            self.config.dialect,
12242            Some(crate::dialects::DialectType::Athena)
12243        ) {
12244            self.athena_hive_context = true;
12245        }
12246
12247        self.write_keyword("CREATE SCHEMA");
12248
12249        if cs.if_not_exists {
12250            self.write_space();
12251            self.write_keyword("IF NOT EXISTS");
12252        }
12253
12254        self.write_space();
12255        for (i, part) in cs.name.iter().enumerate() {
12256            if i > 0 {
12257                self.write(".");
12258            }
12259            self.generate_identifier(part)?;
12260        }
12261
12262        if let Some(ref clone_parts) = cs.clone_from {
12263            self.write_keyword(" CLONE ");
12264            for (i, part) in clone_parts.iter().enumerate() {
12265                if i > 0 {
12266                    self.write(".");
12267                }
12268                self.generate_identifier(part)?;
12269            }
12270        }
12271
12272        if let Some(ref at_clause) = cs.at_clause {
12273            self.write_space();
12274            self.generate_expression(at_clause)?;
12275        }
12276
12277        if let Some(auth) = &cs.authorization {
12278            self.write_space();
12279            self.write_keyword("AUTHORIZATION");
12280            self.write_space();
12281            self.generate_identifier(auth)?;
12282        }
12283
12284        // Generate schema properties (e.g., DEFAULT COLLATE or WITH (props))
12285        // Separate WITH properties from other properties
12286        let with_properties: Vec<_> = cs
12287            .properties
12288            .iter()
12289            .filter(|p| matches!(p, Expression::Property(_)))
12290            .collect();
12291        let other_properties: Vec<_> = cs
12292            .properties
12293            .iter()
12294            .filter(|p| !matches!(p, Expression::Property(_)))
12295            .collect();
12296
12297        // Generate WITH (props) if we have Property expressions
12298        if !with_properties.is_empty() {
12299            self.write_space();
12300            self.write_keyword("WITH");
12301            self.write(" (");
12302            for (i, prop) in with_properties.iter().enumerate() {
12303                if i > 0 {
12304                    self.write(", ");
12305                }
12306                self.generate_expression(prop)?;
12307            }
12308            self.write(")");
12309        }
12310
12311        // Generate other properties (like DEFAULT COLLATE)
12312        for prop in other_properties {
12313            self.write_space();
12314            self.generate_expression(prop)?;
12315        }
12316
12317        // Restore Athena Hive context
12318        self.athena_hive_context = saved_athena_hive_context;
12319
12320        Ok(())
12321    }
12322
12323    fn generate_drop_schema(&mut self, ds: &DropSchema) -> Result<()> {
12324        self.write_keyword("DROP SCHEMA");
12325
12326        if ds.if_exists {
12327            self.write_space();
12328            self.write_keyword("IF EXISTS");
12329        }
12330
12331        self.write_space();
12332        self.generate_identifier(&ds.name)?;
12333
12334        if ds.cascade {
12335            self.write_space();
12336            self.write_keyword("CASCADE");
12337        }
12338
12339        Ok(())
12340    }
12341
12342    fn generate_drop_namespace(&mut self, dn: &DropNamespace) -> Result<()> {
12343        self.write_keyword("DROP NAMESPACE");
12344
12345        if dn.if_exists {
12346            self.write_space();
12347            self.write_keyword("IF EXISTS");
12348        }
12349
12350        self.write_space();
12351        self.generate_identifier(&dn.name)?;
12352
12353        if dn.cascade {
12354            self.write_space();
12355            self.write_keyword("CASCADE");
12356        }
12357
12358        Ok(())
12359    }
12360
12361    fn generate_create_database(&mut self, cd: &CreateDatabase) -> Result<()> {
12362        self.write_keyword("CREATE DATABASE");
12363
12364        if cd.if_not_exists {
12365            self.write_space();
12366            self.write_keyword("IF NOT EXISTS");
12367        }
12368
12369        self.write_space();
12370        self.generate_identifier(&cd.name)?;
12371
12372        if let Some(ref clone_src) = cd.clone_from {
12373            self.write_keyword(" CLONE ");
12374            self.generate_identifier(clone_src)?;
12375        }
12376
12377        // AT/BEFORE clause for time travel (Snowflake)
12378        if let Some(ref at_clause) = cd.at_clause {
12379            self.write_space();
12380            self.generate_expression(at_clause)?;
12381        }
12382
12383        for option in &cd.options {
12384            self.write_space();
12385            match option {
12386                DatabaseOption::CharacterSet(charset) => {
12387                    self.write_keyword("CHARACTER SET");
12388                    self.write(" = ");
12389                    self.write(&format!("'{}'", charset));
12390                }
12391                DatabaseOption::Collate(collate) => {
12392                    self.write_keyword("COLLATE");
12393                    self.write(" = ");
12394                    self.write(&format!("'{}'", collate));
12395                }
12396                DatabaseOption::Owner(owner) => {
12397                    self.write_keyword("OWNER");
12398                    self.write(" = ");
12399                    self.generate_identifier(owner)?;
12400                }
12401                DatabaseOption::Template(template) => {
12402                    self.write_keyword("TEMPLATE");
12403                    self.write(" = ");
12404                    self.generate_identifier(template)?;
12405                }
12406                DatabaseOption::Encoding(encoding) => {
12407                    self.write_keyword("ENCODING");
12408                    self.write(" = ");
12409                    self.write(&format!("'{}'", encoding));
12410                }
12411                DatabaseOption::Location(location) => {
12412                    self.write_keyword("LOCATION");
12413                    self.write(" = ");
12414                    self.write(&format!("'{}'", location));
12415                }
12416            }
12417        }
12418
12419        Ok(())
12420    }
12421
12422    fn generate_drop_database(&mut self, dd: &DropDatabase) -> Result<()> {
12423        self.write_keyword("DROP DATABASE");
12424
12425        if dd.if_exists {
12426            self.write_space();
12427            self.write_keyword("IF EXISTS");
12428        }
12429
12430        self.write_space();
12431        self.generate_identifier(&dd.name)?;
12432
12433        if dd.sync {
12434            self.write_space();
12435            self.write_keyword("SYNC");
12436        }
12437
12438        Ok(())
12439    }
12440
12441    fn generate_create_function(&mut self, cf: &CreateFunction) -> Result<()> {
12442        self.write_keyword("CREATE");
12443
12444        if cf.or_alter {
12445            self.write_space();
12446            self.write_keyword("OR ALTER");
12447        } else if cf.or_replace {
12448            self.write_space();
12449            self.write_keyword("OR REPLACE");
12450        }
12451
12452        if cf.temporary {
12453            self.write_space();
12454            self.write_keyword("TEMPORARY");
12455        }
12456
12457        self.write_space();
12458        if cf.is_table_function {
12459            self.write_keyword("TABLE FUNCTION");
12460        } else {
12461            self.write_keyword("FUNCTION");
12462        }
12463
12464        if cf.if_not_exists {
12465            self.write_space();
12466            self.write_keyword("IF NOT EXISTS");
12467        }
12468
12469        self.write_space();
12470        self.generate_table(&cf.name)?;
12471        if cf.has_parens {
12472            let func_multiline = self.config.pretty
12473                && matches!(
12474                    self.config.dialect,
12475                    Some(crate::dialects::DialectType::TSQL)
12476                        | Some(crate::dialects::DialectType::Fabric)
12477                )
12478                && !cf.parameters.is_empty();
12479            if func_multiline {
12480                self.write("(\n");
12481                self.indent_level += 2;
12482                self.write_indent();
12483                self.generate_function_parameters(&cf.parameters)?;
12484                self.write("\n");
12485                self.indent_level -= 2;
12486                self.write(")");
12487            } else {
12488                self.write("(");
12489                self.generate_function_parameters(&cf.parameters)?;
12490                self.write(")");
12491            }
12492        }
12493
12494        // Output RETURNS clause (always comes first after parameters)
12495        // BigQuery and TSQL use multiline formatting for CREATE FUNCTION structure
12496        let use_multiline = self.config.pretty
12497            && matches!(
12498                self.config.dialect,
12499                Some(crate::dialects::DialectType::BigQuery)
12500                    | Some(crate::dialects::DialectType::TSQL)
12501                    | Some(crate::dialects::DialectType::Fabric)
12502            );
12503
12504        if cf.language_first {
12505            // LANGUAGE first, then SQL data access, then RETURNS
12506            if let Some(lang) = &cf.language {
12507                if use_multiline {
12508                    self.write_newline();
12509                } else {
12510                    self.write_space();
12511                }
12512                self.write_keyword("LANGUAGE");
12513                self.write_space();
12514                self.write(lang);
12515            }
12516
12517            // SQL data access comes after LANGUAGE in this case
12518            if let Some(sql_data) = &cf.sql_data_access {
12519                self.write_space();
12520                match sql_data {
12521                    SqlDataAccess::NoSql => self.write_keyword("NO SQL"),
12522                    SqlDataAccess::ContainsSql => self.write_keyword("CONTAINS SQL"),
12523                    SqlDataAccess::ReadsSqlData => self.write_keyword("READS SQL DATA"),
12524                    SqlDataAccess::ModifiesSqlData => self.write_keyword("MODIFIES SQL DATA"),
12525                }
12526            }
12527
12528            if let Some(ref rtb) = cf.returns_table_body {
12529                if use_multiline {
12530                    self.write_newline();
12531                } else {
12532                    self.write_space();
12533                }
12534                self.write_keyword("RETURNS");
12535                self.write_space();
12536                self.write(rtb);
12537            } else if let Some(return_type) = &cf.return_type {
12538                if use_multiline {
12539                    self.write_newline();
12540                } else {
12541                    self.write_space();
12542                }
12543                self.write_keyword("RETURNS");
12544                self.write_space();
12545                self.generate_data_type(return_type)?;
12546            }
12547        } else {
12548            // RETURNS first (default)
12549            // DuckDB macros: skip RETURNS output (empty marker in returns_table_body means TABLE return)
12550            let is_duckdb = matches!(
12551                self.config.dialect,
12552                Some(crate::dialects::DialectType::DuckDB)
12553            );
12554            if let Some(ref rtb) = cf.returns_table_body {
12555                if !(is_duckdb && rtb.is_empty()) {
12556                    if use_multiline {
12557                        self.write_newline();
12558                    } else {
12559                        self.write_space();
12560                    }
12561                    self.write_keyword("RETURNS");
12562                    self.write_space();
12563                    self.write(rtb);
12564                }
12565            } else if let Some(return_type) = &cf.return_type {
12566                // DuckDB: skip all RETURNS (DuckDB macros don't use RETURNS clause)
12567                if !is_duckdb {
12568                    let is_table_return = matches!(return_type, crate::expressions::DataType::Custom { ref name } if name.eq_ignore_ascii_case("TABLE"));
12569                    if use_multiline {
12570                        self.write_newline();
12571                    } else {
12572                        self.write_space();
12573                    }
12574                    self.write_keyword("RETURNS");
12575                    self.write_space();
12576                    if is_table_return {
12577                        self.write_keyword("TABLE");
12578                    } else {
12579                        self.generate_data_type(return_type)?;
12580                    }
12581                }
12582            }
12583        }
12584
12585        // If we have property_order, use it to output properties in original order
12586        if !cf.property_order.is_empty() {
12587            // For BigQuery, OPTIONS must come before AS - reorder if needed
12588            let is_bigquery = matches!(
12589                self.config.dialect,
12590                Some(crate::dialects::DialectType::BigQuery)
12591            );
12592            let property_order = if is_bigquery {
12593                // Move Options before As if both are present
12594                let mut reordered = Vec::new();
12595                let mut has_as = false;
12596                let mut has_options = false;
12597                for prop in &cf.property_order {
12598                    match prop {
12599                        FunctionPropertyKind::As => has_as = true,
12600                        FunctionPropertyKind::Options => has_options = true,
12601                        _ => {}
12602                    }
12603                }
12604                if has_as && has_options {
12605                    // Output all props except As and Options, then Options, then As
12606                    for prop in &cf.property_order {
12607                        if *prop != FunctionPropertyKind::As
12608                            && *prop != FunctionPropertyKind::Options
12609                        {
12610                            reordered.push(*prop);
12611                        }
12612                    }
12613                    reordered.push(FunctionPropertyKind::Options);
12614                    reordered.push(FunctionPropertyKind::As);
12615                    reordered
12616                } else {
12617                    cf.property_order.clone()
12618                }
12619            } else {
12620                cf.property_order.clone()
12621            };
12622
12623            for prop in &property_order {
12624                match prop {
12625                    FunctionPropertyKind::Set => {
12626                        self.generate_function_set_options(cf)?;
12627                    }
12628                    FunctionPropertyKind::As => {
12629                        self.generate_function_body(cf)?;
12630                    }
12631                    FunctionPropertyKind::Using => {
12632                        self.generate_function_using_resources(cf)?;
12633                    }
12634                    FunctionPropertyKind::Language => {
12635                        if !cf.language_first {
12636                            // Only output here if not already output above
12637                            if let Some(lang) = &cf.language {
12638                                // Only BigQuery uses multiline formatting
12639                                let use_multiline = self.config.pretty
12640                                    && matches!(
12641                                        self.config.dialect,
12642                                        Some(crate::dialects::DialectType::BigQuery)
12643                                    );
12644                                if use_multiline {
12645                                    self.write_newline();
12646                                } else {
12647                                    self.write_space();
12648                                }
12649                                self.write_keyword("LANGUAGE");
12650                                self.write_space();
12651                                self.write(lang);
12652                            }
12653                        }
12654                    }
12655                    FunctionPropertyKind::Determinism => {
12656                        self.generate_function_determinism(cf)?;
12657                    }
12658                    FunctionPropertyKind::NullInput => {
12659                        self.generate_function_null_input(cf)?;
12660                    }
12661                    FunctionPropertyKind::Security => {
12662                        self.generate_function_security(cf)?;
12663                    }
12664                    FunctionPropertyKind::SqlDataAccess => {
12665                        if !cf.language_first {
12666                            // Only output here if not already output above
12667                            self.generate_function_sql_data_access(cf)?;
12668                        }
12669                    }
12670                    FunctionPropertyKind::Options => {
12671                        if !cf.options.is_empty() {
12672                            self.write_space();
12673                            self.generate_options_clause(&cf.options)?;
12674                        }
12675                    }
12676                    FunctionPropertyKind::Environment => {
12677                        if !cf.environment.is_empty() {
12678                            self.write_space();
12679                            self.generate_environment_clause(&cf.environment)?;
12680                        }
12681                    }
12682                    FunctionPropertyKind::Handler => {
12683                        if let Some(ref h) = cf.handler {
12684                            self.write_space();
12685                            self.write_keyword("HANDLER");
12686                            if cf.handler_uses_eq {
12687                                self.write(" = ");
12688                            } else {
12689                                self.write_space();
12690                            }
12691                            self.write("'");
12692                            self.write(h);
12693                            self.write("'");
12694                        }
12695                    }
12696                    FunctionPropertyKind::RuntimeVersion => {
12697                        if let Some(ref runtime_version) = cf.runtime_version {
12698                            self.write_space();
12699                            self.write_keyword("RUNTIME_VERSION");
12700                            self.write("='");
12701                            self.write(runtime_version);
12702                            self.write("'");
12703                        }
12704                    }
12705                    FunctionPropertyKind::Packages => {
12706                        if let Some(ref packages) = cf.packages {
12707                            self.write_space();
12708                            self.write_keyword("PACKAGES");
12709                            self.write("=(");
12710                            for (i, package) in packages.iter().enumerate() {
12711                                if i > 0 {
12712                                    self.write(", ");
12713                                }
12714                                self.write("'");
12715                                self.write(package);
12716                                self.write("'");
12717                            }
12718                            self.write(")");
12719                        }
12720                    }
12721                    FunctionPropertyKind::ParameterStyle => {
12722                        if let Some(ref ps) = cf.parameter_style {
12723                            self.write_space();
12724                            self.write_keyword("PARAMETER STYLE");
12725                            self.write_space();
12726                            self.write_keyword(ps);
12727                        }
12728                    }
12729                }
12730            }
12731
12732            // Output OPTIONS if not tracked in property_order (legacy)
12733            if !cf.options.is_empty() && !cf.property_order.contains(&FunctionPropertyKind::Options)
12734            {
12735                self.write_space();
12736                self.generate_options_clause(&cf.options)?;
12737            }
12738
12739            // Output ENVIRONMENT if not tracked in property_order (legacy)
12740            if !cf.environment.is_empty()
12741                && !cf
12742                    .property_order
12743                    .contains(&FunctionPropertyKind::Environment)
12744            {
12745                self.write_space();
12746                self.generate_environment_clause(&cf.environment)?;
12747            }
12748        } else {
12749            // Legacy behavior when property_order is empty
12750            // BigQuery: DETERMINISTIC/NOT DETERMINISTIC comes before LANGUAGE
12751            if matches!(
12752                self.config.dialect,
12753                Some(crate::dialects::DialectType::BigQuery)
12754            ) {
12755                self.generate_function_determinism(cf)?;
12756            }
12757
12758            // Only BigQuery uses multiline formatting for CREATE FUNCTION structure
12759            let use_multiline = self.config.pretty
12760                && matches!(
12761                    self.config.dialect,
12762                    Some(crate::dialects::DialectType::BigQuery)
12763                );
12764
12765            if !cf.language_first {
12766                if let Some(lang) = &cf.language {
12767                    if use_multiline {
12768                        self.write_newline();
12769                    } else {
12770                        self.write_space();
12771                    }
12772                    self.write_keyword("LANGUAGE");
12773                    self.write_space();
12774                    self.write(lang);
12775                }
12776
12777                // SQL data access characteristic comes after LANGUAGE
12778                self.generate_function_sql_data_access(cf)?;
12779            }
12780
12781            // For non-BigQuery dialects, output DETERMINISTIC/IMMUTABLE/VOLATILE here
12782            if !matches!(
12783                self.config.dialect,
12784                Some(crate::dialects::DialectType::BigQuery)
12785            ) {
12786                self.generate_function_determinism(cf)?;
12787            }
12788
12789            self.generate_function_null_input(cf)?;
12790            self.generate_function_security(cf)?;
12791            self.generate_function_set_options(cf)?;
12792
12793            // BigQuery: OPTIONS (key=value, ...) - comes before AS
12794            if !cf.options.is_empty() {
12795                self.write_space();
12796                self.generate_options_clause(&cf.options)?;
12797            }
12798
12799            // Databricks: ENVIRONMENT (dependencies = '...', ...) - comes before AS
12800            if !cf.environment.is_empty() {
12801                self.write_space();
12802                self.generate_environment_clause(&cf.environment)?;
12803            }
12804
12805            if let Some(ref h) = cf.handler {
12806                self.write_space();
12807                self.write_keyword("HANDLER");
12808                if cf.handler_uses_eq {
12809                    self.write(" = ");
12810                } else {
12811                    self.write_space();
12812                }
12813                self.write("'");
12814                self.write(h);
12815                self.write("'");
12816            }
12817
12818            if let Some(ref runtime_version) = cf.runtime_version {
12819                self.write_space();
12820                self.write_keyword("RUNTIME_VERSION");
12821                self.write("='");
12822                self.write(runtime_version);
12823                self.write("'");
12824            }
12825
12826            if let Some(ref packages) = cf.packages {
12827                self.write_space();
12828                self.write_keyword("PACKAGES");
12829                self.write("=(");
12830                for (i, package) in packages.iter().enumerate() {
12831                    if i > 0 {
12832                        self.write(", ");
12833                    }
12834                    self.write("'");
12835                    self.write(package);
12836                    self.write("'");
12837                }
12838                self.write(")");
12839            }
12840
12841            self.generate_function_body(cf)?;
12842            self.generate_function_using_resources(cf)?;
12843        }
12844
12845        Ok(())
12846    }
12847
12848    /// Generate SET options for CREATE FUNCTION
12849    fn generate_function_set_options(&mut self, cf: &CreateFunction) -> Result<()> {
12850        for opt in &cf.set_options {
12851            self.write_space();
12852            self.write_keyword("SET");
12853            self.write_space();
12854            self.write(&opt.name);
12855            match &opt.value {
12856                FunctionSetValue::Value { value, use_to } => {
12857                    if *use_to {
12858                        self.write(" TO ");
12859                    } else {
12860                        self.write(" = ");
12861                    }
12862                    self.write(value);
12863                }
12864                FunctionSetValue::FromCurrent => {
12865                    self.write_space();
12866                    self.write_keyword("FROM CURRENT");
12867                }
12868            }
12869        }
12870        Ok(())
12871    }
12872
12873    fn generate_function_using_resources(&mut self, cf: &CreateFunction) -> Result<()> {
12874        if cf.using_resources.is_empty() {
12875            return Ok(());
12876        }
12877
12878        self.write_space();
12879        self.write_keyword("USING");
12880        for resource in &cf.using_resources {
12881            self.write_space();
12882            self.write_keyword(&resource.kind);
12883            self.write_space();
12884            self.generate_string_literal(&resource.uri)?;
12885        }
12886        Ok(())
12887    }
12888
12889    /// Generate function body (AS clause)
12890    fn generate_function_body(&mut self, cf: &CreateFunction) -> Result<()> {
12891        if let Some(body) = &cf.body {
12892            // AS stays on same line as previous content (e.g., LANGUAGE js AS)
12893            self.write_space();
12894            // Only BigQuery uses multiline formatting for CREATE FUNCTION body
12895            let use_multiline = self.config.pretty
12896                && matches!(
12897                    self.config.dialect,
12898                    Some(crate::dialects::DialectType::BigQuery)
12899                );
12900            match body {
12901                FunctionBody::Block(block) => {
12902                    self.write_keyword("AS");
12903                    if matches!(
12904                        self.config.dialect,
12905                        Some(crate::dialects::DialectType::TSQL)
12906                    ) {
12907                        self.write(" BEGIN ");
12908                        self.write(block);
12909                        self.write(" END");
12910                    } else if matches!(
12911                        self.config.dialect,
12912                        Some(crate::dialects::DialectType::PostgreSQL)
12913                    ) {
12914                        self.write(" $$");
12915                        self.write(block);
12916                        self.write("$$");
12917                    } else {
12918                        // Escape content for single-quoted output
12919                        let escaped = self.escape_block_for_single_quote(block);
12920                        // In BigQuery pretty mode, body content goes on new line
12921                        if use_multiline {
12922                            self.write_newline();
12923                        } else {
12924                            self.write(" ");
12925                        }
12926                        self.write("'");
12927                        self.write(&escaped);
12928                        self.write("'");
12929                    }
12930                }
12931                FunctionBody::StringLiteral(s) => {
12932                    self.write_keyword("AS");
12933                    // In BigQuery pretty mode, body content goes on new line
12934                    if use_multiline {
12935                        self.write_newline();
12936                    } else {
12937                        self.write(" ");
12938                    }
12939                    self.write("'");
12940                    self.write(s);
12941                    self.write("'");
12942                }
12943                FunctionBody::Expression(expr) => {
12944                    self.write_keyword("AS");
12945                    self.write_space();
12946                    self.generate_expression(expr)?;
12947                }
12948                FunctionBody::External(name) => {
12949                    self.write_keyword("EXTERNAL NAME");
12950                    self.write(" '");
12951                    self.write(name);
12952                    self.write("'");
12953                }
12954                FunctionBody::Return(expr) => {
12955                    if matches!(
12956                        self.config.dialect,
12957                        Some(crate::dialects::DialectType::DuckDB)
12958                    ) {
12959                        // DuckDB macro syntax: AS [TABLE] expression (no RETURN keyword)
12960                        self.write_keyword("AS");
12961                        self.write_space();
12962                        // Check both returns_table_body marker and return_type = Custom "TABLE"
12963                        let is_table_return = cf.returns_table_body.is_some()
12964                            || matches!(&cf.return_type, Some(crate::expressions::DataType::Custom { ref name }) if name.eq_ignore_ascii_case("TABLE"));
12965                        if is_table_return {
12966                            self.write_keyword("TABLE");
12967                            self.write_space();
12968                        }
12969                        self.generate_expression(expr)?;
12970                    } else {
12971                        if self.config.create_function_return_as {
12972                            self.write_keyword("AS");
12973                            // TSQL pretty: newline between AS and RETURN
12974                            if self.config.pretty
12975                                && matches!(
12976                                    self.config.dialect,
12977                                    Some(crate::dialects::DialectType::TSQL)
12978                                        | Some(crate::dialects::DialectType::Fabric)
12979                                )
12980                            {
12981                                self.write_newline();
12982                            } else {
12983                                self.write_space();
12984                            }
12985                        }
12986                        self.write_keyword("RETURN");
12987                        self.write_space();
12988                        self.generate_expression(expr)?;
12989                    }
12990                }
12991                FunctionBody::Statements(stmts) => {
12992                    self.write_keyword("AS");
12993                    self.write(" BEGIN ");
12994                    for (i, stmt) in stmts.iter().enumerate() {
12995                        if i > 0 {
12996                            self.write(" ");
12997                        }
12998                        self.generate_expression(stmt)?;
12999                        self.write(";");
13000                    }
13001                    self.write(" END");
13002                }
13003                FunctionBody::RawBlock(text) => {
13004                    self.write_newline();
13005                    self.write(text);
13006                }
13007                FunctionBody::DollarQuoted { content, tag } => {
13008                    self.write_keyword("AS");
13009                    self.write(" ");
13010                    // Dialects that support dollar-quoted strings: PostgreSQL, Databricks, Redshift, DuckDB
13011                    let supports_dollar_quoting = matches!(
13012                        self.config.dialect,
13013                        Some(crate::dialects::DialectType::PostgreSQL)
13014                            | Some(crate::dialects::DialectType::Databricks)
13015                            | Some(crate::dialects::DialectType::Redshift)
13016                            | Some(crate::dialects::DialectType::DuckDB)
13017                    );
13018                    if supports_dollar_quoting {
13019                        // Output in dollar-quoted format
13020                        self.write("$");
13021                        if let Some(t) = tag {
13022                            self.write(t);
13023                        }
13024                        self.write("$");
13025                        self.write(content);
13026                        self.write("$");
13027                        if let Some(t) = tag {
13028                            self.write(t);
13029                        }
13030                        self.write("$");
13031                    } else {
13032                        // Convert to single-quoted string for other dialects
13033                        let escaped = self.escape_block_for_single_quote(content);
13034                        self.write("'");
13035                        self.write(&escaped);
13036                        self.write("'");
13037                    }
13038                }
13039            }
13040        }
13041        Ok(())
13042    }
13043
13044    /// Generate determinism clause (IMMUTABLE/VOLATILE/DETERMINISTIC)
13045    fn generate_function_determinism(&mut self, cf: &CreateFunction) -> Result<()> {
13046        if let Some(det) = cf.deterministic {
13047            self.write_space();
13048            if matches!(
13049                self.config.dialect,
13050                Some(crate::dialects::DialectType::BigQuery)
13051            ) {
13052                // BigQuery uses DETERMINISTIC/NOT DETERMINISTIC
13053                if det {
13054                    self.write_keyword("DETERMINISTIC");
13055                } else {
13056                    self.write_keyword("NOT DETERMINISTIC");
13057                }
13058            } else {
13059                // PostgreSQL and others use IMMUTABLE/VOLATILE
13060                if det {
13061                    self.write_keyword("IMMUTABLE");
13062                } else {
13063                    self.write_keyword("VOLATILE");
13064                }
13065            }
13066        }
13067        Ok(())
13068    }
13069
13070    /// Generate null input handling clause
13071    fn generate_function_null_input(&mut self, cf: &CreateFunction) -> Result<()> {
13072        if let Some(returns_null) = cf.returns_null_on_null_input {
13073            self.write_space();
13074            if returns_null {
13075                if cf.strict {
13076                    self.write_keyword("STRICT");
13077                } else {
13078                    self.write_keyword("RETURNS NULL ON NULL INPUT");
13079                }
13080            } else {
13081                self.write_keyword("CALLED ON NULL INPUT");
13082            }
13083        }
13084        Ok(())
13085    }
13086
13087    /// Generate security clause
13088    fn generate_function_security(&mut self, cf: &CreateFunction) -> Result<()> {
13089        if let Some(security) = &cf.security {
13090            self.write_space();
13091            // MySQL uses SQL SECURITY prefix
13092            if matches!(
13093                self.config.dialect,
13094                Some(crate::dialects::DialectType::MySQL)
13095            ) {
13096                self.write_keyword("SQL SECURITY");
13097            } else {
13098                self.write_keyword("SECURITY");
13099            }
13100            self.write_space();
13101            match security {
13102                FunctionSecurity::Definer => self.write_keyword("DEFINER"),
13103                FunctionSecurity::Invoker => self.write_keyword("INVOKER"),
13104                FunctionSecurity::None => self.write_keyword("NONE"),
13105            }
13106        }
13107        Ok(())
13108    }
13109
13110    /// Generate SQL data access clause
13111    fn generate_function_sql_data_access(&mut self, cf: &CreateFunction) -> Result<()> {
13112        if let Some(sql_data) = &cf.sql_data_access {
13113            self.write_space();
13114            match sql_data {
13115                SqlDataAccess::NoSql => self.write_keyword("NO SQL"),
13116                SqlDataAccess::ContainsSql => self.write_keyword("CONTAINS SQL"),
13117                SqlDataAccess::ReadsSqlData => self.write_keyword("READS SQL DATA"),
13118                SqlDataAccess::ModifiesSqlData => self.write_keyword("MODIFIES SQL DATA"),
13119            }
13120        }
13121        Ok(())
13122    }
13123
13124    fn generate_function_parameters(&mut self, params: &[FunctionParameter]) -> Result<()> {
13125        for (i, param) in params.iter().enumerate() {
13126            if i > 0 {
13127                self.write(", ");
13128            }
13129
13130            if let Some(mode) = &param.mode {
13131                if let Some(text) = &param.mode_text {
13132                    self.write(text);
13133                } else {
13134                    match mode {
13135                        ParameterMode::In => self.write_keyword("IN"),
13136                        ParameterMode::Out => self.write_keyword("OUT"),
13137                        ParameterMode::InOut => self.write_keyword("INOUT"),
13138                        ParameterMode::Variadic => self.write_keyword("VARIADIC"),
13139                    }
13140                }
13141                self.write_space();
13142            }
13143
13144            if let Some(name) = &param.name {
13145                self.generate_identifier(name)?;
13146                // Skip space and type for empty Custom types (e.g., DuckDB macros)
13147                let skip_type =
13148                    matches!(&param.data_type, DataType::Custom { name } if name.is_empty());
13149                if !skip_type {
13150                    self.write_space();
13151                    self.generate_data_type(&param.data_type)?;
13152                }
13153            } else {
13154                self.generate_data_type(&param.data_type)?;
13155            }
13156
13157            if let Some(default) = &param.default {
13158                if self.config.parameter_default_equals {
13159                    self.write(" = ");
13160                } else {
13161                    self.write(" DEFAULT ");
13162                }
13163                self.generate_expression(default)?;
13164            }
13165        }
13166
13167        Ok(())
13168    }
13169
13170    fn generate_drop_function(&mut self, df: &DropFunction) -> Result<()> {
13171        self.write_keyword("DROP FUNCTION");
13172
13173        if df.if_exists {
13174            self.write_space();
13175            self.write_keyword("IF EXISTS");
13176        }
13177
13178        self.write_space();
13179        self.generate_table(&df.name)?;
13180
13181        if let Some(params) = &df.parameters {
13182            self.write(" (");
13183            for (i, dt) in params.iter().enumerate() {
13184                if i > 0 {
13185                    self.write(", ");
13186                }
13187                self.generate_data_type(dt)?;
13188            }
13189            self.write(")");
13190        }
13191
13192        if df.cascade {
13193            self.write_space();
13194            self.write_keyword("CASCADE");
13195        }
13196
13197        Ok(())
13198    }
13199
13200    fn generate_create_procedure(&mut self, cp: &CreateProcedure) -> Result<()> {
13201        self.write_keyword("CREATE");
13202
13203        if cp.or_alter {
13204            self.write_space();
13205            self.write_keyword("OR ALTER");
13206        } else if cp.or_replace {
13207            self.write_space();
13208            self.write_keyword("OR REPLACE");
13209        }
13210
13211        self.write_space();
13212        if cp.use_proc_keyword {
13213            self.write_keyword("PROC");
13214        } else {
13215            self.write_keyword("PROCEDURE");
13216        }
13217
13218        if cp.if_not_exists {
13219            self.write_space();
13220            self.write_keyword("IF NOT EXISTS");
13221        }
13222
13223        self.write_space();
13224        self.generate_table(&cp.name)?;
13225        if cp.has_parens {
13226            self.write("(");
13227            self.generate_function_parameters(&cp.parameters)?;
13228            self.write(")");
13229        } else if !cp.parameters.is_empty() {
13230            // TSQL: unparenthesized parameters
13231            self.write_space();
13232            self.generate_function_parameters(&cp.parameters)?;
13233        }
13234
13235        // RETURNS clause (Snowflake)
13236        if let Some(return_type) = &cp.return_type {
13237            self.write_space();
13238            self.write_keyword("RETURNS");
13239            self.write_space();
13240            self.generate_data_type(return_type)?;
13241        }
13242
13243        // EXECUTE AS clause (Snowflake)
13244        if let Some(execute_as) = &cp.execute_as {
13245            self.write_space();
13246            self.write_keyword("EXECUTE AS");
13247            self.write_space();
13248            self.write_keyword(execute_as);
13249        }
13250
13251        if let Some(lang) = &cp.language {
13252            self.write_space();
13253            self.write_keyword("LANGUAGE");
13254            self.write_space();
13255            self.write(lang);
13256        }
13257
13258        if let Some(security) = &cp.security {
13259            self.write_space();
13260            self.write_keyword("SECURITY");
13261            self.write_space();
13262            match security {
13263                FunctionSecurity::Definer => self.write_keyword("DEFINER"),
13264                FunctionSecurity::Invoker => self.write_keyword("INVOKER"),
13265                FunctionSecurity::None => self.write_keyword("NONE"),
13266            }
13267        }
13268
13269        // TSQL WITH options (ENCRYPTION, RECOMPILE, etc.)
13270        if !cp.with_options.is_empty() {
13271            self.write_space();
13272            self.write_keyword("WITH");
13273            self.write_space();
13274            for (i, opt) in cp.with_options.iter().enumerate() {
13275                if i > 0 {
13276                    self.write(", ");
13277                }
13278                self.write(opt);
13279            }
13280        }
13281
13282        if let Some(body) = &cp.body {
13283            self.write_space();
13284            match body {
13285                FunctionBody::Block(block) => {
13286                    self.write_keyword("AS");
13287                    if matches!(
13288                        self.config.dialect,
13289                        Some(crate::dialects::DialectType::TSQL)
13290                    ) {
13291                        self.write(" BEGIN ");
13292                        self.write(block);
13293                        self.write(" END");
13294                    } else if matches!(
13295                        self.config.dialect,
13296                        Some(crate::dialects::DialectType::PostgreSQL)
13297                    ) {
13298                        self.write(" $$");
13299                        self.write(block);
13300                        self.write("$$");
13301                    } else {
13302                        // Escape content for single-quoted output
13303                        let escaped = self.escape_block_for_single_quote(block);
13304                        self.write(" '");
13305                        self.write(&escaped);
13306                        self.write("'");
13307                    }
13308                }
13309                FunctionBody::StringLiteral(s) => {
13310                    self.write_keyword("AS");
13311                    self.write(" '");
13312                    self.write(s);
13313                    self.write("'");
13314                }
13315                FunctionBody::Expression(expr) => {
13316                    self.write_keyword("AS");
13317                    self.write_space();
13318                    self.generate_expression(expr)?;
13319                }
13320                FunctionBody::External(name) => {
13321                    self.write_keyword("EXTERNAL NAME");
13322                    self.write(" '");
13323                    self.write(name);
13324                    self.write("'");
13325                }
13326                FunctionBody::Return(expr) => {
13327                    self.write_keyword("RETURN");
13328                    self.write_space();
13329                    self.generate_expression(expr)?;
13330                }
13331                FunctionBody::Statements(stmts) => {
13332                    self.write_keyword("AS");
13333                    self.write(" BEGIN ");
13334                    for (i, stmt) in stmts.iter().enumerate() {
13335                        if i > 0 {
13336                            self.write(" ");
13337                        }
13338                        self.generate_expression(stmt)?;
13339                        self.write(";");
13340                    }
13341                    self.write(" END");
13342                }
13343                FunctionBody::RawBlock(text) => {
13344                    self.write_newline();
13345                    self.write(text);
13346                }
13347                FunctionBody::DollarQuoted { content, tag } => {
13348                    self.write_keyword("AS");
13349                    self.write(" ");
13350                    // Dialects that support dollar-quoted strings: PostgreSQL, Databricks, Redshift, DuckDB
13351                    let supports_dollar_quoting = matches!(
13352                        self.config.dialect,
13353                        Some(crate::dialects::DialectType::PostgreSQL)
13354                            | Some(crate::dialects::DialectType::Databricks)
13355                            | Some(crate::dialects::DialectType::Redshift)
13356                            | Some(crate::dialects::DialectType::DuckDB)
13357                    );
13358                    if supports_dollar_quoting {
13359                        // Output in dollar-quoted format
13360                        self.write("$");
13361                        if let Some(t) = tag {
13362                            self.write(t);
13363                        }
13364                        self.write("$");
13365                        self.write(content);
13366                        self.write("$");
13367                        if let Some(t) = tag {
13368                            self.write(t);
13369                        }
13370                        self.write("$");
13371                    } else {
13372                        // Convert to single-quoted string for other dialects
13373                        let escaped = self.escape_block_for_single_quote(content);
13374                        self.write("'");
13375                        self.write(&escaped);
13376                        self.write("'");
13377                    }
13378                }
13379            }
13380        }
13381
13382        Ok(())
13383    }
13384
13385    fn generate_drop_procedure(&mut self, dp: &DropProcedure) -> Result<()> {
13386        self.write_keyword("DROP PROCEDURE");
13387
13388        if dp.if_exists {
13389            self.write_space();
13390            self.write_keyword("IF EXISTS");
13391        }
13392
13393        self.write_space();
13394        self.generate_table(&dp.name)?;
13395
13396        if let Some(params) = &dp.parameters {
13397            self.write(" (");
13398            for (i, dt) in params.iter().enumerate() {
13399                if i > 0 {
13400                    self.write(", ");
13401                }
13402                self.generate_data_type(dt)?;
13403            }
13404            self.write(")");
13405        }
13406
13407        if dp.cascade {
13408            self.write_space();
13409            self.write_keyword("CASCADE");
13410        }
13411
13412        Ok(())
13413    }
13414
13415    fn generate_create_sequence(&mut self, cs: &CreateSequence) -> Result<()> {
13416        self.write_keyword("CREATE");
13417
13418        if cs.or_replace {
13419            self.write_space();
13420            self.write_keyword("OR REPLACE");
13421        }
13422
13423        if cs.temporary {
13424            self.write_space();
13425            self.write_keyword("TEMPORARY");
13426        }
13427
13428        self.write_space();
13429        self.write_keyword("SEQUENCE");
13430
13431        if cs.if_not_exists {
13432            self.write_space();
13433            self.write_keyword("IF NOT EXISTS");
13434        }
13435
13436        self.write_space();
13437        self.generate_table(&cs.name)?;
13438
13439        // Output AS <type> if present
13440        if let Some(as_type) = &cs.as_type {
13441            self.write_space();
13442            self.write_keyword("AS");
13443            self.write_space();
13444            self.generate_data_type(as_type)?;
13445        }
13446
13447        // Output COMMENT first (Snowflake convention: COMMENT comes before other properties)
13448        if let Some(comment) = &cs.comment {
13449            self.write_space();
13450            self.write_keyword("COMMENT");
13451            self.write("=");
13452            self.generate_string_literal(comment)?;
13453        }
13454
13455        // If property_order is available, use it to preserve original order
13456        if !cs.property_order.is_empty() {
13457            for prop in &cs.property_order {
13458                match prop {
13459                    SeqPropKind::Start => {
13460                        if let Some(start) = cs.start {
13461                            self.write_space();
13462                            self.write_keyword("START WITH");
13463                            self.write(&format!(" {}", start));
13464                        }
13465                    }
13466                    SeqPropKind::Increment => {
13467                        if let Some(inc) = cs.increment {
13468                            self.write_space();
13469                            self.write_keyword("INCREMENT BY");
13470                            self.write(&format!(" {}", inc));
13471                        }
13472                    }
13473                    SeqPropKind::Minvalue => {
13474                        if let Some(min) = &cs.minvalue {
13475                            self.write_space();
13476                            match min {
13477                                SequenceBound::Value(v) => {
13478                                    self.write_keyword("MINVALUE");
13479                                    self.write(&format!(" {}", v));
13480                                }
13481                                SequenceBound::None => {
13482                                    self.write_keyword("NO MINVALUE");
13483                                }
13484                            }
13485                        }
13486                    }
13487                    SeqPropKind::Maxvalue => {
13488                        if let Some(max) = &cs.maxvalue {
13489                            self.write_space();
13490                            match max {
13491                                SequenceBound::Value(v) => {
13492                                    self.write_keyword("MAXVALUE");
13493                                    self.write(&format!(" {}", v));
13494                                }
13495                                SequenceBound::None => {
13496                                    self.write_keyword("NO MAXVALUE");
13497                                }
13498                            }
13499                        }
13500                    }
13501                    SeqPropKind::Cache => {
13502                        if let Some(cache) = cs.cache {
13503                            self.write_space();
13504                            self.write_keyword("CACHE");
13505                            self.write(&format!(" {}", cache));
13506                        }
13507                    }
13508                    SeqPropKind::NoCache => {
13509                        self.write_space();
13510                        self.write_keyword("NO CACHE");
13511                    }
13512                    SeqPropKind::NoCacheWord => {
13513                        self.write_space();
13514                        self.write_keyword("NOCACHE");
13515                    }
13516                    SeqPropKind::Cycle => {
13517                        self.write_space();
13518                        self.write_keyword("CYCLE");
13519                    }
13520                    SeqPropKind::NoCycle => {
13521                        self.write_space();
13522                        self.write_keyword("NO CYCLE");
13523                    }
13524                    SeqPropKind::NoCycleWord => {
13525                        self.write_space();
13526                        self.write_keyword("NOCYCLE");
13527                    }
13528                    SeqPropKind::OwnedBy => {
13529                        // Skip OWNED BY NONE (it's a no-op)
13530                        if !cs.owned_by_none {
13531                            if let Some(owned) = &cs.owned_by {
13532                                self.write_space();
13533                                self.write_keyword("OWNED BY");
13534                                self.write_space();
13535                                self.generate_table(owned)?;
13536                            }
13537                        }
13538                    }
13539                    SeqPropKind::Order => {
13540                        self.write_space();
13541                        self.write_keyword("ORDER");
13542                    }
13543                    SeqPropKind::NoOrder => {
13544                        self.write_space();
13545                        self.write_keyword("NOORDER");
13546                    }
13547                    SeqPropKind::Comment => {
13548                        // COMMENT is output above, before property_order iteration
13549                    }
13550                    SeqPropKind::Sharing => {
13551                        if let Some(val) = &cs.sharing {
13552                            self.write_space();
13553                            self.write(&format!("SHARING={}", val));
13554                        }
13555                    }
13556                    SeqPropKind::Keep => {
13557                        self.write_space();
13558                        self.write_keyword("KEEP");
13559                    }
13560                    SeqPropKind::NoKeep => {
13561                        self.write_space();
13562                        self.write_keyword("NOKEEP");
13563                    }
13564                    SeqPropKind::Scale => {
13565                        self.write_space();
13566                        self.write_keyword("SCALE");
13567                        if let Some(modifier) = &cs.scale_modifier {
13568                            if !modifier.is_empty() {
13569                                self.write_space();
13570                                self.write_keyword(modifier);
13571                            }
13572                        }
13573                    }
13574                    SeqPropKind::NoScale => {
13575                        self.write_space();
13576                        self.write_keyword("NOSCALE");
13577                    }
13578                    SeqPropKind::Shard => {
13579                        self.write_space();
13580                        self.write_keyword("SHARD");
13581                        if let Some(modifier) = &cs.shard_modifier {
13582                            if !modifier.is_empty() {
13583                                self.write_space();
13584                                self.write_keyword(modifier);
13585                            }
13586                        }
13587                    }
13588                    SeqPropKind::NoShard => {
13589                        self.write_space();
13590                        self.write_keyword("NOSHARD");
13591                    }
13592                    SeqPropKind::Session => {
13593                        self.write_space();
13594                        self.write_keyword("SESSION");
13595                    }
13596                    SeqPropKind::Global => {
13597                        self.write_space();
13598                        self.write_keyword("GLOBAL");
13599                    }
13600                    SeqPropKind::NoMinvalueWord => {
13601                        self.write_space();
13602                        self.write_keyword("NOMINVALUE");
13603                    }
13604                    SeqPropKind::NoMaxvalueWord => {
13605                        self.write_space();
13606                        self.write_keyword("NOMAXVALUE");
13607                    }
13608                }
13609            }
13610        } else {
13611            // Fallback: default order for backwards compatibility
13612            if let Some(inc) = cs.increment {
13613                self.write_space();
13614                self.write_keyword("INCREMENT BY");
13615                self.write(&format!(" {}", inc));
13616            }
13617
13618            if let Some(min) = &cs.minvalue {
13619                self.write_space();
13620                match min {
13621                    SequenceBound::Value(v) => {
13622                        self.write_keyword("MINVALUE");
13623                        self.write(&format!(" {}", v));
13624                    }
13625                    SequenceBound::None => {
13626                        self.write_keyword("NO MINVALUE");
13627                    }
13628                }
13629            }
13630
13631            if let Some(max) = &cs.maxvalue {
13632                self.write_space();
13633                match max {
13634                    SequenceBound::Value(v) => {
13635                        self.write_keyword("MAXVALUE");
13636                        self.write(&format!(" {}", v));
13637                    }
13638                    SequenceBound::None => {
13639                        self.write_keyword("NO MAXVALUE");
13640                    }
13641                }
13642            }
13643
13644            if let Some(start) = cs.start {
13645                self.write_space();
13646                self.write_keyword("START WITH");
13647                self.write(&format!(" {}", start));
13648            }
13649
13650            if let Some(cache) = cs.cache {
13651                self.write_space();
13652                self.write_keyword("CACHE");
13653                self.write(&format!(" {}", cache));
13654            }
13655
13656            if cs.cycle {
13657                self.write_space();
13658                self.write_keyword("CYCLE");
13659            }
13660
13661            if let Some(owned) = &cs.owned_by {
13662                self.write_space();
13663                self.write_keyword("OWNED BY");
13664                self.write_space();
13665                self.generate_table(owned)?;
13666            }
13667        }
13668
13669        Ok(())
13670    }
13671
13672    fn generate_drop_sequence(&mut self, ds: &DropSequence) -> Result<()> {
13673        self.write_keyword("DROP SEQUENCE");
13674
13675        if ds.if_exists {
13676            self.write_space();
13677            self.write_keyword("IF EXISTS");
13678        }
13679
13680        self.write_space();
13681        self.generate_table(&ds.name)?;
13682
13683        if ds.cascade {
13684            self.write_space();
13685            self.write_keyword("CASCADE");
13686        }
13687
13688        Ok(())
13689    }
13690
13691    fn generate_alter_sequence(&mut self, als: &AlterSequence) -> Result<()> {
13692        self.write_keyword("ALTER SEQUENCE");
13693
13694        if als.if_exists {
13695            self.write_space();
13696            self.write_keyword("IF EXISTS");
13697        }
13698
13699        self.write_space();
13700        self.generate_table(&als.name)?;
13701
13702        if let Some(inc) = als.increment {
13703            self.write_space();
13704            self.write_keyword("INCREMENT BY");
13705            self.write(&format!(" {}", inc));
13706        }
13707
13708        if let Some(min) = &als.minvalue {
13709            self.write_space();
13710            match min {
13711                SequenceBound::Value(v) => {
13712                    self.write_keyword("MINVALUE");
13713                    self.write(&format!(" {}", v));
13714                }
13715                SequenceBound::None => {
13716                    self.write_keyword("NO MINVALUE");
13717                }
13718            }
13719        }
13720
13721        if let Some(max) = &als.maxvalue {
13722            self.write_space();
13723            match max {
13724                SequenceBound::Value(v) => {
13725                    self.write_keyword("MAXVALUE");
13726                    self.write(&format!(" {}", v));
13727                }
13728                SequenceBound::None => {
13729                    self.write_keyword("NO MAXVALUE");
13730                }
13731            }
13732        }
13733
13734        if let Some(start) = als.start {
13735            self.write_space();
13736            self.write_keyword("START WITH");
13737            self.write(&format!(" {}", start));
13738        }
13739
13740        if let Some(restart) = &als.restart {
13741            self.write_space();
13742            self.write_keyword("RESTART");
13743            if let Some(val) = restart {
13744                self.write_keyword(" WITH");
13745                self.write(&format!(" {}", val));
13746            }
13747        }
13748
13749        if let Some(cache) = als.cache {
13750            self.write_space();
13751            self.write_keyword("CACHE");
13752            self.write(&format!(" {}", cache));
13753        }
13754
13755        if let Some(cycle) = als.cycle {
13756            self.write_space();
13757            if cycle {
13758                self.write_keyword("CYCLE");
13759            } else {
13760                self.write_keyword("NO CYCLE");
13761            }
13762        }
13763
13764        if let Some(owned) = &als.owned_by {
13765            self.write_space();
13766            self.write_keyword("OWNED BY");
13767            self.write_space();
13768            if let Some(table) = owned {
13769                self.generate_table(table)?;
13770            } else {
13771                self.write_keyword("NONE");
13772            }
13773        }
13774
13775        Ok(())
13776    }
13777
13778    fn generate_create_trigger(&mut self, ct: &CreateTrigger) -> Result<()> {
13779        self.write_keyword("CREATE");
13780
13781        if ct.or_alter {
13782            self.write_space();
13783            self.write_keyword("OR ALTER");
13784        } else if ct.or_replace {
13785            self.write_space();
13786            self.write_keyword("OR REPLACE");
13787        }
13788
13789        if ct.constraint {
13790            self.write_space();
13791            self.write_keyword("CONSTRAINT");
13792        }
13793
13794        self.write_space();
13795        self.write_keyword("TRIGGER");
13796        self.write_space();
13797        self.generate_identifier(&ct.name)?;
13798
13799        self.write_space();
13800        match ct.timing {
13801            TriggerTiming::Before => self.write_keyword("BEFORE"),
13802            TriggerTiming::After => self.write_keyword("AFTER"),
13803            TriggerTiming::InsteadOf => self.write_keyword("INSTEAD OF"),
13804        }
13805
13806        // Events
13807        for (i, event) in ct.events.iter().enumerate() {
13808            if i > 0 {
13809                self.write_keyword(" OR");
13810            }
13811            self.write_space();
13812            match event {
13813                TriggerEvent::Insert => self.write_keyword("INSERT"),
13814                TriggerEvent::Update(cols) => {
13815                    self.write_keyword("UPDATE");
13816                    if let Some(cols) = cols {
13817                        self.write_space();
13818                        self.write_keyword("OF");
13819                        for (j, col) in cols.iter().enumerate() {
13820                            if j > 0 {
13821                                self.write(",");
13822                            }
13823                            self.write_space();
13824                            self.generate_identifier(col)?;
13825                        }
13826                    }
13827                }
13828                TriggerEvent::Delete => self.write_keyword("DELETE"),
13829                TriggerEvent::Truncate => self.write_keyword("TRUNCATE"),
13830            }
13831        }
13832
13833        self.write_space();
13834        self.write_keyword("ON");
13835        self.write_space();
13836        self.generate_table(&ct.table)?;
13837
13838        // Referencing clause
13839        if let Some(ref_clause) = &ct.referencing {
13840            self.write_space();
13841            self.write_keyword("REFERENCING");
13842            if let Some(old_table) = &ref_clause.old_table {
13843                self.write_space();
13844                self.write_keyword("OLD TABLE AS");
13845                self.write_space();
13846                self.generate_identifier(old_table)?;
13847            }
13848            if let Some(new_table) = &ref_clause.new_table {
13849                self.write_space();
13850                self.write_keyword("NEW TABLE AS");
13851                self.write_space();
13852                self.generate_identifier(new_table)?;
13853            }
13854            if let Some(old_row) = &ref_clause.old_row {
13855                self.write_space();
13856                self.write_keyword("OLD ROW AS");
13857                self.write_space();
13858                self.generate_identifier(old_row)?;
13859            }
13860            if let Some(new_row) = &ref_clause.new_row {
13861                self.write_space();
13862                self.write_keyword("NEW ROW AS");
13863                self.write_space();
13864                self.generate_identifier(new_row)?;
13865            }
13866        }
13867
13868        // Deferrable options for constraint triggers (must come before FOR EACH)
13869        if let Some(deferrable) = ct.deferrable {
13870            self.write_space();
13871            if deferrable {
13872                self.write_keyword("DEFERRABLE");
13873            } else {
13874                self.write_keyword("NOT DEFERRABLE");
13875            }
13876        }
13877
13878        if let Some(initially) = ct.initially_deferred {
13879            self.write_space();
13880            self.write_keyword("INITIALLY");
13881            self.write_space();
13882            if initially {
13883                self.write_keyword("DEFERRED");
13884            } else {
13885                self.write_keyword("IMMEDIATE");
13886            }
13887        }
13888
13889        if let Some(for_each) = ct.for_each {
13890            self.write_space();
13891            self.write_keyword("FOR EACH");
13892            self.write_space();
13893            match for_each {
13894                TriggerForEach::Row => self.write_keyword("ROW"),
13895                TriggerForEach::Statement => self.write_keyword("STATEMENT"),
13896            }
13897        }
13898
13899        // When clause
13900        if let Some(when) = &ct.when {
13901            self.write_space();
13902            self.write_keyword("WHEN");
13903            if ct.when_paren {
13904                self.write(" (");
13905                self.generate_expression(when)?;
13906                self.write(")");
13907            } else {
13908                self.write_space();
13909                self.generate_expression(when)?;
13910            }
13911        }
13912
13913        // Body
13914        self.write_space();
13915        match &ct.body {
13916            TriggerBody::Execute { function, args } => {
13917                self.write_keyword("EXECUTE FUNCTION");
13918                self.write_space();
13919                self.generate_table(function)?;
13920                self.write("(");
13921                for (i, arg) in args.iter().enumerate() {
13922                    if i > 0 {
13923                        self.write(", ");
13924                    }
13925                    self.generate_expression(arg)?;
13926                }
13927                self.write(")");
13928            }
13929            TriggerBody::Block(block) => {
13930                self.write_keyword("BEGIN");
13931                self.write_space();
13932                self.write(block);
13933                self.write_space();
13934                self.write_keyword("END");
13935            }
13936        }
13937
13938        Ok(())
13939    }
13940
13941    fn generate_drop_trigger(&mut self, dt: &DropTrigger) -> Result<()> {
13942        self.write_keyword("DROP TRIGGER");
13943
13944        if dt.if_exists {
13945            self.write_space();
13946            self.write_keyword("IF EXISTS");
13947        }
13948
13949        self.write_space();
13950        self.generate_identifier(&dt.name)?;
13951
13952        if let Some(table) = &dt.table {
13953            self.write_space();
13954            self.write_keyword("ON");
13955            self.write_space();
13956            self.generate_table(table)?;
13957        }
13958
13959        if dt.cascade {
13960            self.write_space();
13961            self.write_keyword("CASCADE");
13962        }
13963
13964        Ok(())
13965    }
13966
13967    fn generate_create_type(&mut self, ct: &CreateType) -> Result<()> {
13968        self.write_keyword("CREATE TYPE");
13969
13970        if ct.if_not_exists {
13971            self.write_space();
13972            self.write_keyword("IF NOT EXISTS");
13973        }
13974
13975        self.write_space();
13976        self.generate_table(&ct.name)?;
13977
13978        self.write_space();
13979        self.write_keyword("AS");
13980        self.write_space();
13981
13982        match &ct.definition {
13983            TypeDefinition::Enum(values) => {
13984                self.write_keyword("ENUM");
13985                self.write(" (");
13986                for (i, val) in values.iter().enumerate() {
13987                    if i > 0 {
13988                        self.write(", ");
13989                    }
13990                    self.write(&format!("'{}'", val));
13991                }
13992                self.write(")");
13993            }
13994            TypeDefinition::Composite(attrs) => {
13995                self.write("(");
13996                for (i, attr) in attrs.iter().enumerate() {
13997                    if i > 0 {
13998                        self.write(", ");
13999                    }
14000                    self.generate_identifier(&attr.name)?;
14001                    self.write_space();
14002                    self.generate_data_type(&attr.data_type)?;
14003                    if let Some(collate) = &attr.collate {
14004                        self.write_space();
14005                        self.write_keyword("COLLATE");
14006                        self.write_space();
14007                        self.generate_identifier(collate)?;
14008                    }
14009                }
14010                self.write(")");
14011            }
14012            TypeDefinition::Range {
14013                subtype,
14014                subtype_diff,
14015                canonical,
14016            } => {
14017                self.write_keyword("RANGE");
14018                self.write(" (");
14019                self.write_keyword("SUBTYPE");
14020                self.write(" = ");
14021                self.generate_data_type(subtype)?;
14022                if let Some(diff) = subtype_diff {
14023                    self.write(", ");
14024                    self.write_keyword("SUBTYPE_DIFF");
14025                    self.write(" = ");
14026                    self.write(diff);
14027                }
14028                if let Some(canon) = canonical {
14029                    self.write(", ");
14030                    self.write_keyword("CANONICAL");
14031                    self.write(" = ");
14032                    self.write(canon);
14033                }
14034                self.write(")");
14035            }
14036            TypeDefinition::Base {
14037                input,
14038                output,
14039                internallength,
14040            } => {
14041                self.write("(");
14042                self.write_keyword("INPUT");
14043                self.write(" = ");
14044                self.write(input);
14045                self.write(", ");
14046                self.write_keyword("OUTPUT");
14047                self.write(" = ");
14048                self.write(output);
14049                if let Some(len) = internallength {
14050                    self.write(", ");
14051                    self.write_keyword("INTERNALLENGTH");
14052                    self.write(" = ");
14053                    self.write(&len.to_string());
14054                }
14055                self.write(")");
14056            }
14057            TypeDefinition::Domain {
14058                base_type,
14059                default,
14060                constraints,
14061            } => {
14062                self.generate_data_type(base_type)?;
14063                if let Some(def) = default {
14064                    self.write_space();
14065                    self.write_keyword("DEFAULT");
14066                    self.write_space();
14067                    self.generate_expression(def)?;
14068                }
14069                for constr in constraints {
14070                    self.write_space();
14071                    if let Some(name) = &constr.name {
14072                        self.write_keyword("CONSTRAINT");
14073                        self.write_space();
14074                        self.generate_identifier(name)?;
14075                        self.write_space();
14076                    }
14077                    self.write_keyword("CHECK");
14078                    self.write(" (");
14079                    self.generate_expression(&constr.check)?;
14080                    self.write(")");
14081                }
14082            }
14083        }
14084
14085        Ok(())
14086    }
14087
14088    fn generate_create_task(&mut self, task: &crate::expressions::CreateTask) -> Result<()> {
14089        self.write_keyword("CREATE");
14090        if task.or_replace {
14091            self.write_space();
14092            self.write_keyword("OR REPLACE");
14093        }
14094        self.write_space();
14095        self.write_keyword("TASK");
14096        if task.if_not_exists {
14097            self.write_space();
14098            self.write_keyword("IF NOT EXISTS");
14099        }
14100        self.write_space();
14101        self.write(&task.name);
14102        if !task.properties.is_empty() {
14103            // Properties already include leading whitespace from tokens_to_sql
14104            if !task.properties.starts_with('\n') && !task.properties.starts_with(' ') {
14105                self.write_space();
14106            }
14107            self.write(&task.properties);
14108        }
14109        self.write_space();
14110        self.write_keyword("AS");
14111        self.write_space();
14112        self.generate_expression(&task.body)?;
14113        Ok(())
14114    }
14115
14116    fn generate_try_catch(&mut self, try_catch: &TryCatch) -> Result<()> {
14117        self.write_keyword("BEGIN TRY");
14118        self.generate_tsql_block_statements(&try_catch.try_body)?;
14119        self.write_keyword("END TRY");
14120
14121        if let Some(catch_body) = &try_catch.catch_body {
14122            if self.config.pretty {
14123                self.write_newline();
14124                self.write_indent();
14125            } else {
14126                self.write_space();
14127            }
14128            self.write_keyword("BEGIN CATCH");
14129            self.generate_tsql_block_statements(catch_body)?;
14130            self.write_keyword("END CATCH");
14131        }
14132
14133        Ok(())
14134    }
14135
14136    fn generate_tsql_block_statements(&mut self, statements: &[Expression]) -> Result<()> {
14137        if statements.is_empty() {
14138            self.write_space();
14139            return Ok(());
14140        }
14141
14142        if self.config.pretty {
14143            self.indent_level += 1;
14144            for stmt in statements {
14145                self.write_newline();
14146                self.write_indent();
14147                self.generate_expression(stmt)?;
14148                self.write(";");
14149            }
14150            self.indent_level -= 1;
14151            self.write_newline();
14152            self.write_indent();
14153        } else {
14154            self.write_space();
14155            for (i, stmt) in statements.iter().enumerate() {
14156                if i > 0 {
14157                    self.write_space();
14158                }
14159                self.generate_expression(stmt)?;
14160                self.write(";");
14161            }
14162            self.write_space();
14163        }
14164
14165        Ok(())
14166    }
14167
14168    fn generate_drop_type(&mut self, dt: &DropType) -> Result<()> {
14169        self.write_keyword("DROP TYPE");
14170
14171        if dt.if_exists {
14172            self.write_space();
14173            self.write_keyword("IF EXISTS");
14174        }
14175
14176        self.write_space();
14177        self.generate_table(&dt.name)?;
14178
14179        if dt.cascade {
14180            self.write_space();
14181            self.write_keyword("CASCADE");
14182        }
14183
14184        Ok(())
14185    }
14186
14187    fn generate_describe(&mut self, d: &Describe) -> Result<()> {
14188        // Athena: DESCRIBE uses Hive engine (backticks)
14189        let saved_athena_hive_context = self.athena_hive_context;
14190        if matches!(
14191            self.config.dialect,
14192            Some(crate::dialects::DialectType::Athena)
14193        ) {
14194            self.athena_hive_context = true;
14195        }
14196
14197        // Output leading comments before DESCRIBE
14198        for comment in &d.leading_comments {
14199            self.write_formatted_comment(comment);
14200            self.write(" ");
14201        }
14202
14203        self.write_keyword("DESCRIBE");
14204
14205        if d.extended {
14206            self.write_space();
14207            self.write_keyword("EXTENDED");
14208        } else if d.formatted {
14209            self.write_space();
14210            self.write_keyword("FORMATTED");
14211        }
14212
14213        // Output style like ANALYZE, HISTORY
14214        if let Some(ref style) = d.style {
14215            self.write_space();
14216            self.write_keyword(style);
14217        }
14218
14219        // Handle object kind (TABLE, VIEW) based on dialect
14220        let should_output_kind = match self.config.dialect {
14221            // Spark doesn't use TABLE/VIEW after DESCRIBE
14222            Some(DialectType::Spark) | Some(DialectType::Databricks) | Some(DialectType::Hive) => {
14223                false
14224            }
14225            // Snowflake always includes TABLE
14226            Some(DialectType::Snowflake) => true,
14227            _ => d.kind.is_some(),
14228        };
14229        if should_output_kind {
14230            if let Some(ref kind) = d.kind {
14231                self.write_space();
14232                self.write_keyword(kind);
14233            } else if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
14234                self.write_space();
14235                self.write_keyword("TABLE");
14236            }
14237        }
14238
14239        self.write_space();
14240        self.generate_expression(&d.target)?;
14241
14242        // Output parenthesized parameter types for PROCEDURE/FUNCTION
14243        if !d.params.is_empty() {
14244            self.write("(");
14245            for (i, param) in d.params.iter().enumerate() {
14246                if i > 0 {
14247                    self.write(", ");
14248                }
14249                self.write(param);
14250            }
14251            self.write(")");
14252        }
14253
14254        // Output PARTITION clause if present (the Partition expression outputs its own PARTITION keyword)
14255        if let Some(ref partition) = d.partition {
14256            self.write_space();
14257            self.generate_expression(partition)?;
14258        }
14259
14260        // Databricks: AS JSON
14261        if d.as_json {
14262            self.write_space();
14263            self.write_keyword("AS JSON");
14264        }
14265
14266        // Output properties like type=stage
14267        for (name, value) in &d.properties {
14268            self.write_space();
14269            self.write(name);
14270            self.write("=");
14271            self.write(value);
14272        }
14273
14274        // Restore Athena Hive context
14275        self.athena_hive_context = saved_athena_hive_context;
14276
14277        Ok(())
14278    }
14279
14280    /// Generate SHOW statement (Snowflake, MySQL, etc.)
14281    /// SHOW [TERSE] <object_type> [HISTORY] [LIKE pattern] [IN <scope>] [STARTS WITH pattern] [LIMIT n] [FROM object]
14282    fn generate_show(&mut self, s: &Show) -> Result<()> {
14283        self.write_keyword("SHOW");
14284        self.write_space();
14285
14286        // TERSE keyword - but not for PRIMARY KEYS, UNIQUE KEYS, IMPORTED KEYS
14287        // where TERSE is syntactically valid but has no effect on output
14288        let show_terse = s.terse
14289            && !matches!(
14290                s.this.as_str(),
14291                "PRIMARY KEYS" | "UNIQUE KEYS" | "IMPORTED KEYS"
14292            );
14293        if show_terse {
14294            self.write_keyword("TERSE");
14295            self.write_space();
14296        }
14297
14298        // Object type (USERS, TABLES, DATABASES, etc.)
14299        self.write_keyword(&s.this);
14300
14301        // Target identifier (MySQL: engine name in SHOW ENGINE, preserved case)
14302        if let Some(ref target_expr) = s.target {
14303            self.write_space();
14304            self.generate_expression(target_expr)?;
14305        }
14306
14307        // HISTORY keyword
14308        if s.history {
14309            self.write_space();
14310            self.write_keyword("HISTORY");
14311        }
14312
14313        // FOR target (MySQL: SHOW GRANTS FOR foo, SHOW PROFILE ... FOR QUERY 5)
14314        if let Some(ref for_target) = s.for_target {
14315            self.write_space();
14316            self.write_keyword("FOR");
14317            self.write_space();
14318            self.generate_expression(for_target)?;
14319        }
14320
14321        // Determine ordering based on dialect:
14322        // - Snowflake: LIKE, IN, STARTS WITH, LIMIT, FROM
14323        // - MySQL: IN, FROM, LIKE (when FROM is present)
14324        use crate::dialects::DialectType;
14325        let is_snowflake = matches!(self.config.dialect, Some(DialectType::Snowflake));
14326        let is_mysql = matches!(self.config.dialect, Some(DialectType::MySQL));
14327        let mysql_tables_scope_as_from = is_mysql
14328            && matches!(s.this.as_str(), "TABLES" | "FULL TABLES")
14329            && s.scope_kind.as_deref() == Some("SCHEMA")
14330            && s.scope.is_some()
14331            && s.from.is_none();
14332
14333        if !is_snowflake && s.from.is_some() {
14334            // MySQL ordering: IN, FROM, LIKE
14335
14336            // IN scope_kind [scope]
14337            if let Some(ref scope_kind) = s.scope_kind {
14338                self.write_space();
14339                self.write_keyword("IN");
14340                self.write_space();
14341                self.write_keyword(scope_kind);
14342                if let Some(ref scope) = s.scope {
14343                    self.write_space();
14344                    self.generate_expression(scope)?;
14345                }
14346            } else if let Some(ref scope) = s.scope {
14347                self.write_space();
14348                self.write_keyword("IN");
14349                self.write_space();
14350                self.generate_expression(scope)?;
14351            }
14352
14353            // FROM clause
14354            if let Some(ref from) = s.from {
14355                self.write_space();
14356                self.write_keyword("FROM");
14357                self.write_space();
14358                self.generate_expression(from)?;
14359            }
14360
14361            // Second FROM clause (db name)
14362            if let Some(ref db) = s.db {
14363                self.write_space();
14364                self.write_keyword("FROM");
14365                self.write_space();
14366                self.generate_expression(db)?;
14367            }
14368
14369            // LIKE pattern
14370            if let Some(ref like) = s.like {
14371                self.write_space();
14372                self.write_keyword("LIKE");
14373                self.write_space();
14374                self.generate_expression(like)?;
14375            }
14376        } else {
14377            // Snowflake ordering: LIKE, IN, STARTS WITH, LIMIT, FROM
14378
14379            // LIKE pattern
14380            if let Some(ref like) = s.like {
14381                self.write_space();
14382                self.write_keyword("LIKE");
14383                self.write_space();
14384                self.generate_expression(like)?;
14385            }
14386
14387            // IN scope_kind [scope]
14388            if mysql_tables_scope_as_from {
14389                self.write_space();
14390                self.write_keyword("FROM");
14391                self.write_space();
14392                self.generate_expression(s.scope.as_ref().unwrap())?;
14393            } else if let Some(ref scope_kind) = s.scope_kind {
14394                self.write_space();
14395                self.write_keyword("IN");
14396                self.write_space();
14397                self.write_keyword(scope_kind);
14398                if let Some(ref scope) = s.scope {
14399                    self.write_space();
14400                    self.generate_expression(scope)?;
14401                }
14402            } else if let Some(ref scope) = s.scope {
14403                self.write_space();
14404                self.write_keyword("IN");
14405                self.write_space();
14406                self.generate_expression(scope)?;
14407            }
14408        }
14409
14410        // STARTS WITH pattern
14411        if let Some(ref starts_with) = s.starts_with {
14412            self.write_space();
14413            self.write_keyword("STARTS WITH");
14414            self.write_space();
14415            self.generate_expression(starts_with)?;
14416        }
14417
14418        // LIMIT clause
14419        if let Some(ref limit) = s.limit {
14420            self.write_space();
14421            self.generate_limit(limit)?;
14422        }
14423
14424        // FROM clause (for Snowflake, FROM comes after STARTS WITH and LIMIT)
14425        if is_snowflake {
14426            if let Some(ref from) = s.from {
14427                self.write_space();
14428                self.write_keyword("FROM");
14429                self.write_space();
14430                self.generate_expression(from)?;
14431            }
14432        }
14433
14434        // WHERE clause (MySQL: SHOW STATUS WHERE condition)
14435        if let Some(ref where_clause) = s.where_clause {
14436            self.write_space();
14437            self.write_keyword("WHERE");
14438            self.write_space();
14439            self.generate_expression(where_clause)?;
14440        }
14441
14442        // MUTEX/STATUS suffix (MySQL: SHOW ENGINE foo STATUS/MUTEX)
14443        if let Some(is_mutex) = s.mutex {
14444            self.write_space();
14445            if is_mutex {
14446                self.write_keyword("MUTEX");
14447            } else {
14448                self.write_keyword("STATUS");
14449            }
14450        }
14451
14452        // WITH PRIVILEGES clause (Snowflake: SHOW ... WITH PRIVILEGES USAGE, MODIFY)
14453        if !s.privileges.is_empty() {
14454            self.write_space();
14455            self.write_keyword("WITH PRIVILEGES");
14456            self.write_space();
14457            for (i, priv_name) in s.privileges.iter().enumerate() {
14458                if i > 0 {
14459                    self.write(", ");
14460                }
14461                self.write_keyword(priv_name);
14462            }
14463        }
14464
14465        Ok(())
14466    }
14467
14468    // ==================== End DDL Generation ====================
14469
14470    fn generate_literal(&mut self, lit: &Literal) -> Result<()> {
14471        use crate::dialects::DialectType;
14472        match lit {
14473            Literal::String(s) => {
14474                self.generate_string_literal(s)?;
14475            }
14476            Literal::Number(n) => {
14477                if matches!(self.config.dialect, Some(DialectType::MySQL))
14478                    && n.len() > 2
14479                    && (n.starts_with("0x") || n.starts_with("0X"))
14480                    && !n[2..].chars().all(|c| c.is_ascii_hexdigit())
14481                {
14482                    return self.generate_identifier(&Identifier {
14483                        name: n.clone(),
14484                        quoted: true,
14485                        trailing_comments: Vec::new(),
14486                        span: None,
14487                    });
14488                }
14489                // Strip underscore digit separators (e.g., 1_000_000 -> 1000000)
14490                // for dialects that don't support them (MySQL interprets as identifier).
14491                // ClickHouse, DuckDB, PostgreSQL, and Hive/Spark/Databricks support them.
14492                let n = if n.contains('_')
14493                    && !matches!(
14494                        self.config.dialect,
14495                        Some(DialectType::ClickHouse)
14496                            | Some(DialectType::DuckDB)
14497                            | Some(DialectType::PostgreSQL)
14498                            | Some(DialectType::Hive)
14499                            | Some(DialectType::Spark)
14500                            | Some(DialectType::Databricks)
14501                    ) {
14502                    std::borrow::Cow::Owned(n.replace('_', ""))
14503                } else {
14504                    std::borrow::Cow::Borrowed(n.as_str())
14505                };
14506                // Normalize numbers starting with decimal point to have leading zero
14507                // e.g., .25 -> 0.25 (matches sqlglot behavior)
14508                if n.starts_with('.') {
14509                    self.write("0");
14510                    self.write(&n);
14511                } else if n.starts_with("-.") {
14512                    // Handle negative numbers like -.25 -> -0.25
14513                    self.write("-0");
14514                    self.write(&n[1..]);
14515                } else {
14516                    self.write(&n);
14517                }
14518            }
14519            Literal::HexString(h) => {
14520                // Most dialects use lowercase x'...' for hex literals; Spark/Databricks/Teradata use uppercase X'...'
14521                match self.config.dialect {
14522                    Some(DialectType::Spark)
14523                    | Some(DialectType::Databricks)
14524                    | Some(DialectType::Teradata) => self.write("X'"),
14525                    _ => self.write("x'"),
14526                }
14527                self.write(h);
14528                self.write("'");
14529            }
14530            Literal::HexNumber(h) => {
14531                // Hex number (0xA) - integer in hex notation (from BigQuery)
14532                // For BigQuery, TSQL, Fabric output as 0xHEX (native hex notation)
14533                // For other dialects, convert to decimal integer
14534                match self.config.dialect {
14535                    Some(DialectType::BigQuery)
14536                    | Some(DialectType::ClickHouse)
14537                    | Some(DialectType::TSQL)
14538                    | Some(DialectType::Fabric) => {
14539                        self.write("0x");
14540                        self.write(h);
14541                    }
14542                    _ => {
14543                        // Convert hex to decimal
14544                        if let Ok(val) = u64::from_str_radix(h, 16) {
14545                            self.write(&val.to_string());
14546                        } else {
14547                            // Fallback: keep as 0x notation
14548                            self.write("0x");
14549                            self.write(h);
14550                        }
14551                    }
14552                }
14553            }
14554            Literal::BitString(b) => {
14555                // Bit string B'0101...'
14556                self.write("B'");
14557                self.write(b);
14558                self.write("'");
14559            }
14560            Literal::ByteString(b) => {
14561                // Byte string b'...' (BigQuery style)
14562                self.write("b'");
14563                // Escape special characters for output
14564                self.write_escaped_byte_string(b);
14565                self.write("'");
14566            }
14567            Literal::NationalString(s) => {
14568                // N'string' is supported by TSQL, Oracle, MySQL, and generic SQL
14569                // Other dialects strip the N prefix and output as regular string
14570                let keep_n_prefix = matches!(
14571                    self.config.dialect,
14572                    Some(DialectType::TSQL)
14573                        | Some(DialectType::Oracle)
14574                        | Some(DialectType::MySQL)
14575                        | None
14576                );
14577                if keep_n_prefix {
14578                    self.write("N'");
14579                } else {
14580                    self.write("'");
14581                }
14582                self.write(s);
14583                self.write("'");
14584            }
14585            Literal::Date(d) => {
14586                self.generate_date_literal(d)?;
14587            }
14588            Literal::Time(t) => {
14589                self.generate_time_literal(t)?;
14590            }
14591            Literal::Timestamp(ts) => {
14592                self.generate_timestamp_literal(ts)?;
14593            }
14594            Literal::Datetime(dt) => {
14595                self.generate_datetime_literal(dt)?;
14596            }
14597            Literal::TripleQuotedString(s, _quote_char) => {
14598                // For BigQuery and other dialects that don't support triple-quote, normalize to regular strings
14599                if matches!(
14600                    self.config.dialect,
14601                    Some(crate::dialects::DialectType::BigQuery)
14602                        | Some(crate::dialects::DialectType::DuckDB)
14603                        | Some(crate::dialects::DialectType::Snowflake)
14604                        | Some(crate::dialects::DialectType::Spark)
14605                        | Some(crate::dialects::DialectType::Hive)
14606                        | Some(crate::dialects::DialectType::Presto)
14607                        | Some(crate::dialects::DialectType::Trino)
14608                        | Some(crate::dialects::DialectType::PostgreSQL)
14609                        | Some(crate::dialects::DialectType::MySQL)
14610                        | Some(crate::dialects::DialectType::Redshift)
14611                        | Some(crate::dialects::DialectType::TSQL)
14612                        | Some(crate::dialects::DialectType::Oracle)
14613                        | Some(crate::dialects::DialectType::ClickHouse)
14614                        | Some(crate::dialects::DialectType::Databricks)
14615                        | Some(crate::dialects::DialectType::SQLite)
14616                ) {
14617                    self.generate_string_literal(s)?;
14618                } else {
14619                    // Preserve triple-quoted string syntax for generic/unknown dialects
14620                    let quotes = format!("{0}{0}{0}", _quote_char);
14621                    self.write(&quotes);
14622                    self.write(s);
14623                    self.write(&quotes);
14624                }
14625            }
14626            Literal::EscapeString(s) => {
14627                // PostgreSQL escape string: e'...' or E'...'
14628                // Token text format is "e:content" or "E:content"
14629                // Normalize escape sequences: \' -> '' (standard SQL doubled quote)
14630                use crate::dialects::DialectType;
14631                let content = if let Some(c) = s.strip_prefix("e:") {
14632                    c
14633                } else if let Some(c) = s.strip_prefix("E:") {
14634                    c
14635                } else {
14636                    s.as_str()
14637                };
14638
14639                // MySQL: output the content without quotes or prefix
14640                if matches!(
14641                    self.config.dialect,
14642                    Some(DialectType::MySQL) | Some(DialectType::TiDB)
14643                ) {
14644                    self.write(content);
14645                } else {
14646                    // Some dialects use lowercase e' prefix
14647                    let prefix = if matches!(
14648                        self.config.dialect,
14649                        Some(DialectType::SingleStore)
14650                            | Some(DialectType::DuckDB)
14651                            | Some(DialectType::PostgreSQL)
14652                            | Some(DialectType::CockroachDB)
14653                            | Some(DialectType::Materialize)
14654                            | Some(DialectType::RisingWave)
14655                    ) {
14656                        "e'"
14657                    } else {
14658                        "E'"
14659                    };
14660
14661                    // Normalize \' to '' for output
14662                    let normalized = content.replace("\\'", "''");
14663                    self.write(prefix);
14664                    self.write(&normalized);
14665                    self.write("'");
14666                }
14667            }
14668            Literal::DollarString(s) => {
14669                // Convert dollar-quoted strings to single-quoted strings
14670                // (like Python sqlglot's rawstring_sql)
14671                use crate::dialects::DialectType;
14672                // Extract content from tag\x00content format
14673                let (_tag, content) = crate::tokens::parse_dollar_string_token(s);
14674                // Step 1: Escape backslashes if the dialect uses backslash as a string escape
14675                let escape_backslash = matches!(
14676                    self.config.dialect,
14677                    Some(DialectType::ClickHouse) | Some(DialectType::Snowflake)
14678                );
14679                // Step 2: Determine quote escaping style
14680                // Snowflake: ' -> \' (backslash escape)
14681                // PostgreSQL, DuckDB, others: ' -> '' (doubled quote)
14682                let use_backslash_quote =
14683                    matches!(self.config.dialect, Some(DialectType::Snowflake));
14684
14685                let mut escaped = String::with_capacity(content.len() + 4);
14686                for ch in content.chars() {
14687                    if escape_backslash && ch == '\\' {
14688                        // Escape backslash first (before quote escaping)
14689                        escaped.push('\\');
14690                        escaped.push('\\');
14691                    } else if ch == '\'' {
14692                        if use_backslash_quote {
14693                            escaped.push('\\');
14694                            escaped.push('\'');
14695                        } else {
14696                            escaped.push('\'');
14697                            escaped.push('\'');
14698                        }
14699                    } else {
14700                        escaped.push(ch);
14701                    }
14702                }
14703                self.write("'");
14704                self.write(&escaped);
14705                self.write("'");
14706            }
14707            Literal::RawString(s) => {
14708                // Raw strings (r"..." or r'...') contain literal backslashes.
14709                // When converting to a regular string, this follows Python sqlglot's rawstring_sql:
14710                // 1. If \\ is in STRING_ESCAPES, double all backslashes
14711                // 2. Apply ESCAPED_SEQUENCES for special chars (but NOT for backslash itself)
14712                // 3. Escape quotes using STRING_ESCAPES[0] + quote_char
14713                use crate::dialects::DialectType;
14714
14715                // Dialects where \\ is in STRING_ESCAPES (backslashes need doubling)
14716                let escape_backslash = matches!(
14717                    self.config.dialect,
14718                    Some(DialectType::BigQuery)
14719                        | Some(DialectType::MySQL)
14720                        | Some(DialectType::SingleStore)
14721                        | Some(DialectType::TiDB)
14722                        | Some(DialectType::Hive)
14723                        | Some(DialectType::Spark)
14724                        | Some(DialectType::Databricks)
14725                        | Some(DialectType::Drill)
14726                        | Some(DialectType::Snowflake)
14727                        | Some(DialectType::Redshift)
14728                        | Some(DialectType::ClickHouse)
14729                );
14730
14731                // Dialects where backslash is the PRIMARY string escape (STRING_ESCAPES[0] = "\\")
14732                // These escape quotes as \' instead of ''
14733                let backslash_escapes_quote = matches!(
14734                    self.config.dialect,
14735                    Some(DialectType::BigQuery)
14736                        | Some(DialectType::Hive)
14737                        | Some(DialectType::Spark)
14738                        | Some(DialectType::Databricks)
14739                        | Some(DialectType::Drill)
14740                        | Some(DialectType::Snowflake)
14741                        | Some(DialectType::Redshift)
14742                );
14743
14744                // Whether this dialect supports escaped sequences (ESCAPED_SEQUENCES mapping)
14745                // This is True when \\ is in STRING_ESCAPES (same as escape_backslash)
14746                let supports_escape_sequences = escape_backslash;
14747
14748                let mut escaped = String::with_capacity(s.len() + 4);
14749                for ch in s.chars() {
14750                    if escape_backslash && ch == '\\' {
14751                        // Double the backslash for the target dialect
14752                        escaped.push('\\');
14753                        escaped.push('\\');
14754                    } else if ch == '\'' {
14755                        if backslash_escapes_quote {
14756                            // Use backslash to escape the quote: \'
14757                            escaped.push('\\');
14758                            escaped.push('\'');
14759                        } else {
14760                            // Use SQL standard quote doubling: ''
14761                            escaped.push('\'');
14762                            escaped.push('\'');
14763                        }
14764                    } else if supports_escape_sequences {
14765                        // Apply ESCAPED_SEQUENCES mapping for special chars
14766                        // (escape_backslash=False in rawstring_sql, so \\ is NOT escaped here)
14767                        match ch {
14768                            '\n' => {
14769                                escaped.push('\\');
14770                                escaped.push('n');
14771                            }
14772                            '\r' => {
14773                                escaped.push('\\');
14774                                escaped.push('r');
14775                            }
14776                            '\t' => {
14777                                escaped.push('\\');
14778                                escaped.push('t');
14779                            }
14780                            '\x07' => {
14781                                escaped.push('\\');
14782                                escaped.push('a');
14783                            }
14784                            '\x08' => {
14785                                escaped.push('\\');
14786                                escaped.push('b');
14787                            }
14788                            '\x0C' => {
14789                                escaped.push('\\');
14790                                escaped.push('f');
14791                            }
14792                            '\x0B' => {
14793                                escaped.push('\\');
14794                                escaped.push('v');
14795                            }
14796                            _ => escaped.push(ch),
14797                        }
14798                    } else {
14799                        escaped.push(ch);
14800                    }
14801                }
14802                self.write("'");
14803                self.write(&escaped);
14804                self.write("'");
14805            }
14806        }
14807        Ok(())
14808    }
14809
14810    /// Generate a DATE literal with dialect-specific formatting
14811    fn generate_date_literal(&mut self, d: &str) -> Result<()> {
14812        use crate::dialects::DialectType;
14813
14814        match self.config.dialect {
14815            // SQL Server / Fabric use CONVERT or CAST
14816            Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
14817                self.write("CAST('");
14818                self.write(d);
14819                self.write("' AS DATE)");
14820            }
14821            // BigQuery uses CAST syntax for type literals
14822            // DATE 'value' -> CAST('value' AS DATE)
14823            Some(DialectType::BigQuery) => {
14824                self.write("CAST('");
14825                self.write(d);
14826                self.write("' AS DATE)");
14827            }
14828            // Exasol uses CAST syntax for DATE literals
14829            // DATE 'value' -> CAST('value' AS DATE)
14830            Some(DialectType::Exasol) => {
14831                self.write("CAST('");
14832                self.write(d);
14833                self.write("' AS DATE)");
14834            }
14835            // Snowflake uses CAST syntax for DATE literals
14836            // DATE 'value' -> CAST('value' AS DATE)
14837            Some(DialectType::Snowflake) => {
14838                self.write("CAST('");
14839                self.write(d);
14840                self.write("' AS DATE)");
14841            }
14842            // PostgreSQL, MySQL, Redshift: DATE 'value' -> CAST('value' AS DATE)
14843            Some(DialectType::PostgreSQL)
14844            | Some(DialectType::MySQL)
14845            | Some(DialectType::SingleStore)
14846            | Some(DialectType::TiDB)
14847            | Some(DialectType::Redshift) => {
14848                self.write("CAST('");
14849                self.write(d);
14850                self.write("' AS DATE)");
14851            }
14852            // DuckDB, Presto, Trino, Spark: DATE 'value' -> CAST('value' AS DATE)
14853            Some(DialectType::DuckDB)
14854            | Some(DialectType::Presto)
14855            | Some(DialectType::Trino)
14856            | Some(DialectType::Athena)
14857            | Some(DialectType::Spark)
14858            | Some(DialectType::Databricks)
14859            | Some(DialectType::Hive) => {
14860                self.write("CAST('");
14861                self.write(d);
14862                self.write("' AS DATE)");
14863            }
14864            // Oracle: DATE 'value' -> TO_DATE('value', 'YYYY-MM-DD')
14865            Some(DialectType::Oracle) => {
14866                self.write("TO_DATE('");
14867                self.write(d);
14868                self.write("', 'YYYY-MM-DD')");
14869            }
14870            // Standard SQL: DATE '...'
14871            _ => {
14872                self.write_keyword("DATE");
14873                self.write(" '");
14874                self.write(d);
14875                self.write("'");
14876            }
14877        }
14878        Ok(())
14879    }
14880
14881    /// Generate a TIME literal with dialect-specific formatting
14882    fn generate_time_literal(&mut self, t: &str) -> Result<()> {
14883        use crate::dialects::DialectType;
14884
14885        match self.config.dialect {
14886            // SQL Server uses CONVERT or CAST
14887            Some(DialectType::TSQL) => {
14888                self.write("CAST('");
14889                self.write(t);
14890                self.write("' AS TIME)");
14891            }
14892            // Standard SQL: TIME '...'
14893            _ => {
14894                self.write_keyword("TIME");
14895                self.write(" '");
14896                self.write(t);
14897                self.write("'");
14898            }
14899        }
14900        Ok(())
14901    }
14902
14903    /// Generate a date expression for Dremio, converting DATE literals to CAST
14904    fn generate_dremio_date_expression(&mut self, expr: &Expression) -> Result<()> {
14905        use crate::expressions::Literal;
14906
14907        match expr {
14908            Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Date(_)) => {
14909                let Literal::Date(d) = lit.as_ref() else {
14910                    unreachable!()
14911                };
14912                // DATE 'value' -> CAST('value' AS DATE)
14913                self.write("CAST('");
14914                self.write(d);
14915                self.write("' AS DATE)");
14916            }
14917            _ => {
14918                // For all other expressions, generate normally
14919                self.generate_expression(expr)?;
14920            }
14921        }
14922        Ok(())
14923    }
14924
14925    /// Generate a TIMESTAMP literal with dialect-specific formatting
14926    fn generate_timestamp_literal(&mut self, ts: &str) -> Result<()> {
14927        use crate::dialects::DialectType;
14928
14929        match self.config.dialect {
14930            // SQL Server uses CONVERT or CAST
14931            Some(DialectType::TSQL) => {
14932                self.write("CAST('");
14933                self.write(ts);
14934                self.write("' AS DATETIME2)");
14935            }
14936            // BigQuery uses CAST syntax for type literals
14937            // TIMESTAMP 'value' -> CAST('value' AS TIMESTAMP)
14938            Some(DialectType::BigQuery) => {
14939                self.write("CAST('");
14940                self.write(ts);
14941                self.write("' AS TIMESTAMP)");
14942            }
14943            // Snowflake uses CAST syntax for TIMESTAMP literals
14944            // TIMESTAMP 'value' -> CAST('value' AS TIMESTAMP)
14945            Some(DialectType::Snowflake) => {
14946                self.write("CAST('");
14947                self.write(ts);
14948                self.write("' AS TIMESTAMP)");
14949            }
14950            // Dremio uses CAST syntax for TIMESTAMP literals
14951            // TIMESTAMP 'value' -> CAST('value' AS TIMESTAMP)
14952            Some(DialectType::Dremio) => {
14953                self.write("CAST('");
14954                self.write(ts);
14955                self.write("' AS TIMESTAMP)");
14956            }
14957            // Exasol uses CAST syntax for TIMESTAMP literals
14958            // TIMESTAMP 'value' -> CAST('value' AS TIMESTAMP)
14959            Some(DialectType::Exasol) => {
14960                self.write("CAST('");
14961                self.write(ts);
14962                self.write("' AS TIMESTAMP)");
14963            }
14964            // Oracle prefers TO_TIMESTAMP function call
14965            // TIMESTAMP 'value' -> TO_TIMESTAMP('value', 'YYYY-MM-DD HH24:MI:SS.FF6')
14966            Some(DialectType::Oracle) => {
14967                self.write("TO_TIMESTAMP('");
14968                self.write(ts);
14969                self.write("', 'YYYY-MM-DD HH24:MI:SS.FF6')");
14970            }
14971            // Presto/Trino: always use CAST for TIMESTAMP literals
14972            Some(DialectType::Presto) | Some(DialectType::Trino) => {
14973                if Self::timestamp_has_timezone(ts) {
14974                    self.write("CAST('");
14975                    self.write(ts);
14976                    self.write("' AS TIMESTAMP WITH TIME ZONE)");
14977                } else {
14978                    self.write("CAST('");
14979                    self.write(ts);
14980                    self.write("' AS TIMESTAMP)");
14981                }
14982            }
14983            // ClickHouse: CAST('...' AS Nullable(DateTime))
14984            Some(DialectType::ClickHouse) => {
14985                self.write("CAST('");
14986                self.write(ts);
14987                self.write("' AS Nullable(DateTime))");
14988            }
14989            // Spark: CAST('...' AS TIMESTAMP)
14990            Some(DialectType::Spark) => {
14991                self.write("CAST('");
14992                self.write(ts);
14993                self.write("' AS TIMESTAMP)");
14994            }
14995            // Redshift: CAST('...' AS TIMESTAMP) for regular timestamps,
14996            // but TIMESTAMP '...' for special values like 'epoch'
14997            Some(DialectType::Redshift) => {
14998                if ts == "epoch" {
14999                    self.write_keyword("TIMESTAMP");
15000                    self.write(" '");
15001                    self.write(ts);
15002                    self.write("'");
15003                } else {
15004                    self.write("CAST('");
15005                    self.write(ts);
15006                    self.write("' AS TIMESTAMP)");
15007                }
15008            }
15009            // PostgreSQL, Hive, DuckDB, etc.: CAST('...' AS TIMESTAMP)
15010            Some(DialectType::PostgreSQL)
15011            | Some(DialectType::Hive)
15012            | Some(DialectType::SQLite)
15013            | Some(DialectType::DuckDB)
15014            | Some(DialectType::Athena)
15015            | Some(DialectType::Drill)
15016            | Some(DialectType::Teradata) => {
15017                self.write("CAST('");
15018                self.write(ts);
15019                self.write("' AS TIMESTAMP)");
15020            }
15021            // MySQL/StarRocks: CAST('...' AS DATETIME)
15022            Some(DialectType::MySQL) | Some(DialectType::StarRocks) | Some(DialectType::Doris) => {
15023                self.write("CAST('");
15024                self.write(ts);
15025                self.write("' AS DATETIME)");
15026            }
15027            // Databricks: CAST('...' AS TIMESTAMP_NTZ)
15028            Some(DialectType::Databricks) => {
15029                self.write("CAST('");
15030                self.write(ts);
15031                self.write("' AS TIMESTAMP_NTZ)");
15032            }
15033            // Standard SQL: TIMESTAMP '...'
15034            _ => {
15035                self.write_keyword("TIMESTAMP");
15036                self.write(" '");
15037                self.write(ts);
15038                self.write("'");
15039            }
15040        }
15041        Ok(())
15042    }
15043
15044    /// Check if a timestamp string contains a timezone identifier
15045    /// This detects IANA timezone names like Europe/Prague, America/New_York, etc.
15046    fn timestamp_has_timezone(ts: &str) -> bool {
15047        // Check for common IANA timezone patterns: Continent/City format
15048        // Examples: Europe/Prague, America/New_York, Asia/Tokyo, etc.
15049        // Also handles: UTC, GMT, Etc/GMT+0, etc.
15050        let ts_lower = ts.to_ascii_lowercase();
15051
15052        // Check for Continent/City pattern (most common)
15053        let continent_prefixes = [
15054            "africa/",
15055            "america/",
15056            "antarctica/",
15057            "arctic/",
15058            "asia/",
15059            "atlantic/",
15060            "australia/",
15061            "europe/",
15062            "indian/",
15063            "pacific/",
15064            "etc/",
15065            "brazil/",
15066            "canada/",
15067            "chile/",
15068            "mexico/",
15069            "us/",
15070        ];
15071
15072        for prefix in &continent_prefixes {
15073            if ts_lower.contains(prefix) {
15074                return true;
15075            }
15076        }
15077
15078        // Check for standalone timezone abbreviations at the end
15079        // These typically appear after the time portion
15080        let tz_abbrevs = [
15081            " utc", " gmt", " cet", " cest", " eet", " eest", " wet", " west", " est", " edt",
15082            " cst", " cdt", " mst", " mdt", " pst", " pdt", " ist", " bst", " jst", " kst", " hkt",
15083            " sgt", " aest", " aedt", " acst", " acdt", " awst",
15084        ];
15085
15086        for abbrev in &tz_abbrevs {
15087            if ts_lower.ends_with(abbrev) {
15088                return true;
15089            }
15090        }
15091
15092        // Check for numeric timezone offsets: +N, -N, +NN:NN, -NN:NN
15093        // Examples: "2012-10-31 01:00 -2", "2012-10-31 01:00 +02:00"
15094        // Look for pattern: space followed by + or - and digits (optionally with :)
15095        let trimmed = ts.trim();
15096        if let Some(last_space) = trimmed.rfind(' ') {
15097            let suffix = &trimmed[last_space + 1..];
15098            if (suffix.starts_with('+') || suffix.starts_with('-')) && suffix.len() > 1 {
15099                // Check if rest is numeric (possibly with : for hh:mm format)
15100                let rest = &suffix[1..];
15101                if rest.chars().all(|c| c.is_ascii_digit() || c == ':') {
15102                    return true;
15103                }
15104            }
15105        }
15106
15107        false
15108    }
15109
15110    /// Generate a DATETIME literal with dialect-specific formatting
15111    fn generate_datetime_literal(&mut self, dt: &str) -> Result<()> {
15112        use crate::dialects::DialectType;
15113
15114        match self.config.dialect {
15115            // BigQuery uses CAST syntax for type literals
15116            // DATETIME 'value' -> CAST('value' AS DATETIME)
15117            Some(DialectType::BigQuery) => {
15118                self.write("CAST('");
15119                self.write(dt);
15120                self.write("' AS DATETIME)");
15121            }
15122            // DuckDB: DATETIME -> CAST('value' AS TIMESTAMP)
15123            Some(DialectType::DuckDB) => {
15124                self.write("CAST('");
15125                self.write(dt);
15126                self.write("' AS TIMESTAMP)");
15127            }
15128            // DATETIME is primarily a BigQuery type
15129            // Output as DATETIME '...' for dialects that support it
15130            _ => {
15131                self.write_keyword("DATETIME");
15132                self.write(" '");
15133                self.write(dt);
15134                self.write("'");
15135            }
15136        }
15137        Ok(())
15138    }
15139
15140    /// Generate a string literal with dialect-specific escaping
15141    fn generate_string_literal(&mut self, s: &str) -> Result<()> {
15142        use crate::dialects::DialectType;
15143
15144        match self.config.dialect {
15145            // MySQL/Hive: Uses SQL standard quote escaping ('') for quotes,
15146            // and backslash escaping for special characters like newlines
15147            // Hive STRING_ESCAPES = ["\\"] - uses backslash escapes
15148            Some(DialectType::Hive) | Some(DialectType::Spark) | Some(DialectType::Databricks) => {
15149                // Hive/Spark use backslash escaping for quotes (\') and special chars
15150                self.write("'");
15151                for c in s.chars() {
15152                    match c {
15153                        '\'' => self.write("\\'"),
15154                        '\\' => self.write("\\\\"),
15155                        '\n' => self.write("\\n"),
15156                        '\r' => self.write("\\r"),
15157                        '\t' => self.write("\\t"),
15158                        '\0' => self.write("\\0"),
15159                        _ => self.output.push(c),
15160                    }
15161                }
15162                self.write("'");
15163            }
15164            Some(DialectType::Drill) => {
15165                // Drill uses SQL-standard quote doubling ('') for quotes,
15166                // but backslash escaping for special characters
15167                self.write("'");
15168                for c in s.chars() {
15169                    match c {
15170                        '\'' => self.write("''"),
15171                        '\\' => self.write("\\\\"),
15172                        '\n' => self.write("\\n"),
15173                        '\r' => self.write("\\r"),
15174                        '\t' => self.write("\\t"),
15175                        '\0' => self.write("\\0"),
15176                        _ => self.output.push(c),
15177                    }
15178                }
15179                self.write("'");
15180            }
15181            Some(DialectType::MySQL) | Some(DialectType::SingleStore) | Some(DialectType::TiDB) => {
15182                self.write("'");
15183                for c in s.chars() {
15184                    match c {
15185                        // MySQL uses SQL standard quote doubling
15186                        '\'' => self.write("''"),
15187                        '\\' => self.write("\\\\"),
15188                        '\n' => self.write("\\n"),
15189                        '\r' => self.write("\\r"),
15190                        '\t' => self.write("\\t"),
15191                        // sqlglot writes a literal NUL for this case
15192                        '\0' => self.output.push('\0'),
15193                        _ => self.output.push(c),
15194                    }
15195                }
15196                self.write("'");
15197            }
15198            // BigQuery: Uses backslash escaping
15199            Some(DialectType::BigQuery) => {
15200                self.write("'");
15201                for c in s.chars() {
15202                    match c {
15203                        '\'' => self.write("\\'"),
15204                        '\\' => self.write("\\\\"),
15205                        '\n' => self.write("\\n"),
15206                        '\r' => self.write("\\r"),
15207                        '\t' => self.write("\\t"),
15208                        '\0' => self.write("\\0"),
15209                        '\x07' => self.write("\\a"),
15210                        '\x08' => self.write("\\b"),
15211                        '\x0C' => self.write("\\f"),
15212                        '\x0B' => self.write("\\v"),
15213                        _ => self.output.push(c),
15214                    }
15215                }
15216                self.write("'");
15217            }
15218            // Athena: Uses different escaping for DDL (Hive) vs DML (Trino)
15219            // In Hive context (DDL): backslash escaping for single quotes (\') and backslashes (\\)
15220            // In Trino context (DML): SQL-standard escaping ('') and literal backslashes
15221            Some(DialectType::Athena) => {
15222                if self.athena_hive_context {
15223                    // Hive-style: backslash escaping
15224                    self.write("'");
15225                    for c in s.chars() {
15226                        match c {
15227                            '\'' => self.write("\\'"),
15228                            '\\' => self.write("\\\\"),
15229                            '\n' => self.write("\\n"),
15230                            '\r' => self.write("\\r"),
15231                            '\t' => self.write("\\t"),
15232                            '\0' => self.write("\\0"),
15233                            _ => self.output.push(c),
15234                        }
15235                    }
15236                    self.write("'");
15237                } else {
15238                    // Trino-style: SQL-standard escaping, preserve backslashes
15239                    self.write("'");
15240                    for c in s.chars() {
15241                        match c {
15242                            '\'' => self.write("''"),
15243                            // Preserve backslashes literally (no re-escaping)
15244                            _ => self.output.push(c),
15245                        }
15246                    }
15247                    self.write("'");
15248                }
15249            }
15250            // Snowflake: Uses backslash escaping (STRING_ESCAPES = ["\\", "'"])
15251            // The tokenizer preserves backslash escape sequences literally (e.g., input '\\'
15252            // becomes string value '\\'), so we should NOT re-escape backslashes.
15253            // We only need to escape single quotes.
15254            Some(DialectType::Snowflake) => {
15255                self.write("'");
15256                for c in s.chars() {
15257                    match c {
15258                        '\'' => self.write("\\'"),
15259                        // Backslashes are already escaped in the tokenized string, don't re-escape
15260                        // Only escape special characters that might not have been escaped
15261                        '\n' => self.write("\\n"),
15262                        '\r' => self.write("\\r"),
15263                        '\t' => self.write("\\t"),
15264                        _ => self.output.push(c),
15265                    }
15266                }
15267                self.write("'");
15268            }
15269            // PostgreSQL: Output special characters as literal chars in strings (no E-string prefix)
15270            Some(DialectType::PostgreSQL) => {
15271                self.write("'");
15272                for c in s.chars() {
15273                    match c {
15274                        '\'' => self.write("''"),
15275                        _ => self.output.push(c),
15276                    }
15277                }
15278                self.write("'");
15279            }
15280            // Redshift: Uses backslash escaping for single quotes
15281            Some(DialectType::Redshift) => {
15282                self.write("'");
15283                for c in s.chars() {
15284                    match c {
15285                        '\'' => self.write("\\'"),
15286                        _ => self.output.push(c),
15287                    }
15288                }
15289                self.write("'");
15290            }
15291            // Oracle: Uses standard double single-quote escaping
15292            Some(DialectType::Oracle) => {
15293                self.write("'");
15294                for ch in s.chars() {
15295                    if ch == '\'' {
15296                        self.output.push_str("''");
15297                    } else {
15298                        self.output.push(ch);
15299                    }
15300                }
15301                self.write("'");
15302            }
15303            // ClickHouse: Uses SQL-standard quote doubling ('') for quotes,
15304            // backslash escaping for backslashes and special characters
15305            Some(DialectType::ClickHouse) => {
15306                self.write("'");
15307                for c in s.chars() {
15308                    match c {
15309                        '\'' => self.write("''"),
15310                        '\\' => self.write("\\\\"),
15311                        '\n' => self.write("\\n"),
15312                        '\r' => self.write("\\r"),
15313                        '\t' => self.write("\\t"),
15314                        '\0' => self.write("\\0"),
15315                        '\x07' => self.write("\\a"),
15316                        '\x08' => self.write("\\b"),
15317                        '\x0C' => self.write("\\f"),
15318                        '\x0B' => self.write("\\v"),
15319                        // Non-printable characters: emit as \xNN hex escapes
15320                        c if c.is_control() || (c as u32) < 0x20 => {
15321                            let byte = c as u32;
15322                            if byte < 256 {
15323                                self.write(&format!("\\x{:02X}", byte));
15324                            } else {
15325                                self.output.push(c);
15326                            }
15327                        }
15328                        _ => self.output.push(c),
15329                    }
15330                }
15331                self.write("'");
15332            }
15333            // Default: SQL standard double single quotes (works for most dialects)
15334            // PostgreSQL, Snowflake, DuckDB, TSQL, etc.
15335            _ => {
15336                self.write("'");
15337                for ch in s.chars() {
15338                    if ch == '\'' {
15339                        self.output.push_str("''");
15340                    } else {
15341                        self.output.push(ch);
15342                    }
15343                }
15344                self.write("'");
15345            }
15346        }
15347        Ok(())
15348    }
15349
15350    /// Write a byte string with proper escaping for BigQuery-style byte literals
15351    /// Escapes characters as \xNN hex escapes where needed
15352    fn write_escaped_byte_string(&mut self, s: &str) {
15353        for c in s.chars() {
15354            match c {
15355                // Escape single quotes
15356                '\'' => self.write("\\'"),
15357                // Escape backslashes
15358                '\\' => self.write("\\\\"),
15359                // Keep all printable characters (including non-ASCII) as-is
15360                _ if !c.is_control() => self.output.push(c),
15361                // Escape control characters as hex
15362                _ => {
15363                    let byte = c as u32;
15364                    if byte < 256 {
15365                        self.write(&format!("\\x{:02x}", byte));
15366                    } else {
15367                        // For unicode characters, write each UTF-8 byte
15368                        for b in c.to_string().as_bytes() {
15369                            self.write(&format!("\\x{:02x}", b));
15370                        }
15371                    }
15372                }
15373            }
15374        }
15375    }
15376
15377    fn generate_boolean(&mut self, b: &BooleanLiteral) -> Result<()> {
15378        use crate::dialects::DialectType;
15379
15380        // Different dialects have different boolean literal formats
15381        match self.config.dialect {
15382            // SQL Server typically uses 1/0 for boolean literals in many contexts
15383            // However, TRUE/FALSE also works in modern versions
15384            Some(DialectType::TSQL) => {
15385                self.write(if b.value { "1" } else { "0" });
15386            }
15387            // Oracle traditionally uses 1/0 (no native boolean until recent versions)
15388            Some(DialectType::Oracle) => {
15389                self.write(if b.value { "1" } else { "0" });
15390            }
15391            // MySQL accepts TRUE/FALSE as aliases for 1/0
15392            Some(DialectType::MySQL) => {
15393                self.write_keyword(if b.value { "TRUE" } else { "FALSE" });
15394            }
15395            // Most other dialects support TRUE/FALSE
15396            _ => {
15397                self.write_keyword(if b.value { "TRUE" } else { "FALSE" });
15398            }
15399        }
15400        Ok(())
15401    }
15402
15403    /// Generate an identifier that's used as an alias name
15404    /// This quotes reserved keywords in addition to already-quoted identifiers
15405    fn generate_alias_identifier(&mut self, id: &Identifier) -> Result<()> {
15406        let name = &id.name;
15407        let quote_style = &self.config.identifier_quote_style;
15408
15409        // For aliases, quote if:
15410        // 1. The identifier was explicitly quoted in the source
15411        // 2. The identifier is a reserved keyword for the current dialect
15412        let needs_quoting = id.quoted || self.is_reserved_keyword(name);
15413
15414        // Normalize identifier if configured
15415        let output_name = if self.config.normalize_identifiers && !id.quoted {
15416            name.to_ascii_lowercase()
15417        } else {
15418            name.to_string()
15419        };
15420
15421        if needs_quoting {
15422            let quote_style = if matches!(self.config.dialect, Some(DialectType::ClickHouse))
15423                && matches!(self.config.source_dialect, Some(DialectType::ClickHouse))
15424                && quote_style.start == '"'
15425                && output_name.contains('"')
15426            {
15427                &IdentifierQuoteStyle::BACKTICK
15428            } else {
15429                quote_style
15430            };
15431            // Escape any quote characters within the identifier
15432            let escaped_name = if quote_style.start == quote_style.end {
15433                output_name.replace(
15434                    quote_style.end,
15435                    &format!("{}{}", quote_style.end, quote_style.end),
15436                )
15437            } else {
15438                output_name.replace(
15439                    quote_style.end,
15440                    &format!("{}{}", quote_style.end, quote_style.end),
15441                )
15442            };
15443            self.write(&format!(
15444                "{}{}{}",
15445                quote_style.start, escaped_name, quote_style.end
15446            ));
15447        } else {
15448            self.write(&output_name);
15449        }
15450
15451        // Output trailing comments
15452        for comment in &id.trailing_comments {
15453            self.write(" ");
15454            self.write_formatted_comment(comment);
15455        }
15456        Ok(())
15457    }
15458
15459    fn generate_identifier(&mut self, id: &Identifier) -> Result<()> {
15460        use crate::dialects::DialectType;
15461
15462        let name = &id.name;
15463
15464        // For Athena, use backticks in Hive context, double quotes in Trino context
15465        let quote_style = if matches!(self.config.dialect, Some(DialectType::Athena))
15466            && self.athena_hive_context
15467        {
15468            &IdentifierQuoteStyle::BACKTICK
15469        } else {
15470            &self.config.identifier_quote_style
15471        };
15472
15473        // Quote if:
15474        // 1. The identifier was explicitly quoted in the source
15475        // 2. The identifier is a reserved keyword for the current dialect
15476        // 3. The config says to always quote identifiers (e.g., Athena/Presto)
15477        // This matches Python sqlglot's identifier_sql behavior
15478        // Also quote identifiers starting with digits if the target dialect doesn't support them
15479        let starts_with_digit = name.chars().next().map_or(false, |c| c.is_ascii_digit());
15480        let needs_digit_quoting = starts_with_digit
15481            && !self.config.identifiers_can_start_with_digit
15482            && self.config.dialect.is_some();
15483        let mysql_invalid_hex_identifier = matches!(self.config.dialect, Some(DialectType::MySQL))
15484            && name.len() > 2
15485            && (name.starts_with("0x") || name.starts_with("0X"))
15486            && !name[2..].chars().all(|c| c.is_ascii_hexdigit());
15487        let clickhouse_unsafe_identifier =
15488            matches!(self.config.dialect, Some(DialectType::ClickHouse))
15489                && matches!(self.config.source_dialect, Some(DialectType::ClickHouse))
15490                && !name.starts_with('{')
15491                && !name.contains('(')
15492                && !name.contains(')')
15493                && name != "?"
15494                && name
15495                    .chars()
15496                    .any(|c| !(c.is_ascii_alphanumeric() || c == '_'));
15497        let needs_quoting = id.quoted
15498            || self.is_reserved_keyword(name)
15499            || self.config.always_quote_identifiers
15500            || needs_digit_quoting
15501            || mysql_invalid_hex_identifier
15502            || clickhouse_unsafe_identifier;
15503
15504        // Check for MySQL index column prefix length: name(16) or name(16) ASC/DESC
15505        // When quoted, we need to output `name`(16) not `name(16)`
15506        let (base_name, suffix) = if needs_quoting {
15507            // Try to extract prefix length from identifier: name(number) or name(number) ASC/DESC
15508            if let Some(paren_pos) = name.find('(') {
15509                let base = &name[..paren_pos];
15510                let rest = &name[paren_pos..];
15511                // Verify it looks like (digits) or (digits) ASC/DESC
15512                if rest.starts_with('(')
15513                    && (rest.ends_with(')') || rest.ends_with(") ASC") || rest.ends_with(") DESC"))
15514                {
15515                    // Check if content between parens is all digits
15516                    let close_paren = rest.find(')').unwrap_or(rest.len());
15517                    let inside = &rest[1..close_paren];
15518                    if inside.chars().all(|c| c.is_ascii_digit()) {
15519                        (base.to_string(), rest.to_string())
15520                    } else {
15521                        (name.to_string(), String::new())
15522                    }
15523                } else {
15524                    (name.to_string(), String::new())
15525                }
15526            } else if name.ends_with(" ASC") {
15527                let base = &name[..name.len() - 4];
15528                (base.to_string(), " ASC".to_string())
15529            } else if name.ends_with(" DESC") {
15530                let base = &name[..name.len() - 5];
15531                (base.to_string(), " DESC".to_string())
15532            } else {
15533                (name.to_string(), String::new())
15534            }
15535        } else {
15536            (name.to_string(), String::new())
15537        };
15538
15539        // Normalize identifier if configured, with special handling for Exasol
15540        // Exasol uses UPPERCASE normalization strategy, so reserved keywords that need quoting
15541        // should be uppercased when not already quoted (to match Python sqlglot behavior)
15542        let output_name = if self.config.normalize_identifiers && !id.quoted {
15543            base_name.to_ascii_lowercase()
15544        } else if matches!(self.config.dialect, Some(DialectType::Exasol))
15545            && !id.quoted
15546            && self.is_reserved_keyword(name)
15547        {
15548            // Exasol: uppercase reserved keywords when quoting them
15549            // This matches Python sqlglot's behavior with NORMALIZATION_STRATEGY = UPPERCASE
15550            base_name.to_ascii_uppercase()
15551        } else {
15552            base_name
15553        };
15554
15555        if needs_quoting {
15556            // Escape any quote characters within the identifier
15557            let escaped_name = if quote_style.start == quote_style.end {
15558                // Same start/end char (e.g., " or `) - double the quote char
15559                output_name.replace(
15560                    quote_style.end,
15561                    &format!("{}{}", quote_style.end, quote_style.end),
15562                )
15563            } else {
15564                // Different start/end (e.g., [ and ]) - escape only the end char
15565                output_name.replace(
15566                    quote_style.end,
15567                    &format!("{}{}", quote_style.end, quote_style.end),
15568                )
15569            };
15570            self.write(&format!(
15571                "{}{}{}{}",
15572                quote_style.start, escaped_name, quote_style.end, suffix
15573            ));
15574        } else {
15575            self.write(&output_name);
15576        }
15577
15578        // Output trailing comments
15579        for comment in &id.trailing_comments {
15580            self.write(" ");
15581            self.write_formatted_comment(comment);
15582        }
15583        Ok(())
15584    }
15585
15586    fn generate_column(&mut self, col: &Column) -> Result<()> {
15587        use crate::dialects::DialectType;
15588
15589        if let Some(table) = &col.table {
15590            // Exasol special case: LOCAL as column table prefix should NOT be quoted
15591            // LOCAL is a special keyword in Exasol for referencing aliases from the current scope
15592            // Only applies when: dialect is Exasol, name is "LOCAL" (case-insensitive), and not already quoted
15593            let is_exasol_local_prefix = matches!(self.config.dialect, Some(DialectType::Exasol))
15594                && !table.quoted
15595                && table.name.eq_ignore_ascii_case("LOCAL");
15596
15597            if is_exasol_local_prefix {
15598                // Write LOCAL unquoted (this is special Exasol syntax, not a table reference)
15599                self.write("LOCAL");
15600            } else {
15601                self.generate_identifier(table)?;
15602            }
15603            self.write(".");
15604        }
15605        self.generate_identifier(&col.name)?;
15606        // Oracle-style join marker (+)
15607        // Only output if dialect supports it (Oracle, Exasol)
15608        if col.join_mark && self.config.supports_column_join_marks {
15609            self.write(" (+)");
15610        }
15611        // Output trailing comments
15612        for comment in &col.trailing_comments {
15613            self.write_space();
15614            self.write_formatted_comment(comment);
15615        }
15616        Ok(())
15617    }
15618
15619    /// Generate a pseudocolumn (Oracle ROWNUM, ROWID, LEVEL, etc.)
15620    /// Pseudocolumns should NEVER be quoted, as quoting breaks them in Oracle
15621    fn generate_pseudocolumn(&mut self, pc: &Pseudocolumn) -> Result<()> {
15622        use crate::dialects::DialectType;
15623        use crate::expressions::PseudocolumnType;
15624
15625        // SYSDATE -> CURRENT_TIMESTAMP for non-Oracle/Redshift dialects
15626        if pc.kind == PseudocolumnType::Sysdate
15627            && !matches!(
15628                self.config.dialect,
15629                Some(DialectType::Oracle) | Some(DialectType::Redshift) | None
15630            )
15631        {
15632            self.write_keyword("CURRENT_TIMESTAMP");
15633            // Add () for dialects that expect it
15634            if matches!(
15635                self.config.dialect,
15636                Some(DialectType::MySQL)
15637                    | Some(DialectType::ClickHouse)
15638                    | Some(DialectType::Spark)
15639                    | Some(DialectType::Databricks)
15640                    | Some(DialectType::Hive)
15641            ) {
15642                self.write("()");
15643            }
15644        } else {
15645            self.write(pc.kind.as_str());
15646        }
15647        Ok(())
15648    }
15649
15650    /// Generate CONNECT BY clause (Oracle hierarchical queries)
15651    fn generate_connect(&mut self, connect: &Connect) -> Result<()> {
15652        use crate::dialects::DialectType;
15653
15654        // Generate native CONNECT BY for Oracle and Snowflake
15655        // For other dialects, add a comment noting manual conversion needed
15656        let supports_connect_by = matches!(
15657            self.config.dialect,
15658            Some(DialectType::Oracle) | Some(DialectType::Snowflake)
15659        );
15660
15661        if !supports_connect_by && self.config.dialect.is_some() {
15662            // Add comment for unsupported dialects
15663            if self.config.pretty {
15664                self.write_newline();
15665            } else {
15666                self.write_space();
15667            }
15668            self.write_unsupported_comment(
15669                "CONNECT BY requires manual conversion to recursive CTE",
15670            )?;
15671        }
15672
15673        // Generate START WITH if present (before CONNECT BY)
15674        if let Some(start) = &connect.start {
15675            if self.config.pretty {
15676                self.write_newline();
15677            } else {
15678                self.write_space();
15679            }
15680            self.write_keyword("START WITH");
15681            self.write_space();
15682            self.generate_expression(start)?;
15683        }
15684
15685        // Generate CONNECT BY
15686        if self.config.pretty {
15687            self.write_newline();
15688        } else {
15689            self.write_space();
15690        }
15691        self.write_keyword("CONNECT BY");
15692        if connect.nocycle {
15693            self.write_space();
15694            self.write_keyword("NOCYCLE");
15695        }
15696        self.write_space();
15697        self.generate_expression(&connect.connect)?;
15698
15699        Ok(())
15700    }
15701
15702    /// Generate Connect expression (for Expression::Connect variant)
15703    fn generate_connect_expr(&mut self, connect: &Connect) -> Result<()> {
15704        self.generate_connect(connect)
15705    }
15706
15707    /// Generate PRIOR expression
15708    fn generate_prior(&mut self, prior: &Prior) -> Result<()> {
15709        self.write_keyword("PRIOR");
15710        self.write_space();
15711        self.generate_expression(&prior.this)?;
15712        Ok(())
15713    }
15714
15715    /// Generate CONNECT_BY_ROOT function
15716    /// Syntax: CONNECT_BY_ROOT column (no parentheses)
15717    fn generate_connect_by_root(&mut self, cbr: &ConnectByRoot) -> Result<()> {
15718        self.write_keyword("CONNECT_BY_ROOT");
15719        self.write_space();
15720        self.generate_expression(&cbr.this)?;
15721        Ok(())
15722    }
15723
15724    /// Generate MATCH_RECOGNIZE clause
15725    fn generate_match_recognize(&mut self, mr: &MatchRecognize) -> Result<()> {
15726        use crate::dialects::DialectType;
15727
15728        // MATCH_RECOGNIZE is supported in Oracle, Snowflake, Presto, and Trino
15729        let supports_match_recognize = matches!(
15730            self.config.dialect,
15731            Some(DialectType::Oracle)
15732                | Some(DialectType::Snowflake)
15733                | Some(DialectType::Presto)
15734                | Some(DialectType::Trino)
15735        );
15736
15737        // Generate the source table first
15738        if let Some(source) = &mr.this {
15739            self.generate_expression(source)?;
15740        }
15741
15742        if !supports_match_recognize {
15743            self.write_unsupported_comment("MATCH_RECOGNIZE not supported in this dialect")?;
15744            return Ok(());
15745        }
15746
15747        // In pretty mode, MATCH_RECOGNIZE should be on a new line
15748        if self.config.pretty {
15749            self.write_newline();
15750        } else {
15751            self.write_space();
15752        }
15753
15754        self.write_keyword("MATCH_RECOGNIZE");
15755        self.write(" (");
15756
15757        if self.config.pretty {
15758            self.indent_level += 1;
15759        }
15760
15761        let mut needs_separator = false;
15762
15763        // PARTITION BY
15764        if let Some(partition_by) = &mr.partition_by {
15765            if !partition_by.is_empty() {
15766                if self.config.pretty {
15767                    self.write_newline();
15768                    self.write_indent();
15769                }
15770                self.write_keyword("PARTITION BY");
15771                self.write_space();
15772                for (i, expr) in partition_by.iter().enumerate() {
15773                    if i > 0 {
15774                        self.write(", ");
15775                    }
15776                    self.generate_expression(expr)?;
15777                }
15778                needs_separator = true;
15779            }
15780        }
15781
15782        // ORDER BY
15783        if let Some(order_by) = &mr.order_by {
15784            if !order_by.is_empty() {
15785                if needs_separator {
15786                    if self.config.pretty {
15787                        self.write_newline();
15788                        self.write_indent();
15789                    } else {
15790                        self.write_space();
15791                    }
15792                } else if self.config.pretty {
15793                    self.write_newline();
15794                    self.write_indent();
15795                }
15796                self.write_keyword("ORDER BY");
15797                // In pretty mode, put each ORDER BY column on a new indented line
15798                if self.config.pretty {
15799                    self.indent_level += 1;
15800                    for (i, ordered) in order_by.iter().enumerate() {
15801                        if i > 0 {
15802                            self.write(",");
15803                        }
15804                        self.write_newline();
15805                        self.write_indent();
15806                        self.generate_ordered(ordered)?;
15807                    }
15808                    self.indent_level -= 1;
15809                } else {
15810                    self.write_space();
15811                    for (i, ordered) in order_by.iter().enumerate() {
15812                        if i > 0 {
15813                            self.write(", ");
15814                        }
15815                        self.generate_ordered(ordered)?;
15816                    }
15817                }
15818                needs_separator = true;
15819            }
15820        }
15821
15822        // MEASURES
15823        if let Some(measures) = &mr.measures {
15824            if !measures.is_empty() {
15825                if needs_separator {
15826                    if self.config.pretty {
15827                        self.write_newline();
15828                        self.write_indent();
15829                    } else {
15830                        self.write_space();
15831                    }
15832                } else if self.config.pretty {
15833                    self.write_newline();
15834                    self.write_indent();
15835                }
15836                self.write_keyword("MEASURES");
15837                // In pretty mode, put each MEASURE on a new indented line
15838                if self.config.pretty {
15839                    self.indent_level += 1;
15840                    for (i, measure) in measures.iter().enumerate() {
15841                        if i > 0 {
15842                            self.write(",");
15843                        }
15844                        self.write_newline();
15845                        self.write_indent();
15846                        // Handle RUNNING/FINAL prefix
15847                        if let Some(semantics) = &measure.window_frame {
15848                            match semantics {
15849                                MatchRecognizeSemantics::Running => {
15850                                    self.write_keyword("RUNNING");
15851                                    self.write_space();
15852                                }
15853                                MatchRecognizeSemantics::Final => {
15854                                    self.write_keyword("FINAL");
15855                                    self.write_space();
15856                                }
15857                            }
15858                        }
15859                        self.generate_expression(&measure.this)?;
15860                    }
15861                    self.indent_level -= 1;
15862                } else {
15863                    self.write_space();
15864                    for (i, measure) in measures.iter().enumerate() {
15865                        if i > 0 {
15866                            self.write(", ");
15867                        }
15868                        // Handle RUNNING/FINAL prefix
15869                        if let Some(semantics) = &measure.window_frame {
15870                            match semantics {
15871                                MatchRecognizeSemantics::Running => {
15872                                    self.write_keyword("RUNNING");
15873                                    self.write_space();
15874                                }
15875                                MatchRecognizeSemantics::Final => {
15876                                    self.write_keyword("FINAL");
15877                                    self.write_space();
15878                                }
15879                            }
15880                        }
15881                        self.generate_expression(&measure.this)?;
15882                    }
15883                }
15884                needs_separator = true;
15885            }
15886        }
15887
15888        // Row semantics (ONE ROW PER MATCH, ALL ROWS PER MATCH, etc.)
15889        if let Some(rows) = &mr.rows {
15890            if needs_separator {
15891                if self.config.pretty {
15892                    self.write_newline();
15893                    self.write_indent();
15894                } else {
15895                    self.write_space();
15896                }
15897            } else if self.config.pretty {
15898                self.write_newline();
15899                self.write_indent();
15900            }
15901            match rows {
15902                MatchRecognizeRows::OneRowPerMatch => {
15903                    self.write_keyword("ONE ROW PER MATCH");
15904                }
15905                MatchRecognizeRows::AllRowsPerMatch => {
15906                    self.write_keyword("ALL ROWS PER MATCH");
15907                }
15908                MatchRecognizeRows::AllRowsPerMatchShowEmptyMatches => {
15909                    self.write_keyword("ALL ROWS PER MATCH SHOW EMPTY MATCHES");
15910                }
15911                MatchRecognizeRows::AllRowsPerMatchOmitEmptyMatches => {
15912                    self.write_keyword("ALL ROWS PER MATCH OMIT EMPTY MATCHES");
15913                }
15914                MatchRecognizeRows::AllRowsPerMatchWithUnmatchedRows => {
15915                    self.write_keyword("ALL ROWS PER MATCH WITH UNMATCHED ROWS");
15916                }
15917            }
15918            needs_separator = true;
15919        }
15920
15921        // AFTER MATCH SKIP
15922        if let Some(after) = &mr.after {
15923            if needs_separator {
15924                if self.config.pretty {
15925                    self.write_newline();
15926                    self.write_indent();
15927                } else {
15928                    self.write_space();
15929                }
15930            } else if self.config.pretty {
15931                self.write_newline();
15932                self.write_indent();
15933            }
15934            match after {
15935                MatchRecognizeAfter::PastLastRow => {
15936                    self.write_keyword("AFTER MATCH SKIP PAST LAST ROW");
15937                }
15938                MatchRecognizeAfter::ToNextRow => {
15939                    self.write_keyword("AFTER MATCH SKIP TO NEXT ROW");
15940                }
15941                MatchRecognizeAfter::ToFirst(ident) => {
15942                    self.write_keyword("AFTER MATCH SKIP TO FIRST");
15943                    self.write_space();
15944                    self.generate_identifier(ident)?;
15945                }
15946                MatchRecognizeAfter::ToLast(ident) => {
15947                    self.write_keyword("AFTER MATCH SKIP TO LAST");
15948                    self.write_space();
15949                    self.generate_identifier(ident)?;
15950                }
15951            }
15952            needs_separator = true;
15953        }
15954
15955        // PATTERN
15956        if let Some(pattern) = &mr.pattern {
15957            if needs_separator {
15958                if self.config.pretty {
15959                    self.write_newline();
15960                    self.write_indent();
15961                } else {
15962                    self.write_space();
15963                }
15964            } else if self.config.pretty {
15965                self.write_newline();
15966                self.write_indent();
15967            }
15968            self.write_keyword("PATTERN");
15969            self.write_space();
15970            self.write("(");
15971            self.write(pattern);
15972            self.write(")");
15973            needs_separator = true;
15974        }
15975
15976        // DEFINE
15977        if let Some(define) = &mr.define {
15978            if !define.is_empty() {
15979                if needs_separator {
15980                    if self.config.pretty {
15981                        self.write_newline();
15982                        self.write_indent();
15983                    } else {
15984                        self.write_space();
15985                    }
15986                } else if self.config.pretty {
15987                    self.write_newline();
15988                    self.write_indent();
15989                }
15990                self.write_keyword("DEFINE");
15991                // In pretty mode, put each DEFINE on a new indented line
15992                if self.config.pretty {
15993                    self.indent_level += 1;
15994                    for (i, (name, expr)) in define.iter().enumerate() {
15995                        if i > 0 {
15996                            self.write(",");
15997                        }
15998                        self.write_newline();
15999                        self.write_indent();
16000                        self.generate_identifier(name)?;
16001                        self.write(" AS ");
16002                        self.generate_expression(expr)?;
16003                    }
16004                    self.indent_level -= 1;
16005                } else {
16006                    self.write_space();
16007                    for (i, (name, expr)) in define.iter().enumerate() {
16008                        if i > 0 {
16009                            self.write(", ");
16010                        }
16011                        self.generate_identifier(name)?;
16012                        self.write(" AS ");
16013                        self.generate_expression(expr)?;
16014                    }
16015                }
16016            }
16017        }
16018
16019        if self.config.pretty {
16020            self.indent_level -= 1;
16021            self.write_newline();
16022        }
16023        self.write(")");
16024
16025        // Alias - only include AS if it was explicitly present in the input
16026        if let Some(alias) = &mr.alias {
16027            self.write(" ");
16028            if mr.alias_explicit_as {
16029                self.write_keyword("AS");
16030                self.write(" ");
16031            }
16032            self.generate_identifier(alias)?;
16033        }
16034
16035        Ok(())
16036    }
16037
16038    /// Generate a query hint /*+ ... */
16039    fn generate_hint(&mut self, hint: &Hint) -> Result<()> {
16040        use crate::dialects::DialectType;
16041
16042        // Output hints for dialects that support them, or when no dialect is specified (identity tests)
16043        let supports_hints = matches!(
16044            self.config.dialect,
16045            None |  // No dialect = preserve everything
16046            Some(DialectType::Oracle) | Some(DialectType::MySQL) |
16047            Some(DialectType::Spark) | Some(DialectType::Hive) |
16048            Some(DialectType::Databricks) | Some(DialectType::PostgreSQL)
16049        );
16050
16051        if !supports_hints || hint.expressions.is_empty() {
16052            return Ok(());
16053        }
16054
16055        // First, expand raw hint text into individual hint strings
16056        // This handles the case where the parser stored multiple hints as a single raw string
16057        let mut hint_strings: Vec<String> = Vec::new();
16058        for expr in &hint.expressions {
16059            match expr {
16060                HintExpression::Raw(text) => {
16061                    // Parse raw hint text into individual hint function calls
16062                    let parsed = self.parse_raw_hint_text(text);
16063                    hint_strings.extend(parsed);
16064                }
16065                _ => {
16066                    hint_strings.push(self.hint_expression_to_string(expr)?);
16067                }
16068            }
16069        }
16070
16071        // In pretty mode with multiple hints, always use multiline format
16072        // This matches Python sqlglot's behavior where expressions() with default dynamic=False
16073        // always joins with newlines in pretty mode
16074        let use_multiline = self.config.pretty && hint_strings.len() > 1;
16075
16076        if use_multiline {
16077            // Pretty print with each hint on its own line
16078            self.write(" /*+ ");
16079            for (i, hint_str) in hint_strings.iter().enumerate() {
16080                if i > 0 {
16081                    self.write_newline();
16082                    self.write("  "); // 2-space indent within hint block
16083                }
16084                self.write(hint_str);
16085            }
16086            self.write(" */");
16087        } else {
16088            // Single line format
16089            self.write(" /*+ ");
16090            let sep = match self.config.dialect {
16091                Some(DialectType::Spark) | Some(DialectType::Databricks) => ", ",
16092                _ => " ",
16093            };
16094            for (i, hint_str) in hint_strings.iter().enumerate() {
16095                if i > 0 {
16096                    self.write(sep);
16097                }
16098                self.write(hint_str);
16099            }
16100            self.write(" */");
16101        }
16102
16103        Ok(())
16104    }
16105
16106    /// Parse raw hint text into individual hint function calls
16107    /// e.g., "LEADING(a b) USE_NL(c)" -> ["LEADING(a b)", "USE_NL(c)"]
16108    /// If the hint contains unparseable content (like SQL keywords), return as single raw string
16109    fn parse_raw_hint_text(&self, text: &str) -> Vec<String> {
16110        let mut results = Vec::new();
16111        let mut chars = text.chars().peekable();
16112        let mut current = String::new();
16113        let mut paren_depth = 0;
16114        let mut has_unparseable_content = false;
16115        let mut position_after_last_function = 0;
16116        let mut char_position = 0;
16117
16118        while let Some(c) = chars.next() {
16119            char_position += c.len_utf8();
16120            match c {
16121                '(' => {
16122                    paren_depth += 1;
16123                    current.push(c);
16124                }
16125                ')' => {
16126                    paren_depth -= 1;
16127                    current.push(c);
16128                    // When we close the outer parenthesis, we've completed a hint function
16129                    if paren_depth == 0 {
16130                        let trimmed = current.trim().to_string();
16131                        if !trimmed.is_empty() {
16132                            // Format this hint for pretty printing if needed
16133                            let formatted = self.format_hint_function(&trimmed);
16134                            results.push(formatted);
16135                        }
16136                        current.clear();
16137                        position_after_last_function = char_position;
16138                    }
16139                }
16140                ' ' | '\t' | '\n' | ',' if paren_depth == 0 => {
16141                    // Space/comma/whitespace outside parentheses - skip
16142                }
16143                _ if paren_depth == 0 => {
16144                    // Character outside parentheses - accumulate for potential hint name
16145                    current.push(c);
16146                }
16147                _ => {
16148                    current.push(c);
16149                }
16150            }
16151        }
16152
16153        // Check if there's remaining text after the last function call
16154        let remaining_text = text[position_after_last_function..].trim();
16155        if !remaining_text.is_empty() {
16156            // Check if it looks like valid hint function names
16157            // Valid hint identifiers typically are uppercase alphanumeric with underscores
16158            // If we see multiple words without parens, it's likely unparseable
16159            let words: Vec<&str> = remaining_text.split_whitespace().collect();
16160            let looks_like_hint_functions = words.iter().all(|word| {
16161                // A valid hint name followed by opening paren, or a standalone uppercase identifier
16162                word.contains('(') || (word.chars().all(|c| c.is_ascii_uppercase() || c == '_'))
16163            });
16164
16165            if !looks_like_hint_functions && words.len() > 1 {
16166                has_unparseable_content = true;
16167            }
16168        }
16169
16170        // If we detected unparseable content (like SQL keywords), return the whole hint as-is
16171        if has_unparseable_content {
16172            return vec![text.trim().to_string()];
16173        }
16174
16175        // If we couldn't parse anything, return the original text as a single hint
16176        if results.is_empty() {
16177            results.push(text.trim().to_string());
16178        }
16179
16180        results
16181    }
16182
16183    /// Format a hint function for pretty printing
16184    /// e.g., "LEADING(aaa bbb ccc ddd)" -> multiline if args are too wide
16185    fn format_hint_function(&self, hint: &str) -> String {
16186        if !self.config.pretty {
16187            return hint.to_string();
16188        }
16189
16190        // Try to parse NAME(args) pattern
16191        if let Some(paren_pos) = hint.find('(') {
16192            if hint.ends_with(')') {
16193                let name = &hint[..paren_pos];
16194                let args_str = &hint[paren_pos + 1..hint.len() - 1];
16195
16196                // Parse arguments (space-separated for Oracle hints)
16197                let args: Vec<&str> = args_str.split_whitespace().collect();
16198
16199                // Calculate total width of arguments
16200                let total_args_width: usize =
16201                    args.iter().map(|s| s.len()).sum::<usize>() + args.len().saturating_sub(1); // spaces between args
16202
16203                // If too wide, format on multiple lines
16204                if total_args_width > self.config.max_text_width && !args.is_empty() {
16205                    let mut result = format!("{}(\n", name);
16206                    for arg in &args {
16207                        result.push_str("    "); // 4-space indent for args
16208                        result.push_str(arg);
16209                        result.push('\n');
16210                    }
16211                    result.push_str("  )"); // 2-space indent for closing paren
16212                    return result;
16213                }
16214            }
16215        }
16216
16217        hint.to_string()
16218    }
16219
16220    /// Convert a hint expression to a string, handling multiline formatting for long arguments
16221    fn hint_expression_to_string(&mut self, expr: &HintExpression) -> Result<String> {
16222        match expr {
16223            HintExpression::Function { name, args } => {
16224                // Generate each argument to a string
16225                let arg_strings: Vec<String> = args
16226                    .iter()
16227                    .map(|arg| {
16228                        let mut gen = Generator::with_arc_config(self.config.clone());
16229                        gen.generate_expression(arg)?;
16230                        Ok(gen.output)
16231                    })
16232                    .collect::<Result<Vec<_>>>()?;
16233
16234                // Oracle hints use space-separated arguments, not comma-separated
16235                let total_args_width: usize = arg_strings.iter().map(|s| s.len()).sum::<usize>()
16236                    + arg_strings.len().saturating_sub(1); // spaces between args
16237
16238                // Check if function args need multiline formatting
16239                // Use too_wide check for argument formatting
16240                let args_multiline =
16241                    self.config.pretty && total_args_width > self.config.max_text_width;
16242
16243                if args_multiline && !arg_strings.is_empty() {
16244                    // Multiline format for long argument lists
16245                    let mut result = format!("{}(\n", name);
16246                    for arg_str in &arg_strings {
16247                        result.push_str("    "); // 4-space indent for args
16248                        result.push_str(arg_str);
16249                        result.push('\n');
16250                    }
16251                    result.push_str("  )"); // 2-space indent for closing paren
16252                    Ok(result)
16253                } else {
16254                    // Single line format with space-separated args (Oracle style)
16255                    let args_str = arg_strings.join(" ");
16256                    Ok(format!("{}({})", name, args_str))
16257                }
16258            }
16259            HintExpression::Identifier(name) => Ok(name.clone()),
16260            HintExpression::Raw(text) => {
16261                // For pretty printing, try to format the raw text
16262                if self.config.pretty {
16263                    Ok(self.format_hint_function(text))
16264                } else {
16265                    Ok(text.clone())
16266                }
16267            }
16268        }
16269    }
16270
16271    fn generate_table(&mut self, table: &TableRef) -> Result<()> {
16272        // PostgreSQL ONLY modifier: prevents scanning child tables
16273        if table.only {
16274            self.write_keyword("ONLY");
16275            self.write_space();
16276        }
16277
16278        // Check for IDENTIFIER() (Snowflake) or OPENDATASOURCE(...).db.schema.table (TSQL)
16279        if let Some(ref identifier_func) = table.identifier_func {
16280            self.generate_expression(identifier_func)?;
16281            // If table name parts are present, emit .catalog.schema.name after the function
16282            if !table.name.name.is_empty() {
16283                if let Some(catalog) = &table.catalog {
16284                    self.write(".");
16285                    self.generate_identifier(catalog)?;
16286                }
16287                if let Some(schema) = &table.schema {
16288                    self.write(".");
16289                    self.generate_identifier(schema)?;
16290                }
16291                self.write(".");
16292                self.generate_identifier(&table.name)?;
16293            }
16294        } else {
16295            if let Some(catalog) = &table.catalog {
16296                self.generate_identifier(catalog)?;
16297                self.write(".");
16298            }
16299            if let Some(schema) = &table.schema {
16300                self.generate_identifier(schema)?;
16301                self.write(".");
16302            }
16303            self.generate_identifier(&table.name)?;
16304        }
16305
16306        // Output Snowflake CHANGES clause (before partition, includes its own AT/BEFORE/END)
16307        if let Some(changes) = &table.changes {
16308            self.write(" ");
16309            self.generate_changes(changes)?;
16310        }
16311
16312        // Output MySQL PARTITION clause: t1 PARTITION(p0, p1)
16313        if !table.partitions.is_empty() {
16314            self.write_space();
16315            self.write_keyword("PARTITION");
16316            self.write("(");
16317            for (i, partition) in table.partitions.iter().enumerate() {
16318                if i > 0 {
16319                    self.write(", ");
16320                }
16321                self.generate_identifier(partition)?;
16322            }
16323            self.write(")");
16324        }
16325
16326        // Output time travel clause: BEFORE (STATEMENT => ...) or AT (TIMESTAMP => ...)
16327        // Skip if CHANGES clause is present (CHANGES includes its own time travel)
16328        if table.changes.is_none() {
16329            if let Some(when) = &table.when {
16330                self.write_space();
16331                self.generate_historical_data(when)?;
16332            }
16333        }
16334
16335        // Output TSQL FOR SYSTEM_TIME temporal clause (before alias, except BigQuery)
16336        let system_time_post_alias = matches!(self.config.dialect, Some(DialectType::BigQuery));
16337        if !system_time_post_alias {
16338            if let Some(ref system_time) = table.system_time {
16339                self.write_space();
16340                self.write(system_time);
16341            }
16342        }
16343
16344        // Output Presto/Trino time travel: FOR VERSION AS OF / FOR TIMESTAMP AS OF
16345        if let Some(ref version) = table.version {
16346            self.write_space();
16347            self.generate_version(version)?;
16348        }
16349
16350        // When alias_post_tablesample is true, the order is: table TABLESAMPLE (...) alias
16351        // When alias_post_tablesample is false (default), the order is: table alias TABLESAMPLE (...)
16352        // Oracle, Hive, Spark use ALIAS_POST_TABLESAMPLE = true (alias comes after sample)
16353        let alias_post_tablesample = self.config.alias_post_tablesample;
16354
16355        if alias_post_tablesample {
16356            // TABLESAMPLE before alias (Oracle, Hive, Spark)
16357            self.generate_table_sample_clause(table)?;
16358        }
16359
16360        // Output table hints (TSQL: WITH (TABLOCK, INDEX(myindex), ...))
16361        // For SQLite, INDEXED BY hints come after the alias, so skip here
16362        let is_sqlite_hint = matches!(self.config.dialect, Some(DialectType::SQLite))
16363            && table.hints.iter().any(|h| {
16364                if let Expression::Identifier(id) = h {
16365                    id.name.starts_with("INDEXED BY") || id.name == "NOT INDEXED"
16366                } else {
16367                    false
16368                }
16369            });
16370        if !table.hints.is_empty() && !is_sqlite_hint {
16371            for hint in &table.hints {
16372                self.write_space();
16373                self.generate_expression(hint)?;
16374            }
16375        }
16376
16377        if let Some(alias) = &table.alias {
16378            self.write_space();
16379            // Output AS if it was explicitly present in the input, OR for certain dialects/cases
16380            // Generic mode and most dialects always use AS for table aliases
16381            let always_use_as = self.config.dialect.is_none()
16382                || matches!(
16383                    self.config.dialect,
16384                    Some(DialectType::Generic)
16385                        | Some(DialectType::PostgreSQL)
16386                        | Some(DialectType::Redshift)
16387                        | Some(DialectType::Snowflake)
16388                        | Some(DialectType::BigQuery)
16389                        | Some(DialectType::DuckDB)
16390                        | Some(DialectType::Presto)
16391                        | Some(DialectType::Trino)
16392                        | Some(DialectType::TSQL)
16393                        | Some(DialectType::Fabric)
16394                        | Some(DialectType::MySQL)
16395                        | Some(DialectType::Spark)
16396                        | Some(DialectType::Hive)
16397                        | Some(DialectType::SQLite)
16398                        | Some(DialectType::Drill)
16399                );
16400            let is_stage_ref = table.name.name.starts_with('@');
16401            // Oracle never uses AS for table aliases
16402            let suppress_as = matches!(self.config.dialect, Some(DialectType::Oracle));
16403            if !suppress_as && (table.alias_explicit_as || always_use_as || is_stage_ref) {
16404                self.write_keyword("AS");
16405                self.write_space();
16406            }
16407            self.generate_identifier(alias)?;
16408
16409            // Output column aliases if present: AS t(c1, c2)
16410            // Skip for dialects that don't support table alias columns (BigQuery, SQLite)
16411            if !table.column_aliases.is_empty() && self.config.supports_table_alias_columns {
16412                self.write("(");
16413                for (i, col_alias) in table.column_aliases.iter().enumerate() {
16414                    if i > 0 {
16415                        self.write(", ");
16416                    }
16417                    self.generate_identifier(col_alias)?;
16418                }
16419                self.write(")");
16420            }
16421        }
16422
16423        // BigQuery: FOR SYSTEM_TIME AS OF after alias
16424        if system_time_post_alias {
16425            if let Some(ref system_time) = table.system_time {
16426                self.write_space();
16427                self.write(system_time);
16428            }
16429        }
16430
16431        // For default behavior (alias_post_tablesample = false), output TABLESAMPLE after alias
16432        if !alias_post_tablesample {
16433            self.generate_table_sample_clause(table)?;
16434        }
16435
16436        // Output SQLite INDEXED BY / NOT INDEXED hints after alias
16437        if is_sqlite_hint {
16438            for hint in &table.hints {
16439                self.write_space();
16440                self.generate_expression(hint)?;
16441            }
16442        }
16443
16444        // ClickHouse FINAL modifier
16445        if table.final_ && matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
16446            self.write_space();
16447            self.write_keyword("FINAL");
16448        }
16449
16450        // Output trailing comments
16451        for comment in &table.trailing_comments {
16452            self.write_space();
16453            self.write_formatted_comment(comment);
16454        }
16455        // Note: leading_comments (from before table in FROM clause) are intentionally NOT
16456        // output here - they are output by the FROM/PIVOT generator after the full expression
16457
16458        Ok(())
16459    }
16460
16461    /// Helper to output TABLESAMPLE clause for a table reference
16462    fn generate_table_sample_clause(&mut self, table: &TableRef) -> Result<()> {
16463        if let Some(ref ts) = table.table_sample {
16464            self.write_space();
16465            if ts.is_using_sample {
16466                self.write_keyword("USING SAMPLE");
16467            } else {
16468                // Use the configured tablesample keyword (e.g., "TABLESAMPLE" or "SAMPLE")
16469                self.write_keyword(self.config.tablesample_keywords);
16470            }
16471            self.generate_sample_body(ts)?;
16472            // Seed for table-level sample - use dialect's configured keyword
16473            if let Some(ref seed) = ts.seed {
16474                self.write_space();
16475                self.write_keyword(self.config.tablesample_seed_keyword);
16476                self.write(" (");
16477                self.generate_expression(seed)?;
16478                self.write(")");
16479            }
16480        }
16481        Ok(())
16482    }
16483
16484    fn generate_stage_reference(&mut self, sr: &StageReference) -> Result<()> {
16485        // Output: '@stage_name/path' if quoted, or @stage_name/path otherwise
16486        // Optionally followed by (FILE_FORMAT => 'fmt', PATTERN => '*.csv')
16487
16488        if sr.quoted {
16489            self.write("'");
16490        }
16491
16492        self.write(&sr.name);
16493        if let Some(path) = &sr.path {
16494            self.write(path);
16495        }
16496
16497        if sr.quoted {
16498            self.write("'");
16499        }
16500
16501        // Output FILE_FORMAT and PATTERN if present
16502        let has_options = sr.file_format.is_some() || sr.pattern.is_some();
16503        if has_options {
16504            self.write(" (");
16505            let mut first = true;
16506
16507            if let Some(file_format) = &sr.file_format {
16508                if !first {
16509                    self.write(", ");
16510                }
16511                self.write_keyword("FILE_FORMAT");
16512                self.write(" => ");
16513                self.generate_expression(file_format)?;
16514                first = false;
16515            }
16516
16517            if let Some(pattern) = &sr.pattern {
16518                if !first {
16519                    self.write(", ");
16520                }
16521                self.write_keyword("PATTERN");
16522                self.write(" => '");
16523                self.write(pattern);
16524                self.write("'");
16525            }
16526
16527            self.write(")");
16528        }
16529        Ok(())
16530    }
16531
16532    fn generate_star(&mut self, star: &Star) -> Result<()> {
16533        use crate::dialects::DialectType;
16534
16535        if let Some(table) = &star.table {
16536            self.generate_identifier(table)?;
16537            self.write(".");
16538        }
16539        self.write("*");
16540
16541        // Generate EXCLUDE/EXCEPT clause based on dialect
16542        if let Some(except) = &star.except {
16543            if !except.is_empty() {
16544                self.write_space();
16545                // Use dialect-appropriate keyword
16546                match self.config.dialect {
16547                    Some(DialectType::BigQuery) => self.write_keyword("EXCEPT"),
16548                    Some(DialectType::DuckDB) | Some(DialectType::Snowflake) => {
16549                        self.write_keyword("EXCLUDE")
16550                    }
16551                    _ => self.write_keyword("EXCEPT"), // Default to EXCEPT
16552                }
16553                self.write(" (");
16554                for (i, col) in except.iter().enumerate() {
16555                    if i > 0 {
16556                        self.write(", ");
16557                    }
16558                    self.generate_identifier(col)?;
16559                }
16560                self.write(")");
16561            }
16562        }
16563
16564        // Generate REPLACE clause
16565        if let Some(replace) = &star.replace {
16566            if !replace.is_empty() {
16567                self.write_space();
16568                self.write_keyword("REPLACE");
16569                self.write(" (");
16570                for (i, alias) in replace.iter().enumerate() {
16571                    if i > 0 {
16572                        self.write(", ");
16573                    }
16574                    self.generate_expression(&alias.this)?;
16575                    self.write_space();
16576                    self.write_keyword("AS");
16577                    self.write_space();
16578                    self.generate_identifier(&alias.alias)?;
16579                }
16580                self.write(")");
16581            }
16582        }
16583
16584        // Generate RENAME clause (Snowflake specific)
16585        if let Some(rename) = &star.rename {
16586            if !rename.is_empty() {
16587                self.write_space();
16588                self.write_keyword("RENAME");
16589                self.write(" (");
16590                for (i, (old_name, new_name)) in rename.iter().enumerate() {
16591                    if i > 0 {
16592                        self.write(", ");
16593                    }
16594                    self.generate_identifier(old_name)?;
16595                    self.write_space();
16596                    self.write_keyword("AS");
16597                    self.write_space();
16598                    self.generate_identifier(new_name)?;
16599                }
16600                self.write(")");
16601            }
16602        }
16603
16604        // Output trailing comments
16605        for comment in &star.trailing_comments {
16606            self.write_space();
16607            self.write_formatted_comment(comment);
16608        }
16609
16610        Ok(())
16611    }
16612
16613    /// Generate Snowflake braced wildcard syntax: {*}, {tbl.*}, {* EXCLUDE (...)}, {* ILIKE '...'}
16614    fn generate_braced_wildcard(&mut self, expr: &Expression) -> Result<()> {
16615        self.write("{");
16616        match expr {
16617            Expression::Star(star) => {
16618                // Generate the star (table.* or just * with optional EXCLUDE)
16619                self.generate_star(star)?;
16620            }
16621            Expression::ILike(ilike) => {
16622                // {* ILIKE 'pattern'} syntax
16623                self.generate_expression(&ilike.left)?;
16624                self.write_space();
16625                self.write_keyword("ILIKE");
16626                self.write_space();
16627                self.generate_expression(&ilike.right)?;
16628            }
16629            _ => {
16630                self.generate_expression(expr)?;
16631            }
16632        }
16633        self.write("}");
16634        Ok(())
16635    }
16636
16637    fn generate_alias(&mut self, alias: &Alias) -> Result<()> {
16638        // Generate inner expression, but skip trailing comments if they're in pre_alias_comments
16639        // to avoid duplication (comments are captured as both Column.trailing_comments
16640        // and Alias.pre_alias_comments during parsing)
16641        match &alias.this {
16642            Expression::Column(col) => {
16643                // Generate column without trailing comments - they're in pre_alias_comments
16644                if let Some(table) = &col.table {
16645                    self.generate_identifier(table)?;
16646                    self.write(".");
16647                }
16648                self.generate_identifier(&col.name)?;
16649            }
16650            _ => {
16651                self.generate_expression(&alias.this)?;
16652            }
16653        }
16654
16655        // Handle pre-alias comments: when there are no trailing_comments, sqlglot
16656        // moves pre-alias comments to after the alias. When there are also trailing_comments,
16657        // keep pre-alias comments in their original position (between expression and AS).
16658        if !alias.pre_alias_comments.is_empty() && !alias.trailing_comments.is_empty() {
16659            for comment in &alias.pre_alias_comments {
16660                self.write_space();
16661                self.write_formatted_comment(comment);
16662            }
16663        }
16664
16665        use crate::dialects::DialectType;
16666
16667        // Determine if we should skip AS keyword for table-valued function aliases
16668        // Oracle and some other dialects don't use AS for table aliases
16669        // Note: We specifically use TableFromRows here, NOT Function, because Function
16670        // matches regular functions like MATCH_NUMBER() which should include the AS keyword.
16671        // TableFromRows represents TABLE(expr) constructs which are actual table-valued functions.
16672        let is_table_source = matches!(
16673            &alias.this,
16674            Expression::JSONTable(_)
16675                | Expression::XMLTable(_)
16676                | Expression::TableFromRows(_)
16677                | Expression::Unnest(_)
16678                | Expression::MatchRecognize(_)
16679                | Expression::Select(_)
16680                | Expression::Subquery(_)
16681                | Expression::Paren(_)
16682        );
16683        let dialect_skips_table_alias_as = matches!(self.config.dialect, Some(DialectType::Oracle));
16684        let skip_as = is_table_source && dialect_skips_table_alias_as;
16685
16686        self.write_space();
16687        if !skip_as {
16688            if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
16689                if let Some(ref alias_keyword) = alias.alias_keyword {
16690                    self.write(alias_keyword);
16691                } else {
16692                    self.write_keyword("AS");
16693                }
16694            } else {
16695                self.write_keyword("AS");
16696            }
16697            self.write_space();
16698        }
16699
16700        // BigQuery doesn't support column aliases in table aliases: AS t(c1, c2)
16701        let skip_column_aliases = matches!(self.config.dialect, Some(DialectType::BigQuery));
16702
16703        // Check if we have column aliases only (no table alias name)
16704        if alias.alias.is_empty() && !alias.column_aliases.is_empty() && !skip_column_aliases {
16705            // Generate AS (col1, col2, ...)
16706            self.write("(");
16707            for (i, col_alias) in alias.column_aliases.iter().enumerate() {
16708                if i > 0 {
16709                    self.write(", ");
16710                }
16711                self.generate_alias_identifier(col_alias)?;
16712            }
16713            self.write(")");
16714        } else if !alias.column_aliases.is_empty() && !skip_column_aliases {
16715            // Generate AS alias(col1, col2, ...)
16716            self.generate_alias_identifier(&alias.alias)?;
16717            self.write("(");
16718            for (i, col_alias) in alias.column_aliases.iter().enumerate() {
16719                if i > 0 {
16720                    self.write(", ");
16721                }
16722                self.generate_alias_identifier(col_alias)?;
16723            }
16724            self.write(")");
16725        } else {
16726            // Simple alias (or BigQuery without column aliases)
16727            self.generate_alias_identifier(&alias.alias)?;
16728        }
16729
16730        // Output trailing comments (comments after the alias)
16731        for comment in &alias.trailing_comments {
16732            self.write_space();
16733            self.write_formatted_comment(comment);
16734        }
16735
16736        // Output pre-alias comments: when there are no trailing_comments, sqlglot
16737        // moves pre-alias comments to after the alias. When there are trailing_comments,
16738        // the pre-alias comments were already lost (consumed as column trailing comments
16739        // that were then used as pre_alias_comments). We always emit them after alias.
16740        if alias.trailing_comments.is_empty() {
16741            for comment in &alias.pre_alias_comments {
16742                self.write_space();
16743                self.write_formatted_comment(comment);
16744            }
16745        }
16746
16747        Ok(())
16748    }
16749
16750    fn generate_cast(&mut self, cast: &Cast) -> Result<()> {
16751        use crate::dialects::DialectType;
16752
16753        // SingleStore uses :> syntax
16754        if matches!(self.config.dialect, Some(DialectType::SingleStore)) {
16755            self.generate_expression(&cast.this)?;
16756            self.write(" :> ");
16757            self.generate_data_type(&cast.to)?;
16758            return Ok(());
16759        }
16760
16761        // Teradata: CAST(x AS FORMAT 'fmt') (no data type)
16762        if matches!(self.config.dialect, Some(DialectType::Teradata)) {
16763            let is_unknown_type = matches!(cast.to, DataType::Unknown)
16764                || matches!(cast.to, DataType::Custom { ref name } if name.is_empty());
16765            if is_unknown_type {
16766                if let Some(format) = &cast.format {
16767                    self.write_keyword("CAST");
16768                    self.write("(");
16769                    self.generate_expression(&cast.this)?;
16770                    self.write_space();
16771                    self.write_keyword("AS");
16772                    self.write_space();
16773                    self.write_keyword("FORMAT");
16774                    self.write_space();
16775                    self.generate_expression(format)?;
16776                    self.write(")");
16777                    return Ok(());
16778                }
16779            }
16780        }
16781
16782        // Oracle: CAST(x AS DATE/TIMESTAMP ..., 'format') -> TO_DATE/TO_TIMESTAMP(x, 'format')
16783        // This follows Python sqlglot's behavior of transforming CAST with format to native functions
16784        if matches!(self.config.dialect, Some(DialectType::Oracle)) {
16785            if let Some(format) = &cast.format {
16786                // Check if target type is DATE or TIMESTAMP
16787                let is_date = matches!(cast.to, DataType::Date);
16788                let is_timestamp = matches!(cast.to, DataType::Timestamp { .. });
16789
16790                if is_date || is_timestamp {
16791                    let func_name = if is_date { "TO_DATE" } else { "TO_TIMESTAMP" };
16792                    self.write_keyword(func_name);
16793                    self.write("(");
16794                    self.generate_expression(&cast.this)?;
16795                    self.write(", ");
16796
16797                    // Normalize format string for Oracle (HH -> HH12)
16798                    // Oracle HH is 12-hour format, same as HH12. For clarity, Python sqlglot uses HH12.
16799                    if let Expression::Literal(lit) = format.as_ref() {
16800                        if let Literal::String(fmt_str) = lit.as_ref() {
16801                            let normalized = self.normalize_oracle_format(fmt_str);
16802                            self.write("'");
16803                            self.write(&normalized);
16804                            self.write("'");
16805                        }
16806                    } else {
16807                        self.generate_expression(format)?;
16808                    }
16809
16810                    self.write(")");
16811                    return Ok(());
16812                }
16813            }
16814        }
16815
16816        // BigQuery: CAST(ARRAY[...] AS ARRAY<T>) -> ARRAY<T>[...]
16817        // This preserves sqlglot's typed inline array literal output.
16818        if matches!(self.config.dialect, Some(DialectType::BigQuery)) {
16819            if let Expression::Array(arr) = &cast.this {
16820                self.generate_data_type(&cast.to)?;
16821                // Output just the bracket content [values] without the ARRAY prefix
16822                self.write("[");
16823                for (i, expr) in arr.expressions.iter().enumerate() {
16824                    if i > 0 {
16825                        self.write(", ");
16826                    }
16827                    self.generate_expression(expr)?;
16828                }
16829                self.write("]");
16830                return Ok(());
16831            }
16832            if matches!(&cast.this, Expression::ArrayFunc(_)) {
16833                self.generate_data_type(&cast.to)?;
16834                self.generate_expression(&cast.this)?;
16835                return Ok(());
16836            }
16837        }
16838
16839        // DuckDB/Presto/Trino: When CAST(Struct([unnamed]) AS STRUCT(...)),
16840        // convert the inner Struct to ROW(values...) format
16841        if matches!(
16842            self.config.dialect,
16843            Some(DialectType::DuckDB) | Some(DialectType::Presto) | Some(DialectType::Trino)
16844        ) {
16845            if let Expression::Struct(ref s) = cast.this {
16846                let all_unnamed = s.fields.iter().all(|(name, _)| name.is_none());
16847                if all_unnamed && matches!(cast.to, DataType::Struct { .. }) {
16848                    self.write_keyword("CAST");
16849                    self.write("(");
16850                    self.generate_struct_as_row(s)?;
16851                    self.write_space();
16852                    self.write_keyword("AS");
16853                    self.write_space();
16854                    self.generate_data_type(&cast.to)?;
16855                    self.write(")");
16856                    return Ok(());
16857                }
16858            }
16859        }
16860
16861        // Determine if we should use :: syntax based on dialect
16862        // PostgreSQL prefers :: for identity, most others prefer CAST()
16863        let use_double_colon = cast.double_colon_syntax && self.dialect_prefers_double_colon();
16864
16865        if use_double_colon {
16866            // PostgreSQL :: syntax: expr::type
16867            self.generate_expression(&cast.this)?;
16868            self.write("::");
16869            self.generate_data_type(&cast.to)?;
16870        } else {
16871            // Standard CAST() syntax
16872            self.write_keyword("CAST");
16873            self.write("(");
16874            self.generate_expression(&cast.this)?;
16875            self.write_space();
16876            self.write_keyword("AS");
16877            self.write_space();
16878            // For MySQL/SingleStore/TiDB, map text/blob variant types to CHAR in CAST
16879            // This matches Python sqlglot's CAST_MAPPING behavior
16880            if matches!(
16881                self.config.dialect,
16882                Some(DialectType::MySQL) | Some(DialectType::SingleStore) | Some(DialectType::TiDB)
16883            ) {
16884                match &cast.to {
16885                    DataType::Custom { ref name } => {
16886                        if name.eq_ignore_ascii_case("LONGTEXT")
16887                            || name.eq_ignore_ascii_case("MEDIUMTEXT")
16888                            || name.eq_ignore_ascii_case("TINYTEXT")
16889                            || name.eq_ignore_ascii_case("LONGBLOB")
16890                            || name.eq_ignore_ascii_case("MEDIUMBLOB")
16891                            || name.eq_ignore_ascii_case("TINYBLOB")
16892                        {
16893                            self.write_keyword("CHAR");
16894                        } else {
16895                            self.generate_data_type(&cast.to)?;
16896                        }
16897                    }
16898                    DataType::VarChar { length, .. } => {
16899                        // MySQL CAST: VARCHAR -> CHAR
16900                        self.write_keyword("CHAR");
16901                        if let Some(n) = length {
16902                            self.write(&format!("({})", n));
16903                        }
16904                    }
16905                    DataType::Text => {
16906                        // MySQL CAST: TEXT -> CHAR
16907                        self.write_keyword("CHAR");
16908                    }
16909                    DataType::Timestamp {
16910                        precision,
16911                        timezone: false,
16912                    } => {
16913                        // MySQL CAST: TIMESTAMP -> DATETIME
16914                        self.write_keyword("DATETIME");
16915                        if let Some(p) = precision {
16916                            self.write(&format!("({})", p));
16917                        }
16918                    }
16919                    _ => {
16920                        self.generate_data_type(&cast.to)?;
16921                    }
16922                }
16923            } else if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
16924                // Snowflake CAST: STRING -> VARCHAR
16925                match &cast.to {
16926                    DataType::String { length } => {
16927                        self.write_keyword("VARCHAR");
16928                        if let Some(n) = length {
16929                            self.write(&format!("({})", n));
16930                        }
16931                    }
16932                    _ => {
16933                        self.generate_data_type(&cast.to)?;
16934                    }
16935                }
16936            } else {
16937                self.generate_data_type(&cast.to)?;
16938            }
16939
16940            // Output DEFAULT ... ON CONVERSION ERROR clause if present (Oracle)
16941            if let Some(default) = &cast.default {
16942                self.write_space();
16943                self.write_keyword("DEFAULT");
16944                self.write_space();
16945                self.generate_expression(default)?;
16946                self.write_space();
16947                self.write_keyword("ON");
16948                self.write_space();
16949                self.write_keyword("CONVERSION");
16950                self.write_space();
16951                self.write_keyword("ERROR");
16952            }
16953
16954            // Output FORMAT clause if present (BigQuery: CAST(x AS STRING FORMAT 'format'))
16955            // For Oracle with comma-separated format: CAST(x AS DATE DEFAULT NULL ON CONVERSION ERROR, 'format')
16956            if let Some(format) = &cast.format {
16957                // Check if Oracle dialect - use comma syntax
16958                if matches!(
16959                    self.config.dialect,
16960                    Some(crate::dialects::DialectType::Oracle)
16961                ) {
16962                    self.write(", ");
16963                } else {
16964                    self.write_space();
16965                    self.write_keyword("FORMAT");
16966                    self.write_space();
16967                }
16968                self.generate_expression(format)?;
16969            }
16970
16971            self.write(")");
16972            // Output trailing comments
16973            for comment in &cast.trailing_comments {
16974                self.write_space();
16975                self.write_formatted_comment(comment);
16976            }
16977        }
16978        Ok(())
16979    }
16980
16981    /// Generate a Struct as ROW(values...) format, recursively converting inner Struct to ROW too.
16982    /// Used for DuckDB/Presto/Trino CAST(Struct AS STRUCT(...)) context.
16983    fn generate_struct_as_row(&mut self, s: &crate::expressions::Struct) -> Result<()> {
16984        self.write_keyword("ROW");
16985        self.write("(");
16986        for (i, (_, expr)) in s.fields.iter().enumerate() {
16987            if i > 0 {
16988                self.write(", ");
16989            }
16990            // Recursively convert inner Struct to ROW format
16991            if let Expression::Struct(ref inner_s) = expr {
16992                self.generate_struct_as_row(inner_s)?;
16993            } else {
16994                self.generate_expression(expr)?;
16995            }
16996        }
16997        self.write(")");
16998        Ok(())
16999    }
17000
17001    /// Normalize Oracle date/time format strings
17002    /// HH -> HH12 (both are 12-hour format, but Python sqlglot prefers explicit HH12)
17003    fn normalize_oracle_format(&self, format: &str) -> String {
17004        // Replace standalone HH with HH12 (but not HH12 or HH24)
17005        // We need to be careful not to replace HH12 -> HH1212 or HH24 -> HH1224
17006        let mut result = String::new();
17007        let chars: Vec<char> = format.chars().collect();
17008        let mut i = 0;
17009
17010        while i < chars.len() {
17011            if i + 1 < chars.len() && chars[i] == 'H' && chars[i + 1] == 'H' {
17012                // Check what follows HH
17013                if i + 2 < chars.len() {
17014                    let next = chars[i + 2];
17015                    if next == '1' || next == '2' {
17016                        // This is HH12 or HH24, keep as is
17017                        result.push('H');
17018                        result.push('H');
17019                        i += 2;
17020                        continue;
17021                    }
17022                }
17023                // Standalone HH -> HH12
17024                result.push_str("HH12");
17025                i += 2;
17026            } else {
17027                result.push(chars[i]);
17028                i += 1;
17029            }
17030        }
17031
17032        result
17033    }
17034
17035    /// Check if the current dialect prefers :: cast syntax
17036    /// Preserve ClickHouse's native `::` shorthand when the parser saw it.
17037    fn dialect_prefers_double_colon(&self) -> bool {
17038        matches!(self.config.dialect, Some(DialectType::ClickHouse))
17039    }
17040
17041    /// Generate MOD function - uses % operator for Snowflake/MySQL/Presto/Trino, MOD() for others
17042    fn generate_mod_func(&mut self, f: &crate::expressions::BinaryFunc) -> Result<()> {
17043        use crate::dialects::DialectType;
17044
17045        // Snowflake, MySQL, Presto, Trino, PostgreSQL, and DuckDB prefer x % y instead of MOD(x, y)
17046        let use_percent_operator = matches!(
17047            self.config.dialect,
17048            Some(DialectType::Snowflake)
17049                | Some(DialectType::MySQL)
17050                | Some(DialectType::Presto)
17051                | Some(DialectType::Trino)
17052                | Some(DialectType::PostgreSQL)
17053                | Some(DialectType::DuckDB)
17054                | Some(DialectType::Hive)
17055                | Some(DialectType::Spark)
17056                | Some(DialectType::Databricks)
17057                | Some(DialectType::Athena)
17058        );
17059
17060        if use_percent_operator {
17061            // Wrap complex expressions in parens to preserve precedence
17062            // Since % has higher precedence than +/-, we need parens for Add/Sub on either side
17063            let needs_paren = |e: &Expression| matches!(e, Expression::Add(_) | Expression::Sub(_));
17064            if needs_paren(&f.this) {
17065                self.write("(");
17066                self.generate_expression(&f.this)?;
17067                self.write(")");
17068            } else {
17069                self.generate_expression(&f.this)?;
17070            }
17071            self.write(" % ");
17072            if needs_paren(&f.expression) {
17073                self.write("(");
17074                self.generate_expression(&f.expression)?;
17075                self.write(")");
17076            } else {
17077                self.generate_expression(&f.expression)?;
17078            }
17079            Ok(())
17080        } else {
17081            self.generate_binary_func("MOD", &f.this, &f.expression)
17082        }
17083    }
17084
17085    /// Generate IFNULL - uses COALESCE for Snowflake, IFNULL for others
17086    fn generate_ifnull(&mut self, f: &crate::expressions::BinaryFunc) -> Result<()> {
17087        use crate::dialects::DialectType;
17088
17089        // Snowflake normalizes IFNULL to COALESCE
17090        let func_name = match self.config.dialect {
17091            Some(DialectType::Snowflake) => "COALESCE",
17092            _ => "IFNULL",
17093        };
17094
17095        self.generate_binary_func(func_name, &f.this, &f.expression)
17096    }
17097
17098    /// Generate NVL - preserves original name if available, otherwise uses dialect-specific output
17099    fn generate_nvl(&mut self, f: &crate::expressions::BinaryFunc) -> Result<()> {
17100        // Use original function name if preserved (for identity tests)
17101        if let Some(ref original_name) = f.original_name {
17102            return self.generate_binary_func(original_name, &f.this, &f.expression);
17103        }
17104
17105        // Otherwise, use dialect-specific function names
17106        use crate::dialects::DialectType;
17107        let func_name = match self.config.dialect {
17108            Some(DialectType::Snowflake)
17109            | Some(DialectType::ClickHouse)
17110            | Some(DialectType::PostgreSQL)
17111            | Some(DialectType::Presto)
17112            | Some(DialectType::Trino)
17113            | Some(DialectType::Athena)
17114            | Some(DialectType::DuckDB)
17115            | Some(DialectType::BigQuery)
17116            | Some(DialectType::Spark)
17117            | Some(DialectType::Databricks)
17118            | Some(DialectType::Hive) => "COALESCE",
17119            Some(DialectType::MySQL)
17120            | Some(DialectType::Doris)
17121            | Some(DialectType::StarRocks)
17122            | Some(DialectType::SingleStore)
17123            | Some(DialectType::TiDB) => "IFNULL",
17124            _ => "NVL",
17125        };
17126
17127        self.generate_binary_func(func_name, &f.this, &f.expression)
17128    }
17129
17130    /// Generate STDDEV_SAMP - uses STDDEV for Snowflake, STDDEV_SAMP for others
17131    fn generate_stddev_samp(&mut self, f: &crate::expressions::AggFunc) -> Result<()> {
17132        use crate::dialects::DialectType;
17133
17134        // Snowflake normalizes STDDEV_SAMP to STDDEV
17135        let func_name = match self.config.dialect {
17136            Some(DialectType::Snowflake) => "STDDEV",
17137            _ => "STDDEV_SAMP",
17138        };
17139
17140        self.generate_agg_func(func_name, f)
17141    }
17142
17143    fn generate_collation(&mut self, coll: &CollationExpr) -> Result<()> {
17144        self.generate_expression(&coll.this)?;
17145        self.write_space();
17146        self.write_keyword("COLLATE");
17147        self.write_space();
17148        if coll.quoted {
17149            // Single-quoted string: COLLATE 'de_DE'
17150            self.write("'");
17151            self.write(&coll.collation);
17152            self.write("'");
17153        } else if coll.double_quoted {
17154            // Double-quoted identifier: COLLATE "de_DE"
17155            self.write("\"");
17156            self.write(&coll.collation);
17157            self.write("\"");
17158        } else {
17159            // Unquoted identifier: COLLATE de_DE
17160            self.write(&coll.collation);
17161        }
17162        Ok(())
17163    }
17164
17165    fn generate_case(&mut self, case: &Case) -> Result<()> {
17166        // In pretty mode, decide whether to expand based on total text width
17167        let multiline_case = if self.config.pretty {
17168            // Build the flat representation to check width
17169            let mut statements: Vec<String> = Vec::new();
17170            let operand_str = if let Some(operand) = &case.operand {
17171                let s = self.generate_to_string(operand)?;
17172                statements.push(format!("CASE {}", s));
17173                s
17174            } else {
17175                statements.push("CASE".to_string());
17176                String::new()
17177            };
17178            let _ = operand_str;
17179            for (condition, result) in &case.whens {
17180                statements.push(format!("WHEN {}", self.generate_to_string(condition)?));
17181                statements.push(format!("THEN {}", self.generate_to_string(result)?));
17182            }
17183            if let Some(else_) = &case.else_ {
17184                statements.push(format!("ELSE {}", self.generate_to_string(else_)?));
17185            }
17186            statements.push("END".to_string());
17187            self.too_wide(&statements)
17188        } else {
17189            false
17190        };
17191
17192        self.write_keyword("CASE");
17193        if let Some(operand) = &case.operand {
17194            self.write_space();
17195            self.generate_expression(operand)?;
17196        }
17197        if multiline_case {
17198            self.indent_level += 1;
17199        }
17200        for (condition, result) in &case.whens {
17201            if multiline_case {
17202                self.write_newline();
17203                self.write_indent();
17204            } else {
17205                self.write_space();
17206            }
17207            self.write_keyword("WHEN");
17208            self.write_space();
17209            self.generate_expression(condition)?;
17210            if multiline_case {
17211                self.write_newline();
17212                self.write_indent();
17213            } else {
17214                self.write_space();
17215            }
17216            self.write_keyword("THEN");
17217            self.write_space();
17218            self.generate_expression(result)?;
17219        }
17220        if let Some(else_) = &case.else_ {
17221            if multiline_case {
17222                self.write_newline();
17223                self.write_indent();
17224            } else {
17225                self.write_space();
17226            }
17227            self.write_keyword("ELSE");
17228            self.write_space();
17229            self.generate_expression(else_)?;
17230        }
17231        if multiline_case {
17232            self.indent_level -= 1;
17233            self.write_newline();
17234            self.write_indent();
17235        } else {
17236            self.write_space();
17237        }
17238        self.write_keyword("END");
17239        // Emit any comments that were attached to the CASE keyword
17240        for comment in &case.comments {
17241            self.write(" ");
17242            self.write_formatted_comment(comment);
17243        }
17244        Ok(())
17245    }
17246
17247    fn generate_function(&mut self, func: &Function) -> Result<()> {
17248        // Normalize function name based on dialect settings
17249        let normalized_name = self.normalize_func_name(&func.name);
17250
17251        // DuckDB: ARRAY_CONSTRUCT_COMPACT(a, b, c) -> LIST_FILTER([a, b, c], _u -> NOT _u IS NULL)
17252        if matches!(self.config.dialect, Some(DialectType::DuckDB))
17253            && func.name.eq_ignore_ascii_case("ARRAY_CONSTRUCT_COMPACT")
17254        {
17255            self.write("LIST_FILTER(");
17256            self.write("[");
17257            for (i, arg) in func.args.iter().enumerate() {
17258                if i > 0 {
17259                    self.write(", ");
17260                }
17261                self.generate_expression(arg)?;
17262            }
17263            self.write("], _u -> NOT _u IS NULL)");
17264            return Ok(());
17265        }
17266
17267        // Snowflake fixtures expect TO_VARIANT applied to arrays to keep ARRAY_CONSTRUCT(...)
17268        // rather than bracket-array syntax.
17269        if matches!(self.config.dialect, Some(DialectType::Snowflake))
17270            && func.name.eq_ignore_ascii_case("TO_VARIANT")
17271            && func.args.len() == 1
17272        {
17273            let array_expressions = match &func.args[0] {
17274                Expression::ArrayFunc(arr) => Some(&arr.expressions),
17275                Expression::Array(arr) => Some(&arr.expressions),
17276                _ => None,
17277            };
17278            if let Some(expressions) = array_expressions {
17279                self.write_keyword("TO_VARIANT");
17280                self.write("(");
17281                self.write_keyword("ARRAY_CONSTRUCT");
17282                self.write("(");
17283                for (i, arg) in expressions.iter().enumerate() {
17284                    if i > 0 {
17285                        self.write(", ");
17286                    }
17287                    self.generate_expression(arg)?;
17288                }
17289                self.write(")");
17290                self.write(")");
17291                return Ok(());
17292            }
17293        }
17294
17295        // STRUCT function: BigQuery STRUCT('Alice' AS name, 85 AS score) -> dialect-specific
17296        if func.name.eq_ignore_ascii_case("STRUCT")
17297            && !matches!(
17298                self.config.dialect,
17299                Some(DialectType::BigQuery)
17300                    | Some(DialectType::Spark)
17301                    | Some(DialectType::Databricks)
17302                    | Some(DialectType::Hive)
17303                    | None
17304            )
17305        {
17306            return self.generate_struct_function_cross_dialect(func);
17307        }
17308
17309        // SingleStore: __SS_JSON_PATH_QMARK__(expr, key) -> expr::?key
17310        // This is an internal marker function for ::? JSON path syntax
17311        if func.name.eq_ignore_ascii_case("__SS_JSON_PATH_QMARK__") && func.args.len() == 2 {
17312            self.generate_expression(&func.args[0])?;
17313            self.write("::?");
17314            // Extract the key from the string literal
17315            if let Expression::Literal(lit) = &func.args[1] {
17316                if let crate::expressions::Literal::String(key) = lit.as_ref() {
17317                    self.write(key);
17318                }
17319            } else {
17320                self.generate_expression(&func.args[1])?;
17321            }
17322            return Ok(());
17323        }
17324
17325        // PostgreSQL: __PG_BITWISE_XOR__(a, b) -> a # b
17326        if func.name.eq_ignore_ascii_case("__PG_BITWISE_XOR__") && func.args.len() == 2 {
17327            self.generate_expression(&func.args[0])?;
17328            self.write(" # ");
17329            self.generate_expression(&func.args[1])?;
17330            return Ok(());
17331        }
17332
17333        // Spark/Hive family: unwrap TRY(expr) since these dialects don't emit TRY as a scalar wrapper.
17334        if matches!(
17335            self.config.dialect,
17336            Some(DialectType::Spark | DialectType::Databricks | DialectType::Hive)
17337        ) && func.name.eq_ignore_ascii_case("TRY")
17338            && func.args.len() == 1
17339        {
17340            self.generate_expression(&func.args[0])?;
17341            return Ok(());
17342        }
17343
17344        // ClickHouse normalization: toStartOfDay(x) -> dateTrunc('DAY', x)
17345        if self.config.dialect == Some(DialectType::ClickHouse)
17346            && func.name.eq_ignore_ascii_case("TOSTARTOFDAY")
17347            && func.args.len() == 1
17348        {
17349            self.write("dateTrunc('DAY', ");
17350            self.generate_expression(&func.args[0])?;
17351            self.write(")");
17352            return Ok(());
17353        }
17354
17355        // ClickHouse uses dateTrunc casing.
17356        if self.config.dialect == Some(DialectType::ClickHouse)
17357            && func.name.eq_ignore_ascii_case("DATE_TRUNC")
17358            && func.args.len() == 2
17359        {
17360            self.write("dateTrunc(");
17361            self.generate_expression(&func.args[0])?;
17362            self.write(", ");
17363            self.generate_expression(&func.args[1])?;
17364            self.write(")");
17365            return Ok(());
17366        }
17367
17368        // Presto-family dialects spell SUBSTRING as SUBSTR in SQLGlot outputs.
17369        if matches!(
17370            self.config.dialect,
17371            Some(DialectType::Presto | DialectType::Trino | DialectType::Athena)
17372        ) && func.name.eq_ignore_ascii_case("SUBSTRING")
17373        {
17374            self.write_keyword("SUBSTR");
17375            self.write("(");
17376            for (i, arg) in func.args.iter().enumerate() {
17377                if i > 0 {
17378                    self.write(", ");
17379                }
17380                self.generate_expression(arg)?;
17381            }
17382            self.write(")");
17383            return Ok(());
17384        }
17385
17386        if self.config.dialect == Some(DialectType::Snowflake)
17387            && func.name.eq_ignore_ascii_case("LIST_DISTINCT")
17388            && func.args.len() == 1
17389        {
17390            self.write_keyword("ARRAY_DISTINCT");
17391            self.write("(");
17392            self.write_keyword("ARRAY_COMPACT");
17393            self.write("(");
17394            self.generate_expression(&func.args[0])?;
17395            self.write("))");
17396            return Ok(());
17397        }
17398
17399        if self.config.dialect == Some(DialectType::Snowflake)
17400            && func.name.eq_ignore_ascii_case("LIST")
17401            && func.args.len() == 1
17402            && !matches!(func.args.first(), Some(Expression::Select(_)))
17403        {
17404            self.write_keyword("ARRAY_AGG");
17405            self.write("(");
17406            self.generate_expression(&func.args[0])?;
17407            self.write(")");
17408            return Ok(());
17409        }
17410
17411        // Redshift: CONCAT(a, b, ...) -> a || b || ...
17412        if self.config.dialect == Some(DialectType::Redshift)
17413            && func.name.eq_ignore_ascii_case("CONCAT")
17414            && func.args.len() >= 2
17415        {
17416            for (i, arg) in func.args.iter().enumerate() {
17417                if i > 0 {
17418                    self.write(" || ");
17419                }
17420                self.generate_expression(arg)?;
17421            }
17422            return Ok(());
17423        }
17424
17425        // Redshift: CONCAT_WS(delim, a, b, c) -> a || delim || b || delim || c
17426        if self.config.dialect == Some(DialectType::Redshift)
17427            && func.name.eq_ignore_ascii_case("CONCAT_WS")
17428            && func.args.len() >= 2
17429        {
17430            let sep = &func.args[0];
17431            for (i, arg) in func.args.iter().skip(1).enumerate() {
17432                if i > 0 {
17433                    self.write(" || ");
17434                    self.generate_expression(sep)?;
17435                    self.write(" || ");
17436                }
17437                self.generate_expression(arg)?;
17438            }
17439            return Ok(());
17440        }
17441
17442        // Redshift: DATEDIFF/DATE_DIFF(unit, start, end) -> DATEDIFF(UNIT, start, end)
17443        // Unit should be unquoted uppercase identifier
17444        if self.config.dialect == Some(DialectType::Redshift)
17445            && (func.name.eq_ignore_ascii_case("DATEDIFF")
17446                || func.name.eq_ignore_ascii_case("DATE_DIFF"))
17447            && func.args.len() == 3
17448        {
17449            self.write_keyword("DATEDIFF");
17450            self.write("(");
17451            // First arg is unit - normalize to unquoted uppercase
17452            self.write_redshift_date_part(&func.args[0]);
17453            self.write(", ");
17454            self.generate_expression(&func.args[1])?;
17455            self.write(", ");
17456            self.generate_expression(&func.args[2])?;
17457            self.write(")");
17458            return Ok(());
17459        }
17460
17461        // Redshift: DATEADD/DATE_ADD(unit, interval, date) -> DATEADD(UNIT, interval, date)
17462        // Unit should be unquoted uppercase identifier
17463        if self.config.dialect == Some(DialectType::Redshift)
17464            && (func.name.eq_ignore_ascii_case("DATEADD")
17465                || func.name.eq_ignore_ascii_case("DATE_ADD"))
17466            && func.args.len() == 3
17467        {
17468            self.write_keyword("DATEADD");
17469            self.write("(");
17470            // First arg is unit - normalize to unquoted uppercase
17471            self.write_redshift_date_part(&func.args[0]);
17472            self.write(", ");
17473            self.generate_expression(&func.args[1])?;
17474            self.write(", ");
17475            self.generate_expression(&func.args[2])?;
17476            self.write(")");
17477            return Ok(());
17478        }
17479
17480        // UUID_STRING(args) from Snowflake -> dialect-specific UUID function.
17481        if func.name.eq_ignore_ascii_case("UUID_STRING")
17482            && !matches!(self.config.dialect, Some(DialectType::Snowflake) | None)
17483        {
17484            if matches!(
17485                self.config.dialect,
17486                Some(DialectType::Hive | DialectType::Spark | DialectType::Databricks)
17487            ) {
17488                self.write_keyword("CAST");
17489                self.write("(");
17490                self.write_keyword("UUID");
17491                self.write("() ");
17492                self.write_keyword("AS");
17493                self.write(" ");
17494                self.write_keyword("STRING");
17495                self.write(")");
17496                return Ok(());
17497            }
17498
17499            if matches!(
17500                self.config.dialect,
17501                Some(DialectType::Presto | DialectType::Trino)
17502            ) {
17503                self.write_keyword("CAST");
17504                self.write("(");
17505                self.write_keyword("UUID");
17506                self.write("() ");
17507                self.write_keyword("AS");
17508                self.write(" ");
17509                self.write_keyword("VARCHAR");
17510                self.write(")");
17511                return Ok(());
17512            }
17513
17514            if self.config.dialect == Some(DialectType::DuckDB) && func.args.len() == 2 {
17515                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(");
17516                self.generate_expression(&func.args[0])?;
17517                self.write(", '-', '')) || ENCODE(");
17518                self.generate_expression(&func.args[1])?;
17519                self.write(")), 1, 32) AS h))");
17520                return Ok(());
17521            }
17522
17523            let func_name = match self.config.dialect {
17524                Some(DialectType::PostgreSQL) | Some(DialectType::Redshift) => "GEN_RANDOM_UUID",
17525                Some(DialectType::BigQuery) => "GENERATE_UUID",
17526                _ => "UUID",
17527            };
17528            self.write_keyword(func_name);
17529            self.write("()");
17530            return Ok(());
17531        }
17532
17533        // Snowflake: GENERATOR(val) -> GENERATOR(ROWCOUNT => val)
17534        // GENERATOR(val1, val2) -> GENERATOR(ROWCOUNT => val1, TIMELIMIT => val2)
17535        // Positional args are mapped to named parameters.
17536        if matches!(self.config.dialect, Some(DialectType::Snowflake))
17537            && func.name.eq_ignore_ascii_case("GENERATOR")
17538        {
17539            let has_positional_args =
17540                !func.args.is_empty() && !matches!(&func.args[0], Expression::NamedArgument(_));
17541            if has_positional_args {
17542                let param_names = ["ROWCOUNT", "TIMELIMIT"];
17543                self.write_keyword("GENERATOR");
17544                self.write("(");
17545                for (i, arg) in func.args.iter().enumerate() {
17546                    if i > 0 {
17547                        self.write(", ");
17548                    }
17549                    if i < param_names.len() {
17550                        self.write_keyword(param_names[i]);
17551                        self.write(" => ");
17552                        self.generate_expression(arg)?;
17553                    } else {
17554                        self.generate_expression(arg)?;
17555                    }
17556                }
17557                self.write(")");
17558                return Ok(());
17559            }
17560        }
17561
17562        // Redshift: DATE_TRUNC('unit', date) -> DATE_TRUNC('UNIT', date)
17563        // Unit should be quoted uppercase string
17564        if self.config.dialect == Some(DialectType::Redshift)
17565            && func.name.eq_ignore_ascii_case("DATE_TRUNC")
17566            && func.args.len() == 2
17567        {
17568            self.write_keyword("DATE_TRUNC");
17569            self.write("(");
17570            // First arg is unit - normalize to quoted uppercase
17571            self.write_redshift_date_part_quoted(&func.args[0]);
17572            self.write(", ");
17573            self.generate_expression(&func.args[1])?;
17574            self.write(")");
17575            return Ok(());
17576        }
17577
17578        // TSQL/Fabric: DATE_PART -> DATEPART (no underscore)
17579        if matches!(
17580            self.config.dialect,
17581            Some(DialectType::TSQL) | Some(DialectType::Fabric)
17582        ) && (func.name.eq_ignore_ascii_case("DATE_PART")
17583            || func.name.eq_ignore_ascii_case("DATEPART"))
17584            && func.args.len() == 2
17585        {
17586            self.write_keyword("DATEPART");
17587            self.write("(");
17588            self.generate_expression(&func.args[0])?;
17589            self.write(", ");
17590            self.generate_expression(&func.args[1])?;
17591            self.write(")");
17592            return Ok(());
17593        }
17594
17595        // PostgreSQL/Redshift: DATE_PART(part, value) -> EXTRACT(part FROM value)
17596        if matches!(
17597            self.config.dialect,
17598            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
17599        ) && (func.name.eq_ignore_ascii_case("DATE_PART")
17600            || func.name.eq_ignore_ascii_case("DATEPART"))
17601            && func.args.len() == 2
17602        {
17603            self.write_keyword("EXTRACT");
17604            self.write("(");
17605            // Extract the datetime field - if it's a string literal, strip quotes to make it a keyword
17606            match &func.args[0] {
17607                Expression::Literal(lit)
17608                    if matches!(lit.as_ref(), crate::expressions::Literal::String(_)) =>
17609                {
17610                    let crate::expressions::Literal::String(s) = lit.as_ref() else {
17611                        unreachable!()
17612                    };
17613                    self.write(&s.to_ascii_lowercase());
17614                }
17615                _ => self.generate_expression(&func.args[0])?,
17616            }
17617            self.write_space();
17618            self.write_keyword("FROM");
17619            self.write_space();
17620            self.generate_expression(&func.args[1])?;
17621            self.write(")");
17622            return Ok(());
17623        }
17624
17625        // PostgreSQL: DATE_ADD(date, INTERVAL '...') / DATE_SUB(...) -> infix interval arithmetic.
17626        if self.config.dialect == Some(DialectType::PostgreSQL)
17627            && matches!(
17628                func.name.to_ascii_uppercase().as_str(),
17629                "DATE_ADD" | "DATE_SUB"
17630            )
17631            && func.args.len() == 2
17632            && matches!(func.args[1], Expression::Interval(_))
17633        {
17634            self.generate_expression(&func.args[0])?;
17635            self.write_space();
17636            if func.name.eq_ignore_ascii_case("DATE_SUB") {
17637                self.write("-");
17638            } else {
17639                self.write("+");
17640            }
17641            self.write_space();
17642            self.generate_expression(&func.args[1])?;
17643            return Ok(());
17644        }
17645
17646        // Dremio: DATE_PART(part, value) -> EXTRACT(part FROM value)
17647        // Also DATE literals in Dremio should be CAST(...AS DATE)
17648        if self.config.dialect == Some(DialectType::Dremio)
17649            && (func.name.eq_ignore_ascii_case("DATE_PART")
17650                || func.name.eq_ignore_ascii_case("DATEPART"))
17651            && func.args.len() == 2
17652        {
17653            self.write_keyword("EXTRACT");
17654            self.write("(");
17655            self.generate_expression(&func.args[0])?;
17656            self.write_space();
17657            self.write_keyword("FROM");
17658            self.write_space();
17659            // For Dremio, DATE literals should become CAST('value' AS DATE)
17660            self.generate_dremio_date_expression(&func.args[1])?;
17661            self.write(")");
17662            return Ok(());
17663        }
17664
17665        // Dremio: CURRENT_DATE_UTC() -> CURRENT_DATE_UTC (no parentheses)
17666        if self.config.dialect == Some(DialectType::Dremio)
17667            && func.name.eq_ignore_ascii_case("CURRENT_DATE_UTC")
17668            && func.args.is_empty()
17669        {
17670            self.write_keyword("CURRENT_DATE_UTC");
17671            return Ok(());
17672        }
17673
17674        // Dremio: DATETYPE(year, month, day) transformation
17675        // - If all args are integer literals: DATE('YYYY-MM-DD')
17676        // - If args are expressions: CAST(CONCAT(x, '-', y, '-', z) AS DATE)
17677        if self.config.dialect == Some(DialectType::Dremio)
17678            && func.name.eq_ignore_ascii_case("DATETYPE")
17679            && func.args.len() == 3
17680        {
17681            // Helper function to extract integer from number literal
17682            fn get_int_literal(expr: &Expression) -> Option<i64> {
17683                if let Expression::Literal(lit) = expr {
17684                    if let crate::expressions::Literal::Number(s) = lit.as_ref() {
17685                        s.parse::<i64>().ok()
17686                    } else {
17687                        None
17688                    }
17689                } else {
17690                    None
17691                }
17692            }
17693
17694            // Check if all arguments are integer literals
17695            if let (Some(year), Some(month), Some(day)) = (
17696                get_int_literal(&func.args[0]),
17697                get_int_literal(&func.args[1]),
17698                get_int_literal(&func.args[2]),
17699            ) {
17700                // All are integer literals: DATE('YYYY-MM-DD')
17701                self.write_keyword("DATE");
17702                self.write(&format!("('{:04}-{:02}-{:02}')", year, month, day));
17703                return Ok(());
17704            }
17705
17706            // For expressions: CAST(CONCAT(x, '-', y, '-', z) AS DATE)
17707            self.write_keyword("CAST");
17708            self.write("(");
17709            self.write_keyword("CONCAT");
17710            self.write("(");
17711            self.generate_expression(&func.args[0])?;
17712            self.write(", '-', ");
17713            self.generate_expression(&func.args[1])?;
17714            self.write(", '-', ");
17715            self.generate_expression(&func.args[2])?;
17716            self.write(")");
17717            self.write_space();
17718            self.write_keyword("AS");
17719            self.write_space();
17720            self.write_keyword("DATE");
17721            self.write(")");
17722            return Ok(());
17723        }
17724
17725        // Presto/Trino: DATE_ADD('unit', interval, date) - wrap interval in CAST(...AS BIGINT)
17726        // when it's not an integer literal
17727        let is_presto_like = matches!(
17728            self.config.dialect,
17729            Some(DialectType::Presto) | Some(DialectType::Trino)
17730        );
17731        if is_presto_like && func.name.eq_ignore_ascii_case("DATE_ADD") && func.args.len() == 3 {
17732            self.write_keyword("DATE_ADD");
17733            self.write("(");
17734            // First arg: unit (pass through as-is, e.g., 'DAY')
17735            self.generate_expression(&func.args[0])?;
17736            self.write(", ");
17737            // Second arg: interval - wrap in CAST(...AS BIGINT) if it doesn't return integer type
17738            let interval = &func.args[1];
17739            let needs_cast = !self.returns_integer_type(interval);
17740            if needs_cast {
17741                self.write_keyword("CAST");
17742                self.write("(");
17743            }
17744            self.generate_expression(interval)?;
17745            if needs_cast {
17746                self.write_space();
17747                self.write_keyword("AS");
17748                self.write_space();
17749                self.write_keyword("BIGINT");
17750                self.write(")");
17751            }
17752            self.write(", ");
17753            // Third arg: date
17754            self.generate_expression(&func.args[2])?;
17755            self.write(")");
17756            return Ok(());
17757        }
17758
17759        // Use bracket syntax if the function was parsed with brackets (e.g., MAP[keys, values])
17760        let use_brackets = func.use_bracket_syntax;
17761
17762        // Special case: functions WITH ORDINALITY need special output order
17763        // Input: FUNC(args) WITH ORDINALITY
17764        // Stored as: name="FUNC WITH ORDINALITY", args=[...]
17765        // Output must be: FUNC(args) WITH ORDINALITY
17766        let has_ordinality = func.name.len() >= 16
17767            && func.name[func.name.len() - 16..].eq_ignore_ascii_case(" WITH ORDINALITY");
17768        let output_name = if has_ordinality {
17769            let base_name = &func.name[..func.name.len() - " WITH ORDINALITY".len()];
17770            self.normalize_func_name(base_name)
17771        } else {
17772            normalized_name.clone()
17773        };
17774
17775        // For qualified names (schema.function or object.method), preserve original case
17776        // because they can be case-sensitive (e.g., TSQL XML methods like .nodes(), .value())
17777        let quote_source_clickhouse_function =
17778            matches!(self.config.dialect, Some(DialectType::ClickHouse))
17779                && matches!(self.config.source_dialect, Some(DialectType::ClickHouse))
17780                && func.quoted;
17781
17782        if quote_source_clickhouse_function {
17783            self.generate_identifier(&Identifier {
17784                name: func.name.clone(),
17785                quoted: true,
17786                trailing_comments: Vec::new(),
17787                span: None,
17788            })?;
17789        } else if func.name.contains('.') && !has_ordinality {
17790            // Don't normalize qualified functions - preserve original case
17791            // If the function was quoted (e.g., BigQuery `p.d.UdF`), wrap it in backticks
17792            if func.quoted {
17793                self.write("`");
17794                self.write(&func.name);
17795                self.write("`");
17796            } else {
17797                self.write(&func.name);
17798            }
17799        } else {
17800            self.write(&output_name);
17801        }
17802
17803        // If no_parens is true and there are no args, output just the function name
17804        // Unless the target dialect requires parens for this function
17805        let force_parens = func.no_parens && func.args.is_empty() && !func.distinct && {
17806            let needs_parens = if func.name.eq_ignore_ascii_case("CURRENT_USER")
17807                || func.name.eq_ignore_ascii_case("SESSION_USER")
17808                || func.name.eq_ignore_ascii_case("SYSTEM_USER")
17809            {
17810                matches!(
17811                    self.config.dialect,
17812                    Some(DialectType::Snowflake)
17813                        | Some(DialectType::Spark)
17814                        | Some(DialectType::Databricks)
17815                        | Some(DialectType::Hive)
17816                )
17817            } else {
17818                false
17819            };
17820            !needs_parens
17821        };
17822        if force_parens {
17823            // Output trailing comments
17824            for comment in &func.trailing_comments {
17825                self.write_space();
17826                self.write_formatted_comment(comment);
17827            }
17828            return Ok(());
17829        }
17830
17831        // CUBE, ROLLUP, GROUPING SETS need a space before the parenthesis
17832        if func.name.eq_ignore_ascii_case("CUBE")
17833            || func.name.eq_ignore_ascii_case("ROLLUP")
17834            || func.name.eq_ignore_ascii_case("GROUPING SETS")
17835        {
17836            self.write(" (");
17837        } else if use_brackets {
17838            self.write("[");
17839        } else {
17840            self.write("(");
17841        }
17842        if func.distinct {
17843            self.write_keyword("DISTINCT");
17844            self.write_space();
17845        }
17846
17847        // Check if arguments should be split onto multiple lines (pretty + too wide)
17848        let compact_pretty_func = matches!(self.config.dialect, Some(DialectType::Snowflake))
17849            && (func.name.eq_ignore_ascii_case("TABLE")
17850                || func.name.eq_ignore_ascii_case("FLATTEN"));
17851        // GROUPING SETS, CUBE, ROLLUP always expand in pretty mode
17852        let is_grouping_func = func.name.eq_ignore_ascii_case("GROUPING SETS")
17853            || func.name.eq_ignore_ascii_case("CUBE")
17854            || func.name.eq_ignore_ascii_case("ROLLUP");
17855        let should_split = if self.config.pretty && !func.args.is_empty() && !compact_pretty_func {
17856            if is_grouping_func {
17857                true
17858            } else {
17859                // Pre-render arguments to check total width
17860                let mut expr_strings: Vec<String> = Vec::with_capacity(func.args.len());
17861                for arg in &func.args {
17862                    let mut temp_gen = Generator::with_arc_config(self.config.clone());
17863                    Arc::make_mut(&mut temp_gen.config).pretty = false; // Don't recurse into pretty
17864                    temp_gen.generate_expression(arg)?;
17865                    expr_strings.push(temp_gen.output);
17866                }
17867                self.too_wide(&expr_strings)
17868            }
17869        } else {
17870            false
17871        };
17872
17873        if should_split {
17874            // Split onto multiple lines
17875            self.write_newline();
17876            self.indent_level += 1;
17877            for (i, arg) in func.args.iter().enumerate() {
17878                self.write_indent();
17879                self.generate_expression(arg)?;
17880                if i + 1 < func.args.len() {
17881                    self.write(",");
17882                }
17883                self.write_newline();
17884            }
17885            self.indent_level -= 1;
17886            self.write_indent();
17887        } else {
17888            // All on one line
17889            for (i, arg) in func.args.iter().enumerate() {
17890                if i > 0 {
17891                    self.write(", ");
17892                }
17893                self.generate_expression(arg)?;
17894            }
17895        }
17896
17897        if use_brackets {
17898            self.write("]");
17899        } else {
17900            self.write(")");
17901        }
17902        // Append WITH ORDINALITY after closing paren for table-valued functions
17903        if has_ordinality {
17904            self.write_space();
17905            self.write_keyword("WITH ORDINALITY");
17906        }
17907        // Output trailing comments
17908        for comment in &func.trailing_comments {
17909            self.write_space();
17910            self.write_formatted_comment(comment);
17911        }
17912        Ok(())
17913    }
17914
17915    fn generate_function_emits(&mut self, fe: &FunctionEmits) -> Result<()> {
17916        self.generate_expression(&fe.this)?;
17917        self.write_keyword(" EMITS ");
17918        self.generate_expression(&fe.emits)?;
17919        Ok(())
17920    }
17921
17922    fn generate_aggregate_function(&mut self, func: &AggregateFunction) -> Result<()> {
17923        // Normalize function name based on dialect settings
17924        let mut normalized_name = self.normalize_func_name(&func.name);
17925
17926        // Dialect-specific name mappings for aggregate functions
17927        if func.name.eq_ignore_ascii_case("MAX_BY") || func.name.eq_ignore_ascii_case("MIN_BY") {
17928            let is_max = func.name.eq_ignore_ascii_case("MAX_BY");
17929            match self.config.dialect {
17930                Some(DialectType::ClickHouse) => {
17931                    normalized_name = if is_max {
17932                        Cow::Borrowed("argMax")
17933                    } else {
17934                        Cow::Borrowed("argMin")
17935                    };
17936                }
17937                Some(DialectType::DuckDB) => {
17938                    normalized_name = if is_max {
17939                        Cow::Borrowed("ARG_MAX")
17940                    } else {
17941                        Cow::Borrowed("ARG_MIN")
17942                    };
17943                }
17944                _ => {}
17945            }
17946        }
17947        self.write(normalized_name.as_ref());
17948        self.write("(");
17949        if func.distinct {
17950            self.write_keyword("DISTINCT");
17951            self.write_space();
17952        }
17953
17954        // Check if we need to transform multi-arg COUNT DISTINCT
17955        // When dialect doesn't support multi_arg_distinct, transform:
17956        // COUNT(DISTINCT a, b) -> COUNT(DISTINCT CASE WHEN a IS NULL THEN NULL WHEN b IS NULL THEN NULL ELSE (a, b) END)
17957        let is_count = normalized_name.eq_ignore_ascii_case("COUNT");
17958        let needs_multi_arg_transform =
17959            func.distinct && is_count && func.args.len() > 1 && !self.config.multi_arg_distinct;
17960
17961        if needs_multi_arg_transform {
17962            // Generate: CASE WHEN a IS NULL THEN NULL WHEN b IS NULL THEN NULL ELSE (a, b) END
17963            self.write_keyword("CASE");
17964            for arg in &func.args {
17965                self.write_space();
17966                self.write_keyword("WHEN");
17967                self.write_space();
17968                self.generate_expression(arg)?;
17969                self.write_space();
17970                self.write_keyword("IS NULL THEN NULL");
17971            }
17972            self.write_space();
17973            self.write_keyword("ELSE");
17974            self.write(" (");
17975            for (i, arg) in func.args.iter().enumerate() {
17976                if i > 0 {
17977                    self.write(", ");
17978                }
17979                self.generate_expression(arg)?;
17980            }
17981            self.write(")");
17982            self.write_space();
17983            self.write_keyword("END");
17984        } else {
17985            for (i, arg) in func.args.iter().enumerate() {
17986                if i > 0 {
17987                    self.write(", ");
17988                }
17989                self.generate_expression(arg)?;
17990            }
17991        }
17992
17993        // IGNORE NULLS / RESPECT NULLS inside parens (for BigQuery style or when config says in_func)
17994        let clickhouse_ignore_nulls_outside =
17995            matches!(self.config.dialect, Some(DialectType::ClickHouse));
17996        if self.config.ignore_nulls_in_func
17997            && !matches!(
17998                self.config.dialect,
17999                Some(DialectType::DuckDB) | Some(DialectType::ClickHouse)
18000            )
18001        {
18002            if let Some(ignore) = func.ignore_nulls {
18003                self.write_space();
18004                if ignore {
18005                    self.write_keyword("IGNORE NULLS");
18006                } else {
18007                    self.write_keyword("RESPECT NULLS");
18008                }
18009            }
18010        }
18011
18012        // ORDER BY inside aggregate
18013        if !func.order_by.is_empty() {
18014            self.write_space();
18015            self.write_keyword("ORDER BY");
18016            self.write_space();
18017            for (i, ord) in func.order_by.iter().enumerate() {
18018                if i > 0 {
18019                    self.write(", ");
18020                }
18021                self.generate_ordered(ord)?;
18022            }
18023        }
18024
18025        // LIMIT inside aggregate
18026        if let Some(limit) = &func.limit {
18027            self.write_space();
18028            self.write_keyword("LIMIT");
18029            self.write_space();
18030            // Check if this is a Tuple representing LIMIT offset, count
18031            if let Expression::Tuple(t) = limit.as_ref() {
18032                if t.expressions.len() == 2 {
18033                    self.generate_expression(&t.expressions[0])?;
18034                    self.write(", ");
18035                    self.generate_expression(&t.expressions[1])?;
18036                } else {
18037                    self.generate_expression(limit)?;
18038                }
18039            } else {
18040                self.generate_expression(limit)?;
18041            }
18042        }
18043
18044        self.write(")");
18045
18046        // IGNORE NULLS / RESPECT NULLS outside parens (standard style)
18047        if (!self.config.ignore_nulls_in_func || clickhouse_ignore_nulls_outside)
18048            && !matches!(self.config.dialect, Some(DialectType::DuckDB))
18049        {
18050            if let Some(ignore) = func.ignore_nulls {
18051                self.write_space();
18052                if ignore {
18053                    self.write_keyword("IGNORE NULLS");
18054                } else {
18055                    self.write_keyword("RESPECT NULLS");
18056                }
18057            }
18058        }
18059
18060        if let Some(filter) = &func.filter {
18061            self.write_space();
18062            self.write_keyword("FILTER");
18063            self.write("(");
18064            self.write_keyword("WHERE");
18065            self.write_space();
18066            self.generate_expression(filter)?;
18067            self.write(")");
18068        }
18069
18070        Ok(())
18071    }
18072
18073    fn generate_window_function(&mut self, wf: &WindowFunction) -> Result<()> {
18074        self.generate_expression(&wf.this)?;
18075
18076        // Generate KEEP clause if present (Oracle KEEP (DENSE_RANK FIRST|LAST ORDER BY ...))
18077        if let Some(keep) = &wf.keep {
18078            self.write_space();
18079            self.write_keyword("KEEP");
18080            self.write(" (");
18081            self.write_keyword("DENSE_RANK");
18082            self.write_space();
18083            if keep.first {
18084                self.write_keyword("FIRST");
18085            } else {
18086                self.write_keyword("LAST");
18087            }
18088            self.write_space();
18089            self.write_keyword("ORDER BY");
18090            self.write_space();
18091            for (i, ord) in keep.order_by.iter().enumerate() {
18092                if i > 0 {
18093                    self.write(", ");
18094                }
18095                self.generate_ordered(ord)?;
18096            }
18097            self.write(")");
18098        }
18099
18100        // Check if there's any OVER clause content
18101        let has_over = !wf.over.partition_by.is_empty()
18102            || !wf.over.order_by.is_empty()
18103            || wf.over.frame.is_some()
18104            || wf.over.window_name.is_some();
18105
18106        // Only output OVER if there's actual window specification (not just KEEP alone)
18107        if has_over {
18108            self.write_space();
18109            self.write_keyword("OVER");
18110
18111            // Check if this is just a bare named window reference (no parens needed)
18112            let has_specs = !wf.over.partition_by.is_empty()
18113                || !wf.over.order_by.is_empty()
18114                || wf.over.frame.is_some();
18115
18116            if wf.over.window_name.is_some() && !has_specs {
18117                // OVER window_name (without parentheses)
18118                self.write_space();
18119                self.write(&wf.over.window_name.as_ref().unwrap().name);
18120            } else {
18121                // OVER (...) or OVER (window_name ...)
18122                self.write(" (");
18123                self.generate_over(&wf.over)?;
18124                self.write(")");
18125            }
18126        } else if wf.keep.is_none() {
18127            // No KEEP and no OVER content, but still a WindowFunction - output empty OVER ()
18128            self.write_space();
18129            self.write_keyword("OVER");
18130            self.write(" ()");
18131        }
18132
18133        Ok(())
18134    }
18135
18136    /// Generate WITHIN GROUP clause (for ordered-set aggregate functions)
18137    fn generate_within_group(&mut self, wg: &WithinGroup) -> Result<()> {
18138        self.generate_expression(&wg.this)?;
18139        self.write_space();
18140        self.write_keyword("WITHIN GROUP");
18141        self.write(" (");
18142        self.write_keyword("ORDER BY");
18143        self.write_space();
18144        for (i, ord) in wg.order_by.iter().enumerate() {
18145            if i > 0 {
18146                self.write(", ");
18147            }
18148            self.generate_ordered(ord)?;
18149        }
18150        self.write(")");
18151        Ok(())
18152    }
18153
18154    /// Generate the contents of an OVER clause (without parentheses)
18155    fn generate_over(&mut self, over: &Over) -> Result<()> {
18156        let mut has_content = false;
18157
18158        // Named window reference
18159        if let Some(name) = &over.window_name {
18160            self.write(&name.name);
18161            has_content = true;
18162        }
18163
18164        // PARTITION BY
18165        if !over.partition_by.is_empty() {
18166            if has_content {
18167                self.write_space();
18168            }
18169            self.write_keyword("PARTITION BY");
18170            self.write_space();
18171            for (i, expr) in over.partition_by.iter().enumerate() {
18172                if i > 0 {
18173                    self.write(", ");
18174                }
18175                self.generate_expression(expr)?;
18176            }
18177            has_content = true;
18178        }
18179
18180        // ORDER BY
18181        if !over.order_by.is_empty() {
18182            if has_content {
18183                self.write_space();
18184            }
18185            self.write_keyword("ORDER BY");
18186            self.write_space();
18187            for (i, ordered) in over.order_by.iter().enumerate() {
18188                if i > 0 {
18189                    self.write(", ");
18190                }
18191                self.generate_ordered(ordered)?;
18192            }
18193            has_content = true;
18194        }
18195
18196        // Window frame
18197        if let Some(frame) = &over.frame {
18198            if has_content {
18199                self.write_space();
18200            }
18201            self.generate_window_frame(frame)?;
18202        }
18203
18204        Ok(())
18205    }
18206
18207    fn generate_window_frame(&mut self, frame: &WindowFrame) -> Result<()> {
18208        // Exasol uses lowercase for frame kind (rows/range/groups)
18209        let lowercase_frame = self.config.lowercase_window_frame_keywords;
18210
18211        // Use preserved kind_text if available (for case preservation), unless lowercase override is active
18212        if !lowercase_frame {
18213            if let Some(kind_text) = &frame.kind_text {
18214                self.write(kind_text);
18215            } else {
18216                match frame.kind {
18217                    WindowFrameKind::Rows => self.write_keyword("ROWS"),
18218                    WindowFrameKind::Range => self.write_keyword("RANGE"),
18219                    WindowFrameKind::Groups => self.write_keyword("GROUPS"),
18220                }
18221            }
18222        } else {
18223            match frame.kind {
18224                WindowFrameKind::Rows => self.write("rows"),
18225                WindowFrameKind::Range => self.write("range"),
18226                WindowFrameKind::Groups => self.write("groups"),
18227            }
18228        }
18229
18230        // Use BETWEEN format only when there's an explicit end bound,
18231        // or when normalize_window_frame_between is enabled and the start is a directional bound
18232        self.write_space();
18233        let should_normalize = self.config.normalize_window_frame_between
18234            && frame.end.is_none()
18235            && matches!(
18236                frame.start,
18237                WindowFrameBound::Preceding(_)
18238                    | WindowFrameBound::Following(_)
18239                    | WindowFrameBound::UnboundedPreceding
18240                    | WindowFrameBound::UnboundedFollowing
18241            );
18242
18243        if let Some(end) = &frame.end {
18244            // BETWEEN format: RANGE BETWEEN start AND end
18245            self.write_keyword("BETWEEN");
18246            self.write_space();
18247            self.generate_window_frame_bound(&frame.start, frame.start_side_text.as_deref())?;
18248            self.write_space();
18249            self.write_keyword("AND");
18250            self.write_space();
18251            self.generate_window_frame_bound(end, frame.end_side_text.as_deref())?;
18252        } else if should_normalize {
18253            // Normalize single-bound to BETWEEN form: ROWS 1 PRECEDING → ROWS BETWEEN 1 PRECEDING AND CURRENT ROW
18254            self.write_keyword("BETWEEN");
18255            self.write_space();
18256            self.generate_window_frame_bound(&frame.start, frame.start_side_text.as_deref())?;
18257            self.write_space();
18258            self.write_keyword("AND");
18259            self.write_space();
18260            self.write_keyword("CURRENT ROW");
18261        } else {
18262            // Single bound format: RANGE CURRENT ROW
18263            self.generate_window_frame_bound(&frame.start, frame.start_side_text.as_deref())?;
18264        }
18265
18266        // EXCLUDE clause
18267        if let Some(exclude) = &frame.exclude {
18268            self.write_space();
18269            self.write_keyword("EXCLUDE");
18270            self.write_space();
18271            match exclude {
18272                WindowFrameExclude::CurrentRow => self.write_keyword("CURRENT ROW"),
18273                WindowFrameExclude::Group => self.write_keyword("GROUP"),
18274                WindowFrameExclude::Ties => self.write_keyword("TIES"),
18275                WindowFrameExclude::NoOthers => self.write_keyword("NO OTHERS"),
18276            }
18277        }
18278
18279        Ok(())
18280    }
18281
18282    fn generate_window_frame_bound(
18283        &mut self,
18284        bound: &WindowFrameBound,
18285        side_text: Option<&str>,
18286    ) -> Result<()> {
18287        // Exasol uses lowercase for preceding/following
18288        let lowercase_frame = self.config.lowercase_window_frame_keywords;
18289
18290        match bound {
18291            WindowFrameBound::CurrentRow => {
18292                self.write_keyword("CURRENT ROW");
18293            }
18294            WindowFrameBound::UnboundedPreceding => {
18295                self.write_keyword("UNBOUNDED");
18296                self.write_space();
18297                if lowercase_frame {
18298                    self.write("preceding");
18299                } else if let Some(text) = side_text {
18300                    self.write(text);
18301                } else {
18302                    self.write_keyword("PRECEDING");
18303                }
18304            }
18305            WindowFrameBound::UnboundedFollowing => {
18306                self.write_keyword("UNBOUNDED");
18307                self.write_space();
18308                if lowercase_frame {
18309                    self.write("following");
18310                } else if let Some(text) = side_text {
18311                    self.write(text);
18312                } else {
18313                    self.write_keyword("FOLLOWING");
18314                }
18315            }
18316            WindowFrameBound::Preceding(expr) => {
18317                self.generate_expression(expr)?;
18318                self.write_space();
18319                if lowercase_frame {
18320                    self.write("preceding");
18321                } else if let Some(text) = side_text {
18322                    self.write(text);
18323                } else {
18324                    self.write_keyword("PRECEDING");
18325                }
18326            }
18327            WindowFrameBound::Following(expr) => {
18328                self.generate_expression(expr)?;
18329                self.write_space();
18330                if lowercase_frame {
18331                    self.write("following");
18332                } else if let Some(text) = side_text {
18333                    self.write(text);
18334                } else {
18335                    self.write_keyword("FOLLOWING");
18336                }
18337            }
18338            WindowFrameBound::BarePreceding => {
18339                if lowercase_frame {
18340                    self.write("preceding");
18341                } else if let Some(text) = side_text {
18342                    self.write(text);
18343                } else {
18344                    self.write_keyword("PRECEDING");
18345                }
18346            }
18347            WindowFrameBound::BareFollowing => {
18348                if lowercase_frame {
18349                    self.write("following");
18350                } else if let Some(text) = side_text {
18351                    self.write(text);
18352                } else {
18353                    self.write_keyword("FOLLOWING");
18354                }
18355            }
18356            WindowFrameBound::Value(expr) => {
18357                // Bare numeric bound without PRECEDING/FOLLOWING
18358                self.generate_expression(expr)?;
18359            }
18360        }
18361        Ok(())
18362    }
18363
18364    fn generate_interval(&mut self, interval: &Interval) -> Result<()> {
18365        // For Oracle with ExprSpan: only output INTERVAL if `this` is a literal
18366        // (e.g., `(expr) DAY(9) TO SECOND(3)` should NOT have INTERVAL prefix)
18367        let skip_interval_keyword = matches!(self.config.dialect, Some(DialectType::Oracle))
18368            && matches!(&interval.unit, Some(IntervalUnitSpec::ExprSpan(_)))
18369            && !matches!(&interval.this, Some(Expression::Literal(_)));
18370
18371        // SINGLE_STRING_INTERVAL: combine value and unit into a single quoted string
18372        // e.g., INTERVAL '1' DAY -> INTERVAL '1 DAY'
18373        if self.config.single_string_interval {
18374            if let (
18375                Some(Expression::Literal(lit)),
18376                Some(IntervalUnitSpec::Simple {
18377                    ref unit,
18378                    ref use_plural,
18379                }),
18380            ) = (&interval.this, &interval.unit)
18381            {
18382                if let Literal::String(ref val) = lit.as_ref() {
18383                    self.write_keyword("INTERVAL");
18384                    self.write_space();
18385                    let effective_plural = *use_plural && self.config.interval_allows_plural_form;
18386                    let unit_str = self.interval_unit_str(unit, effective_plural);
18387                    self.write("'");
18388                    self.write(val);
18389                    self.write(" ");
18390                    self.write(&unit_str);
18391                    self.write("'");
18392                    return Ok(());
18393                }
18394            }
18395        }
18396
18397        if !skip_interval_keyword {
18398            self.write_keyword("INTERVAL");
18399        }
18400
18401        // Generate value if present
18402        if let Some(ref value) = interval.this {
18403            if !skip_interval_keyword {
18404                self.write_space();
18405            }
18406            // If the value is a complex expression (not a literal/column/function call)
18407            // and there's a unit, wrap it in parentheses
18408            // e.g., INTERVAL (2 * 2) MONTH, INTERVAL (DAYOFMONTH(dt) - 1) DAY
18409            let needs_parens = interval.unit.is_some()
18410                && matches!(
18411                    value,
18412                    Expression::Add(_)
18413                        | Expression::Sub(_)
18414                        | Expression::Mul(_)
18415                        | Expression::Div(_)
18416                        | Expression::Mod(_)
18417                        | Expression::BitwiseAnd(_)
18418                        | Expression::BitwiseOr(_)
18419                        | Expression::BitwiseXor(_)
18420                );
18421            if needs_parens {
18422                self.write("(");
18423            }
18424            self.generate_expression(value)?;
18425            if needs_parens {
18426                self.write(")");
18427            }
18428        }
18429
18430        // Generate unit if present
18431        if let Some(ref unit_spec) = interval.unit {
18432            self.write_space();
18433            self.write_interval_unit_spec(unit_spec)?;
18434        }
18435
18436        Ok(())
18437    }
18438
18439    /// Return the string representation of an interval unit
18440    fn interval_unit_str(&self, unit: &IntervalUnit, use_plural: bool) -> &'static str {
18441        match (unit, use_plural) {
18442            (IntervalUnit::Year, false) => "YEAR",
18443            (IntervalUnit::Year, true) => "YEARS",
18444            (IntervalUnit::Quarter, false) => "QUARTER",
18445            (IntervalUnit::Quarter, true) => "QUARTERS",
18446            (IntervalUnit::Month, false) => "MONTH",
18447            (IntervalUnit::Month, true) => "MONTHS",
18448            (IntervalUnit::Week, false) => "WEEK",
18449            (IntervalUnit::Week, true) => "WEEKS",
18450            (IntervalUnit::Day, false) => "DAY",
18451            (IntervalUnit::Day, true) => "DAYS",
18452            (IntervalUnit::Hour, false) => "HOUR",
18453            (IntervalUnit::Hour, true) => "HOURS",
18454            (IntervalUnit::Minute, false) => "MINUTE",
18455            (IntervalUnit::Minute, true) => "MINUTES",
18456            (IntervalUnit::Second, false) => "SECOND",
18457            (IntervalUnit::Second, true) => "SECONDS",
18458            (IntervalUnit::Millisecond, false) => "MILLISECOND",
18459            (IntervalUnit::Millisecond, true) => "MILLISECONDS",
18460            (IntervalUnit::Microsecond, false) => "MICROSECOND",
18461            (IntervalUnit::Microsecond, true) => "MICROSECONDS",
18462            (IntervalUnit::Nanosecond, false) => "NANOSECOND",
18463            (IntervalUnit::Nanosecond, true) => "NANOSECONDS",
18464        }
18465    }
18466
18467    fn write_interval_unit_spec(&mut self, unit_spec: &IntervalUnitSpec) -> Result<()> {
18468        match unit_spec {
18469            IntervalUnitSpec::Simple { unit, use_plural } => {
18470                // If dialect doesn't allow plural forms, force singular
18471                let effective_plural = *use_plural && self.config.interval_allows_plural_form;
18472                self.write_simple_interval_unit(unit, effective_plural);
18473            }
18474            IntervalUnitSpec::Span(span) => {
18475                self.write_simple_interval_unit(&span.this, false);
18476                self.write_space();
18477                self.write_keyword("TO");
18478                self.write_space();
18479                self.write_simple_interval_unit(&span.expression, false);
18480            }
18481            IntervalUnitSpec::ExprSpan(span) => {
18482                // Expression-based interval span (e.g., DAY(9) TO SECOND(3))
18483                self.generate_expression(&span.this)?;
18484                self.write_space();
18485                self.write_keyword("TO");
18486                self.write_space();
18487                self.generate_expression(&span.expression)?;
18488            }
18489            IntervalUnitSpec::Expr(expr) => {
18490                self.generate_expression(expr)?;
18491            }
18492        }
18493        Ok(())
18494    }
18495
18496    fn write_simple_interval_unit(&mut self, unit: &IntervalUnit, use_plural: bool) {
18497        // Output interval unit, respecting plural preference
18498        match (unit, use_plural) {
18499            (IntervalUnit::Year, false) => self.write_keyword("YEAR"),
18500            (IntervalUnit::Year, true) => self.write_keyword("YEARS"),
18501            (IntervalUnit::Quarter, false) => self.write_keyword("QUARTER"),
18502            (IntervalUnit::Quarter, true) => self.write_keyword("QUARTERS"),
18503            (IntervalUnit::Month, false) => self.write_keyword("MONTH"),
18504            (IntervalUnit::Month, true) => self.write_keyword("MONTHS"),
18505            (IntervalUnit::Week, false) => self.write_keyword("WEEK"),
18506            (IntervalUnit::Week, true) => self.write_keyword("WEEKS"),
18507            (IntervalUnit::Day, false) => self.write_keyword("DAY"),
18508            (IntervalUnit::Day, true) => self.write_keyword("DAYS"),
18509            (IntervalUnit::Hour, false) => self.write_keyword("HOUR"),
18510            (IntervalUnit::Hour, true) => self.write_keyword("HOURS"),
18511            (IntervalUnit::Minute, false) => self.write_keyword("MINUTE"),
18512            (IntervalUnit::Minute, true) => self.write_keyword("MINUTES"),
18513            (IntervalUnit::Second, false) => self.write_keyword("SECOND"),
18514            (IntervalUnit::Second, true) => self.write_keyword("SECONDS"),
18515            (IntervalUnit::Millisecond, false) => self.write_keyword("MILLISECOND"),
18516            (IntervalUnit::Millisecond, true) => self.write_keyword("MILLISECONDS"),
18517            (IntervalUnit::Microsecond, false) => self.write_keyword("MICROSECOND"),
18518            (IntervalUnit::Microsecond, true) => self.write_keyword("MICROSECONDS"),
18519            (IntervalUnit::Nanosecond, false) => self.write_keyword("NANOSECOND"),
18520            (IntervalUnit::Nanosecond, true) => self.write_keyword("NANOSECONDS"),
18521        }
18522    }
18523
18524    /// Normalize a date part expression to unquoted uppercase for Redshift DATEDIFF/DATEADD
18525    /// Converts: 'day', 'days', day, days, DAY -> DAY (unquoted)
18526    fn write_redshift_date_part(&mut self, expr: &Expression) {
18527        let part_str = self.extract_date_part_string(expr);
18528        if let Some(part) = part_str {
18529            let normalized = self.normalize_date_part(&part);
18530            self.write_keyword(&normalized);
18531        } else {
18532            // If we can't extract a date part string, fall back to generating the expression
18533            let _ = self.generate_expression(expr);
18534        }
18535    }
18536
18537    /// Normalize a date part expression to quoted uppercase for Redshift DATE_TRUNC
18538    /// Converts: 'day', day, DAY -> 'DAY' (quoted)
18539    fn write_redshift_date_part_quoted(&mut self, expr: &Expression) {
18540        let part_str = self.extract_date_part_string(expr);
18541        if let Some(part) = part_str {
18542            let normalized = self.normalize_date_part(&part);
18543            self.write("'");
18544            self.write(&normalized);
18545            self.write("'");
18546        } else {
18547            // If we can't extract a date part string, fall back to generating the expression
18548            let _ = self.generate_expression(expr);
18549        }
18550    }
18551
18552    /// Extract date part string from expression (handles string literals and identifiers)
18553    fn extract_date_part_string(&self, expr: &Expression) -> Option<String> {
18554        match expr {
18555            Expression::Literal(lit)
18556                if matches!(lit.as_ref(), crate::expressions::Literal::String(_)) =>
18557            {
18558                let crate::expressions::Literal::String(s) = lit.as_ref() else {
18559                    unreachable!()
18560                };
18561                Some(s.clone())
18562            }
18563            Expression::Identifier(id) => Some(id.name.clone()),
18564            Expression::Var(v) => Some(v.this.clone()),
18565            Expression::Column(col) if col.table.is_none() => {
18566                // Simple column reference without table prefix, treat as identifier
18567                Some(col.name.name.clone())
18568            }
18569            _ => None,
18570        }
18571    }
18572
18573    /// Normalize date part to uppercase singular form
18574    /// days -> DAY, months -> MONTH, etc.
18575    fn normalize_date_part(&self, part: &str) -> String {
18576        let mut buf = [0u8; 64];
18577        let lower: &str = if part.len() <= 64 {
18578            for (i, b) in part.bytes().enumerate() {
18579                buf[i] = b.to_ascii_lowercase();
18580            }
18581            std::str::from_utf8(&buf[..part.len()]).unwrap_or(part)
18582        } else {
18583            return part.to_ascii_uppercase();
18584        };
18585        match lower {
18586            "day" | "days" | "d" => "DAY".to_string(),
18587            "month" | "months" | "mon" | "mons" | "mm" => "MONTH".to_string(),
18588            "year" | "years" | "y" | "yy" | "yyyy" => "YEAR".to_string(),
18589            "week" | "weeks" | "w" | "wk" => "WEEK".to_string(),
18590            "hour" | "hours" | "h" | "hh" => "HOUR".to_string(),
18591            "minute" | "minutes" | "m" | "mi" | "n" => "MINUTE".to_string(),
18592            "second" | "seconds" | "s" | "ss" => "SECOND".to_string(),
18593            "millisecond" | "milliseconds" | "ms" => "MILLISECOND".to_string(),
18594            "microsecond" | "microseconds" | "us" => "MICROSECOND".to_string(),
18595            "quarter" | "quarters" | "q" | "qq" => "QUARTER".to_string(),
18596            _ => part.to_ascii_uppercase(),
18597        }
18598    }
18599
18600    fn write_datetime_field(&mut self, field: &DateTimeField) {
18601        match field {
18602            DateTimeField::Year => self.write_keyword("YEAR"),
18603            DateTimeField::Month => self.write_keyword("MONTH"),
18604            DateTimeField::Day => self.write_keyword("DAY"),
18605            DateTimeField::Hour => self.write_keyword("HOUR"),
18606            DateTimeField::Minute => self.write_keyword("MINUTE"),
18607            DateTimeField::Second => self.write_keyword("SECOND"),
18608            DateTimeField::Millisecond => self.write_keyword("MILLISECOND"),
18609            DateTimeField::Microsecond => self.write_keyword("MICROSECOND"),
18610            DateTimeField::DayOfWeek => {
18611                let name = match self.config.dialect {
18612                    Some(DialectType::DuckDB) | Some(DialectType::Snowflake) => "DAYOFWEEK",
18613                    Some(DialectType::TSQL) | Some(DialectType::Fabric) => "WEEKDAY",
18614                    _ => "DOW",
18615                };
18616                self.write_keyword(name);
18617            }
18618            DateTimeField::DayOfYear => {
18619                let name = match self.config.dialect {
18620                    Some(DialectType::DuckDB) | Some(DialectType::Snowflake) => "DAYOFYEAR",
18621                    _ => "DOY",
18622                };
18623                self.write_keyword(name);
18624            }
18625            DateTimeField::Week => self.write_keyword("WEEK"),
18626            DateTimeField::WeekWithModifier(modifier) => {
18627                self.write_keyword("WEEK");
18628                self.write("(");
18629                self.write(modifier);
18630                self.write(")");
18631            }
18632            DateTimeField::Quarter => self.write_keyword("QUARTER"),
18633            DateTimeField::Epoch => self.write_keyword("EPOCH"),
18634            DateTimeField::Timezone => self.write_keyword("TIMEZONE"),
18635            DateTimeField::TimezoneHour => self.write_keyword("TIMEZONE_HOUR"),
18636            DateTimeField::TimezoneMinute => self.write_keyword("TIMEZONE_MINUTE"),
18637            DateTimeField::Date => self.write_keyword("DATE"),
18638            DateTimeField::Time => self.write_keyword("TIME"),
18639            DateTimeField::Custom(name) => self.write(name),
18640        }
18641    }
18642
18643    /// Write datetime field in lowercase (for Spark/Hive/Databricks)
18644    fn write_datetime_field_lower(&mut self, field: &DateTimeField) {
18645        match field {
18646            DateTimeField::Year => self.write("year"),
18647            DateTimeField::Month => self.write("month"),
18648            DateTimeField::Day => self.write("day"),
18649            DateTimeField::Hour => self.write("hour"),
18650            DateTimeField::Minute => self.write("minute"),
18651            DateTimeField::Second => self.write("second"),
18652            DateTimeField::Millisecond => self.write("millisecond"),
18653            DateTimeField::Microsecond => self.write("microsecond"),
18654            DateTimeField::DayOfWeek => self.write("dow"),
18655            DateTimeField::DayOfYear => self.write("doy"),
18656            DateTimeField::Week => self.write("week"),
18657            DateTimeField::WeekWithModifier(modifier) => {
18658                self.write("week(");
18659                self.write(modifier);
18660                self.write(")");
18661            }
18662            DateTimeField::Quarter => self.write("quarter"),
18663            DateTimeField::Epoch => self.write("epoch"),
18664            DateTimeField::Timezone => self.write("timezone"),
18665            DateTimeField::TimezoneHour => self.write("timezone_hour"),
18666            DateTimeField::TimezoneMinute => self.write("timezone_minute"),
18667            DateTimeField::Date => self.write("date"),
18668            DateTimeField::Time => self.write("time"),
18669            DateTimeField::Custom(name) => self.write(name),
18670        }
18671    }
18672
18673    // Helper function generators
18674
18675    fn generate_simple_func(&mut self, name: &str, arg: &Expression) -> Result<()> {
18676        self.write_keyword(name);
18677        self.write("(");
18678        self.generate_expression(arg)?;
18679        self.write(")");
18680        Ok(())
18681    }
18682
18683    /// Generate a unary function, using the original name if available for round-trip preservation
18684    fn generate_unary_func(
18685        &mut self,
18686        default_name: &str,
18687        f: &crate::expressions::UnaryFunc,
18688    ) -> Result<()> {
18689        let name = f.original_name.as_deref().unwrap_or(default_name);
18690        self.write_keyword(name);
18691        self.write("(");
18692        self.generate_expression(&f.this)?;
18693        self.write(")");
18694        Ok(())
18695    }
18696
18697    /// Generate SQRT/CBRT - always use function form (matches Python SQLGlot normalization)
18698    fn generate_sqrt_cbrt(
18699        &mut self,
18700        f: &crate::expressions::UnaryFunc,
18701        func_name: &str,
18702        _op: &str,
18703    ) -> Result<()> {
18704        // Python SQLGlot normalizes |/ and ||/ to SQRT() and CBRT()
18705        // Always use function syntax for consistency
18706        self.write_keyword(func_name);
18707        self.write("(");
18708        self.generate_expression(&f.this)?;
18709        self.write(")");
18710        Ok(())
18711    }
18712
18713    fn generate_binary_func(
18714        &mut self,
18715        name: &str,
18716        arg1: &Expression,
18717        arg2: &Expression,
18718    ) -> Result<()> {
18719        self.write_keyword(name);
18720        self.write("(");
18721        self.generate_expression(arg1)?;
18722        self.write(", ");
18723        self.generate_expression(arg2)?;
18724        self.write(")");
18725        Ok(())
18726    }
18727
18728    /// Generate CHAR/CHR function with optional USING charset
18729    /// e.g., CHAR(77, 77.3, '77.3' USING utf8mb4)
18730    /// e.g., CHR(187 USING NCHAR_CS) -- Oracle
18731    fn generate_char_func(&mut self, f: &crate::expressions::CharFunc) -> Result<()> {
18732        // Use stored name if available, otherwise default to CHAR
18733        let func_name = f.name.as_deref().unwrap_or("CHAR");
18734        self.write_keyword(func_name);
18735        self.write("(");
18736        for (i, arg) in f.args.iter().enumerate() {
18737            if i > 0 {
18738                self.write(", ");
18739            }
18740            self.generate_expression(arg)?;
18741        }
18742        if let Some(ref charset) = f.charset {
18743            self.write(" ");
18744            self.write_keyword("USING");
18745            self.write(" ");
18746            self.write(charset);
18747        }
18748        self.write(")");
18749        Ok(())
18750    }
18751
18752    fn generate_power(&mut self, f: &BinaryFunc) -> Result<()> {
18753        use crate::dialects::DialectType;
18754
18755        match self.config.dialect {
18756            Some(DialectType::Teradata) => {
18757                // Teradata uses ** operator for exponentiation
18758                self.generate_expression(&f.this)?;
18759                self.write(" ** ");
18760                self.generate_expression(&f.expression)?;
18761                Ok(())
18762            }
18763            _ => {
18764                // Other dialects use POWER function
18765                self.generate_binary_func("POWER", &f.this, &f.expression)
18766            }
18767        }
18768    }
18769
18770    fn generate_vararg_func(&mut self, name: &str, args: &[Expression]) -> Result<()> {
18771        self.write_func_name(name);
18772        self.write("(");
18773        for (i, arg) in args.iter().enumerate() {
18774            if i > 0 {
18775                self.write(", ");
18776            }
18777            self.generate_expression(arg)?;
18778        }
18779        self.write(")");
18780        Ok(())
18781    }
18782
18783    // String function generators
18784
18785    fn generate_concat_ws(&mut self, f: &ConcatWs) -> Result<()> {
18786        self.write_keyword("CONCAT_WS");
18787        self.write("(");
18788        self.generate_expression(&f.separator)?;
18789        for expr in &f.expressions {
18790            self.write(", ");
18791            self.generate_expression(expr)?;
18792        }
18793        self.write(")");
18794        Ok(())
18795    }
18796
18797    fn collect_concat_operands<'a>(expr: &'a Expression, out: &mut Vec<&'a Expression>) {
18798        if let Expression::Concat(op) = expr {
18799            Self::collect_concat_operands(&op.left, out);
18800            Self::collect_concat_operands(&op.right, out);
18801        } else {
18802            out.push(expr);
18803        }
18804    }
18805
18806    fn generate_mysql_concat_from_concat(&mut self, op: &BinaryOp) -> Result<()> {
18807        let mut operands = Vec::new();
18808        Self::collect_concat_operands(&op.left, &mut operands);
18809        Self::collect_concat_operands(&op.right, &mut operands);
18810
18811        self.write_keyword("CONCAT");
18812        self.write("(");
18813        for (i, operand) in operands.iter().enumerate() {
18814            if i > 0 {
18815                self.write(", ");
18816            }
18817            self.generate_expression(operand)?;
18818        }
18819        self.write(")");
18820        Ok(())
18821    }
18822
18823    fn collect_dpipe_operands<'a>(expr: &'a Expression, out: &mut Vec<&'a Expression>) {
18824        if let Expression::DPipe(dpipe) = expr {
18825            Self::collect_dpipe_operands(&dpipe.this, out);
18826            Self::collect_dpipe_operands(&dpipe.expression, out);
18827        } else {
18828            out.push(expr);
18829        }
18830    }
18831
18832    fn generate_mysql_concat_from_dpipe(&mut self, e: &DPipe) -> Result<()> {
18833        let mut operands = Vec::new();
18834        Self::collect_dpipe_operands(&e.this, &mut operands);
18835        Self::collect_dpipe_operands(&e.expression, &mut operands);
18836
18837        self.write_keyword("CONCAT");
18838        self.write("(");
18839        for (i, operand) in operands.iter().enumerate() {
18840            if i > 0 {
18841                self.write(", ");
18842            }
18843            self.generate_expression(operand)?;
18844        }
18845        self.write(")");
18846        Ok(())
18847    }
18848
18849    fn generate_substring(&mut self, f: &SubstringFunc) -> Result<()> {
18850        // Oracle and Presto-family dialects use SUBSTR; most others use SUBSTRING
18851        let use_substr = matches!(
18852            self.config.dialect,
18853            Some(
18854                DialectType::Oracle
18855                    | DialectType::Presto
18856                    | DialectType::Trino
18857                    | DialectType::Athena
18858            )
18859        );
18860        if use_substr {
18861            self.write_keyword("SUBSTR");
18862        } else {
18863            self.write_keyword("SUBSTRING");
18864        }
18865        self.write("(");
18866        self.generate_expression(&f.this)?;
18867        // PostgreSQL always uses FROM/FOR syntax
18868        let force_from_for = matches!(self.config.dialect, Some(DialectType::PostgreSQL));
18869        // Spark/Hive/TSQL/Fabric use comma syntax, not FROM/FOR syntax
18870        let use_comma_syntax = matches!(
18871            self.config.dialect,
18872            Some(DialectType::Spark)
18873                | Some(DialectType::Hive)
18874                | Some(DialectType::Databricks)
18875                | Some(DialectType::TSQL)
18876                | Some(DialectType::Fabric)
18877        );
18878        if (f.from_for_syntax || force_from_for) && !use_comma_syntax {
18879            // SQL standard syntax: SUBSTRING(str FROM pos FOR len)
18880            self.write_space();
18881            self.write_keyword("FROM");
18882            self.write_space();
18883            self.generate_expression(&f.start)?;
18884            if let Some(length) = &f.length {
18885                self.write_space();
18886                self.write_keyword("FOR");
18887                self.write_space();
18888                self.generate_expression(length)?;
18889            }
18890        } else {
18891            // Comma-separated syntax: SUBSTRING(str, pos, len) or SUBSTR(str, pos, len)
18892            self.write(", ");
18893            self.generate_expression(&f.start)?;
18894            if let Some(length) = &f.length {
18895                self.write(", ");
18896                self.generate_expression(length)?;
18897            }
18898        }
18899        self.write(")");
18900        Ok(())
18901    }
18902
18903    fn generate_overlay(&mut self, f: &OverlayFunc) -> Result<()> {
18904        self.write_keyword("OVERLAY");
18905        self.write("(");
18906        self.generate_expression(&f.this)?;
18907        self.write_space();
18908        self.write_keyword("PLACING");
18909        self.write_space();
18910        self.generate_expression(&f.replacement)?;
18911        self.write_space();
18912        self.write_keyword("FROM");
18913        self.write_space();
18914        self.generate_expression(&f.from)?;
18915        if let Some(length) = &f.length {
18916            self.write_space();
18917            self.write_keyword("FOR");
18918            self.write_space();
18919            self.generate_expression(length)?;
18920        }
18921        self.write(")");
18922        Ok(())
18923    }
18924
18925    fn generate_trim(&mut self, f: &TrimFunc) -> Result<()> {
18926        // Special case: TRIM(LEADING str) -> LTRIM(str), TRIM(TRAILING str) -> RTRIM(str)
18927        // when no characters are specified (PostgreSQL style)
18928        if f.position_explicit && f.characters.is_none() {
18929            match f.position {
18930                TrimPosition::Leading => {
18931                    self.write_keyword("LTRIM");
18932                    self.write("(");
18933                    self.generate_expression(&f.this)?;
18934                    self.write(")");
18935                    return Ok(());
18936                }
18937                TrimPosition::Trailing => {
18938                    self.write_keyword("RTRIM");
18939                    self.write("(");
18940                    self.generate_expression(&f.this)?;
18941                    self.write(")");
18942                    return Ok(());
18943                }
18944                TrimPosition::Both => {
18945                    // TRIM(BOTH str) -> BTRIM(str) in PostgreSQL, but TRIM(str) is more standard
18946                    // Fall through to standard TRIM handling
18947                }
18948            }
18949        }
18950
18951        self.write_keyword("TRIM");
18952        self.write("(");
18953        // When BOTH is specified without trim characters, simplify to just TRIM(str)
18954        // Force standard syntax for dialects that require it (Hive, Spark, Databricks, ClickHouse)
18955        let force_standard = f.characters.is_some()
18956            && !f.sql_standard_syntax
18957            && matches!(
18958                self.config.dialect,
18959                Some(DialectType::Hive)
18960                    | Some(DialectType::Spark)
18961                    | Some(DialectType::Databricks)
18962                    | Some(DialectType::ClickHouse)
18963            );
18964        let use_standard = (f.sql_standard_syntax || force_standard)
18965            && !(f.position_explicit
18966                && f.characters.is_none()
18967                && matches!(f.position, TrimPosition::Both));
18968        if use_standard {
18969            // SQL standard syntax: TRIM(BOTH chars FROM str)
18970            // Only output position if it was explicitly specified
18971            if f.position_explicit {
18972                match f.position {
18973                    TrimPosition::Both => self.write_keyword("BOTH"),
18974                    TrimPosition::Leading => self.write_keyword("LEADING"),
18975                    TrimPosition::Trailing => self.write_keyword("TRAILING"),
18976                }
18977                self.write_space();
18978            }
18979            if let Some(chars) = &f.characters {
18980                self.generate_expression(chars)?;
18981                self.write_space();
18982            }
18983            self.write_keyword("FROM");
18984            self.write_space();
18985            self.generate_expression(&f.this)?;
18986        } else {
18987            // Simple function syntax: TRIM(str) or TRIM(str, chars)
18988            self.generate_expression(&f.this)?;
18989            if let Some(chars) = &f.characters {
18990                self.write(", ");
18991                self.generate_expression(chars)?;
18992            }
18993        }
18994        self.write(")");
18995        Ok(())
18996    }
18997
18998    fn generate_replace(&mut self, f: &ReplaceFunc) -> Result<()> {
18999        self.write_keyword("REPLACE");
19000        self.write("(");
19001        self.generate_expression(&f.this)?;
19002        self.write(", ");
19003        self.generate_expression(&f.old)?;
19004        self.write(", ");
19005        self.generate_expression(&f.new)?;
19006        self.write(")");
19007        Ok(())
19008    }
19009
19010    fn generate_left_right(&mut self, name: &str, f: &LeftRightFunc) -> Result<()> {
19011        self.write_keyword(name);
19012        self.write("(");
19013        self.generate_expression(&f.this)?;
19014        self.write(", ");
19015        self.generate_expression(&f.length)?;
19016        self.write(")");
19017        Ok(())
19018    }
19019
19020    fn generate_repeat(&mut self, f: &RepeatFunc) -> Result<()> {
19021        self.write_keyword("REPEAT");
19022        self.write("(");
19023        self.generate_expression(&f.this)?;
19024        self.write(", ");
19025        self.generate_expression(&f.times)?;
19026        self.write(")");
19027        Ok(())
19028    }
19029
19030    fn generate_pad(&mut self, name: &str, f: &PadFunc) -> Result<()> {
19031        self.write_keyword(name);
19032        self.write("(");
19033        self.generate_expression(&f.this)?;
19034        self.write(", ");
19035        self.generate_expression(&f.length)?;
19036        if let Some(fill) = &f.fill {
19037            self.write(", ");
19038            self.generate_expression(fill)?;
19039        }
19040        self.write(")");
19041        Ok(())
19042    }
19043
19044    fn generate_split(&mut self, f: &SplitFunc) -> Result<()> {
19045        self.write_keyword("SPLIT");
19046        self.write("(");
19047        self.generate_expression(&f.this)?;
19048        self.write(", ");
19049        self.generate_expression(&f.delimiter)?;
19050        self.write(")");
19051        Ok(())
19052    }
19053
19054    fn generate_regexp_like(&mut self, f: &RegexpFunc) -> Result<()> {
19055        use crate::dialects::DialectType;
19056        // PostgreSQL uses ~ operator for regex matching
19057        if matches!(self.config.dialect, Some(DialectType::PostgreSQL)) && f.flags.is_none() {
19058            self.generate_expression(&f.this)?;
19059            self.write(" ~ ");
19060            self.generate_expression(&f.pattern)?;
19061        } else if matches!(self.config.dialect, Some(DialectType::Exasol)) && f.flags.is_none() {
19062            // Exasol uses REGEXP_LIKE as infix binary operator
19063            self.generate_expression(&f.this)?;
19064            self.write_keyword(" REGEXP_LIKE ");
19065            self.generate_expression(&f.pattern)?;
19066        } else if matches!(
19067            self.config.dialect,
19068            Some(DialectType::SingleStore)
19069                | Some(DialectType::Spark)
19070                | Some(DialectType::Hive)
19071                | Some(DialectType::Databricks)
19072        ) && f.flags.is_none()
19073        {
19074            // SingleStore/Spark/Hive/Databricks use RLIKE infix operator
19075            self.generate_expression(&f.this)?;
19076            self.write_keyword(" RLIKE ");
19077            self.generate_expression(&f.pattern)?;
19078        } else if matches!(self.config.dialect, Some(DialectType::StarRocks)) {
19079            // StarRocks uses REGEXP function syntax
19080            self.write_keyword("REGEXP");
19081            self.write("(");
19082            self.generate_expression(&f.this)?;
19083            self.write(", ");
19084            self.generate_expression(&f.pattern)?;
19085            if let Some(flags) = &f.flags {
19086                self.write(", ");
19087                self.generate_expression(flags)?;
19088            }
19089            self.write(")");
19090        } else {
19091            self.write_keyword("REGEXP_LIKE");
19092            self.write("(");
19093            self.generate_expression(&f.this)?;
19094            self.write(", ");
19095            self.generate_expression(&f.pattern)?;
19096            if let Some(flags) = &f.flags {
19097                self.write(", ");
19098                self.generate_expression(flags)?;
19099            }
19100            self.write(")");
19101        }
19102        Ok(())
19103    }
19104
19105    fn generate_regexp_replace(&mut self, f: &RegexpReplaceFunc) -> Result<()> {
19106        self.write_keyword("REGEXP_REPLACE");
19107        self.write("(");
19108        self.generate_expression(&f.this)?;
19109        self.write(", ");
19110        self.generate_expression(&f.pattern)?;
19111        self.write(", ");
19112        self.generate_expression(&f.replacement)?;
19113        if let Some(flags) = &f.flags {
19114            self.write(", ");
19115            self.generate_expression(flags)?;
19116        }
19117        self.write(")");
19118        Ok(())
19119    }
19120
19121    fn generate_regexp_extract(&mut self, f: &RegexpExtractFunc) -> Result<()> {
19122        self.write_keyword("REGEXP_EXTRACT");
19123        self.write("(");
19124        self.generate_expression(&f.this)?;
19125        self.write(", ");
19126        self.generate_expression(&f.pattern)?;
19127        if let Some(group) = &f.group {
19128            self.write(", ");
19129            self.generate_expression(group)?;
19130        }
19131        self.write(")");
19132        Ok(())
19133    }
19134
19135    // Math function generators
19136
19137    fn generate_round(&mut self, f: &RoundFunc) -> Result<()> {
19138        self.write_keyword("ROUND");
19139        self.write("(");
19140        self.generate_expression(&f.this)?;
19141        if let Some(decimals) = &f.decimals {
19142            self.write(", ");
19143            self.generate_expression(decimals)?;
19144        }
19145        self.write(")");
19146        Ok(())
19147    }
19148
19149    fn generate_floor(&mut self, f: &FloorFunc) -> Result<()> {
19150        self.write_keyword("FLOOR");
19151        self.write("(");
19152        self.generate_expression(&f.this)?;
19153        // Handle Druid-style FLOOR(time TO unit) syntax
19154        if let Some(to) = &f.to {
19155            self.write(" ");
19156            self.write_keyword("TO");
19157            self.write(" ");
19158            self.generate_expression(to)?;
19159        } else if let Some(scale) = &f.scale {
19160            self.write(", ");
19161            self.generate_expression(scale)?;
19162        }
19163        self.write(")");
19164        Ok(())
19165    }
19166
19167    fn generate_ceil(&mut self, f: &CeilFunc) -> Result<()> {
19168        self.write_keyword("CEIL");
19169        self.write("(");
19170        self.generate_expression(&f.this)?;
19171        // Handle Druid-style CEIL(time TO unit) syntax
19172        if let Some(to) = &f.to {
19173            self.write(" ");
19174            self.write_keyword("TO");
19175            self.write(" ");
19176            self.generate_expression(to)?;
19177        } else if let Some(decimals) = &f.decimals {
19178            self.write(", ");
19179            self.generate_expression(decimals)?;
19180        }
19181        self.write(")");
19182        Ok(())
19183    }
19184
19185    fn generate_log(&mut self, f: &LogFunc) -> Result<()> {
19186        use crate::expressions::Literal;
19187
19188        if let Some(base) = &f.base {
19189            // Check for LOG_BASE_FIRST = None dialects (Presto, Trino, ClickHouse, Athena)
19190            // These dialects use LOG2()/LOG10() instead of LOG(base, value)
19191            if self.is_log_base_none() {
19192                if matches!(base, Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(s) if s == "2"))
19193                {
19194                    self.write_func_name("LOG2");
19195                    self.write("(");
19196                    self.generate_expression(&f.this)?;
19197                    self.write(")");
19198                    return Ok(());
19199                } else if matches!(base, Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(s) if s == "10"))
19200                {
19201                    self.write_func_name("LOG10");
19202                    self.write("(");
19203                    self.generate_expression(&f.this)?;
19204                    self.write(")");
19205                    return Ok(());
19206                }
19207                // Other bases: fall through to LOG(base, value) — best effort
19208            }
19209
19210            self.write_func_name("LOG");
19211            self.write("(");
19212            if self.is_log_value_first() {
19213                // BigQuery, TSQL, Tableau, Fabric: LOG(value, base)
19214                self.generate_expression(&f.this)?;
19215                self.write(", ");
19216                self.generate_expression(base)?;
19217            } else {
19218                // Default (PostgreSQL, etc.): LOG(base, value)
19219                self.generate_expression(base)?;
19220                self.write(", ");
19221                self.generate_expression(&f.this)?;
19222            }
19223            self.write(")");
19224        } else {
19225            // Single arg: LOG(x) — unspecified base (log base 10 in default dialect)
19226            self.write_func_name("LOG");
19227            self.write("(");
19228            self.generate_expression(&f.this)?;
19229            self.write(")");
19230        }
19231        Ok(())
19232    }
19233
19234    /// Whether the target dialect uses LOG(value, base) order (value first).
19235    /// BigQuery, TSQL, Tableau, Fabric use LOG(value, base).
19236    fn is_log_value_first(&self) -> bool {
19237        use crate::dialects::DialectType;
19238        matches!(
19239            self.config.dialect,
19240            Some(DialectType::BigQuery)
19241                | Some(DialectType::TSQL)
19242                | Some(DialectType::Tableau)
19243                | Some(DialectType::Fabric)
19244        )
19245    }
19246
19247    /// Whether the target dialect has LOG_BASE_FIRST = None (uses LOG2/LOG10 instead).
19248    /// Presto, Trino, ClickHouse, Athena.
19249    fn is_log_base_none(&self) -> bool {
19250        use crate::dialects::DialectType;
19251        matches!(
19252            self.config.dialect,
19253            Some(DialectType::Presto)
19254                | Some(DialectType::Trino)
19255                | Some(DialectType::ClickHouse)
19256                | Some(DialectType::Athena)
19257        )
19258    }
19259
19260    // Date/time function generators
19261
19262    fn generate_current_time(&mut self, f: &CurrentTime) -> Result<()> {
19263        self.write_keyword("CURRENT_TIME");
19264        if let Some(precision) = f.precision {
19265            self.write(&format!("({})", precision));
19266        } else if matches!(
19267            self.config.dialect,
19268            Some(crate::dialects::DialectType::MySQL)
19269                | Some(crate::dialects::DialectType::SingleStore)
19270                | Some(crate::dialects::DialectType::TiDB)
19271        ) {
19272            self.write("()");
19273        }
19274        Ok(())
19275    }
19276
19277    fn generate_current_timestamp(&mut self, f: &CurrentTimestamp) -> Result<()> {
19278        use crate::dialects::DialectType;
19279
19280        // Oracle/Redshift SYSDATE handling
19281        if f.sysdate {
19282            match self.config.dialect {
19283                Some(DialectType::Oracle) | Some(DialectType::Redshift) => {
19284                    self.write_keyword("SYSDATE");
19285                    return Ok(());
19286                }
19287                Some(DialectType::Snowflake) => {
19288                    // Snowflake uses SYSDATE() function
19289                    self.write_keyword("SYSDATE");
19290                    self.write("()");
19291                    return Ok(());
19292                }
19293                _ => {
19294                    // Other dialects use CURRENT_TIMESTAMP for SYSDATE
19295                }
19296            }
19297        }
19298
19299        self.write_keyword("CURRENT_TIMESTAMP");
19300        // MySQL, Spark, Hive always use CURRENT_TIMESTAMP() with parentheses
19301        if let Some(precision) = f.precision {
19302            self.write(&format!("({})", precision));
19303        } else if matches!(
19304            self.config.dialect,
19305            Some(crate::dialects::DialectType::MySQL)
19306                | Some(crate::dialects::DialectType::SingleStore)
19307                | Some(crate::dialects::DialectType::TiDB)
19308                | Some(crate::dialects::DialectType::Spark)
19309                | Some(crate::dialects::DialectType::Hive)
19310                | Some(crate::dialects::DialectType::Databricks)
19311                | Some(crate::dialects::DialectType::ClickHouse)
19312                | Some(crate::dialects::DialectType::BigQuery)
19313                | Some(crate::dialects::DialectType::Snowflake)
19314                | Some(crate::dialects::DialectType::Exasol)
19315        ) {
19316            self.write("()");
19317        }
19318        Ok(())
19319    }
19320
19321    fn generate_at_time_zone(&mut self, f: &AtTimeZone) -> Result<()> {
19322        // Exasol uses CONVERT_TZ(timestamp, 'UTC', zone) instead of AT TIME ZONE
19323        if self.config.dialect == Some(DialectType::Exasol) {
19324            self.write_keyword("CONVERT_TZ");
19325            self.write("(");
19326            self.generate_expression(&f.this)?;
19327            self.write(", 'UTC', ");
19328            self.generate_expression(&f.zone)?;
19329            self.write(")");
19330            return Ok(());
19331        }
19332
19333        self.generate_expression(&f.this)?;
19334        self.write_space();
19335        self.write_keyword("AT TIME ZONE");
19336        self.write_space();
19337        self.generate_expression(&f.zone)?;
19338        Ok(())
19339    }
19340
19341    fn generate_date_add(&mut self, f: &DateAddFunc, name: &str) -> Result<()> {
19342        use crate::dialects::DialectType;
19343
19344        // Presto/Trino use DATE_ADD('unit', interval, date) format
19345        // with the interval cast to BIGINT when needed
19346        let is_presto_like = matches!(
19347            self.config.dialect,
19348            Some(DialectType::Presto) | Some(DialectType::Trino)
19349        );
19350
19351        if is_presto_like {
19352            self.write_keyword(name);
19353            self.write("(");
19354            // Unit as string literal
19355            self.write("'");
19356            self.write_simple_interval_unit(&f.unit, false);
19357            self.write("'");
19358            self.write(", ");
19359            // Interval - wrap in CAST(...AS BIGINT) if it doesn't return integer type
19360            let needs_cast = !self.returns_integer_type(&f.interval);
19361            if needs_cast {
19362                self.write_keyword("CAST");
19363                self.write("(");
19364            }
19365            self.generate_expression(&f.interval)?;
19366            if needs_cast {
19367                self.write_space();
19368                self.write_keyword("AS");
19369                self.write_space();
19370                self.write_keyword("BIGINT");
19371                self.write(")");
19372            }
19373            self.write(", ");
19374            self.generate_expression(&f.this)?;
19375            self.write(")");
19376        } else if matches!(self.config.dialect, Some(DialectType::PostgreSQL)) {
19377            self.generate_expression(&f.this)?;
19378            self.write_space();
19379            if name.eq_ignore_ascii_case("DATE_SUB") {
19380                self.write("-");
19381            } else {
19382                self.write("+");
19383            }
19384            self.write_space();
19385            self.write_keyword("INTERVAL");
19386            self.write_space();
19387            self.write("'");
19388            let mut interval_gen = Generator::with_arc_config(self.config.clone());
19389            let interval_sql = interval_gen.generate(&f.interval)?;
19390            self.write(&interval_sql);
19391            self.write(" ");
19392            self.write_simple_interval_unit(&f.unit, false);
19393            self.write("'");
19394        } else {
19395            self.write_keyword(name);
19396            self.write("(");
19397            self.generate_expression(&f.this)?;
19398            self.write(", ");
19399            self.write_keyword("INTERVAL");
19400            self.write_space();
19401            self.generate_expression(&f.interval)?;
19402            self.write_space();
19403            self.write_simple_interval_unit(&f.unit, false); // Use singular form for DATEADD
19404            self.write(")");
19405        }
19406        Ok(())
19407    }
19408
19409    /// Check if an expression returns an integer type (doesn't need cast to BIGINT in Presto DATE_ADD)
19410    /// This is a heuristic to avoid full type inference
19411    fn returns_integer_type(&self, expr: &Expression) -> bool {
19412        use crate::expressions::{DataType, Literal};
19413        match expr {
19414            // Integer literals (no decimal point)
19415            Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(_)) => {
19416                let Literal::Number(n) = lit.as_ref() else {
19417                    unreachable!()
19418                };
19419                !n.contains('.')
19420            }
19421
19422            // FLOOR(x) returns integer if x is integer
19423            Expression::Floor(f) => self.returns_integer_type(&f.this),
19424
19425            // ROUND(x) returns integer if x is integer
19426            Expression::Round(f) => {
19427                // Only if no decimals arg or it's returning an integer
19428                f.decimals.is_none() && self.returns_integer_type(&f.this)
19429            }
19430
19431            // SIGN returns integer if input is integer
19432            Expression::Sign(f) => self.returns_integer_type(&f.this),
19433
19434            // ABS returns the same type as input
19435            Expression::Abs(f) => self.returns_integer_type(&f.this),
19436
19437            // Arithmetic operations on integers return integers
19438            Expression::Mul(op) => {
19439                self.returns_integer_type(&op.left) && self.returns_integer_type(&op.right)
19440            }
19441            Expression::Add(op) => {
19442                self.returns_integer_type(&op.left) && self.returns_integer_type(&op.right)
19443            }
19444            Expression::Sub(op) => {
19445                self.returns_integer_type(&op.left) && self.returns_integer_type(&op.right)
19446            }
19447            Expression::Mod(op) => self.returns_integer_type(&op.left),
19448
19449            // CAST(x AS BIGINT/INT/INTEGER/SMALLINT/TINYINT) returns integer
19450            Expression::Cast(c) => matches!(
19451                &c.to,
19452                DataType::BigInt { .. }
19453                    | DataType::Int { .. }
19454                    | DataType::SmallInt { .. }
19455                    | DataType::TinyInt { .. }
19456            ),
19457
19458            // Negation: -x returns integer if x is integer
19459            Expression::Neg(op) => self.returns_integer_type(&op.this),
19460
19461            // Parenthesized expression
19462            Expression::Paren(p) => self.returns_integer_type(&p.this),
19463
19464            // Column references and most expressions are assumed to need casting
19465            // since we don't have full type information
19466            _ => false,
19467        }
19468    }
19469
19470    fn generate_datediff(&mut self, f: &DateDiffFunc) -> Result<()> {
19471        self.write_keyword("DATEDIFF");
19472        self.write("(");
19473        if let Some(unit) = &f.unit {
19474            self.write_simple_interval_unit(unit, false); // Use singular form for DATEDIFF
19475            self.write(", ");
19476        }
19477        self.generate_expression(&f.this)?;
19478        self.write(", ");
19479        self.generate_expression(&f.expression)?;
19480        self.write(")");
19481        Ok(())
19482    }
19483
19484    fn generate_date_trunc(&mut self, f: &DateTruncFunc) -> Result<()> {
19485        if self.config.dialect == Some(DialectType::ClickHouse) {
19486            self.write("dateTrunc");
19487        } else {
19488            self.write_keyword("DATE_TRUNC");
19489        }
19490        self.write("('");
19491        self.write_datetime_field(&f.unit);
19492        self.write("', ");
19493        self.generate_expression(&f.this)?;
19494        self.write(")");
19495        Ok(())
19496    }
19497
19498    fn generate_last_day(&mut self, f: &LastDayFunc) -> Result<()> {
19499        use crate::dialects::DialectType;
19500        use crate::expressions::DateTimeField;
19501
19502        self.write_keyword("LAST_DAY");
19503        self.write("(");
19504        self.generate_expression(&f.this)?;
19505        if let Some(unit) = &f.unit {
19506            self.write(", ");
19507            // BigQuery: strip week-start modifier from WEEK(SUNDAY), WEEK(MONDAY), etc.
19508            // WEEK(SUNDAY) -> WEEK
19509            if matches!(self.config.dialect, Some(DialectType::BigQuery)) {
19510                if let DateTimeField::WeekWithModifier(_) = unit {
19511                    self.write_keyword("WEEK");
19512                } else {
19513                    self.write_datetime_field(unit);
19514                }
19515            } else {
19516                self.write_datetime_field(unit);
19517            }
19518        }
19519        self.write(")");
19520        Ok(())
19521    }
19522
19523    fn generate_extract(&mut self, f: &ExtractFunc) -> Result<()> {
19524        // TSQL/Fabric use DATEPART(part, expr) instead of EXTRACT(part FROM expr)
19525        if matches!(
19526            self.config.dialect,
19527            Some(DialectType::TSQL) | Some(DialectType::Fabric)
19528        ) {
19529            self.write_keyword("DATEPART");
19530            self.write("(");
19531            self.write_datetime_field(&f.field);
19532            self.write(", ");
19533            self.generate_expression(&f.this)?;
19534            self.write(")");
19535            return Ok(());
19536        }
19537        self.write_keyword("EXTRACT");
19538        self.write("(");
19539        // Hive/Spark use lowercase datetime fields in EXTRACT
19540        if matches!(
19541            self.config.dialect,
19542            Some(DialectType::Hive) | Some(DialectType::Spark) | Some(DialectType::Databricks)
19543        ) {
19544            self.write_datetime_field_lower(&f.field);
19545        } else {
19546            self.write_datetime_field(&f.field);
19547        }
19548        self.write_space();
19549        self.write_keyword("FROM");
19550        self.write_space();
19551        self.generate_expression(&f.this)?;
19552        self.write(")");
19553        Ok(())
19554    }
19555
19556    fn generate_to_date(&mut self, f: &ToDateFunc) -> Result<()> {
19557        self.write_keyword("TO_DATE");
19558        self.write("(");
19559        self.generate_expression(&f.this)?;
19560        if let Some(format) = &f.format {
19561            self.write(", ");
19562            self.generate_expression(format)?;
19563        }
19564        self.write(")");
19565        Ok(())
19566    }
19567
19568    fn generate_to_timestamp(&mut self, f: &ToTimestampFunc) -> Result<()> {
19569        self.write_keyword("TO_TIMESTAMP");
19570        self.write("(");
19571        self.generate_expression(&f.this)?;
19572        if let Some(format) = &f.format {
19573            self.write(", ");
19574            self.generate_expression(format)?;
19575        }
19576        self.write(")");
19577        Ok(())
19578    }
19579
19580    // Control flow function generators
19581
19582    fn generate_if_func(&mut self, f: &IfFunc) -> Result<()> {
19583        use crate::dialects::DialectType;
19584
19585        // Generic mode: normalize IF to CASE WHEN
19586        if self.config.dialect.is_none() || self.config.dialect == Some(DialectType::Generic) {
19587            self.write_keyword("CASE WHEN");
19588            self.write_space();
19589            self.generate_expression(&f.condition)?;
19590            self.write_space();
19591            self.write_keyword("THEN");
19592            self.write_space();
19593            self.generate_expression(&f.true_value)?;
19594            if let Some(false_val) = &f.false_value {
19595                self.write_space();
19596                self.write_keyword("ELSE");
19597                self.write_space();
19598                self.generate_expression(false_val)?;
19599            }
19600            self.write_space();
19601            self.write_keyword("END");
19602            return Ok(());
19603        }
19604
19605        // Exasol uses IF condition THEN true_value ELSE false_value ENDIF syntax
19606        if self.config.dialect == Some(DialectType::Exasol) {
19607            self.write_keyword("IF");
19608            self.write_space();
19609            self.generate_expression(&f.condition)?;
19610            self.write_space();
19611            self.write_keyword("THEN");
19612            self.write_space();
19613            self.generate_expression(&f.true_value)?;
19614            if let Some(false_val) = &f.false_value {
19615                self.write_space();
19616                self.write_keyword("ELSE");
19617                self.write_space();
19618                self.generate_expression(false_val)?;
19619            }
19620            self.write_space();
19621            self.write_keyword("ENDIF");
19622            return Ok(());
19623        }
19624
19625        // Choose function name based on target dialect
19626        let func_name = match self.config.dialect {
19627            Some(DialectType::ClickHouse) => f.original_name.as_deref().unwrap_or("IF"),
19628            Some(DialectType::Snowflake) => "IFF",
19629            Some(DialectType::SQLite) | Some(DialectType::TSQL) => "IIF",
19630            Some(DialectType::Drill) => "`IF`",
19631            _ => "IF",
19632        };
19633        self.write(func_name);
19634        self.write("(");
19635        self.generate_expression(&f.condition)?;
19636        self.write(", ");
19637        self.generate_expression(&f.true_value)?;
19638        if let Some(false_val) = &f.false_value {
19639            self.write(", ");
19640            self.generate_expression(false_val)?;
19641        }
19642        self.write(")");
19643        Ok(())
19644    }
19645
19646    fn generate_nvl2(&mut self, f: &Nvl2Func) -> Result<()> {
19647        self.write_keyword("NVL2");
19648        self.write("(");
19649        self.generate_expression(&f.this)?;
19650        self.write(", ");
19651        self.generate_expression(&f.true_value)?;
19652        self.write(", ");
19653        self.generate_expression(&f.false_value)?;
19654        self.write(")");
19655        Ok(())
19656    }
19657
19658    // Typed aggregate function generators
19659
19660    fn generate_count(&mut self, f: &CountFunc) -> Result<()> {
19661        // Use normalize_functions for COUNT to respect ClickHouse case preservation
19662        let count_name = match self.config.normalize_functions {
19663            NormalizeFunctions::Upper => "COUNT".to_string(),
19664            NormalizeFunctions::Lower => "count".to_string(),
19665            NormalizeFunctions::None => f
19666                .original_name
19667                .clone()
19668                .unwrap_or_else(|| "COUNT".to_string()),
19669        };
19670        self.write(&count_name);
19671        self.write("(");
19672        if f.distinct {
19673            self.write_keyword("DISTINCT");
19674            self.write_space();
19675        }
19676        if f.star {
19677            self.write("*");
19678        } else if let Some(ref expr) = f.this {
19679            // For COUNT(DISTINCT a, b), unwrap the Tuple to avoid extra parentheses
19680            if let Expression::Tuple(tuple) = expr {
19681                // Check if we need to transform multi-arg COUNT DISTINCT
19682                // When dialect doesn't support multi_arg_distinct, transform:
19683                // COUNT(DISTINCT a, b) -> COUNT(DISTINCT CASE WHEN a IS NULL THEN NULL WHEN b IS NULL THEN NULL ELSE (a, b) END)
19684                let needs_transform =
19685                    f.distinct && tuple.expressions.len() > 1 && !self.config.multi_arg_distinct;
19686
19687                if needs_transform {
19688                    // Generate: CASE WHEN a IS NULL THEN NULL WHEN b IS NULL THEN NULL ELSE (a, b) END
19689                    self.write_keyword("CASE");
19690                    for e in &tuple.expressions {
19691                        self.write_space();
19692                        self.write_keyword("WHEN");
19693                        self.write_space();
19694                        self.generate_expression(e)?;
19695                        self.write_space();
19696                        self.write_keyword("IS NULL THEN NULL");
19697                    }
19698                    self.write_space();
19699                    self.write_keyword("ELSE");
19700                    self.write(" (");
19701                    for (i, e) in tuple.expressions.iter().enumerate() {
19702                        if i > 0 {
19703                            self.write(", ");
19704                        }
19705                        self.generate_expression(e)?;
19706                    }
19707                    self.write(")");
19708                    self.write_space();
19709                    self.write_keyword("END");
19710                } else {
19711                    for (i, e) in tuple.expressions.iter().enumerate() {
19712                        if i > 0 {
19713                            self.write(", ");
19714                        }
19715                        self.generate_expression(e)?;
19716                    }
19717                }
19718            } else {
19719                self.generate_expression(expr)?;
19720            }
19721        }
19722        let clickhouse_ignore_nulls_outside =
19723            matches!(self.config.dialect, Some(DialectType::ClickHouse));
19724        if let Some(ignore) = f.ignore_nulls.filter(|_| !clickhouse_ignore_nulls_outside) {
19725            self.write_space();
19726            if ignore {
19727                self.write_keyword("IGNORE NULLS");
19728            } else {
19729                self.write_keyword("RESPECT NULLS");
19730            }
19731        }
19732        self.write(")");
19733        if let Some(ignore) = f.ignore_nulls.filter(|_| clickhouse_ignore_nulls_outside) {
19734            self.write_space();
19735            if ignore {
19736                self.write_keyword("IGNORE NULLS");
19737            } else {
19738                self.write_keyword("RESPECT NULLS");
19739            }
19740        }
19741        if let Some(ref filter) = f.filter {
19742            self.write_space();
19743            self.write_keyword("FILTER");
19744            self.write("(");
19745            self.write_keyword("WHERE");
19746            self.write_space();
19747            self.generate_expression(filter)?;
19748            self.write(")");
19749        }
19750        Ok(())
19751    }
19752
19753    fn generate_agg_func(&mut self, name: &str, f: &AggFunc) -> Result<()> {
19754        // Apply function name normalization based on config
19755        let func_name: Cow<'_, str> = match self.config.normalize_functions {
19756            NormalizeFunctions::Upper => Cow::Owned(name.to_ascii_uppercase()),
19757            NormalizeFunctions::Lower => Cow::Owned(name.to_ascii_lowercase()),
19758            NormalizeFunctions::None => {
19759                // Use the original function name from parsing if available,
19760                // otherwise fall back to lowercase of the hardcoded constant
19761                if let Some(ref original) = f.name {
19762                    Cow::Owned(original.clone())
19763                } else {
19764                    Cow::Owned(name.to_ascii_lowercase())
19765                }
19766            }
19767        };
19768        self.write(func_name.as_ref());
19769        self.write("(");
19770        if f.distinct {
19771            self.write_keyword("DISTINCT");
19772            self.write_space();
19773        }
19774        // MODE() uses a NULL placeholder internally for its zero-arg ordered-set form.
19775        // Other aggregates may legitimately receive NULL as an explicit argument.
19776        let is_zero_arg_mode =
19777            name.eq_ignore_ascii_case("MODE") && matches!(f.this, Expression::Null(_));
19778        if !is_zero_arg_mode {
19779            self.generate_expression(&f.this)?;
19780        }
19781        // Generate IGNORE NULLS / RESPECT NULLS inside parens if config says so (BigQuery style)
19782        // DuckDB doesn't support IGNORE NULLS / RESPECT NULLS in aggregate functions - skip it
19783        if self.config.ignore_nulls_in_func
19784            && !matches!(self.config.dialect, Some(DialectType::DuckDB))
19785        {
19786            match f.ignore_nulls {
19787                Some(true) => {
19788                    self.write_space();
19789                    self.write_keyword("IGNORE NULLS");
19790                }
19791                Some(false) => {
19792                    self.write_space();
19793                    self.write_keyword("RESPECT NULLS");
19794                }
19795                None => {}
19796            }
19797        }
19798        // Generate HAVING MAX/MIN if present (BigQuery syntax)
19799        // e.g., ANY_VALUE(fruit HAVING MAX sold)
19800        if let Some((ref expr, is_max)) = f.having_max {
19801            self.write_space();
19802            self.write_keyword("HAVING");
19803            self.write_space();
19804            if is_max {
19805                self.write_keyword("MAX");
19806            } else {
19807                self.write_keyword("MIN");
19808            }
19809            self.write_space();
19810            self.generate_expression(expr)?;
19811        }
19812        // Generate ORDER BY if present (for aggregates like ARRAY_AGG(x ORDER BY y))
19813        if !f.order_by.is_empty() {
19814            self.write_space();
19815            self.write_keyword("ORDER BY");
19816            self.write_space();
19817            for (i, ord) in f.order_by.iter().enumerate() {
19818                if i > 0 {
19819                    self.write(", ");
19820                }
19821                self.generate_ordered(ord)?;
19822            }
19823        }
19824        // Generate LIMIT if present (for aggregates like ARRAY_AGG(x ORDER BY y LIMIT 2))
19825        if let Some(ref limit) = f.limit {
19826            self.write_space();
19827            self.write_keyword("LIMIT");
19828            self.write_space();
19829            // Check if this is a Tuple representing LIMIT offset, count
19830            if let Expression::Tuple(t) = limit.as_ref() {
19831                if t.expressions.len() == 2 {
19832                    self.generate_expression(&t.expressions[0])?;
19833                    self.write(", ");
19834                    self.generate_expression(&t.expressions[1])?;
19835                } else {
19836                    self.generate_expression(limit)?;
19837                }
19838            } else {
19839                self.generate_expression(limit)?;
19840            }
19841        }
19842        self.write(")");
19843        // Generate IGNORE NULLS / RESPECT NULLS outside parens if config says so (standard style)
19844        // DuckDB doesn't support IGNORE NULLS / RESPECT NULLS in aggregate functions - skip it
19845        if !self.config.ignore_nulls_in_func
19846            && !matches!(self.config.dialect, Some(DialectType::DuckDB))
19847        {
19848            match f.ignore_nulls {
19849                Some(true) => {
19850                    self.write_space();
19851                    self.write_keyword("IGNORE NULLS");
19852                }
19853                Some(false) => {
19854                    self.write_space();
19855                    self.write_keyword("RESPECT NULLS");
19856                }
19857                None => {}
19858            }
19859        }
19860        if let Some(ref filter) = f.filter {
19861            self.write_space();
19862            self.write_keyword("FILTER");
19863            self.write("(");
19864            self.write_keyword("WHERE");
19865            self.write_space();
19866            self.generate_expression(filter)?;
19867            self.write(")");
19868        }
19869        Ok(())
19870    }
19871
19872    /// Generate FIRST/LAST aggregate functions with Hive/Spark2-style boolean argument
19873    /// for IGNORE NULLS. In Hive/Spark2, `FIRST(col) IGNORE NULLS` is written as `FIRST(col, TRUE)`.
19874    fn generate_agg_func_with_ignore_nulls_bool(&mut self, name: &str, f: &AggFunc) -> Result<()> {
19875        // For Hive/Spark2 dialects, convert IGNORE NULLS to boolean TRUE argument
19876        if matches!(self.config.dialect, Some(DialectType::Hive)) && f.ignore_nulls == Some(true) {
19877            // Create a modified copy without ignore_nulls, add TRUE as part of the output
19878            let func_name: Cow<'_, str> = match self.config.normalize_functions {
19879                NormalizeFunctions::Upper => Cow::Owned(name.to_ascii_uppercase()),
19880                NormalizeFunctions::Lower => Cow::Owned(name.to_ascii_lowercase()),
19881                NormalizeFunctions::None => {
19882                    if let Some(ref original) = f.name {
19883                        Cow::Owned(original.clone())
19884                    } else {
19885                        Cow::Owned(name.to_ascii_lowercase())
19886                    }
19887                }
19888            };
19889            self.write(func_name.as_ref());
19890            self.write("(");
19891            if f.distinct {
19892                self.write_keyword("DISTINCT");
19893                self.write_space();
19894            }
19895            if !matches!(f.this, Expression::Null(_)) {
19896                self.generate_expression(&f.this)?;
19897            }
19898            self.write(", ");
19899            self.write_keyword("TRUE");
19900            self.write(")");
19901            return Ok(());
19902        }
19903        self.generate_agg_func(name, f)
19904    }
19905
19906    fn generate_group_concat(&mut self, f: &GroupConcatFunc) -> Result<()> {
19907        self.write_keyword("GROUP_CONCAT");
19908        self.write("(");
19909        if f.distinct {
19910            self.write_keyword("DISTINCT");
19911            self.write_space();
19912        }
19913        self.generate_expression(&f.this)?;
19914        if let Some(ref order_by) = f.order_by {
19915            self.write_space();
19916            self.write_keyword("ORDER BY");
19917            self.write_space();
19918            for (i, ord) in order_by.iter().enumerate() {
19919                if i > 0 {
19920                    self.write(", ");
19921                }
19922                self.generate_ordered(ord)?;
19923            }
19924        }
19925        if let Some(ref sep) = f.separator {
19926            // SQLite uses GROUP_CONCAT(x, sep) syntax (comma-separated)
19927            // MySQL and others use GROUP_CONCAT(x SEPARATOR sep) syntax
19928            if matches!(
19929                self.config.dialect,
19930                Some(crate::dialects::DialectType::SQLite)
19931            ) {
19932                self.write(", ");
19933                self.generate_expression(sep)?;
19934            } else {
19935                self.write_space();
19936                self.write_keyword("SEPARATOR");
19937                self.write_space();
19938                self.generate_expression(sep)?;
19939            }
19940        }
19941        if let Some(ref limit) = f.limit {
19942            self.write_space();
19943            self.write_keyword("LIMIT");
19944            self.write_space();
19945            self.generate_expression(limit)?;
19946        }
19947        self.write(")");
19948        if let Some(ref filter) = f.filter {
19949            self.write_space();
19950            self.write_keyword("FILTER");
19951            self.write("(");
19952            self.write_keyword("WHERE");
19953            self.write_space();
19954            self.generate_expression(filter)?;
19955            self.write(")");
19956        }
19957        Ok(())
19958    }
19959
19960    fn generate_string_agg(&mut self, f: &StringAggFunc) -> Result<()> {
19961        let is_tsql = matches!(
19962            self.config.dialect,
19963            Some(crate::dialects::DialectType::TSQL)
19964        );
19965        self.write_keyword("STRING_AGG");
19966        self.write("(");
19967        if f.distinct {
19968            self.write_keyword("DISTINCT");
19969            self.write_space();
19970        }
19971        self.generate_expression(&f.this)?;
19972        if let Some(ref separator) = f.separator {
19973            self.write(", ");
19974            self.generate_expression(separator)?;
19975        }
19976        // For TSQL, ORDER BY goes in WITHIN GROUP clause after the closing paren
19977        if !is_tsql {
19978            if let Some(ref order_by) = f.order_by {
19979                self.write_space();
19980                self.write_keyword("ORDER BY");
19981                self.write_space();
19982                for (i, ord) in order_by.iter().enumerate() {
19983                    if i > 0 {
19984                        self.write(", ");
19985                    }
19986                    self.generate_ordered(ord)?;
19987                }
19988            }
19989        }
19990        if let Some(ref limit) = f.limit {
19991            self.write_space();
19992            self.write_keyword("LIMIT");
19993            self.write_space();
19994            self.generate_expression(limit)?;
19995        }
19996        self.write(")");
19997        // TSQL uses WITHIN GROUP (ORDER BY ...) after the function call
19998        if is_tsql {
19999            if let Some(ref order_by) = f.order_by {
20000                self.write_space();
20001                self.write_keyword("WITHIN GROUP");
20002                self.write(" (");
20003                self.write_keyword("ORDER BY");
20004                self.write_space();
20005                for (i, ord) in order_by.iter().enumerate() {
20006                    if i > 0 {
20007                        self.write(", ");
20008                    }
20009                    self.generate_ordered(ord)?;
20010                }
20011                self.write(")");
20012            }
20013        }
20014        if let Some(ref filter) = f.filter {
20015            self.write_space();
20016            self.write_keyword("FILTER");
20017            self.write("(");
20018            self.write_keyword("WHERE");
20019            self.write_space();
20020            self.generate_expression(filter)?;
20021            self.write(")");
20022        }
20023        Ok(())
20024    }
20025
20026    fn generate_listagg(&mut self, f: &ListAggFunc) -> Result<()> {
20027        use crate::dialects::DialectType;
20028        self.write_keyword("LISTAGG");
20029        self.write("(");
20030        if f.distinct {
20031            self.write_keyword("DISTINCT");
20032            self.write_space();
20033        }
20034        self.generate_expression(&f.this)?;
20035        if let Some(ref sep) = f.separator {
20036            self.write(", ");
20037            self.generate_expression(sep)?;
20038        } else if matches!(
20039            self.config.dialect,
20040            Some(DialectType::Trino) | Some(DialectType::Presto)
20041        ) {
20042            // Trino/Presto require explicit separator; default to ','
20043            self.write(", ','");
20044        }
20045        if let Some(ref overflow) = f.on_overflow {
20046            self.write_space();
20047            self.write_keyword("ON OVERFLOW");
20048            self.write_space();
20049            match overflow {
20050                ListAggOverflow::Error => self.write_keyword("ERROR"),
20051                ListAggOverflow::Truncate { filler, with_count } => {
20052                    self.write_keyword("TRUNCATE");
20053                    if let Some(ref fill) = filler {
20054                        self.write_space();
20055                        self.generate_expression(fill)?;
20056                    }
20057                    if *with_count {
20058                        self.write_space();
20059                        self.write_keyword("WITH COUNT");
20060                    } else {
20061                        self.write_space();
20062                        self.write_keyword("WITHOUT COUNT");
20063                    }
20064                }
20065            }
20066        }
20067        self.write(")");
20068        if let Some(ref order_by) = f.order_by {
20069            self.write_space();
20070            self.write_keyword("WITHIN GROUP");
20071            self.write(" (");
20072            self.write_keyword("ORDER BY");
20073            self.write_space();
20074            for (i, ord) in order_by.iter().enumerate() {
20075                if i > 0 {
20076                    self.write(", ");
20077                }
20078                self.generate_ordered(ord)?;
20079            }
20080            self.write(")");
20081        }
20082        if let Some(ref filter) = f.filter {
20083            self.write_space();
20084            self.write_keyword("FILTER");
20085            self.write("(");
20086            self.write_keyword("WHERE");
20087            self.write_space();
20088            self.generate_expression(filter)?;
20089            self.write(")");
20090        }
20091        Ok(())
20092    }
20093
20094    fn generate_sum_if(&mut self, f: &SumIfFunc) -> Result<()> {
20095        self.write_keyword("SUM_IF");
20096        self.write("(");
20097        self.generate_expression(&f.this)?;
20098        self.write(", ");
20099        self.generate_expression(&f.condition)?;
20100        self.write(")");
20101        if let Some(ref filter) = f.filter {
20102            self.write_space();
20103            self.write_keyword("FILTER");
20104            self.write("(");
20105            self.write_keyword("WHERE");
20106            self.write_space();
20107            self.generate_expression(filter)?;
20108            self.write(")");
20109        }
20110        Ok(())
20111    }
20112
20113    fn generate_approx_percentile(&mut self, f: &ApproxPercentileFunc) -> Result<()> {
20114        self.write_keyword("APPROX_PERCENTILE");
20115        self.write("(");
20116        self.generate_expression(&f.this)?;
20117        self.write(", ");
20118        self.generate_expression(&f.percentile)?;
20119        if let Some(ref acc) = f.accuracy {
20120            self.write(", ");
20121            self.generate_expression(acc)?;
20122        }
20123        self.write(")");
20124        if let Some(ref filter) = f.filter {
20125            self.write_space();
20126            self.write_keyword("FILTER");
20127            self.write("(");
20128            self.write_keyword("WHERE");
20129            self.write_space();
20130            self.generate_expression(filter)?;
20131            self.write(")");
20132        }
20133        Ok(())
20134    }
20135
20136    fn generate_percentile(&mut self, name: &str, f: &PercentileFunc) -> Result<()> {
20137        self.write_keyword(name);
20138        self.write("(");
20139        self.generate_expression(&f.percentile)?;
20140        self.write(")");
20141        if let Some(ref order_by) = f.order_by {
20142            self.write_space();
20143            self.write_keyword("WITHIN GROUP");
20144            self.write(" (");
20145            self.write_keyword("ORDER BY");
20146            self.write_space();
20147            self.generate_expression(&f.this)?;
20148            for ord in order_by.iter() {
20149                if ord.desc {
20150                    self.write_space();
20151                    self.write_keyword("DESC");
20152                }
20153            }
20154            self.write(")");
20155        }
20156        if let Some(ref filter) = f.filter {
20157            self.write_space();
20158            self.write_keyword("FILTER");
20159            self.write("(");
20160            self.write_keyword("WHERE");
20161            self.write_space();
20162            self.generate_expression(filter)?;
20163            self.write(")");
20164        }
20165        Ok(())
20166    }
20167
20168    // Window function generators
20169
20170    fn generate_ntile(&mut self, f: &NTileFunc) -> Result<()> {
20171        self.write_keyword("NTILE");
20172        self.write("(");
20173        if let Some(num_buckets) = &f.num_buckets {
20174            self.generate_expression(num_buckets)?;
20175        }
20176        if let Some(order_by) = &f.order_by {
20177            self.write_keyword(" ORDER BY ");
20178            for (i, ob) in order_by.iter().enumerate() {
20179                if i > 0 {
20180                    self.write(", ");
20181                }
20182                self.generate_ordered(ob)?;
20183            }
20184        }
20185        self.write(")");
20186        Ok(())
20187    }
20188
20189    fn generate_lead_lag(&mut self, name: &str, f: &LeadLagFunc) -> Result<()> {
20190        self.write_keyword(name);
20191        self.write("(");
20192        self.generate_expression(&f.this)?;
20193        if let Some(ref offset) = f.offset {
20194            self.write(", ");
20195            self.generate_expression(offset)?;
20196            if let Some(ref default) = f.default {
20197                self.write(", ");
20198                self.generate_expression(default)?;
20199            }
20200        }
20201        // IGNORE NULLS / RESPECT NULLS inside parens for dialects like BigQuery
20202        if self.config.ignore_nulls_in_func {
20203            match f.ignore_nulls {
20204                Some(true) => {
20205                    self.write_space();
20206                    self.write_keyword("IGNORE NULLS");
20207                }
20208                Some(false) => {
20209                    self.write_space();
20210                    self.write_keyword("RESPECT NULLS");
20211                }
20212                None => {}
20213            }
20214        }
20215        self.write(")");
20216        // IGNORE NULLS / RESPECT NULLS outside parens for other dialects
20217        if !self.config.ignore_nulls_in_func {
20218            match f.ignore_nulls {
20219                Some(true) => {
20220                    self.write_space();
20221                    self.write_keyword("IGNORE NULLS");
20222                }
20223                Some(false) => {
20224                    self.write_space();
20225                    self.write_keyword("RESPECT NULLS");
20226                }
20227                None => {}
20228            }
20229        }
20230        Ok(())
20231    }
20232
20233    fn generate_value_func(&mut self, name: &str, f: &ValueFunc) -> Result<()> {
20234        self.write_keyword(name);
20235        self.write("(");
20236        self.generate_expression(&f.this)?;
20237        // ORDER BY inside parens (e.g., DuckDB: LAST_VALUE(x ORDER BY x))
20238        if !f.order_by.is_empty() {
20239            self.write_space();
20240            self.write_keyword("ORDER BY");
20241            self.write_space();
20242            for (i, ordered) in f.order_by.iter().enumerate() {
20243                if i > 0 {
20244                    self.write(", ");
20245                }
20246                self.generate_ordered(ordered)?;
20247            }
20248        }
20249        // IGNORE NULLS / RESPECT NULLS inside parens for dialects like BigQuery, DuckDB
20250        if self.config.ignore_nulls_in_func {
20251            match f.ignore_nulls {
20252                Some(true) => {
20253                    self.write_space();
20254                    self.write_keyword("IGNORE NULLS");
20255                }
20256                Some(false) => {
20257                    self.write_space();
20258                    self.write_keyword("RESPECT NULLS");
20259                }
20260                None => {}
20261            }
20262        }
20263        self.write(")");
20264        // IGNORE NULLS / RESPECT NULLS outside parens for other dialects
20265        if !self.config.ignore_nulls_in_func {
20266            match f.ignore_nulls {
20267                Some(true) => {
20268                    self.write_space();
20269                    self.write_keyword("IGNORE NULLS");
20270                }
20271                Some(false) => {
20272                    self.write_space();
20273                    self.write_keyword("RESPECT NULLS");
20274                }
20275                None => {}
20276            }
20277        }
20278        Ok(())
20279    }
20280
20281    /// Generate FIRST_VALUE/LAST_VALUE with Hive/Spark2-style boolean argument for IGNORE NULLS.
20282    /// In Hive/Spark2, `FIRST_VALUE(col) IGNORE NULLS` is written as `FIRST_VALUE(col, TRUE)`.
20283    fn generate_value_func_with_ignore_nulls_bool(
20284        &mut self,
20285        name: &str,
20286        f: &ValueFunc,
20287    ) -> Result<()> {
20288        if matches!(self.config.dialect, Some(DialectType::Hive)) && f.ignore_nulls == Some(true) {
20289            self.write_keyword(name);
20290            self.write("(");
20291            self.generate_expression(&f.this)?;
20292            self.write(", ");
20293            self.write_keyword("TRUE");
20294            self.write(")");
20295            return Ok(());
20296        }
20297        self.generate_value_func(name, f)
20298    }
20299
20300    fn generate_nth_value(&mut self, f: &NthValueFunc) -> Result<()> {
20301        self.write_keyword("NTH_VALUE");
20302        self.write("(");
20303        self.generate_expression(&f.this)?;
20304        self.write(", ");
20305        self.generate_expression(&f.offset)?;
20306        // IGNORE NULLS / RESPECT NULLS inside parens for dialects like BigQuery, DuckDB
20307        if self.config.ignore_nulls_in_func {
20308            match f.ignore_nulls {
20309                Some(true) => {
20310                    self.write_space();
20311                    self.write_keyword("IGNORE NULLS");
20312                }
20313                Some(false) => {
20314                    self.write_space();
20315                    self.write_keyword("RESPECT NULLS");
20316                }
20317                None => {}
20318            }
20319        }
20320        self.write(")");
20321        // FROM FIRST / FROM LAST (Snowflake-specific, before IGNORE/RESPECT NULLS)
20322        if matches!(
20323            self.config.dialect,
20324            Some(crate::dialects::DialectType::Snowflake)
20325        ) {
20326            match f.from_first {
20327                Some(true) => {
20328                    self.write_space();
20329                    self.write_keyword("FROM FIRST");
20330                }
20331                Some(false) => {
20332                    self.write_space();
20333                    self.write_keyword("FROM LAST");
20334                }
20335                None => {}
20336            }
20337        }
20338        // IGNORE NULLS / RESPECT NULLS outside parens for other dialects
20339        if !self.config.ignore_nulls_in_func {
20340            match f.ignore_nulls {
20341                Some(true) => {
20342                    self.write_space();
20343                    self.write_keyword("IGNORE NULLS");
20344                }
20345                Some(false) => {
20346                    self.write_space();
20347                    self.write_keyword("RESPECT NULLS");
20348                }
20349                None => {}
20350            }
20351        }
20352        Ok(())
20353    }
20354
20355    // Additional string function generators
20356
20357    fn generate_position(&mut self, f: &PositionFunc) -> Result<()> {
20358        // Standard syntax: POSITION(substr IN str)
20359        // ClickHouse prefers comma syntax with reversed arg order: POSITION(str, substr[, start])
20360        if matches!(
20361            self.config.dialect,
20362            Some(crate::dialects::DialectType::ClickHouse)
20363        ) {
20364            self.write_keyword("POSITION");
20365            self.write("(");
20366            self.generate_expression(&f.string)?;
20367            self.write(", ");
20368            self.generate_expression(&f.substring)?;
20369            if let Some(ref start) = f.start {
20370                self.write(", ");
20371                self.generate_expression(start)?;
20372            }
20373            self.write(")");
20374            return Ok(());
20375        }
20376
20377        self.write_keyword("POSITION");
20378        self.write("(");
20379        self.generate_expression(&f.substring)?;
20380        self.write_space();
20381        self.write_keyword("IN");
20382        self.write_space();
20383        self.generate_expression(&f.string)?;
20384        if let Some(ref start) = f.start {
20385            self.write(", ");
20386            self.generate_expression(start)?;
20387        }
20388        self.write(")");
20389        Ok(())
20390    }
20391
20392    // Additional math function generators
20393
20394    fn generate_rand(&mut self, f: &Rand) -> Result<()> {
20395        // Teradata RANDOM(lower, upper)
20396        if f.lower.is_some() || f.upper.is_some() {
20397            self.write_keyword("RANDOM");
20398            self.write("(");
20399            if let Some(ref lower) = f.lower {
20400                self.generate_expression(lower)?;
20401            }
20402            if let Some(ref upper) = f.upper {
20403                self.write(", ");
20404                self.generate_expression(upper)?;
20405            }
20406            self.write(")");
20407            return Ok(());
20408        }
20409        // Snowflake uses RANDOM instead of RAND, DuckDB uses RANDOM without seed
20410        let func_name = match self.config.dialect {
20411            Some(crate::dialects::DialectType::Snowflake)
20412            | Some(crate::dialects::DialectType::DuckDB) => "RANDOM",
20413            _ => "RAND",
20414        };
20415        self.write_keyword(func_name);
20416        self.write("(");
20417        // DuckDB doesn't support seeded RANDOM, so skip the seed
20418        if !matches!(
20419            self.config.dialect,
20420            Some(crate::dialects::DialectType::DuckDB)
20421        ) {
20422            if let Some(ref seed) = f.seed {
20423                self.generate_expression(seed)?;
20424            }
20425        }
20426        self.write(")");
20427        Ok(())
20428    }
20429
20430    fn generate_truncate_func(&mut self, f: &TruncateFunc) -> Result<()> {
20431        self.write_keyword("TRUNCATE");
20432        self.write("(");
20433        self.generate_expression(&f.this)?;
20434        if let Some(ref decimals) = f.decimals {
20435            self.write(", ");
20436            self.generate_expression(decimals)?;
20437        }
20438        self.write(")");
20439        Ok(())
20440    }
20441
20442    // Control flow generators
20443
20444    fn generate_decode(&mut self, f: &DecodeFunc) -> Result<()> {
20445        self.write_keyword("DECODE");
20446        self.write("(");
20447        self.generate_expression(&f.this)?;
20448        for (search, result) in &f.search_results {
20449            self.write(", ");
20450            self.generate_expression(search)?;
20451            self.write(", ");
20452            self.generate_expression(result)?;
20453        }
20454        if let Some(ref default) = f.default {
20455            self.write(", ");
20456            self.generate_expression(default)?;
20457        }
20458        self.write(")");
20459        Ok(())
20460    }
20461
20462    // Date/time function generators
20463
20464    fn generate_date_format(&mut self, name: &str, f: &DateFormatFunc) -> Result<()> {
20465        self.write_keyword(name);
20466        self.write("(");
20467        self.generate_expression(&f.this)?;
20468        self.write(", ");
20469        self.generate_expression(&f.format)?;
20470        self.write(")");
20471        Ok(())
20472    }
20473
20474    fn generate_from_unixtime(&mut self, f: &FromUnixtimeFunc) -> Result<()> {
20475        self.write_keyword("FROM_UNIXTIME");
20476        self.write("(");
20477        self.generate_expression(&f.this)?;
20478        if let Some(ref format) = f.format {
20479            self.write(", ");
20480            self.generate_expression(format)?;
20481        }
20482        self.write(")");
20483        Ok(())
20484    }
20485
20486    fn generate_unix_timestamp(&mut self, f: &UnixTimestampFunc) -> Result<()> {
20487        self.write_keyword("UNIX_TIMESTAMP");
20488        self.write("(");
20489        if let Some(ref expr) = f.this {
20490            self.generate_expression(expr)?;
20491            if let Some(ref format) = f.format {
20492                self.write(", ");
20493                self.generate_expression(format)?;
20494            }
20495        } else if matches!(
20496            self.config.dialect,
20497            Some(DialectType::Spark) | Some(DialectType::Hive) | Some(DialectType::Databricks)
20498        ) {
20499            // Spark/Hive: UNIX_TIMESTAMP() -> UNIX_TIMESTAMP(CURRENT_TIMESTAMP())
20500            self.write_keyword("CURRENT_TIMESTAMP");
20501            self.write("()");
20502        }
20503        self.write(")");
20504        Ok(())
20505    }
20506
20507    fn generate_make_date(&mut self, f: &MakeDateFunc) -> Result<()> {
20508        self.write_keyword("MAKE_DATE");
20509        self.write("(");
20510        self.generate_expression(&f.year)?;
20511        self.write(", ");
20512        self.generate_expression(&f.month)?;
20513        self.write(", ");
20514        self.generate_expression(&f.day)?;
20515        self.write(")");
20516        Ok(())
20517    }
20518
20519    fn generate_make_timestamp(&mut self, f: &MakeTimestampFunc) -> Result<()> {
20520        self.write_keyword("MAKE_TIMESTAMP");
20521        self.write("(");
20522        self.generate_expression(&f.year)?;
20523        self.write(", ");
20524        self.generate_expression(&f.month)?;
20525        self.write(", ");
20526        self.generate_expression(&f.day)?;
20527        self.write(", ");
20528        self.generate_expression(&f.hour)?;
20529        self.write(", ");
20530        self.generate_expression(&f.minute)?;
20531        self.write(", ");
20532        self.generate_expression(&f.second)?;
20533        if let Some(ref tz) = f.timezone {
20534            self.write(", ");
20535            self.generate_expression(tz)?;
20536        }
20537        self.write(")");
20538        Ok(())
20539    }
20540
20541    /// Extract field names from a struct expression (either Struct or Function named STRUCT with Alias args)
20542    fn extract_struct_field_names(expr: &Expression) -> Option<Vec<String>> {
20543        match expr {
20544            Expression::Struct(s) => {
20545                if s.fields.iter().all(|(name, _)| name.is_some()) {
20546                    Some(
20547                        s.fields
20548                            .iter()
20549                            .map(|(name, _)| name.as_deref().unwrap_or("").to_string())
20550                            .collect(),
20551                    )
20552                } else {
20553                    None
20554                }
20555            }
20556            Expression::Function(f) if f.name.eq_ignore_ascii_case("STRUCT") => {
20557                // Check if all args are Alias (named fields)
20558                if f.args.iter().all(|a| matches!(a, Expression::Alias(_))) {
20559                    Some(
20560                        f.args
20561                            .iter()
20562                            .filter_map(|a| {
20563                                if let Expression::Alias(alias) = a {
20564                                    Some(alias.alias.name.clone())
20565                                } else {
20566                                    None
20567                                }
20568                            })
20569                            .collect(),
20570                    )
20571                } else {
20572                    None
20573                }
20574            }
20575            _ => None,
20576        }
20577    }
20578
20579    /// Check if a struct expression has any unnamed fields
20580    fn struct_has_unnamed_fields(expr: &Expression) -> bool {
20581        match expr {
20582            Expression::Struct(s) => s.fields.iter().any(|(name, _)| name.is_none()),
20583            Expression::Function(f) if f.name.eq_ignore_ascii_case("STRUCT") => {
20584                f.args.iter().any(|a| !matches!(a, Expression::Alias(_)))
20585            }
20586            _ => false,
20587        }
20588    }
20589
20590    /// Get the field count of a struct expression
20591    fn struct_field_count(expr: &Expression) -> usize {
20592        match expr {
20593            Expression::Struct(s) => s.fields.len(),
20594            Expression::Function(f) if f.name.eq_ignore_ascii_case("STRUCT") => f.args.len(),
20595            _ => 0,
20596        }
20597    }
20598
20599    /// Apply field names to an unnamed struct expression, producing a new expression with names
20600    fn apply_struct_field_names(expr: &Expression, field_names: &[String]) -> Expression {
20601        match expr {
20602            Expression::Struct(s) => {
20603                let mut new_fields = Vec::with_capacity(s.fields.len());
20604                for (i, (name, value)) in s.fields.iter().enumerate() {
20605                    if name.is_none() && i < field_names.len() {
20606                        new_fields.push((Some(field_names[i].clone()), value.clone()));
20607                    } else {
20608                        new_fields.push((name.clone(), value.clone()));
20609                    }
20610                }
20611                Expression::Struct(Box::new(crate::expressions::Struct { fields: new_fields }))
20612            }
20613            Expression::Function(f) if f.name.eq_ignore_ascii_case("STRUCT") => {
20614                let mut new_args = Vec::with_capacity(f.args.len());
20615                for (i, arg) in f.args.iter().enumerate() {
20616                    if !matches!(arg, Expression::Alias(_)) && i < field_names.len() {
20617                        // Wrap the value in an Alias with the inherited name
20618                        new_args.push(Expression::Alias(Box::new(crate::expressions::Alias {
20619                            this: arg.clone(),
20620                            alias: crate::expressions::Identifier::new(field_names[i].clone()),
20621                            column_aliases: Vec::new(),
20622                            alias_explicit_as: false,
20623                            alias_keyword: None,
20624                            pre_alias_comments: Vec::new(),
20625                            trailing_comments: Vec::new(),
20626                            inferred_type: None,
20627                        })));
20628                    } else {
20629                        new_args.push(arg.clone());
20630                    }
20631                }
20632                Expression::Function(Box::new(crate::expressions::Function {
20633                    name: f.name.clone(),
20634                    args: new_args,
20635                    distinct: f.distinct,
20636                    trailing_comments: f.trailing_comments.clone(),
20637                    use_bracket_syntax: f.use_bracket_syntax,
20638                    no_parens: f.no_parens,
20639                    quoted: f.quoted,
20640                    span: None,
20641                    inferred_type: None,
20642                }))
20643            }
20644            _ => expr.clone(),
20645        }
20646    }
20647
20648    /// Propagate struct field names from the first struct in an array to subsequent unnamed structs.
20649    /// This implements BigQuery's implicit field name inheritance for struct arrays.
20650    /// Handles both Expression::Struct and Expression::Function named "STRUCT".
20651    fn inherit_struct_field_names(expressions: &[Expression]) -> Vec<Expression> {
20652        let first = match expressions.first() {
20653            Some(e) => e,
20654            None => return expressions.to_vec(),
20655        };
20656
20657        let field_names = match Self::extract_struct_field_names(first) {
20658            Some(names) if !names.is_empty() => names,
20659            _ => return expressions.to_vec(),
20660        };
20661
20662        let mut result = Vec::with_capacity(expressions.len());
20663        for (idx, expr) in expressions.iter().enumerate() {
20664            if idx == 0 {
20665                result.push(expr.clone());
20666                continue;
20667            }
20668            // Check if this is a struct with unnamed fields that needs name propagation
20669            if Self::struct_field_count(expr) == field_names.len()
20670                && Self::struct_has_unnamed_fields(expr)
20671            {
20672                result.push(Self::apply_struct_field_names(expr, &field_names));
20673            } else {
20674                result.push(expr.clone());
20675            }
20676        }
20677        result
20678    }
20679
20680    // Array function generators
20681
20682    fn generate_array_constructor(&mut self, f: &ArrayConstructor) -> Result<()> {
20683        // Apply struct name inheritance for target dialects that need it
20684        // (DuckDB, Spark, Databricks, Hive, Snowflake, Presto, Trino)
20685        let needs_inheritance = matches!(
20686            self.config.dialect,
20687            Some(DialectType::DuckDB)
20688                | Some(DialectType::Spark)
20689                | Some(DialectType::Databricks)
20690                | Some(DialectType::Hive)
20691                | Some(DialectType::Snowflake)
20692                | Some(DialectType::Presto)
20693                | Some(DialectType::Trino)
20694        );
20695        let propagated: Vec<Expression>;
20696        let expressions = if needs_inheritance && f.expressions.len() > 1 {
20697            propagated = Self::inherit_struct_field_names(&f.expressions);
20698            &propagated
20699        } else {
20700            &f.expressions
20701        };
20702
20703        // Check if elements should be split onto multiple lines (pretty + too wide)
20704        let should_split = if self.config.pretty && !expressions.is_empty() {
20705            let mut expr_strings: Vec<String> = Vec::with_capacity(expressions.len());
20706            for expr in expressions {
20707                let mut temp_gen = Generator::with_arc_config(self.config.clone());
20708                Arc::make_mut(&mut temp_gen.config).pretty = false;
20709                temp_gen.generate_expression(expr)?;
20710                expr_strings.push(temp_gen.output);
20711            }
20712            self.too_wide(&expr_strings)
20713        } else {
20714            false
20715        };
20716
20717        if f.bracket_notation {
20718            // For Spark/Databricks, use ARRAY(...) with parens
20719            // For Presto/Trino/PostgreSQL, use ARRAY[...] with keyword prefix
20720            // For others (DuckDB, Snowflake), use bare [...]
20721            let (open, close) = match self.config.dialect {
20722                None
20723                | Some(DialectType::Generic)
20724                | Some(DialectType::Spark)
20725                | Some(DialectType::Databricks)
20726                | Some(DialectType::Hive) => {
20727                    self.write_keyword("ARRAY");
20728                    ("(", ")")
20729                }
20730                Some(DialectType::Presto)
20731                | Some(DialectType::Trino)
20732                | Some(DialectType::PostgreSQL)
20733                | Some(DialectType::Redshift)
20734                | Some(DialectType::Materialize)
20735                | Some(DialectType::RisingWave)
20736                | Some(DialectType::CockroachDB) => {
20737                    self.write_keyword("ARRAY");
20738                    ("[", "]")
20739                }
20740                _ => ("[", "]"),
20741            };
20742            self.write(open);
20743            if should_split {
20744                self.write_newline();
20745                self.indent_level += 1;
20746                for (i, expr) in expressions.iter().enumerate() {
20747                    self.write_indent();
20748                    self.generate_expression(expr)?;
20749                    if i + 1 < expressions.len() {
20750                        self.write(",");
20751                    }
20752                    self.write_newline();
20753                }
20754                self.indent_level -= 1;
20755                self.write_indent();
20756            } else {
20757                for (i, expr) in expressions.iter().enumerate() {
20758                    if i > 0 {
20759                        self.write(", ");
20760                    }
20761                    self.generate_expression(expr)?;
20762                }
20763            }
20764            self.write(close);
20765        } else {
20766            // Use LIST keyword if that was the original syntax (DuckDB)
20767            if f.use_list_keyword {
20768                self.write_keyword("LIST");
20769            } else {
20770                self.write_keyword("ARRAY");
20771            }
20772            // For Spark/Hive, always use ARRAY(...) with parens
20773            // Also use parens for BigQuery when the array contains a subquery (ARRAY(SELECT ...))
20774            let has_subquery = expressions
20775                .iter()
20776                .any(|e| matches!(e, Expression::Select(_)));
20777            let (open, close) = if matches!(
20778                self.config.dialect,
20779                Some(DialectType::Spark) | Some(DialectType::Databricks) | Some(DialectType::Hive)
20780            ) || (matches!(self.config.dialect, Some(DialectType::BigQuery))
20781                && has_subquery)
20782            {
20783                ("(", ")")
20784            } else {
20785                ("[", "]")
20786            };
20787            self.write(open);
20788            if should_split {
20789                self.write_newline();
20790                self.indent_level += 1;
20791                for (i, expr) in expressions.iter().enumerate() {
20792                    self.write_indent();
20793                    self.generate_expression(expr)?;
20794                    if i + 1 < expressions.len() {
20795                        self.write(",");
20796                    }
20797                    self.write_newline();
20798                }
20799                self.indent_level -= 1;
20800                self.write_indent();
20801            } else {
20802                for (i, expr) in expressions.iter().enumerate() {
20803                    if i > 0 {
20804                        self.write(", ");
20805                    }
20806                    self.generate_expression(expr)?;
20807                }
20808            }
20809            self.write(close);
20810        }
20811        Ok(())
20812    }
20813
20814    fn generate_array_sort(&mut self, f: &ArraySortFunc) -> Result<()> {
20815        self.write_keyword("ARRAY_SORT");
20816        self.write("(");
20817        self.generate_expression(&f.this)?;
20818        if let Some(ref comp) = f.comparator {
20819            self.write(", ");
20820            self.generate_expression(comp)?;
20821        }
20822        self.write(")");
20823        Ok(())
20824    }
20825
20826    fn generate_array_join(&mut self, name: &str, f: &ArrayJoinFunc) -> Result<()> {
20827        self.write_keyword(name);
20828        self.write("(");
20829        self.generate_expression(&f.this)?;
20830        self.write(", ");
20831        self.generate_expression(&f.separator)?;
20832        if let Some(ref null_rep) = f.null_replacement {
20833            self.write(", ");
20834            self.generate_expression(null_rep)?;
20835        }
20836        self.write(")");
20837        Ok(())
20838    }
20839
20840    fn generate_unnest(&mut self, f: &UnnestFunc) -> Result<()> {
20841        self.write_keyword("UNNEST");
20842        self.write("(");
20843        self.generate_expression(&f.this)?;
20844        for extra in &f.expressions {
20845            self.write(", ");
20846            self.generate_expression(extra)?;
20847        }
20848        self.write(")");
20849        if f.with_ordinality {
20850            self.write_space();
20851            if self.config.unnest_with_ordinality {
20852                // Presto/Trino: UNNEST(arr) WITH ORDINALITY [AS alias]
20853                self.write_keyword("WITH ORDINALITY");
20854            } else if f.offset_alias.is_some() {
20855                // BigQuery: UNNEST(arr) [AS col] WITH OFFSET AS pos
20856                // Alias (if any) comes BEFORE WITH OFFSET
20857                if let Some(ref alias) = f.alias {
20858                    self.write_keyword("AS");
20859                    self.write_space();
20860                    self.generate_identifier(alias)?;
20861                    self.write_space();
20862                }
20863                self.write_keyword("WITH OFFSET");
20864                if let Some(ref offset_alias) = f.offset_alias {
20865                    self.write_space();
20866                    self.write_keyword("AS");
20867                    self.write_space();
20868                    self.generate_identifier(offset_alias)?;
20869                }
20870            } else {
20871                // WITH OFFSET (BigQuery identity) - add default "AS offset" if no explicit alias
20872                self.write_keyword("WITH OFFSET");
20873                if f.alias.is_none() {
20874                    self.write(" AS offset");
20875                }
20876            }
20877        }
20878        if let Some(ref alias) = f.alias {
20879            // Add alias for: non-WITH-OFFSET cases, Presto/Trino WITH ORDINALITY, or BigQuery WITH OFFSET + alias (no offset_alias)
20880            let should_add_alias = if !f.with_ordinality {
20881                true
20882            } else if self.config.unnest_with_ordinality {
20883                // Presto/Trino: alias comes after WITH ORDINALITY
20884                true
20885            } else if f.offset_alias.is_some() {
20886                // BigQuery expansion: alias already handled above
20887                false
20888            } else {
20889                // BigQuery WITH OFFSET + alias but no offset_alias: alias comes after
20890                true
20891            };
20892            if should_add_alias {
20893                self.write_space();
20894                self.write_keyword("AS");
20895                self.write_space();
20896                self.generate_identifier(alias)?;
20897            }
20898        }
20899        Ok(())
20900    }
20901
20902    fn generate_array_filter(&mut self, f: &ArrayFilterFunc) -> Result<()> {
20903        self.write_keyword("FILTER");
20904        self.write("(");
20905        self.generate_expression(&f.this)?;
20906        self.write(", ");
20907        self.generate_expression(&f.filter)?;
20908        self.write(")");
20909        Ok(())
20910    }
20911
20912    fn generate_array_transform(&mut self, f: &ArrayTransformFunc) -> Result<()> {
20913        self.write_keyword("TRANSFORM");
20914        self.write("(");
20915        self.generate_expression(&f.this)?;
20916        self.write(", ");
20917        self.generate_expression(&f.transform)?;
20918        self.write(")");
20919        Ok(())
20920    }
20921
20922    fn generate_sequence(&mut self, name: &str, f: &SequenceFunc) -> Result<()> {
20923        self.write_keyword(name);
20924        self.write("(");
20925        self.generate_expression(&f.start)?;
20926        self.write(", ");
20927        self.generate_expression(&f.stop)?;
20928        if let Some(ref step) = f.step {
20929            self.write(", ");
20930            self.generate_expression(step)?;
20931        }
20932        self.write(")");
20933        Ok(())
20934    }
20935
20936    // Struct function generators
20937
20938    fn generate_struct_constructor(&mut self, f: &StructConstructor) -> Result<()> {
20939        self.write_keyword("STRUCT");
20940        self.write("(");
20941        for (i, (name, expr)) in f.fields.iter().enumerate() {
20942            if i > 0 {
20943                self.write(", ");
20944            }
20945            if let Some(ref id) = name {
20946                self.generate_identifier(id)?;
20947                self.write(" ");
20948                self.write_keyword("AS");
20949                self.write(" ");
20950            }
20951            self.generate_expression(expr)?;
20952        }
20953        self.write(")");
20954        Ok(())
20955    }
20956
20957    /// Convert BigQuery STRUCT function (parsed as Function with Alias args) to target dialect
20958    fn generate_struct_function_cross_dialect(&mut self, func: &Function) -> Result<()> {
20959        // Extract named/unnamed fields from function args
20960        // Args are either Alias(this=value, alias=name) for named or plain expressions for unnamed
20961        let mut names: Vec<Option<String>> = Vec::new();
20962        let mut values: Vec<&Expression> = Vec::new();
20963        let mut all_named = true;
20964
20965        for arg in &func.args {
20966            match arg {
20967                Expression::Alias(a) => {
20968                    names.push(Some(a.alias.name.clone()));
20969                    values.push(&a.this);
20970                }
20971                _ => {
20972                    names.push(None);
20973                    values.push(arg);
20974                    all_named = false;
20975                }
20976            }
20977        }
20978
20979        if matches!(self.config.dialect, Some(DialectType::DuckDB)) {
20980            // DuckDB: {'name': value, ...} for named, {'_0': value, ...} for unnamed
20981            self.write("{");
20982            for (i, (name, value)) in names.iter().zip(values.iter()).enumerate() {
20983                if i > 0 {
20984                    self.write(", ");
20985                }
20986                if let Some(n) = name {
20987                    self.write("'");
20988                    self.write(n);
20989                    self.write("'");
20990                } else {
20991                    self.write("'_");
20992                    self.write(&i.to_string());
20993                    self.write("'");
20994                }
20995                self.write(": ");
20996                self.generate_expression(value)?;
20997            }
20998            self.write("}");
20999            return Ok(());
21000        }
21001
21002        if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
21003            // Snowflake: OBJECT_CONSTRUCT('name', value, ...)
21004            self.write_keyword("OBJECT_CONSTRUCT");
21005            self.write("(");
21006            for (i, (name, value)) in names.iter().zip(values.iter()).enumerate() {
21007                if i > 0 {
21008                    self.write(", ");
21009                }
21010                if let Some(n) = name {
21011                    self.write("'");
21012                    self.write(n);
21013                    self.write("'");
21014                } else {
21015                    self.write("'_");
21016                    self.write(&i.to_string());
21017                    self.write("'");
21018                }
21019                self.write(", ");
21020                self.generate_expression(value)?;
21021            }
21022            self.write(")");
21023            return Ok(());
21024        }
21025
21026        if matches!(
21027            self.config.dialect,
21028            Some(DialectType::Presto) | Some(DialectType::Trino)
21029        ) {
21030            if all_named && !names.is_empty() {
21031                // Presto/Trino: CAST(ROW(values...) AS ROW(name TYPE, ...))
21032                // Need to infer types from values
21033                self.write_keyword("CAST");
21034                self.write("(");
21035                self.write_keyword("ROW");
21036                self.write("(");
21037                for (i, value) in values.iter().enumerate() {
21038                    if i > 0 {
21039                        self.write(", ");
21040                    }
21041                    self.generate_expression(value)?;
21042                }
21043                self.write(")");
21044                self.write(" ");
21045                self.write_keyword("AS");
21046                self.write(" ");
21047                self.write_keyword("ROW");
21048                self.write("(");
21049                for (i, (name, value)) in names.iter().zip(values.iter()).enumerate() {
21050                    if i > 0 {
21051                        self.write(", ");
21052                    }
21053                    if let Some(n) = name {
21054                        self.write(n);
21055                    }
21056                    self.write(" ");
21057                    let type_str = Self::infer_sql_type_for_presto(value);
21058                    self.write_keyword(&type_str);
21059                }
21060                self.write(")");
21061                self.write(")");
21062            } else {
21063                // Unnamed: ROW(values...)
21064                self.write_keyword("ROW");
21065                self.write("(");
21066                for (i, value) in values.iter().enumerate() {
21067                    if i > 0 {
21068                        self.write(", ");
21069                    }
21070                    self.generate_expression(value)?;
21071                }
21072                self.write(")");
21073            }
21074            return Ok(());
21075        }
21076
21077        // Default: ROW(values...) for other dialects
21078        self.write_keyword("ROW");
21079        self.write("(");
21080        for (i, value) in values.iter().enumerate() {
21081            if i > 0 {
21082                self.write(", ");
21083            }
21084            self.generate_expression(value)?;
21085        }
21086        self.write(")");
21087        Ok(())
21088    }
21089
21090    /// Infer SQL type name for a Presto/Trino ROW CAST from a literal expression
21091    fn infer_sql_type_for_presto(expr: &Expression) -> String {
21092        match expr {
21093            Expression::Literal(lit)
21094                if matches!(lit.as_ref(), crate::expressions::Literal::String(_)) =>
21095            {
21096                "VARCHAR".to_string()
21097            }
21098            Expression::Literal(lit)
21099                if matches!(lit.as_ref(), crate::expressions::Literal::Number(_)) =>
21100            {
21101                let crate::expressions::Literal::Number(n) = lit.as_ref() else {
21102                    unreachable!()
21103                };
21104                if n.contains('.') {
21105                    "DOUBLE".to_string()
21106                } else {
21107                    "INTEGER".to_string()
21108                }
21109            }
21110            Expression::Boolean(_) => "BOOLEAN".to_string(),
21111            Expression::Literal(lit)
21112                if matches!(lit.as_ref(), crate::expressions::Literal::Date(_)) =>
21113            {
21114                "DATE".to_string()
21115            }
21116            Expression::Literal(lit)
21117                if matches!(lit.as_ref(), crate::expressions::Literal::Timestamp(_)) =>
21118            {
21119                "TIMESTAMP".to_string()
21120            }
21121            Expression::Literal(lit)
21122                if matches!(lit.as_ref(), crate::expressions::Literal::Datetime(_)) =>
21123            {
21124                "TIMESTAMP".to_string()
21125            }
21126            Expression::Array(_) | Expression::ArrayFunc(_) => {
21127                // Try to infer element type from first element
21128                "ARRAY(VARCHAR)".to_string()
21129            }
21130            // For nested structs - generate a nested ROW type by inspecting fields
21131            Expression::Struct(_) | Expression::StructFunc(_) => "ROW".to_string(),
21132            Expression::Function(f) => {
21133                if f.name.eq_ignore_ascii_case("STRUCT") {
21134                    "ROW".to_string()
21135                } else if f.name.eq_ignore_ascii_case("CURRENT_DATE") {
21136                    "DATE".to_string()
21137                } else if f.name.eq_ignore_ascii_case("CURRENT_TIMESTAMP")
21138                    || f.name.eq_ignore_ascii_case("NOW")
21139                {
21140                    "TIMESTAMP".to_string()
21141                } else {
21142                    "VARCHAR".to_string()
21143                }
21144            }
21145            _ => "VARCHAR".to_string(),
21146        }
21147    }
21148
21149    fn generate_struct_extract(&mut self, f: &StructExtractFunc) -> Result<()> {
21150        // DuckDB uses STRUCT_EXTRACT function syntax
21151        if matches!(self.config.dialect, Some(DialectType::DuckDB)) {
21152            self.write_keyword("STRUCT_EXTRACT");
21153            self.write("(");
21154            self.generate_expression(&f.this)?;
21155            self.write(", ");
21156            // Output field name as string literal
21157            self.write("'");
21158            self.write(&f.field.name);
21159            self.write("'");
21160            self.write(")");
21161            return Ok(());
21162        }
21163        self.generate_expression(&f.this)?;
21164        self.write(".");
21165        self.generate_identifier(&f.field)
21166    }
21167
21168    fn generate_named_struct(&mut self, f: &NamedStructFunc) -> Result<()> {
21169        if matches!(
21170            self.config.dialect,
21171            Some(DialectType::Spark | DialectType::Databricks)
21172        ) {
21173            self.write_keyword("STRUCT");
21174            self.write("(");
21175            for (i, (name, value)) in f.pairs.iter().enumerate() {
21176                if i > 0 {
21177                    self.write(", ");
21178                }
21179                self.generate_expression(value)?;
21180                self.write(" ");
21181                self.write_keyword("AS");
21182                self.write(" ");
21183                if let Expression::Literal(lit) = name {
21184                    if let Literal::String(field_name) = lit.as_ref() {
21185                        self.generate_identifier(&Identifier::new(field_name))?;
21186                    } else {
21187                        self.generate_expression(name)?;
21188                    }
21189                } else {
21190                    self.generate_expression(name)?;
21191                }
21192            }
21193            self.write(")");
21194            return Ok(());
21195        }
21196
21197        self.write_keyword("NAMED_STRUCT");
21198        self.write("(");
21199        for (i, (name, value)) in f.pairs.iter().enumerate() {
21200            if i > 0 {
21201                self.write(", ");
21202            }
21203            self.generate_expression(name)?;
21204            self.write(", ");
21205            self.generate_expression(value)?;
21206        }
21207        self.write(")");
21208        Ok(())
21209    }
21210
21211    // Map function generators
21212
21213    fn generate_map_constructor(&mut self, f: &MapConstructor) -> Result<()> {
21214        if f.curly_brace_syntax {
21215            // Curly brace syntax: MAP {'a': 1, 'b': 2} or just {'a': 1, 'b': 2}
21216            if f.with_map_keyword {
21217                self.write_keyword("MAP");
21218                self.write(" ");
21219            }
21220            self.write("{");
21221            for (i, (key, val)) in f.keys.iter().zip(f.values.iter()).enumerate() {
21222                if i > 0 {
21223                    self.write(", ");
21224                }
21225                self.generate_expression(key)?;
21226                self.write(": ");
21227                self.generate_expression(val)?;
21228            }
21229            self.write("}");
21230        } else {
21231            // MAP function syntax: MAP(ARRAY[keys], ARRAY[values])
21232            self.write_keyword("MAP");
21233            self.write("(");
21234            self.write_keyword("ARRAY");
21235            self.write("[");
21236            for (i, key) in f.keys.iter().enumerate() {
21237                if i > 0 {
21238                    self.write(", ");
21239                }
21240                self.generate_expression(key)?;
21241            }
21242            self.write("], ");
21243            self.write_keyword("ARRAY");
21244            self.write("[");
21245            for (i, val) in f.values.iter().enumerate() {
21246                if i > 0 {
21247                    self.write(", ");
21248                }
21249                self.generate_expression(val)?;
21250            }
21251            self.write("])");
21252        }
21253        Ok(())
21254    }
21255
21256    fn generate_transform_func(&mut self, name: &str, f: &TransformFunc) -> Result<()> {
21257        self.write_keyword(name);
21258        self.write("(");
21259        self.generate_expression(&f.this)?;
21260        self.write(", ");
21261        self.generate_expression(&f.transform)?;
21262        self.write(")");
21263        Ok(())
21264    }
21265
21266    // JSON function generators
21267
21268    fn generate_json_extract(&mut self, name: &str, f: &JsonExtractFunc) -> Result<()> {
21269        use crate::dialects::DialectType;
21270
21271        // Check if we should use arrow syntax (-> or ->>)
21272        let use_arrow = f.arrow_syntax && self.dialect_supports_json_arrow();
21273
21274        if use_arrow {
21275            // Output arrow syntax: expr -> path or expr ->> path
21276            self.generate_expression(&f.this)?;
21277            if name == "JSON_EXTRACT_SCALAR" || name == "JSON_EXTRACT_PATH_TEXT" {
21278                self.write(" ->> ");
21279            } else {
21280                self.write(" -> ");
21281            }
21282            self.generate_expression(&f.path)?;
21283            return Ok(());
21284        }
21285
21286        // PostgreSQL uses #>> operator for JSONB path text extraction (only when hash_arrow_syntax is true)
21287        if f.hash_arrow_syntax
21288            && matches!(
21289                self.config.dialect,
21290                Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
21291            )
21292        {
21293            self.generate_expression(&f.this)?;
21294            self.write(" #>> ");
21295            self.generate_expression(&f.path)?;
21296            return Ok(());
21297        }
21298
21299        // For PostgreSQL/Redshift, use JSON_EXTRACT_PATH / JSON_EXTRACT_PATH_TEXT for extraction without arrow syntax
21300        // Redshift maps everything to JSON_EXTRACT_PATH_TEXT since it doesn't have JSON_EXTRACT_PATH
21301        let func_name = if matches!(self.config.dialect, Some(DialectType::Redshift)) {
21302            match name {
21303                "JSON_EXTRACT_SCALAR"
21304                | "JSON_EXTRACT_PATH_TEXT"
21305                | "JSON_EXTRACT"
21306                | "JSON_EXTRACT_PATH" => "JSON_EXTRACT_PATH_TEXT",
21307                _ => name,
21308            }
21309        } else if matches!(self.config.dialect, Some(DialectType::PostgreSQL)) {
21310            match name {
21311                "JSON_EXTRACT_SCALAR" | "JSON_EXTRACT_PATH_TEXT" => "JSON_EXTRACT_PATH_TEXT",
21312                "JSON_EXTRACT" | "JSON_EXTRACT_PATH" => "JSON_EXTRACT_PATH",
21313                _ => name,
21314            }
21315        } else {
21316            name
21317        };
21318
21319        self.write_keyword(func_name);
21320        self.write("(");
21321        // For Redshift, strip CAST(... AS JSON) wrapper from the expression
21322        if matches!(self.config.dialect, Some(DialectType::Redshift)) {
21323            if let Expression::Cast(ref cast) = f.this {
21324                if matches!(cast.to, crate::expressions::DataType::Json) {
21325                    self.generate_expression(&cast.this)?;
21326                } else {
21327                    self.generate_expression(&f.this)?;
21328                }
21329            } else {
21330                self.generate_expression(&f.this)?;
21331            }
21332        } else {
21333            self.generate_expression(&f.this)?;
21334        }
21335        // For PostgreSQL/Redshift JSON_EXTRACT_PATH/JSON_EXTRACT_PATH_TEXT,
21336        // decompose JSON path into separate string arguments
21337        if matches!(
21338            self.config.dialect,
21339            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
21340        ) && (func_name == "JSON_EXTRACT_PATH" || func_name == "JSON_EXTRACT_PATH_TEXT")
21341        {
21342            if let Expression::Literal(ref lit) = f.path {
21343                if let Literal::String(ref s) = lit.as_ref() {
21344                    let parts = Self::decompose_json_path(s);
21345                    for part in &parts {
21346                        self.write(", '");
21347                        self.write(part);
21348                        self.write("'");
21349                    }
21350                }
21351            } else {
21352                self.write(", ");
21353                self.generate_expression(&f.path)?;
21354            }
21355        } else {
21356            self.write(", ");
21357            self.generate_expression(&f.path)?;
21358        }
21359
21360        // Output JSON_QUERY/JSON_VALUE options (Trino/Presto style)
21361        // These go BEFORE the closing parenthesis
21362        if let Some(ref wrapper) = f.wrapper_option {
21363            self.write_space();
21364            self.write_keyword(wrapper);
21365        }
21366        if let Some(ref quotes) = f.quotes_option {
21367            self.write_space();
21368            self.write_keyword(quotes);
21369            if f.on_scalar_string {
21370                self.write_space();
21371                self.write_keyword("ON SCALAR STRING");
21372            }
21373        }
21374        if let Some(ref on_err) = f.on_error {
21375            self.write_space();
21376            self.write_keyword(on_err);
21377        }
21378        if let Some(ref ret_type) = f.returning {
21379            self.write_space();
21380            self.write_keyword("RETURNING");
21381            self.write_space();
21382            self.generate_data_type(ret_type)?;
21383        }
21384
21385        self.write(")");
21386        Ok(())
21387    }
21388
21389    /// Check if the current dialect supports JSON arrow operators (-> and ->>)
21390    fn dialect_supports_json_arrow(&self) -> bool {
21391        use crate::dialects::DialectType;
21392        match self.config.dialect {
21393            // PostgreSQL, MySQL, DuckDB support -> and ->> operators
21394            Some(DialectType::PostgreSQL) => true,
21395            Some(DialectType::MySQL) => true,
21396            Some(DialectType::DuckDB) => true,
21397            Some(DialectType::CockroachDB) => true,
21398            Some(DialectType::StarRocks) => true,
21399            Some(DialectType::SQLite) => true,
21400            // Other dialects use function syntax
21401            _ => false,
21402        }
21403    }
21404
21405    fn generate_json_path(&mut self, name: &str, f: &JsonPathFunc) -> Result<()> {
21406        use crate::dialects::DialectType;
21407
21408        // PostgreSQL uses #> operator for JSONB path extraction
21409        if matches!(
21410            self.config.dialect,
21411            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
21412        ) && name == "JSON_EXTRACT_PATH"
21413        {
21414            self.generate_expression(&f.this)?;
21415            self.write(" #> ");
21416            if f.paths.len() == 1 {
21417                self.generate_expression(&f.paths[0])?;
21418            } else {
21419                // Multiple paths: ARRAY[path1, path2, ...]
21420                self.write_keyword("ARRAY");
21421                self.write("[");
21422                for (i, path) in f.paths.iter().enumerate() {
21423                    if i > 0 {
21424                        self.write(", ");
21425                    }
21426                    self.generate_expression(path)?;
21427                }
21428                self.write("]");
21429            }
21430            return Ok(());
21431        }
21432
21433        self.write_keyword(name);
21434        self.write("(");
21435        self.generate_expression(&f.this)?;
21436        for path in &f.paths {
21437            self.write(", ");
21438            self.generate_expression(path)?;
21439        }
21440        self.write(")");
21441        Ok(())
21442    }
21443
21444    fn generate_json_object(&mut self, f: &JsonObjectFunc) -> Result<()> {
21445        use crate::dialects::DialectType;
21446
21447        self.write_keyword("JSON_OBJECT");
21448        self.write("(");
21449        if f.star {
21450            self.write("*");
21451        } else {
21452            // BigQuery, MySQL, and SQLite use comma syntax: JSON_OBJECT('key', value)
21453            // Standard SQL uses colon syntax: JSON_OBJECT('key': value)
21454            // Also respect the json_key_value_pair_sep config
21455            let use_comma_syntax = self.config.json_key_value_pair_sep == ","
21456                || matches!(
21457                    self.config.dialect,
21458                    Some(DialectType::BigQuery)
21459                        | Some(DialectType::MySQL)
21460                        | Some(DialectType::SQLite)
21461                );
21462
21463            for (i, (key, value)) in f.pairs.iter().enumerate() {
21464                if i > 0 {
21465                    self.write(", ");
21466                }
21467                self.generate_expression(key)?;
21468                if use_comma_syntax {
21469                    self.write(", ");
21470                } else {
21471                    self.write(": ");
21472                }
21473                self.generate_expression(value)?;
21474            }
21475        }
21476        if let Some(null_handling) = f.null_handling {
21477            self.write_space();
21478            match null_handling {
21479                JsonNullHandling::NullOnNull => self.write_keyword("NULL ON NULL"),
21480                JsonNullHandling::AbsentOnNull => self.write_keyword("ABSENT ON NULL"),
21481            }
21482        }
21483        if f.with_unique_keys {
21484            self.write_space();
21485            self.write_keyword("WITH UNIQUE KEYS");
21486        }
21487        if let Some(ref ret_type) = f.returning_type {
21488            self.write_space();
21489            self.write_keyword("RETURNING");
21490            self.write_space();
21491            self.generate_data_type(ret_type)?;
21492            if f.format_json {
21493                self.write_space();
21494                self.write_keyword("FORMAT JSON");
21495            }
21496            if let Some(ref enc) = f.encoding {
21497                self.write_space();
21498                self.write_keyword("ENCODING");
21499                self.write_space();
21500                self.write(enc);
21501            }
21502        }
21503        self.write(")");
21504        Ok(())
21505    }
21506
21507    fn generate_json_modify(&mut self, name: &str, f: &JsonModifyFunc) -> Result<()> {
21508        self.write_keyword(name);
21509        self.write("(");
21510        self.generate_expression(&f.this)?;
21511        for (path, value) in &f.path_values {
21512            self.write(", ");
21513            self.generate_expression(path)?;
21514            self.write(", ");
21515            self.generate_expression(value)?;
21516        }
21517        self.write(")");
21518        Ok(())
21519    }
21520
21521    fn generate_json_array_agg(&mut self, f: &JsonArrayAggFunc) -> Result<()> {
21522        self.write_keyword("JSON_ARRAYAGG");
21523        self.write("(");
21524        self.generate_expression(&f.this)?;
21525        if let Some(ref order_by) = f.order_by {
21526            self.write_space();
21527            self.write_keyword("ORDER BY");
21528            self.write_space();
21529            for (i, ord) in order_by.iter().enumerate() {
21530                if i > 0 {
21531                    self.write(", ");
21532                }
21533                self.generate_ordered(ord)?;
21534            }
21535        }
21536        if let Some(null_handling) = f.null_handling {
21537            self.write_space();
21538            match null_handling {
21539                JsonNullHandling::NullOnNull => self.write_keyword("NULL ON NULL"),
21540                JsonNullHandling::AbsentOnNull => self.write_keyword("ABSENT ON NULL"),
21541            }
21542        }
21543        self.write(")");
21544        if let Some(ref filter) = f.filter {
21545            self.write_space();
21546            self.write_keyword("FILTER");
21547            self.write("(");
21548            self.write_keyword("WHERE");
21549            self.write_space();
21550            self.generate_expression(filter)?;
21551            self.write(")");
21552        }
21553        Ok(())
21554    }
21555
21556    fn generate_json_object_agg(&mut self, f: &JsonObjectAggFunc) -> Result<()> {
21557        self.write_keyword("JSON_OBJECTAGG");
21558        self.write("(");
21559        self.generate_expression(&f.key)?;
21560        self.write(": ");
21561        self.generate_expression(&f.value)?;
21562        if let Some(null_handling) = f.null_handling {
21563            self.write_space();
21564            match null_handling {
21565                JsonNullHandling::NullOnNull => self.write_keyword("NULL ON NULL"),
21566                JsonNullHandling::AbsentOnNull => self.write_keyword("ABSENT ON NULL"),
21567            }
21568        }
21569        self.write(")");
21570        if let Some(ref filter) = f.filter {
21571            self.write_space();
21572            self.write_keyword("FILTER");
21573            self.write("(");
21574            self.write_keyword("WHERE");
21575            self.write_space();
21576            self.generate_expression(filter)?;
21577            self.write(")");
21578        }
21579        Ok(())
21580    }
21581
21582    // Type casting/conversion generators
21583
21584    fn generate_convert(&mut self, f: &ConvertFunc) -> Result<()> {
21585        use crate::dialects::DialectType;
21586
21587        // Redshift: CONVERT(type, expr) -> CAST(expr AS type)
21588        if self.config.dialect == Some(DialectType::Redshift) {
21589            self.write_keyword("CAST");
21590            self.write("(");
21591            self.generate_expression(&f.this)?;
21592            self.write_space();
21593            self.write_keyword("AS");
21594            self.write_space();
21595            self.generate_data_type(&f.to)?;
21596            self.write(")");
21597            return Ok(());
21598        }
21599
21600        self.write_keyword("CONVERT");
21601        self.write("(");
21602        self.generate_data_type(&f.to)?;
21603        self.write(", ");
21604        self.generate_expression(&f.this)?;
21605        if let Some(ref style) = f.style {
21606            self.write(", ");
21607            self.generate_expression(style)?;
21608        }
21609        self.write(")");
21610        Ok(())
21611    }
21612
21613    // Additional expression generators
21614
21615    fn generate_lambda(&mut self, f: &LambdaExpr) -> Result<()> {
21616        if f.colon {
21617            // DuckDB syntax: LAMBDA x : expr
21618            self.write_keyword("LAMBDA");
21619            self.write_space();
21620            for (i, param) in f.parameters.iter().enumerate() {
21621                if i > 0 {
21622                    self.write(", ");
21623                }
21624                self.generate_identifier(param)?;
21625            }
21626            self.write(" : ");
21627        } else {
21628            // Standard syntax: x -> expr or (x, y) -> expr
21629            if f.parameters.len() == 1 {
21630                self.generate_identifier(&f.parameters[0])?;
21631            } else {
21632                self.write("(");
21633                for (i, param) in f.parameters.iter().enumerate() {
21634                    if i > 0 {
21635                        self.write(", ");
21636                    }
21637                    self.generate_identifier(param)?;
21638                }
21639                self.write(")");
21640            }
21641            self.write(" -> ");
21642        }
21643        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
21644            if let Expression::Lambda(inner) = &f.body {
21645                self.generate_lambda_with_parenthesized_single_param(inner)?;
21646                return Ok(());
21647            }
21648        }
21649
21650        self.generate_expression(&f.body)
21651    }
21652
21653    fn generate_lambda_with_parenthesized_single_param(&mut self, f: &LambdaExpr) -> Result<()> {
21654        if f.colon {
21655            return self.generate_lambda(f);
21656        }
21657
21658        self.write("(");
21659        for (i, param) in f.parameters.iter().enumerate() {
21660            if i > 0 {
21661                self.write(", ");
21662            }
21663            self.generate_identifier(param)?;
21664        }
21665        self.write(") -> ");
21666        self.generate_expression(&f.body)
21667    }
21668
21669    fn generate_named_argument(&mut self, f: &NamedArgument) -> Result<()> {
21670        self.generate_identifier(&f.name)?;
21671        match f.separator {
21672            NamedArgSeparator::DArrow => self.write(" => "),
21673            NamedArgSeparator::ColonEq => self.write(" := "),
21674            NamedArgSeparator::Eq => self.write(" = "),
21675        }
21676        self.generate_expression(&f.value)
21677    }
21678
21679    fn generate_table_argument(&mut self, f: &TableArgument) -> Result<()> {
21680        self.write_keyword(&f.prefix);
21681        self.write(" ");
21682        self.generate_expression(&f.this)
21683    }
21684
21685    fn generate_parameter(&mut self, f: &Parameter) -> Result<()> {
21686        match f.style {
21687            ParameterStyle::Question => self.write("?"),
21688            ParameterStyle::Dollar => {
21689                self.write("$");
21690                if let Some(idx) = f.index {
21691                    self.write(&idx.to_string());
21692                } else if let Some(ref name) = f.name {
21693                    // Session variable like $x or $query_id
21694                    self.write(name);
21695                }
21696            }
21697            ParameterStyle::DollarBrace => {
21698                // Template variable like ${x} or ${hiveconf:name} (Databricks, Hive)
21699                self.write("${");
21700                if let Some(ref name) = f.name {
21701                    self.write(name);
21702                }
21703                if let Some(ref expr) = f.expression {
21704                    self.write(":");
21705                    self.write(expr);
21706                }
21707                self.write("}");
21708            }
21709            ParameterStyle::Colon => {
21710                self.write(":");
21711                if let Some(idx) = f.index {
21712                    self.write(&idx.to_string());
21713                } else if let Some(ref name) = f.name {
21714                    self.write(name);
21715                }
21716            }
21717            ParameterStyle::At => {
21718                self.write("@");
21719                if let Some(ref name) = f.name {
21720                    if f.string_quoted {
21721                        self.write("'");
21722                        self.write(name);
21723                        self.write("'");
21724                    } else if f.quoted {
21725                        self.write("\"");
21726                        self.write(name);
21727                        self.write("\"");
21728                    } else {
21729                        self.write(name);
21730                    }
21731                }
21732            }
21733            ParameterStyle::DoubleAt => {
21734                self.write("@@");
21735                if let Some(ref name) = f.name {
21736                    self.write(name);
21737                }
21738            }
21739            ParameterStyle::DoubleDollar => {
21740                self.write("$$");
21741                if let Some(ref name) = f.name {
21742                    self.write(name);
21743                }
21744            }
21745            ParameterStyle::Percent => {
21746                if let Some(ref name) = f.name {
21747                    // %(name)s format
21748                    self.write("%(");
21749                    self.write(name);
21750                    self.write(")s");
21751                } else {
21752                    // %s format
21753                    self.write("%s");
21754                }
21755            }
21756            ParameterStyle::Brace => {
21757                // Spark/Databricks widget template variable: {name}
21758                // ClickHouse query parameter may include kind: {name: Type}
21759                self.write("{");
21760                if let Some(ref name) = f.name {
21761                    self.write(name);
21762                }
21763                if let Some(ref expr) = f.expression {
21764                    self.write(": ");
21765                    self.write(expr);
21766                }
21767                self.write("}");
21768            }
21769        }
21770        Ok(())
21771    }
21772
21773    fn generate_placeholder(&mut self, f: &Placeholder) -> Result<()> {
21774        self.write("?");
21775        if let Some(idx) = f.index {
21776            self.write(&idx.to_string());
21777        }
21778        Ok(())
21779    }
21780
21781    fn generate_sql_comment(&mut self, f: &SqlComment) -> Result<()> {
21782        if f.is_block {
21783            self.write("/*");
21784            self.write(&f.text);
21785            self.write("*/");
21786        } else {
21787            self.write("--");
21788            self.write(&f.text);
21789        }
21790        Ok(())
21791    }
21792
21793    // Additional predicate generators
21794
21795    fn generate_similar_to(&mut self, f: &SimilarToExpr) -> Result<()> {
21796        self.generate_expression(&f.this)?;
21797        if f.not {
21798            self.write_space();
21799            self.write_keyword("NOT");
21800        }
21801        self.write_space();
21802        self.write_keyword("SIMILAR TO");
21803        self.write_space();
21804        self.generate_expression(&f.pattern)?;
21805        if let Some(ref escape) = f.escape {
21806            self.write_space();
21807            self.write_keyword("ESCAPE");
21808            self.write_space();
21809            self.generate_expression(escape)?;
21810        }
21811        Ok(())
21812    }
21813
21814    fn generate_quantified(&mut self, name: &str, f: &QuantifiedExpr) -> Result<()> {
21815        self.generate_expression(&f.this)?;
21816        self.write_space();
21817        // Output comparison operator if present
21818        if let Some(op) = &f.op {
21819            match op {
21820                QuantifiedOp::Eq => self.write("="),
21821                QuantifiedOp::Neq => self.write("<>"),
21822                QuantifiedOp::Lt => self.write("<"),
21823                QuantifiedOp::Lte => self.write("<="),
21824                QuantifiedOp::Gt => self.write(">"),
21825                QuantifiedOp::Gte => self.write(">="),
21826            }
21827            self.write_space();
21828        }
21829        self.write_keyword(name);
21830
21831        // If the child is a Subquery, it provides its own parens — output with space
21832        if matches!(&f.subquery, Expression::Subquery(_)) {
21833            self.write_space();
21834            self.generate_expression(&f.subquery)?;
21835        } else {
21836            self.write("(");
21837
21838            let is_statement = matches!(
21839                &f.subquery,
21840                Expression::Select(_)
21841                    | Expression::Union(_)
21842                    | Expression::Intersect(_)
21843                    | Expression::Except(_)
21844            );
21845
21846            if self.config.pretty && is_statement {
21847                self.write_newline();
21848                self.indent_level += 1;
21849                self.write_indent();
21850            }
21851            self.generate_expression(&f.subquery)?;
21852            if self.config.pretty && is_statement {
21853                self.write_newline();
21854                self.indent_level -= 1;
21855                self.write_indent();
21856            }
21857            self.write(")");
21858        }
21859        Ok(())
21860    }
21861
21862    fn generate_overlaps(&mut self, f: &OverlapsExpr) -> Result<()> {
21863        // Check if this is a simple binary form (this OVERLAPS expression)
21864        if let (Some(this), Some(expr)) = (&f.this, &f.expression) {
21865            self.generate_expression(this)?;
21866            self.write_space();
21867            self.write_keyword("OVERLAPS");
21868            self.write_space();
21869            self.generate_expression(expr)?;
21870        } else if let (Some(ls), Some(le), Some(rs), Some(re)) =
21871            (&f.left_start, &f.left_end, &f.right_start, &f.right_end)
21872        {
21873            // Full ANSI form: (a, b) OVERLAPS (c, d)
21874            self.write("(");
21875            self.generate_expression(ls)?;
21876            self.write(", ");
21877            self.generate_expression(le)?;
21878            self.write(")");
21879            self.write_space();
21880            self.write_keyword("OVERLAPS");
21881            self.write_space();
21882            self.write("(");
21883            self.generate_expression(rs)?;
21884            self.write(", ");
21885            self.generate_expression(re)?;
21886            self.write(")");
21887        }
21888        Ok(())
21889    }
21890
21891    // Type conversion generators
21892
21893    fn generate_try_cast(&mut self, cast: &Cast) -> Result<()> {
21894        use crate::dialects::DialectType;
21895
21896        // SingleStore uses !:> syntax for try cast
21897        if matches!(self.config.dialect, Some(DialectType::SingleStore)) {
21898            self.generate_expression(&cast.this)?;
21899            self.write(" !:> ");
21900            self.generate_data_type(&cast.to)?;
21901            return Ok(());
21902        }
21903
21904        // Teradata uses TRYCAST (no underscore)
21905        if matches!(self.config.dialect, Some(DialectType::Teradata)) {
21906            self.write_keyword("TRYCAST");
21907            self.write("(");
21908            self.generate_expression(&cast.this)?;
21909            self.write_space();
21910            self.write_keyword("AS");
21911            self.write_space();
21912            self.generate_data_type(&cast.to)?;
21913            self.write(")");
21914            return Ok(());
21915        }
21916
21917        // Dialects without TRY_CAST: generate as regular CAST
21918        let keyword = if matches!(
21919            self.config.dialect,
21920            Some(DialectType::Hive)
21921                | Some(DialectType::MySQL)
21922                | Some(DialectType::SQLite)
21923                | Some(DialectType::Oracle)
21924                | Some(DialectType::ClickHouse)
21925                | Some(DialectType::Redshift)
21926                | Some(DialectType::PostgreSQL)
21927                | Some(DialectType::StarRocks)
21928                | Some(DialectType::Doris)
21929        ) {
21930            "CAST"
21931        } else {
21932            "TRY_CAST"
21933        };
21934
21935        self.write_keyword(keyword);
21936        self.write("(");
21937        self.generate_expression(&cast.this)?;
21938        self.write_space();
21939        self.write_keyword("AS");
21940        self.write_space();
21941        self.generate_data_type(&cast.to)?;
21942
21943        // Output FORMAT clause if present
21944        if let Some(format) = &cast.format {
21945            self.write_space();
21946            self.write_keyword("FORMAT");
21947            self.write_space();
21948            self.generate_expression(format)?;
21949        }
21950
21951        self.write(")");
21952        Ok(())
21953    }
21954
21955    fn generate_safe_cast(&mut self, cast: &Cast) -> Result<()> {
21956        self.write_keyword("SAFE_CAST");
21957        self.write("(");
21958        self.generate_expression(&cast.this)?;
21959        self.write_space();
21960        self.write_keyword("AS");
21961        self.write_space();
21962        self.generate_data_type(&cast.to)?;
21963
21964        // Output FORMAT clause if present
21965        if let Some(format) = &cast.format {
21966            self.write_space();
21967            self.write_keyword("FORMAT");
21968            self.write_space();
21969            self.generate_expression(format)?;
21970        }
21971
21972        self.write(")");
21973        Ok(())
21974    }
21975
21976    // Array/struct/map access generators
21977
21978    fn generate_subscript(&mut self, s: &Subscript) -> Result<()> {
21979        // Wrap the base expression in parentheses when it uses arrow syntax (->)
21980        // which has lower precedence than bracket subscript ([]).
21981        // E.g., (t.v -> '$.a')[s.x] instead of t.v -> '$.a'[s.x]
21982        let needs_parens = matches!(&s.this, Expression::JsonExtract(ref f) if f.arrow_syntax);
21983        if needs_parens {
21984            self.write("(");
21985        }
21986        self.generate_expression(&s.this)?;
21987        if needs_parens {
21988            self.write(")");
21989        }
21990        self.write("[");
21991        self.generate_expression(&s.index)?;
21992        self.write("]");
21993        Ok(())
21994    }
21995
21996    fn generate_dot_access(&mut self, d: &DotAccess) -> Result<()> {
21997        self.generate_expression(&d.this)?;
21998        // Snowflake uses : (colon) for first-level struct/object field access on CAST/column expressions
21999        // e.g., CAST(col AS OBJECT(fld1 OBJECT(fld2 INT))):fld1.fld2
22000        let use_colon = matches!(self.config.dialect, Some(DialectType::Snowflake))
22001            && matches!(
22002                &d.this,
22003                Expression::Cast(_) | Expression::SafeCast(_) | Expression::TryCast(_)
22004            );
22005        if use_colon {
22006            self.write(":");
22007        } else {
22008            self.write(".");
22009        }
22010        self.generate_identifier(&d.field)
22011    }
22012
22013    fn generate_method_call(&mut self, m: &MethodCall) -> Result<()> {
22014        self.generate_expression(&m.this)?;
22015        self.write(".");
22016        // Method names after a dot should not be quoted based on reserved keywords
22017        // Only quote if explicitly marked as quoted in the AST
22018        if m.method.quoted {
22019            let q = self.config.identifier_quote;
22020            self.write(&format!("{}{}{}", q, m.method.name, q));
22021        } else {
22022            self.write(&m.method.name);
22023        }
22024        self.write("(");
22025        for (i, arg) in m.args.iter().enumerate() {
22026            if i > 0 {
22027                self.write(", ");
22028            }
22029            self.generate_expression(arg)?;
22030        }
22031        self.write(")");
22032        Ok(())
22033    }
22034
22035    fn generate_array_slice(&mut self, s: &ArraySlice) -> Result<()> {
22036        // Check if we need to wrap the inner expression in parentheses
22037        // JSON arrow expressions have lower precedence than array subscript
22038        let needs_parens = matches!(
22039            &s.this,
22040            Expression::JsonExtract(f) if f.arrow_syntax
22041        ) || matches!(
22042            &s.this,
22043            Expression::JsonExtractScalar(f) if f.arrow_syntax
22044        );
22045
22046        if needs_parens {
22047            self.write("(");
22048        }
22049        self.generate_expression(&s.this)?;
22050        if needs_parens {
22051            self.write(")");
22052        }
22053        self.write("[");
22054        if let Some(start) = &s.start {
22055            self.generate_expression(start)?;
22056        }
22057        self.write(":");
22058        if let Some(end) = &s.end {
22059            self.generate_expression(end)?;
22060        }
22061        self.write("]");
22062        Ok(())
22063    }
22064
22065    fn generate_binary_op(&mut self, op: &BinaryOp, operator: &str) -> Result<()> {
22066        // Generate left expression, but skip trailing comments if they're already in left_comments
22067        // to avoid duplication (comments are captured as both expr.trailing_comments
22068        // and BinaryOp.left_comments during parsing)
22069        match &op.left {
22070            Expression::Column(col) => {
22071                // Generate column with trailing comments but skip them if they're
22072                // already captured in BinaryOp.left_comments to avoid duplication
22073                if let Some(table) = &col.table {
22074                    self.generate_identifier(table)?;
22075                    self.write(".");
22076                }
22077                self.generate_identifier(&col.name)?;
22078                // Oracle-style join marker (+)
22079                if col.join_mark && self.config.supports_column_join_marks {
22080                    self.write(" (+)");
22081                }
22082                // Output column trailing comments if they're not already in left_comments
22083                if op.left_comments.is_empty() {
22084                    for comment in &col.trailing_comments {
22085                        self.write_space();
22086                        self.write_formatted_comment(comment);
22087                    }
22088                }
22089            }
22090            Expression::Add(inner_op)
22091            | Expression::Sub(inner_op)
22092            | Expression::Mul(inner_op)
22093            | Expression::Div(inner_op)
22094            | Expression::Concat(inner_op) => {
22095                // Generate binary op without its trailing comments
22096                self.generate_binary_op_no_trailing(inner_op, match &op.left {
22097                    Expression::Add(_) => "+",
22098                    Expression::Sub(_) => "-",
22099                    Expression::Mul(_) => "*",
22100                    Expression::Div(_) => "/",
22101                    Expression::Concat(_) => "||",
22102                    _ => unreachable!("op.left variant already matched by outer arm as Add/Sub/Mul/Div/Concat"),
22103                })?;
22104            }
22105            _ => {
22106                self.generate_expression(&op.left)?;
22107            }
22108        }
22109        // Output comments after left operand
22110        for comment in &op.left_comments {
22111            self.write_space();
22112            self.write_formatted_comment(comment);
22113        }
22114        if self.config.pretty
22115            && matches!(self.config.dialect, Some(DialectType::Snowflake))
22116            && (operator == "AND" || operator == "OR")
22117        {
22118            self.write_newline();
22119            self.write_indent();
22120            self.write_keyword(operator);
22121        } else {
22122            self.write_space();
22123            if operator.chars().all(|c| c.is_alphabetic()) {
22124                self.write_keyword(operator);
22125            } else {
22126                self.write(operator);
22127            }
22128        }
22129        // Output comments after operator (before right operand)
22130        for comment in &op.operator_comments {
22131            self.write_space();
22132            self.write_formatted_comment(comment);
22133        }
22134        self.write_space();
22135        self.generate_expression(&op.right)?;
22136        // Output trailing comments after right operand
22137        for comment in &op.trailing_comments {
22138            self.write_space();
22139            self.write_formatted_comment(comment);
22140        }
22141        Ok(())
22142    }
22143
22144    fn generate_connector_op(&mut self, op: &BinaryOp, connector: ConnectorOperator) -> Result<()> {
22145        let keyword = connector.keyword();
22146        let Some(terms) = self.flatten_connector_terms(op, connector) else {
22147            return self.generate_binary_op(op, keyword);
22148        };
22149
22150        let wrap_clickhouse_or_term = |generator: &mut Self, term: &Expression| -> Result<()> {
22151            let should_wrap = matches!(connector, ConnectorOperator::Or)
22152                && matches!(generator.config.dialect, Some(DialectType::ClickHouse))
22153                && matches!(
22154                    generator.config.source_dialect,
22155                    Some(DialectType::ClickHouse)
22156                )
22157                && matches!(term, Expression::And(_));
22158            if should_wrap {
22159                generator.write("(");
22160                generator.generate_expression(term)?;
22161                generator.write(")");
22162            } else {
22163                generator.generate_expression(term)?;
22164            }
22165            Ok(())
22166        };
22167
22168        wrap_clickhouse_or_term(self, terms[0])?;
22169        for term in terms.iter().skip(1) {
22170            if self.config.pretty && matches!(self.config.dialect, Some(DialectType::Snowflake)) {
22171                self.write_newline();
22172                self.write_indent();
22173                self.write_keyword(keyword);
22174            } else {
22175                self.write_space();
22176                self.write_keyword(keyword);
22177            }
22178            self.write_space();
22179            wrap_clickhouse_or_term(self, term)?;
22180        }
22181
22182        Ok(())
22183    }
22184
22185    fn flatten_connector_terms<'a>(
22186        &self,
22187        root: &'a BinaryOp,
22188        connector: ConnectorOperator,
22189    ) -> Option<Vec<&'a Expression>> {
22190        if !root.left_comments.is_empty()
22191            || !root.operator_comments.is_empty()
22192            || !root.trailing_comments.is_empty()
22193        {
22194            return None;
22195        }
22196
22197        let mut terms = Vec::new();
22198        let mut stack: Vec<&Expression> = vec![&root.right, &root.left];
22199
22200        while let Some(expr) = stack.pop() {
22201            match (connector, expr) {
22202                (ConnectorOperator::And, Expression::And(inner))
22203                    if inner.left_comments.is_empty()
22204                        && inner.operator_comments.is_empty()
22205                        && inner.trailing_comments.is_empty() =>
22206                {
22207                    stack.push(&inner.right);
22208                    stack.push(&inner.left);
22209                }
22210                (ConnectorOperator::Or, Expression::Or(inner))
22211                    if inner.left_comments.is_empty()
22212                        && inner.operator_comments.is_empty()
22213                        && inner.trailing_comments.is_empty() =>
22214                {
22215                    stack.push(&inner.right);
22216                    stack.push(&inner.left);
22217                }
22218                _ => terms.push(expr),
22219            }
22220        }
22221
22222        if terms.len() > 1 {
22223            Some(terms)
22224        } else {
22225            None
22226        }
22227    }
22228
22229    /// Generate LIKE/ILIKE operation with optional ESCAPE clause
22230    fn generate_like_op(&mut self, op: &LikeOp, operator: &str) -> Result<()> {
22231        self.generate_expression(&op.left)?;
22232        self.write_space();
22233        // Drill backtick-quotes ILIKE
22234        if operator == "ILIKE" && matches!(self.config.dialect, Some(DialectType::Drill)) {
22235            self.write("`ILIKE`");
22236        } else {
22237            self.write_keyword(operator);
22238        }
22239        if let Some(quantifier) = &op.quantifier {
22240            self.write_space();
22241            self.write_keyword(quantifier);
22242            // Match Python sqlglot behavior:
22243            // ANY + Paren (single value): no space → ILIKE ANY('%a%')
22244            // ANY + Tuple (multiple values): space → LIKE ANY ('a', 'b')
22245            // ALL + anything: always space → LIKE ALL ('%a%'), LIKE ALL ('a', 'b')
22246            let is_any =
22247                quantifier.eq_ignore_ascii_case("ANY") || quantifier.eq_ignore_ascii_case("SOME");
22248            if !(is_any && matches!(&op.right, Expression::Paren(_))) {
22249                self.write_space();
22250            }
22251        } else {
22252            self.write_space();
22253        }
22254        self.generate_expression(&op.right)?;
22255        if let Some(escape) = &op.escape {
22256            self.write_space();
22257            self.write_keyword("ESCAPE");
22258            self.write_space();
22259            self.generate_expression(escape)?;
22260        }
22261        Ok(())
22262    }
22263
22264    /// Generate null-safe equality
22265    /// MySQL uses <=>, other dialects use IS NOT DISTINCT FROM
22266    fn generate_null_safe_eq(&mut self, op: &BinaryOp) -> Result<()> {
22267        use crate::dialects::DialectType;
22268        self.generate_expression(&op.left)?;
22269        self.write_space();
22270        if matches!(self.config.dialect, Some(DialectType::MySQL)) {
22271            self.write("<=>");
22272        } else {
22273            self.write_keyword("IS NOT DISTINCT FROM");
22274        }
22275        self.write_space();
22276        self.generate_expression(&op.right)?;
22277        Ok(())
22278    }
22279
22280    /// Generate IS DISTINCT FROM (null-safe inequality)
22281    fn generate_null_safe_neq(&mut self, op: &BinaryOp) -> Result<()> {
22282        self.generate_expression(&op.left)?;
22283        self.write_space();
22284        self.write_keyword("IS DISTINCT FROM");
22285        self.write_space();
22286        self.generate_expression(&op.right)?;
22287        Ok(())
22288    }
22289
22290    /// Generate binary op without trailing comments (used when nested inside another binary op)
22291    fn generate_binary_op_no_trailing(&mut self, op: &BinaryOp, operator: &str) -> Result<()> {
22292        // Generate left expression, but skip trailing comments
22293        match &op.left {
22294            Expression::Column(col) => {
22295                if let Some(table) = &col.table {
22296                    self.generate_identifier(table)?;
22297                    self.write(".");
22298                }
22299                self.generate_identifier(&col.name)?;
22300                // Oracle-style join marker (+)
22301                if col.join_mark && self.config.supports_column_join_marks {
22302                    self.write(" (+)");
22303                }
22304            }
22305            Expression::Add(inner_op)
22306            | Expression::Sub(inner_op)
22307            | Expression::Mul(inner_op)
22308            | Expression::Div(inner_op)
22309            | Expression::Concat(inner_op) => {
22310                self.generate_binary_op_no_trailing(inner_op, match &op.left {
22311                    Expression::Add(_) => "+",
22312                    Expression::Sub(_) => "-",
22313                    Expression::Mul(_) => "*",
22314                    Expression::Div(_) => "/",
22315                    Expression::Concat(_) => "||",
22316                    _ => unreachable!("op.left variant already matched by outer arm as Add/Sub/Mul/Div/Concat"),
22317                })?;
22318            }
22319            _ => {
22320                self.generate_expression(&op.left)?;
22321            }
22322        }
22323        // Output left_comments
22324        for comment in &op.left_comments {
22325            self.write_space();
22326            self.write_formatted_comment(comment);
22327        }
22328        self.write_space();
22329        if operator.chars().all(|c| c.is_alphabetic()) {
22330            self.write_keyword(operator);
22331        } else {
22332            self.write(operator);
22333        }
22334        // Output operator_comments
22335        for comment in &op.operator_comments {
22336            self.write_space();
22337            self.write_formatted_comment(comment);
22338        }
22339        self.write_space();
22340        // Generate right expression, but skip trailing comments if it's a Column
22341        // (the parent's left_comments will output them)
22342        match &op.right {
22343            Expression::Column(col) => {
22344                if let Some(table) = &col.table {
22345                    self.generate_identifier(table)?;
22346                    self.write(".");
22347                }
22348                self.generate_identifier(&col.name)?;
22349                // Oracle-style join marker (+)
22350                if col.join_mark && self.config.supports_column_join_marks {
22351                    self.write(" (+)");
22352                }
22353            }
22354            _ => {
22355                self.generate_expression(&op.right)?;
22356            }
22357        }
22358        // Skip trailing_comments - parent will handle them via its left_comments
22359        Ok(())
22360    }
22361
22362    fn generate_unary_op(&mut self, op: &UnaryOp, operator: &str) -> Result<()> {
22363        if operator.chars().all(|c| c.is_alphabetic()) {
22364            self.write_keyword(operator);
22365            self.write_space();
22366        } else {
22367            self.write(operator);
22368            // Add space between consecutive unary operators (e.g., "- -5" not "--5")
22369            if matches!(&op.this, Expression::Neg(_) | Expression::BitwiseNot(_)) {
22370                self.write_space();
22371            }
22372        }
22373        self.generate_expression(&op.this)
22374    }
22375
22376    fn generate_in(&mut self, in_expr: &In) -> Result<()> {
22377        // Generic mode supports two styles for negated IN:
22378        // - Prefix: NOT a IN (...)
22379        // - Infix:  a NOT IN (...)
22380        let is_generic =
22381            self.config.dialect.is_none() || self.config.dialect == Some(DialectType::Generic);
22382        let use_prefix_not =
22383            in_expr.not && is_generic && self.config.not_in_style == NotInStyle::Prefix;
22384        if use_prefix_not {
22385            self.write_keyword("NOT");
22386            self.write_space();
22387        }
22388        self.generate_expression(&in_expr.this)?;
22389        if in_expr.global {
22390            self.write_space();
22391            self.write_keyword("GLOBAL");
22392        }
22393        if in_expr.not && !use_prefix_not {
22394            self.write_space();
22395            self.write_keyword("NOT");
22396        }
22397        self.write_space();
22398        self.write_keyword("IN");
22399
22400        // BigQuery: IN UNNEST(expr)
22401        if let Some(unnest_expr) = &in_expr.unnest {
22402            self.write_space();
22403            self.write_keyword("UNNEST");
22404            self.write("(");
22405            self.generate_expression(unnest_expr)?;
22406            self.write(")");
22407            return Ok(());
22408        }
22409
22410        if let Some(query) = &in_expr.query {
22411            // Check if this is a bare identifier (PIVOT FOR foo IN y_enum)
22412            // vs a subquery (col IN (SELECT ...))
22413            let is_bare = in_expr.expressions.is_empty()
22414                && !matches!(
22415                    query,
22416                    Expression::Select(_)
22417                        | Expression::Union(_)
22418                        | Expression::Intersect(_)
22419                        | Expression::Except(_)
22420                        | Expression::Subquery(_)
22421                );
22422            if is_bare {
22423                // Bare identifier: no parentheses
22424                self.write_space();
22425                self.generate_expression(query)?;
22426            } else {
22427                // Subquery: with parentheses
22428                self.write(" (");
22429                let is_statement = matches!(
22430                    query,
22431                    Expression::Select(_)
22432                        | Expression::Union(_)
22433                        | Expression::Intersect(_)
22434                        | Expression::Except(_)
22435                        | Expression::Subquery(_)
22436                );
22437                if self.config.pretty && is_statement {
22438                    self.write_newline();
22439                    self.indent_level += 1;
22440                    self.write_indent();
22441                }
22442                self.generate_expression(query)?;
22443                if self.config.pretty && is_statement {
22444                    self.write_newline();
22445                    self.indent_level -= 1;
22446                    self.write_indent();
22447                }
22448                self.write(")");
22449            }
22450        } else {
22451            // DuckDB: IN without parentheses for single expression that is NOT a literal
22452            // (array/list membership like 'red' IN tbl.flags)
22453            // ClickHouse: IN without parentheses for single non-array expressions
22454            let is_duckdb = matches!(
22455                self.config.dialect,
22456                Some(crate::dialects::DialectType::DuckDB)
22457            );
22458            let is_clickhouse = matches!(
22459                self.config.dialect,
22460                Some(crate::dialects::DialectType::ClickHouse)
22461            );
22462            let single_expr = in_expr.expressions.len() == 1;
22463            if is_clickhouse && single_expr {
22464                if let Expression::Array(arr) = &in_expr.expressions[0] {
22465                    // ClickHouse: x IN [1, 2] -> x IN (1, 2)
22466                    self.write(" (");
22467                    for (i, expr) in arr.expressions.iter().enumerate() {
22468                        if i > 0 {
22469                            self.write(", ");
22470                        }
22471                        self.generate_expression(expr)?;
22472                    }
22473                    self.write(")");
22474                } else if in_expr.is_field {
22475                    self.write_space();
22476                    self.generate_expression(&in_expr.expressions[0])?;
22477                } else {
22478                    self.write(" (");
22479                    self.generate_expression(&in_expr.expressions[0])?;
22480                    self.write(")");
22481                }
22482            } else {
22483                let is_bare_ref = single_expr
22484                    && matches!(
22485                        &in_expr.expressions[0],
22486                        Expression::Column(_) | Expression::Identifier(_) | Expression::Dot(_)
22487                    );
22488                if (is_duckdb && is_bare_ref) || (in_expr.is_field && single_expr) {
22489                    // Bare field reference (no parens in source): IN identifier
22490                    // Also DuckDB: IN without parentheses for array/list membership
22491                    self.write_space();
22492                    self.generate_expression(&in_expr.expressions[0])?;
22493                } else {
22494                    // Standard IN (list)
22495                    self.write(" (");
22496                    for (i, expr) in in_expr.expressions.iter().enumerate() {
22497                        if i > 0 {
22498                            self.write(", ");
22499                        }
22500                        self.generate_expression(expr)?;
22501                    }
22502                    self.write(")");
22503                }
22504            }
22505        }
22506
22507        Ok(())
22508    }
22509
22510    fn generate_between(&mut self, between: &Between) -> Result<()> {
22511        // Generic mode: normalize NOT BETWEEN to prefix form: NOT a BETWEEN b AND c
22512        let use_prefix_not = between.not
22513            && (self.config.dialect.is_none() || self.config.dialect == Some(DialectType::Generic));
22514        if use_prefix_not {
22515            self.write_keyword("NOT");
22516            self.write_space();
22517        }
22518        self.generate_expression(&between.this)?;
22519        if between.not && !use_prefix_not {
22520            self.write_space();
22521            self.write_keyword("NOT");
22522        }
22523        self.write_space();
22524        self.write_keyword("BETWEEN");
22525        // Emit SYMMETRIC/ASYMMETRIC if present
22526        if let Some(sym) = between.symmetric {
22527            if sym {
22528                self.write(" SYMMETRIC");
22529            } else {
22530                self.write(" ASYMMETRIC");
22531            }
22532        }
22533        self.write_space();
22534        self.generate_expression(&between.low)?;
22535        self.write_space();
22536        self.write_keyword("AND");
22537        self.write_space();
22538        self.generate_expression(&between.high)
22539    }
22540
22541    fn generate_is_null(&mut self, is_null: &IsNull) -> Result<()> {
22542        // Generic mode: normalize IS NOT NULL to prefix form: NOT x IS NULL
22543        let use_prefix_not = is_null.not
22544            && (self.config.dialect.is_none()
22545                || self.config.dialect == Some(DialectType::Generic)
22546                || is_null.postfix_form);
22547        if use_prefix_not {
22548            // NOT x IS NULL (generic normalization and NOTNULL postfix form)
22549            self.write_keyword("NOT");
22550            self.write_space();
22551            self.generate_expression(&is_null.this)?;
22552            self.write_space();
22553            self.write_keyword("IS");
22554            self.write_space();
22555            self.write_keyword("NULL");
22556        } else {
22557            self.generate_expression(&is_null.this)?;
22558            self.write_space();
22559            self.write_keyword("IS");
22560            if is_null.not {
22561                self.write_space();
22562                self.write_keyword("NOT");
22563            }
22564            self.write_space();
22565            self.write_keyword("NULL");
22566        }
22567        Ok(())
22568    }
22569
22570    fn generate_is_true(&mut self, is_true: &IsTrueFalse) -> Result<()> {
22571        self.generate_expression(&is_true.this)?;
22572        self.write_space();
22573        self.write_keyword("IS");
22574        if is_true.not {
22575            self.write_space();
22576            self.write_keyword("NOT");
22577        }
22578        self.write_space();
22579        self.write_keyword("TRUE");
22580        Ok(())
22581    }
22582
22583    fn generate_is_false(&mut self, is_false: &IsTrueFalse) -> Result<()> {
22584        self.generate_expression(&is_false.this)?;
22585        self.write_space();
22586        self.write_keyword("IS");
22587        if is_false.not {
22588            self.write_space();
22589            self.write_keyword("NOT");
22590        }
22591        self.write_space();
22592        self.write_keyword("FALSE");
22593        Ok(())
22594    }
22595
22596    fn generate_is_json(&mut self, is_json: &IsJson) -> Result<()> {
22597        self.generate_expression(&is_json.this)?;
22598        self.write_space();
22599        self.write_keyword("IS");
22600        if is_json.negated {
22601            self.write_space();
22602            self.write_keyword("NOT");
22603        }
22604        self.write_space();
22605        self.write_keyword("JSON");
22606
22607        // Output JSON type if specified (VALUE, SCALAR, OBJECT, ARRAY)
22608        if let Some(ref json_type) = is_json.json_type {
22609            self.write_space();
22610            self.write_keyword(json_type);
22611        }
22612
22613        // Output key uniqueness constraint if specified
22614        match &is_json.unique_keys {
22615            Some(JsonUniqueKeys::With) => {
22616                self.write_space();
22617                self.write_keyword("WITH UNIQUE KEYS");
22618            }
22619            Some(JsonUniqueKeys::Without) => {
22620                self.write_space();
22621                self.write_keyword("WITHOUT UNIQUE KEYS");
22622            }
22623            Some(JsonUniqueKeys::Shorthand) => {
22624                self.write_space();
22625                self.write_keyword("UNIQUE KEYS");
22626            }
22627            None => {}
22628        }
22629
22630        Ok(())
22631    }
22632
22633    fn generate_is(&mut self, is_expr: &BinaryOp) -> Result<()> {
22634        self.generate_expression(&is_expr.left)?;
22635        self.write_space();
22636        self.write_keyword("IS");
22637        self.write_space();
22638        self.generate_expression(&is_expr.right)
22639    }
22640
22641    fn generate_exists(&mut self, exists: &Exists) -> Result<()> {
22642        if exists.not {
22643            self.write_keyword("NOT");
22644            self.write_space();
22645        }
22646        self.write_keyword("EXISTS");
22647        self.write("(");
22648        let is_statement = matches!(
22649            &exists.this,
22650            Expression::Select(_)
22651                | Expression::Union(_)
22652                | Expression::Intersect(_)
22653                | Expression::Except(_)
22654        );
22655        if self.config.pretty && is_statement {
22656            self.write_newline();
22657            self.indent_level += 1;
22658            self.write_indent();
22659            self.generate_expression(&exists.this)?;
22660            self.write_newline();
22661            self.indent_level -= 1;
22662            self.write_indent();
22663            self.write(")");
22664        } else {
22665            self.generate_expression(&exists.this)?;
22666            self.write(")");
22667        }
22668        Ok(())
22669    }
22670
22671    fn generate_member_of(&mut self, op: &BinaryOp) -> Result<()> {
22672        self.generate_expression(&op.left)?;
22673        self.write_space();
22674        self.write_keyword("MEMBER OF");
22675        self.write("(");
22676        self.generate_expression(&op.right)?;
22677        self.write(")");
22678        Ok(())
22679    }
22680
22681    fn generate_subquery(&mut self, subquery: &Subquery) -> Result<()> {
22682        if subquery.lateral {
22683            self.write_keyword("LATERAL");
22684            self.write_space();
22685        }
22686
22687        // If the inner expression is a Paren wrapping a statement, don't add extra parentheses
22688        // This handles cases like ((SELECT 1)) LIMIT 1 where we wrap Paren in Subquery
22689        // to carry the LIMIT modifier without adding more parens
22690        let skip_outer_parens = if let Expression::Paren(ref p) = &subquery.this {
22691            matches!(
22692                &p.this,
22693                Expression::Select(_)
22694                    | Expression::Union(_)
22695                    | Expression::Intersect(_)
22696                    | Expression::Except(_)
22697                    | Expression::Subquery(_)
22698            )
22699        } else {
22700            false
22701        };
22702
22703        // Check if inner expression is a statement for pretty formatting
22704        let is_statement = matches!(
22705            &subquery.this,
22706            Expression::Select(_)
22707                | Expression::Union(_)
22708                | Expression::Intersect(_)
22709                | Expression::Except(_)
22710                | Expression::Merge(_)
22711        );
22712
22713        if !skip_outer_parens {
22714            self.write("(");
22715            if self.config.pretty && is_statement {
22716                self.write_newline();
22717                self.indent_level += 1;
22718                self.write_indent();
22719            }
22720        }
22721        self.generate_expression(&subquery.this)?;
22722
22723        // Generate ORDER BY, LIMIT, OFFSET based on modifiers_inside flag
22724        if subquery.modifiers_inside {
22725            // Generate modifiers INSIDE the parentheses: (SELECT ... LIMIT 1)
22726            if let Some(order_by) = &subquery.order_by {
22727                self.write_space();
22728                self.write_keyword("ORDER BY");
22729                self.write_space();
22730                for (i, ord) in order_by.expressions.iter().enumerate() {
22731                    if i > 0 {
22732                        self.write(", ");
22733                    }
22734                    self.generate_ordered(ord)?;
22735                }
22736            }
22737
22738            if let Some(limit) = &subquery.limit {
22739                self.write_space();
22740                self.write_keyword("LIMIT");
22741                self.write_space();
22742                self.generate_expression(&limit.this)?;
22743                if limit.percent {
22744                    self.write_space();
22745                    self.write_keyword("PERCENT");
22746                }
22747            }
22748
22749            if let Some(offset) = &subquery.offset {
22750                self.write_space();
22751                self.write_keyword("OFFSET");
22752                self.write_space();
22753                self.generate_expression(&offset.this)?;
22754            }
22755        }
22756
22757        if !skip_outer_parens {
22758            if self.config.pretty && is_statement {
22759                self.write_newline();
22760                self.indent_level -= 1;
22761                self.write_indent();
22762            }
22763            self.write(")");
22764        }
22765
22766        // Generate modifiers OUTSIDE the parentheses: (SELECT ...) LIMIT 1
22767        if !subquery.modifiers_inside {
22768            if let Some(order_by) = &subquery.order_by {
22769                self.write_space();
22770                self.write_keyword("ORDER BY");
22771                self.write_space();
22772                for (i, ord) in order_by.expressions.iter().enumerate() {
22773                    if i > 0 {
22774                        self.write(", ");
22775                    }
22776                    self.generate_ordered(ord)?;
22777                }
22778            }
22779
22780            if let Some(limit) = &subquery.limit {
22781                self.write_space();
22782                self.write_keyword("LIMIT");
22783                self.write_space();
22784                self.generate_expression(&limit.this)?;
22785                if limit.percent {
22786                    self.write_space();
22787                    self.write_keyword("PERCENT");
22788                }
22789            }
22790
22791            if let Some(offset) = &subquery.offset {
22792                self.write_space();
22793                self.write_keyword("OFFSET");
22794                self.write_space();
22795                self.generate_expression(&offset.this)?;
22796            }
22797
22798            // Generate DISTRIBUTE BY (Hive/Spark)
22799            if let Some(distribute_by) = &subquery.distribute_by {
22800                self.write_space();
22801                self.write_keyword("DISTRIBUTE BY");
22802                self.write_space();
22803                for (i, expr) in distribute_by.expressions.iter().enumerate() {
22804                    if i > 0 {
22805                        self.write(", ");
22806                    }
22807                    self.generate_expression(expr)?;
22808                }
22809            }
22810
22811            // Generate SORT BY (Hive/Spark)
22812            if let Some(sort_by) = &subquery.sort_by {
22813                self.write_space();
22814                self.write_keyword("SORT BY");
22815                self.write_space();
22816                for (i, ord) in sort_by.expressions.iter().enumerate() {
22817                    if i > 0 {
22818                        self.write(", ");
22819                    }
22820                    self.generate_ordered(ord)?;
22821                }
22822            }
22823
22824            // Generate CLUSTER BY (Hive/Spark)
22825            if let Some(cluster_by) = &subquery.cluster_by {
22826                self.write_space();
22827                self.write_keyword("CLUSTER BY");
22828                self.write_space();
22829                for (i, ord) in cluster_by.expressions.iter().enumerate() {
22830                    if i > 0 {
22831                        self.write(", ");
22832                    }
22833                    self.generate_ordered(ord)?;
22834                }
22835            }
22836        }
22837
22838        if let Some(alias) = &subquery.alias {
22839            self.write_space();
22840            let skip_as = matches!(self.config.dialect, Some(DialectType::Oracle))
22841                || (matches!(self.config.dialect, Some(DialectType::ClickHouse))
22842                    && !subquery.alias_explicit_as);
22843            if !skip_as {
22844                if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
22845                    self.write(subquery.alias_keyword.as_deref().unwrap_or("AS"));
22846                } else {
22847                    self.write_keyword("AS");
22848                }
22849                self.write_space();
22850            }
22851            self.generate_identifier(alias)?;
22852            if !subquery.column_aliases.is_empty() {
22853                self.write("(");
22854                for (i, col) in subquery.column_aliases.iter().enumerate() {
22855                    if i > 0 {
22856                        self.write(", ");
22857                    }
22858                    self.generate_identifier(col)?;
22859                }
22860                self.write(")");
22861            }
22862        }
22863        // Output trailing comments
22864        for comment in &subquery.trailing_comments {
22865            self.write(" ");
22866            self.write_formatted_comment(comment);
22867        }
22868        Ok(())
22869    }
22870
22871    fn generate_pivot(&mut self, pivot: &Pivot) -> Result<()> {
22872        // Generate WITH clause if present
22873        if let Some(ref with) = pivot.with {
22874            self.generate_with(with)?;
22875            self.write_space();
22876        }
22877
22878        let direction = if pivot.unpivot { "UNPIVOT" } else { "PIVOT" };
22879
22880        // Check for Redshift UNPIVOT in FROM clause:
22881        // UNPIVOT expr [AS val AT attr]
22882        // This is when unpivot=true, expressions is empty, fields is empty, and this is not Null
22883        let is_redshift_unpivot = pivot.unpivot
22884            && pivot.expressions.is_empty()
22885            && pivot.fields.is_empty()
22886            && pivot.using.is_empty()
22887            && pivot.into.is_none()
22888            && !matches!(&pivot.this, Expression::Null(_));
22889
22890        if is_redshift_unpivot {
22891            // Redshift UNPIVOT: UNPIVOT expr [AS alias]
22892            self.write_keyword("UNPIVOT");
22893            self.write_space();
22894            self.generate_expression(&pivot.this)?;
22895            // Alias - for Redshift it can be "val AT attr" format
22896            if let Some(alias) = &pivot.alias {
22897                self.write_space();
22898                self.write_keyword("AS");
22899                self.write_space();
22900                // The alias might contain " AT " for the attr part
22901                self.write(&alias.name);
22902            }
22903            return Ok(());
22904        }
22905
22906        // Check if this is a DuckDB simplified pivot (has `using` or `into`, or no `fields`)
22907        let is_simplified = !pivot.using.is_empty()
22908            || pivot.into.is_some()
22909            || (pivot.fields.is_empty()
22910                && !pivot.expressions.is_empty()
22911                && !matches!(&pivot.this, Expression::Null(_)));
22912
22913        if is_simplified {
22914            // DuckDB simplified syntax:
22915            //   PIVOT table ON cols [IN (...)] USING agg [AS alias], ... [GROUP BY ...]
22916            //   UNPIVOT table ON cols INTO NAME col VALUE col
22917            self.write_keyword(direction);
22918            self.write_space();
22919            self.generate_expression(&pivot.this)?;
22920
22921            if !pivot.expressions.is_empty() {
22922                self.write_space();
22923                self.write_keyword("ON");
22924                self.write_space();
22925                for (i, expr) in pivot.expressions.iter().enumerate() {
22926                    if i > 0 {
22927                        self.write(", ");
22928                    }
22929                    self.generate_expression(expr)?;
22930                }
22931            }
22932
22933            // INTO (for UNPIVOT)
22934            if let Some(into) = &pivot.into {
22935                self.write_space();
22936                self.write_keyword("INTO");
22937                self.write_space();
22938                self.generate_expression(into)?;
22939            }
22940
22941            // USING (for PIVOT)
22942            if !pivot.using.is_empty() {
22943                self.write_space();
22944                self.write_keyword("USING");
22945                self.write_space();
22946                for (i, expr) in pivot.using.iter().enumerate() {
22947                    if i > 0 {
22948                        self.write(", ");
22949                    }
22950                    self.generate_expression(expr)?;
22951                }
22952            }
22953
22954            // GROUP BY
22955            if let Some(group) = &pivot.group {
22956                self.write_space();
22957                self.generate_expression(group)?;
22958            }
22959        } else {
22960            // Standard syntax:
22961            //   table PIVOT(agg [AS alias], ... FOR col IN (val [AS alias], ...) [GROUP BY ...])
22962            //   table UNPIVOT(value_col FOR name_col IN (col1, col2, ...))
22963            // Only output the table expression if it's not a Null (null is used when PIVOT comes after JOIN ON)
22964            if !matches!(&pivot.this, Expression::Null(_)) {
22965                self.generate_expression(&pivot.this)?;
22966                self.write_space();
22967            }
22968            self.write_keyword(direction);
22969            self.write("(");
22970
22971            // Aggregation expressions
22972            for (i, expr) in pivot.expressions.iter().enumerate() {
22973                if i > 0 {
22974                    self.write(", ");
22975                }
22976                self.generate_expression(expr)?;
22977            }
22978
22979            // FOR...IN fields
22980            if !pivot.fields.is_empty() {
22981                if !pivot.expressions.is_empty() {
22982                    self.write_space();
22983                }
22984                self.write_keyword("FOR");
22985                self.write_space();
22986                for (i, field) in pivot.fields.iter().enumerate() {
22987                    if i > 0 {
22988                        self.write_space();
22989                    }
22990                    // field is an In expression: column IN (values)
22991                    self.generate_expression(field)?;
22992                }
22993            }
22994
22995            // DEFAULT ON NULL
22996            if let Some(default_val) = &pivot.default_on_null {
22997                self.write_space();
22998                self.write_keyword("DEFAULT ON NULL");
22999                self.write(" (");
23000                self.generate_expression(default_val)?;
23001                self.write(")");
23002            }
23003
23004            // GROUP BY inside PIVOT parens
23005            if let Some(group) = &pivot.group {
23006                self.write_space();
23007                self.generate_expression(group)?;
23008            }
23009
23010            self.write(")");
23011        }
23012
23013        // Alias
23014        if let Some(alias) = &pivot.alias {
23015            self.write_space();
23016            self.write_keyword("AS");
23017            self.write_space();
23018            self.generate_identifier(alias)?;
23019        }
23020
23021        Ok(())
23022    }
23023
23024    fn generate_unpivot(&mut self, unpivot: &Unpivot) -> Result<()> {
23025        self.generate_expression(&unpivot.this)?;
23026        self.write_space();
23027        self.write_keyword("UNPIVOT");
23028        // Output INCLUDE NULLS or EXCLUDE NULLS if specified
23029        if let Some(include) = unpivot.include_nulls {
23030            self.write_space();
23031            if include {
23032                self.write_keyword("INCLUDE NULLS");
23033            } else {
23034                self.write_keyword("EXCLUDE NULLS");
23035            }
23036            self.write_space();
23037        }
23038        self.write("(");
23039        if unpivot.value_column_parenthesized {
23040            self.write("(");
23041        }
23042        self.generate_identifier(&unpivot.value_column)?;
23043        // Output additional value columns if present
23044        for extra_col in &unpivot.extra_value_columns {
23045            self.write(", ");
23046            self.generate_identifier(extra_col)?;
23047        }
23048        if unpivot.value_column_parenthesized {
23049            self.write(")");
23050        }
23051        self.write_space();
23052        self.write_keyword("FOR");
23053        self.write_space();
23054        self.generate_identifier(&unpivot.name_column)?;
23055        self.write_space();
23056        self.write_keyword("IN");
23057        self.write(" (");
23058        for (i, col) in unpivot.columns.iter().enumerate() {
23059            if i > 0 {
23060                self.write(", ");
23061            }
23062            self.generate_expression(col)?;
23063        }
23064        self.write("))");
23065        if let Some(alias) = &unpivot.alias {
23066            self.write_space();
23067            self.write_keyword("AS");
23068            self.write_space();
23069            self.generate_identifier(alias)?;
23070        }
23071        Ok(())
23072    }
23073
23074    fn generate_values(&mut self, values: &Values) -> Result<()> {
23075        self.write_keyword("VALUES");
23076        for (i, row) in values.expressions.iter().enumerate() {
23077            if i > 0 {
23078                self.write(",");
23079            }
23080            self.write(" (");
23081            for (j, expr) in row.expressions.iter().enumerate() {
23082                if j > 0 {
23083                    self.write(", ");
23084                }
23085                self.generate_expression(expr)?;
23086            }
23087            self.write(")");
23088        }
23089        if let Some(alias) = &values.alias {
23090            self.write_space();
23091            self.write_keyword("AS");
23092            self.write_space();
23093            self.generate_identifier(alias)?;
23094            if !values.column_aliases.is_empty() {
23095                self.write("(");
23096                for (i, col) in values.column_aliases.iter().enumerate() {
23097                    if i > 0 {
23098                        self.write(", ");
23099                    }
23100                    self.generate_identifier(col)?;
23101                }
23102                self.write(")");
23103            }
23104        }
23105        Ok(())
23106    }
23107
23108    fn generate_array(&mut self, arr: &Array) -> Result<()> {
23109        // Apply struct name inheritance for target dialects that need it
23110        let needs_inheritance = matches!(
23111            self.config.dialect,
23112            Some(DialectType::DuckDB)
23113                | Some(DialectType::Spark)
23114                | Some(DialectType::Databricks)
23115                | Some(DialectType::Hive)
23116                | Some(DialectType::Snowflake)
23117                | Some(DialectType::Presto)
23118                | Some(DialectType::Trino)
23119        );
23120        let propagated: Vec<Expression>;
23121        let expressions = if needs_inheritance && arr.expressions.len() > 1 {
23122            propagated = Self::inherit_struct_field_names(&arr.expressions);
23123            &propagated
23124        } else {
23125            &arr.expressions
23126        };
23127
23128        // Generic mode: ARRAY(1, 2, 3) with parentheses
23129        // Dialect mode: ARRAY[1, 2, 3] with brackets (or just [1, 2, 3] if array_bracket_only)
23130        let use_parens =
23131            self.config.dialect.is_none() || self.config.dialect == Some(DialectType::Generic);
23132        if !self.config.array_bracket_only {
23133            self.write_keyword("ARRAY");
23134        }
23135        if use_parens {
23136            self.write("(");
23137        } else {
23138            self.write("[");
23139        }
23140        for (i, expr) in expressions.iter().enumerate() {
23141            if i > 0 {
23142                self.write(", ");
23143            }
23144            self.generate_expression(expr)?;
23145        }
23146        if use_parens {
23147            self.write(")");
23148        } else {
23149            self.write("]");
23150        }
23151        Ok(())
23152    }
23153
23154    fn generate_tuple(&mut self, tuple: &Tuple) -> Result<()> {
23155        // Special case: Tuple(function/expr, TableAlias) pattern for table functions with typed aliases
23156        // Used for PostgreSQL functions like JSON_TO_RECORDSET: FUNC(args) AS alias(col1 type1, col2 type2)
23157        if tuple.expressions.len() == 2 {
23158            if let Expression::TableAlias(_) = &tuple.expressions[1] {
23159                // First element is the function/expression, second is the TableAlias
23160                self.generate_expression(&tuple.expressions[0])?;
23161                self.write_space();
23162                self.write_keyword("AS");
23163                self.write_space();
23164                self.generate_expression(&tuple.expressions[1])?;
23165                return Ok(());
23166            }
23167        }
23168
23169        // In pretty mode, format long tuples with each element on a new line
23170        // Only expand if total width exceeds threshold
23171        let expand_tuple = if self.config.pretty && tuple.expressions.len() > 1 {
23172            let mut expr_strings: Vec<String> = Vec::with_capacity(tuple.expressions.len());
23173            for expr in &tuple.expressions {
23174                expr_strings.push(self.generate_to_string(expr)?);
23175            }
23176            self.too_wide(&expr_strings)
23177        } else {
23178            false
23179        };
23180
23181        if expand_tuple {
23182            self.write("(");
23183            self.write_newline();
23184            self.indent_level += 1;
23185            for (i, expr) in tuple.expressions.iter().enumerate() {
23186                if i > 0 {
23187                    self.write(",");
23188                    self.write_newline();
23189                }
23190                self.write_indent();
23191                self.generate_expression(expr)?;
23192            }
23193            self.indent_level -= 1;
23194            self.write_newline();
23195            self.write_indent();
23196            self.write(")");
23197        } else {
23198            self.write("(");
23199            for (i, expr) in tuple.expressions.iter().enumerate() {
23200                if i > 0 {
23201                    self.write(", ");
23202                }
23203                self.generate_expression(expr)?;
23204            }
23205            self.write(")");
23206        }
23207        Ok(())
23208    }
23209
23210    fn generate_pipe_operator(&mut self, pipe: &PipeOperator) -> Result<()> {
23211        self.generate_expression(&pipe.this)?;
23212        self.write(" |> ");
23213        self.generate_expression(&pipe.expression)?;
23214        Ok(())
23215    }
23216
23217    fn generate_ordered(&mut self, ordered: &Ordered) -> Result<()> {
23218        let unsupported_tsql_null_ordering = ordered.nulls_first.is_some()
23219            && !self.config.null_ordering_supported
23220            && matches!(
23221                self.config.dialect,
23222                Some(DialectType::TSQL) | Some(DialectType::Fabric)
23223            );
23224        let random_ordering = matches!(ordered.this, Expression::Rand(_) | Expression::Random(_));
23225        let emulate_tsql_null_ordering = if let Some(nulls_first) = ordered.nulls_first {
23226            let target_default_nulls_first = !ordered.desc;
23227
23228            unsupported_tsql_null_ordering
23229                && nulls_first != target_default_nulls_first
23230                && !random_ordering
23231        } else {
23232            false
23233        };
23234
23235        if emulate_tsql_null_ordering {
23236            self.write_keyword("CASE WHEN");
23237            self.write_space();
23238            self.generate_expression(&ordered.this)?;
23239            self.write_space();
23240            self.write_keyword("IS NULL THEN 1 ELSE 0 END");
23241            if ordered.nulls_first == Some(true) {
23242                self.write_space();
23243                self.write_keyword("DESC");
23244            }
23245            self.write(", ");
23246        }
23247
23248        self.generate_expression(&ordered.this)?;
23249        if ordered.desc {
23250            self.write_space();
23251            self.write_keyword("DESC");
23252        } else if ordered.explicit_asc {
23253            self.write_space();
23254            self.write_keyword("ASC");
23255        }
23256        if let Some(nulls_first) = ordered.nulls_first {
23257            if !unsupported_tsql_null_ordering
23258                && (self.config.null_ordering_supported
23259                    || !matches!(self.config.dialect, Some(DialectType::Fabric)))
23260            {
23261                // Determine if we should skip outputting NULLS FIRST/LAST when it's the default
23262                // for the dialect. Different dialects have different NULL ordering defaults:
23263                //
23264                // nulls_are_large (Oracle, Postgres, Snowflake, etc.):
23265                //   - ASC: NULLS LAST is default (omit NULLS LAST for ASC)
23266                //   - DESC: NULLS FIRST is default (omit NULLS FIRST for DESC)
23267                //
23268                // nulls_are_small (Spark, Hive, BigQuery, most others):
23269                //   - ASC: NULLS FIRST is default
23270                //   - DESC: NULLS LAST is default
23271                //
23272                // nulls_are_last (DuckDB, Presto, Trino, Dremio, etc.):
23273                //   - NULLS LAST is always the default regardless of sort direction
23274                let is_asc = !ordered.desc;
23275                let is_nulls_are_large = matches!(
23276                    self.config.dialect,
23277                    Some(DialectType::Oracle)
23278                        | Some(DialectType::PostgreSQL)
23279                        | Some(DialectType::Redshift)
23280                        | Some(DialectType::Snowflake)
23281                );
23282                let is_nulls_are_last = matches!(
23283                    self.config.dialect,
23284                    Some(DialectType::Dremio)
23285                        | Some(DialectType::DuckDB)
23286                        | Some(DialectType::Presto)
23287                        | Some(DialectType::Trino)
23288                        | Some(DialectType::Athena)
23289                        | Some(DialectType::ClickHouse)
23290                        | Some(DialectType::Drill)
23291                        | Some(DialectType::Exasol)
23292                );
23293
23294                // Check if the NULLS ordering matches the default for this dialect
23295                let is_default_nulls = if is_nulls_are_large {
23296                    // For nulls_are_large: ASC + NULLS LAST or DESC + NULLS FIRST is default
23297                    (is_asc && !nulls_first) || (!is_asc && nulls_first)
23298                } else if is_nulls_are_last {
23299                    // For nulls_are_last: NULLS LAST is always default
23300                    !nulls_first
23301                } else {
23302                    false
23303                };
23304
23305                if !is_default_nulls {
23306                    self.write_space();
23307                    self.write_keyword("NULLS");
23308                    self.write_space();
23309                    self.write_keyword(if nulls_first { "FIRST" } else { "LAST" });
23310                }
23311            }
23312        }
23313        // WITH FILL clause (ClickHouse)
23314        if let Some(ref with_fill) = ordered.with_fill {
23315            self.write_space();
23316            self.generate_with_fill(with_fill)?;
23317        }
23318        Ok(())
23319    }
23320
23321    /// Write a ClickHouse type string, wrapping in Nullable unless in map key context.
23322    fn write_clickhouse_type(&mut self, type_str: &str) {
23323        if self.clickhouse_nullable_depth < 0 {
23324            // Map key context: don't wrap in Nullable
23325            self.write(type_str);
23326        } else {
23327            self.write(&format!("Nullable({})", type_str));
23328        }
23329    }
23330
23331    fn generate_data_type(&mut self, dt: &DataType) -> Result<()> {
23332        use crate::dialects::DialectType;
23333
23334        match dt {
23335            DataType::Boolean => {
23336                // Dialect-specific boolean type mappings
23337                match self.config.dialect {
23338                    Some(DialectType::TSQL) => self.write_keyword("BIT"),
23339                    Some(DialectType::MySQL) => self.write_keyword("BOOLEAN"), // alias for TINYINT(1)
23340                    Some(DialectType::Oracle) => {
23341                        // Oracle 23c+ supports BOOLEAN, older versions use NUMBER(1)
23342                        self.write_keyword("NUMBER(1)")
23343                    }
23344                    Some(DialectType::ClickHouse) => self.write("Bool"), // ClickHouse uses Bool (case-sensitive)
23345                    _ => self.write_keyword("BOOLEAN"),
23346                }
23347            }
23348            DataType::TinyInt { length } => {
23349                // PostgreSQL, Oracle, and Exasol don't have TINYINT, use SMALLINT
23350                // Dremio maps TINYINT to INT
23351                // ClickHouse maps TINYINT to Int8
23352                match self.config.dialect {
23353                    Some(DialectType::PostgreSQL)
23354                    | Some(DialectType::Redshift)
23355                    | Some(DialectType::Oracle)
23356                    | Some(DialectType::Exasol) => {
23357                        self.write_keyword("SMALLINT");
23358                    }
23359                    Some(DialectType::Teradata) => {
23360                        // Teradata uses BYTEINT for smallest integer
23361                        self.write_keyword("BYTEINT");
23362                    }
23363                    Some(DialectType::Dremio) => {
23364                        // Dremio maps TINYINT to INT
23365                        self.write_keyword("INT");
23366                    }
23367                    Some(DialectType::ClickHouse) => {
23368                        self.write_clickhouse_type("Int8");
23369                    }
23370                    _ => {
23371                        self.write_keyword("TINYINT");
23372                    }
23373                }
23374                if let Some(n) = length {
23375                    if !matches!(
23376                        self.config.dialect,
23377                        Some(DialectType::Dremio) | Some(DialectType::ClickHouse)
23378                    ) {
23379                        self.write(&format!("({})", n));
23380                    }
23381                }
23382            }
23383            DataType::SmallInt { length } => {
23384                // Dremio maps SMALLINT to INT, SQLite/Drill maps SMALLINT to INTEGER
23385                match self.config.dialect {
23386                    Some(DialectType::Dremio) => {
23387                        self.write_keyword("INT");
23388                    }
23389                    Some(DialectType::SQLite) | Some(DialectType::Drill) => {
23390                        self.write_keyword("INTEGER");
23391                    }
23392                    Some(DialectType::BigQuery) => {
23393                        self.write_keyword("INT64");
23394                    }
23395                    Some(DialectType::ClickHouse) => {
23396                        self.write_clickhouse_type("Int16");
23397                    }
23398                    _ => {
23399                        self.write_keyword("SMALLINT");
23400                        if let Some(n) = length {
23401                            self.write(&format!("({})", n));
23402                        }
23403                    }
23404                }
23405            }
23406            DataType::Int {
23407                length,
23408                integer_spelling: _,
23409            } => {
23410                // BigQuery uses INT64 for INT
23411                if matches!(self.config.dialect, Some(DialectType::BigQuery)) {
23412                    self.write_keyword("INT64");
23413                } else if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
23414                    self.write_clickhouse_type("Int32");
23415                } else {
23416                    // TSQL, Presto, Trino, SQLite, Redshift use INTEGER as the canonical form
23417                    let use_integer = match self.config.dialect {
23418                        Some(DialectType::TSQL)
23419                        | Some(DialectType::Fabric)
23420                        | Some(DialectType::Presto)
23421                        | Some(DialectType::Trino)
23422                        | Some(DialectType::SQLite)
23423                        | Some(DialectType::Redshift) => true,
23424                        _ => false,
23425                    };
23426                    if use_integer {
23427                        self.write_keyword("INTEGER");
23428                    } else {
23429                        self.write_keyword("INT");
23430                    }
23431                    if let Some(n) = length {
23432                        self.write(&format!("({})", n));
23433                    }
23434                }
23435            }
23436            DataType::BigInt { length } => {
23437                // Dialect-specific bigint type mappings
23438                match self.config.dialect {
23439                    Some(DialectType::Oracle) => {
23440                        // Oracle doesn't have BIGINT, uses INT
23441                        self.write_keyword("INT");
23442                    }
23443                    Some(DialectType::ClickHouse) => {
23444                        self.write_clickhouse_type("Int64");
23445                    }
23446                    _ => {
23447                        self.write_keyword("BIGINT");
23448                        if let Some(n) = length {
23449                            self.write(&format!("({})", n));
23450                        }
23451                    }
23452                }
23453            }
23454            DataType::Float {
23455                precision,
23456                scale,
23457                real_spelling,
23458            } => {
23459                // Dialect-specific float type mappings
23460                // If real_spelling is true, preserve REAL; otherwise use dialect default
23461                // Spark/Hive don't support REAL, always use FLOAT
23462                if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
23463                    self.write_clickhouse_type("Float32");
23464                } else if *real_spelling
23465                    && !matches!(
23466                        self.config.dialect,
23467                        Some(DialectType::Spark)
23468                            | Some(DialectType::Databricks)
23469                            | Some(DialectType::Hive)
23470                            | Some(DialectType::Snowflake)
23471                            | Some(DialectType::MySQL)
23472                            | Some(DialectType::BigQuery)
23473                    )
23474                {
23475                    self.write_keyword("REAL")
23476                } else {
23477                    match self.config.dialect {
23478                        Some(DialectType::PostgreSQL) => self.write_keyword("REAL"),
23479                        Some(DialectType::BigQuery) => self.write_keyword("FLOAT64"),
23480                        _ => self.write_keyword("FLOAT"),
23481                    }
23482                }
23483                // MySQL supports FLOAT(precision) or FLOAT(precision, scale)
23484                // Spark/Hive don't support FLOAT(precision)
23485                if !matches!(
23486                    self.config.dialect,
23487                    Some(DialectType::Spark)
23488                        | Some(DialectType::Databricks)
23489                        | Some(DialectType::Hive)
23490                        | Some(DialectType::Presto)
23491                        | Some(DialectType::Trino)
23492                ) {
23493                    if let Some(p) = precision {
23494                        self.write(&format!("({}", p));
23495                        if let Some(s) = scale {
23496                            self.write(&format!(", {})", s));
23497                        } else {
23498                            self.write(")");
23499                        }
23500                    }
23501                }
23502            }
23503            DataType::Double { precision, scale } => {
23504                // Dialect-specific double type mappings
23505                match self.config.dialect {
23506                    Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
23507                        self.write_keyword("FLOAT")
23508                    } // SQL Server/Fabric FLOAT is double
23509                    Some(DialectType::Oracle) => self.write_keyword("DOUBLE PRECISION"),
23510                    Some(DialectType::ClickHouse) => self.write_clickhouse_type("Float64"),
23511                    Some(DialectType::BigQuery) => self.write_keyword("FLOAT64"),
23512                    Some(DialectType::SQLite) => self.write_keyword("REAL"),
23513                    Some(DialectType::PostgreSQL)
23514                    | Some(DialectType::Redshift)
23515                    | Some(DialectType::Teradata)
23516                    | Some(DialectType::Materialize) => self.write_keyword("DOUBLE PRECISION"),
23517                    _ => self.write_keyword("DOUBLE"),
23518                }
23519                // MySQL supports DOUBLE(precision, scale)
23520                if let Some(p) = precision {
23521                    self.write(&format!("({}", p));
23522                    if let Some(s) = scale {
23523                        self.write(&format!(", {})", s));
23524                    } else {
23525                        self.write(")");
23526                    }
23527                }
23528            }
23529            DataType::Decimal { precision, scale } => {
23530                // Dialect-specific decimal type mappings
23531                match self.config.dialect {
23532                    Some(DialectType::ClickHouse) => {
23533                        self.write("Decimal");
23534                        if let Some(p) = precision {
23535                            self.write(&format!("({}", p));
23536                            if let Some(s) = scale {
23537                                self.write(&format!(", {}", s));
23538                            }
23539                            self.write(")");
23540                        }
23541                    }
23542                    Some(DialectType::Oracle) => {
23543                        // Oracle uses NUMBER instead of DECIMAL
23544                        self.write_keyword("NUMBER");
23545                        if let Some(p) = precision {
23546                            self.write(&format!("({}", p));
23547                            if let Some(s) = scale {
23548                                self.write(&format!(", {}", s));
23549                            }
23550                            self.write(")");
23551                        }
23552                    }
23553                    Some(DialectType::BigQuery) => {
23554                        // BigQuery uses NUMERIC instead of DECIMAL
23555                        self.write_keyword("NUMERIC");
23556                        if let Some(p) = precision {
23557                            self.write(&format!("({}", p));
23558                            if let Some(s) = scale {
23559                                self.write(&format!(", {}", s));
23560                            }
23561                            self.write(")");
23562                        }
23563                    }
23564                    _ => {
23565                        self.write_keyword("DECIMAL");
23566                        if let Some(p) = precision {
23567                            self.write(&format!("({}", p));
23568                            if let Some(s) = scale {
23569                                self.write(&format!(", {}", s));
23570                            }
23571                            self.write(")");
23572                        }
23573                    }
23574                }
23575            }
23576            DataType::Char { length } => {
23577                // Dialect-specific char type mappings
23578                match self.config.dialect {
23579                    Some(DialectType::DuckDB) | Some(DialectType::SQLite) => {
23580                        // DuckDB/SQLite maps CHAR to TEXT
23581                        self.write_keyword("TEXT");
23582                    }
23583                    Some(DialectType::Hive)
23584                    | Some(DialectType::Spark)
23585                    | Some(DialectType::Databricks) => {
23586                        // Hive/Spark/Databricks maps CHAR to STRING (when no length)
23587                        // CHAR(n) with explicit length is kept as CHAR(n) for Spark/Databricks
23588                        if length.is_some()
23589                            && !matches!(self.config.dialect, Some(DialectType::Hive))
23590                        {
23591                            self.write_keyword("CHAR");
23592                            if let Some(n) = length {
23593                                self.write(&format!("({})", n));
23594                            }
23595                        } else {
23596                            self.write_keyword("STRING");
23597                        }
23598                    }
23599                    Some(DialectType::Dremio) => {
23600                        // Dremio maps CHAR to VARCHAR
23601                        self.write_keyword("VARCHAR");
23602                        if let Some(n) = length {
23603                            self.write(&format!("({})", n));
23604                        }
23605                    }
23606                    _ => {
23607                        self.write_keyword("CHAR");
23608                        if let Some(n) = length {
23609                            self.write(&format!("({})", n));
23610                        }
23611                    }
23612                }
23613            }
23614            DataType::VarChar {
23615                length,
23616                parenthesized_length,
23617            } => {
23618                // Dialect-specific varchar type mappings
23619                match self.config.dialect {
23620                    Some(DialectType::Oracle) => {
23621                        self.write_keyword("VARCHAR2");
23622                        if let Some(n) = length {
23623                            self.write(&format!("({})", n));
23624                        }
23625                    }
23626                    Some(DialectType::DuckDB) => {
23627                        // DuckDB maps VARCHAR to TEXT, preserving length
23628                        self.write_keyword("TEXT");
23629                        if let Some(n) = length {
23630                            self.write(&format!("({})", n));
23631                        }
23632                    }
23633                    Some(DialectType::SQLite) => {
23634                        // SQLite maps VARCHAR to TEXT, preserving length
23635                        self.write_keyword("TEXT");
23636                        if let Some(n) = length {
23637                            self.write(&format!("({})", n));
23638                        }
23639                    }
23640                    Some(DialectType::MySQL) if length.is_none() => {
23641                        // MySQL requires VARCHAR to have a size - if it doesn't, use TEXT
23642                        self.write_keyword("TEXT");
23643                    }
23644                    Some(DialectType::Hive)
23645                    | Some(DialectType::Spark)
23646                    | Some(DialectType::Databricks)
23647                        if length.is_none() =>
23648                    {
23649                        // Hive/Spark/Databricks: VARCHAR without length → STRING
23650                        self.write_keyword("STRING");
23651                    }
23652                    _ => {
23653                        self.write_keyword("VARCHAR");
23654                        if let Some(n) = length {
23655                            // Hive uses VARCHAR((n)) with extra parentheses in STRUCT definitions
23656                            if *parenthesized_length {
23657                                self.write(&format!("(({}))", n));
23658                            } else {
23659                                self.write(&format!("({})", n));
23660                            }
23661                        }
23662                    }
23663                }
23664            }
23665            DataType::Text => {
23666                // Dialect-specific text type mappings
23667                match self.config.dialect {
23668                    Some(DialectType::Oracle) => self.write_keyword("CLOB"),
23669                    Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
23670                        self.write_keyword("VARCHAR(MAX)")
23671                    }
23672                    Some(DialectType::BigQuery) => self.write_keyword("STRING"),
23673                    Some(DialectType::Snowflake)
23674                    | Some(DialectType::Dremio)
23675                    | Some(DialectType::Drill) => self.write_keyword("VARCHAR"),
23676                    Some(DialectType::Exasol) => self.write_keyword("LONG VARCHAR"),
23677                    Some(DialectType::Presto)
23678                    | Some(DialectType::Trino)
23679                    | Some(DialectType::Athena) => self.write_keyword("VARCHAR"),
23680                    Some(DialectType::Spark)
23681                    | Some(DialectType::Databricks)
23682                    | Some(DialectType::Hive) => self.write_keyword("STRING"),
23683                    Some(DialectType::Redshift) => self.write_keyword("VARCHAR(MAX)"),
23684                    Some(DialectType::StarRocks) | Some(DialectType::Doris) => {
23685                        self.write_keyword("STRING")
23686                    }
23687                    Some(DialectType::ClickHouse) => self.write_clickhouse_type("String"),
23688                    _ => self.write_keyword("TEXT"),
23689                }
23690            }
23691            DataType::TextWithLength { length } => {
23692                // TEXT(n) - dialect-specific type with length
23693                match self.config.dialect {
23694                    Some(DialectType::Oracle) => self.write(&format!("CLOB({})", length)),
23695                    Some(DialectType::Hive)
23696                    | Some(DialectType::Spark)
23697                    | Some(DialectType::Databricks) => {
23698                        self.write(&format!("VARCHAR({})", length));
23699                    }
23700                    Some(DialectType::Redshift) => self.write(&format!("VARCHAR({})", length)),
23701                    Some(DialectType::BigQuery) => self.write(&format!("STRING({})", length)),
23702                    Some(DialectType::Snowflake)
23703                    | Some(DialectType::Presto)
23704                    | Some(DialectType::Trino)
23705                    | Some(DialectType::Athena)
23706                    | Some(DialectType::Drill)
23707                    | Some(DialectType::Dremio) => {
23708                        self.write(&format!("VARCHAR({})", length));
23709                    }
23710                    Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
23711                        self.write(&format!("VARCHAR({})", length))
23712                    }
23713                    Some(DialectType::StarRocks) | Some(DialectType::Doris) => {
23714                        self.write(&format!("STRING({})", length))
23715                    }
23716                    Some(DialectType::ClickHouse) => self.write_clickhouse_type("String"),
23717                    _ => self.write(&format!("TEXT({})", length)),
23718                }
23719            }
23720            DataType::String { length } => {
23721                // STRING type with optional length (BigQuery STRING(n))
23722                match self.config.dialect {
23723                    Some(DialectType::ClickHouse) => {
23724                        // ClickHouse uses String with specific casing
23725                        self.write("String");
23726                        if let Some(n) = length {
23727                            self.write(&format!("({})", n));
23728                        }
23729                    }
23730                    Some(DialectType::BigQuery)
23731                    | Some(DialectType::Hive)
23732                    | Some(DialectType::Spark)
23733                    | Some(DialectType::Databricks)
23734                    | Some(DialectType::StarRocks)
23735                    | Some(DialectType::Doris) => {
23736                        self.write_keyword("STRING");
23737                        if let Some(n) = length {
23738                            self.write(&format!("({})", n));
23739                        }
23740                    }
23741                    Some(DialectType::PostgreSQL) => {
23742                        // PostgreSQL doesn't have STRING - use VARCHAR or TEXT
23743                        if let Some(n) = length {
23744                            self.write_keyword("VARCHAR");
23745                            self.write(&format!("({})", n));
23746                        } else {
23747                            self.write_keyword("TEXT");
23748                        }
23749                    }
23750                    Some(DialectType::Redshift) => {
23751                        // Redshift: STRING -> VARCHAR(MAX)
23752                        if let Some(n) = length {
23753                            self.write_keyword("VARCHAR");
23754                            self.write(&format!("({})", n));
23755                        } else {
23756                            self.write_keyword("VARCHAR(MAX)");
23757                        }
23758                    }
23759                    Some(DialectType::MySQL) => {
23760                        // MySQL doesn't have STRING - use VARCHAR or TEXT
23761                        if let Some(n) = length {
23762                            self.write_keyword("VARCHAR");
23763                            self.write(&format!("({})", n));
23764                        } else {
23765                            self.write_keyword("TEXT");
23766                        }
23767                    }
23768                    Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
23769                        // TSQL: STRING -> VARCHAR(MAX)
23770                        if let Some(n) = length {
23771                            self.write_keyword("VARCHAR");
23772                            self.write(&format!("({})", n));
23773                        } else {
23774                            self.write_keyword("VARCHAR(MAX)");
23775                        }
23776                    }
23777                    Some(DialectType::Oracle) => {
23778                        // Oracle: STRING -> CLOB
23779                        self.write_keyword("CLOB");
23780                    }
23781                    Some(DialectType::DuckDB) | Some(DialectType::Materialize) => {
23782                        // DuckDB/Materialize uses TEXT for string types
23783                        self.write_keyword("TEXT");
23784                        if let Some(n) = length {
23785                            self.write(&format!("({})", n));
23786                        }
23787                    }
23788                    Some(DialectType::Presto)
23789                    | Some(DialectType::Trino)
23790                    | Some(DialectType::Drill)
23791                    | Some(DialectType::Dremio) => {
23792                        // Presto/Trino/Drill use VARCHAR for string types
23793                        self.write_keyword("VARCHAR");
23794                        if let Some(n) = length {
23795                            self.write(&format!("({})", n));
23796                        }
23797                    }
23798                    Some(DialectType::Snowflake) => {
23799                        // Snowflake: STRING stays as STRING (identity/DDL)
23800                        // CAST context STRING -> VARCHAR is handled in generate_cast
23801                        self.write_keyword("STRING");
23802                        if let Some(n) = length {
23803                            self.write(&format!("({})", n));
23804                        }
23805                    }
23806                    _ => {
23807                        // Default: output STRING with optional length
23808                        self.write_keyword("STRING");
23809                        if let Some(n) = length {
23810                            self.write(&format!("({})", n));
23811                        }
23812                    }
23813                }
23814            }
23815            DataType::Binary { length } => {
23816                // Dialect-specific binary type mappings
23817                match self.config.dialect {
23818                    Some(DialectType::PostgreSQL) | Some(DialectType::Materialize) => {
23819                        self.write_keyword("BYTEA");
23820                        if let Some(n) = length {
23821                            self.write(&format!("({})", n));
23822                        }
23823                    }
23824                    Some(DialectType::Redshift) => {
23825                        self.write_keyword("VARBYTE");
23826                        if let Some(n) = length {
23827                            self.write(&format!("({})", n));
23828                        }
23829                    }
23830                    Some(DialectType::DuckDB)
23831                    | Some(DialectType::SQLite)
23832                    | Some(DialectType::Oracle) => {
23833                        // DuckDB/SQLite/Oracle maps BINARY to BLOB
23834                        self.write_keyword("BLOB");
23835                        if let Some(n) = length {
23836                            self.write(&format!("({})", n));
23837                        }
23838                    }
23839                    Some(DialectType::Presto)
23840                    | Some(DialectType::Trino)
23841                    | Some(DialectType::Athena)
23842                    | Some(DialectType::Drill)
23843                    | Some(DialectType::Dremio) => {
23844                        // These dialects map BINARY to VARBINARY
23845                        self.write_keyword("VARBINARY");
23846                        if let Some(n) = length {
23847                            self.write(&format!("({})", n));
23848                        }
23849                    }
23850                    Some(DialectType::ClickHouse) => {
23851                        // ClickHouse: wrap BINARY in Nullable (unless map key context)
23852                        if self.clickhouse_nullable_depth < 0 {
23853                            self.write("BINARY");
23854                        } else {
23855                            self.write("Nullable(BINARY");
23856                        }
23857                        if let Some(n) = length {
23858                            self.write(&format!("({})", n));
23859                        }
23860                        if self.clickhouse_nullable_depth >= 0 {
23861                            self.write(")");
23862                        }
23863                    }
23864                    _ => {
23865                        self.write_keyword("BINARY");
23866                        if let Some(n) = length {
23867                            self.write(&format!("({})", n));
23868                        }
23869                    }
23870                }
23871            }
23872            DataType::VarBinary { length } => {
23873                // Dialect-specific varbinary type mappings
23874                match self.config.dialect {
23875                    Some(DialectType::PostgreSQL) | Some(DialectType::Materialize) => {
23876                        self.write_keyword("BYTEA");
23877                        if let Some(n) = length {
23878                            self.write(&format!("({})", n));
23879                        }
23880                    }
23881                    Some(DialectType::Redshift) => {
23882                        self.write_keyword("VARBYTE");
23883                        if let Some(n) = length {
23884                            self.write(&format!("({})", n));
23885                        }
23886                    }
23887                    Some(DialectType::DuckDB)
23888                    | Some(DialectType::SQLite)
23889                    | Some(DialectType::Oracle) => {
23890                        // DuckDB/SQLite/Oracle maps VARBINARY to BLOB
23891                        self.write_keyword("BLOB");
23892                        if let Some(n) = length {
23893                            self.write(&format!("({})", n));
23894                        }
23895                    }
23896                    Some(DialectType::Exasol) => {
23897                        // Exasol maps VARBINARY to VARCHAR
23898                        self.write_keyword("VARCHAR");
23899                    }
23900                    Some(DialectType::Spark)
23901                    | Some(DialectType::Hive)
23902                    | Some(DialectType::Databricks) => {
23903                        // Spark/Hive use BINARY instead of VARBINARY
23904                        self.write_keyword("BINARY");
23905                        if let Some(n) = length {
23906                            self.write(&format!("({})", n));
23907                        }
23908                    }
23909                    Some(DialectType::ClickHouse) => {
23910                        // ClickHouse maps VARBINARY to String (wrapped in Nullable unless map key)
23911                        self.write_clickhouse_type("String");
23912                    }
23913                    _ => {
23914                        self.write_keyword("VARBINARY");
23915                        if let Some(n) = length {
23916                            self.write(&format!("({})", n));
23917                        }
23918                    }
23919                }
23920            }
23921            DataType::Blob => {
23922                // Dialect-specific blob type mappings
23923                match self.config.dialect {
23924                    Some(DialectType::PostgreSQL) => self.write_keyword("BYTEA"),
23925                    Some(DialectType::Redshift) => self.write_keyword("VARBYTE"),
23926                    Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
23927                        self.write_keyword("VARBINARY")
23928                    }
23929                    Some(DialectType::BigQuery) => self.write_keyword("BYTES"),
23930                    Some(DialectType::Exasol) => self.write_keyword("VARCHAR"),
23931                    Some(DialectType::Presto)
23932                    | Some(DialectType::Trino)
23933                    | Some(DialectType::Athena) => self.write_keyword("VARBINARY"),
23934                    Some(DialectType::DuckDB) => {
23935                        // Python sqlglot: BLOB -> VARBINARY for DuckDB (base TYPE_MAPPING)
23936                        // DuckDB identity works via: BLOB -> transform VarBinary -> generator BLOB
23937                        self.write_keyword("VARBINARY");
23938                    }
23939                    Some(DialectType::Spark)
23940                    | Some(DialectType::Databricks)
23941                    | Some(DialectType::Hive) => self.write_keyword("BINARY"),
23942                    Some(DialectType::ClickHouse) => {
23943                        // BLOB maps to Nullable(String) in ClickHouse, even in column defs
23944                        // where we normally suppress Nullable wrapping (clickhouse_nullable_depth = -1).
23945                        // This matches Python sqlglot behavior.
23946                        self.write("Nullable(String)");
23947                    }
23948                    _ => self.write_keyword("BLOB"),
23949                }
23950            }
23951            DataType::Bit { length } => {
23952                // Dialect-specific bit type mappings
23953                match self.config.dialect {
23954                    Some(DialectType::Dremio)
23955                    | Some(DialectType::Spark)
23956                    | Some(DialectType::Databricks)
23957                    | Some(DialectType::Hive)
23958                    | Some(DialectType::Snowflake)
23959                    | Some(DialectType::BigQuery)
23960                    | Some(DialectType::Presto)
23961                    | Some(DialectType::Trino)
23962                    | Some(DialectType::ClickHouse)
23963                    | Some(DialectType::Redshift) => {
23964                        // These dialects don't support BIT type, use BOOLEAN
23965                        self.write_keyword("BOOLEAN");
23966                    }
23967                    _ => {
23968                        self.write_keyword("BIT");
23969                        if let Some(n) = length {
23970                            self.write(&format!("({})", n));
23971                        }
23972                    }
23973                }
23974            }
23975            DataType::VarBit { length } => {
23976                self.write_keyword("VARBIT");
23977                if let Some(n) = length {
23978                    self.write(&format!("({})", n));
23979                }
23980            }
23981            DataType::Date => self.write_keyword("DATE"),
23982            DataType::Time {
23983                precision,
23984                timezone,
23985            } => {
23986                if *timezone {
23987                    // Dialect-specific TIME WITH TIME ZONE output
23988                    match self.config.dialect {
23989                        Some(DialectType::DuckDB) => {
23990                            // DuckDB: TIMETZ (drops precision)
23991                            self.write_keyword("TIMETZ");
23992                        }
23993                        Some(DialectType::PostgreSQL) => {
23994                            // PostgreSQL: TIMETZ or TIMETZ(p)
23995                            self.write_keyword("TIMETZ");
23996                            if let Some(p) = precision {
23997                                self.write(&format!("({})", p));
23998                            }
23999                        }
24000                        _ => {
24001                            // Presto/Trino/Redshift/others: TIME(p) WITH TIME ZONE
24002                            self.write_keyword("TIME");
24003                            if let Some(p) = precision {
24004                                self.write(&format!("({})", p));
24005                            }
24006                            self.write_keyword(" WITH TIME ZONE");
24007                        }
24008                    }
24009                } else {
24010                    // Spark/Hive/Databricks: TIME -> TIMESTAMP (TIME not supported)
24011                    if matches!(
24012                        self.config.dialect,
24013                        Some(DialectType::Spark)
24014                            | Some(DialectType::Databricks)
24015                            | Some(DialectType::Hive)
24016                    ) {
24017                        self.write_keyword("TIMESTAMP");
24018                    } else {
24019                        self.write_keyword("TIME");
24020                        if let Some(p) = precision {
24021                            self.write(&format!("({})", p));
24022                        }
24023                    }
24024                }
24025            }
24026            DataType::Timestamp {
24027                precision,
24028                timezone,
24029            } => {
24030                // Dialect-specific timestamp type mappings
24031                match self.config.dialect {
24032                    Some(DialectType::Snowflake) if *timezone => {
24033                        self.write_keyword("TIMESTAMPTZ");
24034                        if let Some(p) = precision {
24035                            self.write(&format!("({})", p));
24036                        }
24037                    }
24038                    Some(DialectType::ClickHouse) => {
24039                        self.write("DateTime");
24040                        if let Some(p) = precision {
24041                            self.write(&format!("({})", p));
24042                        }
24043                    }
24044                    Some(DialectType::TSQL) => {
24045                        if *timezone {
24046                            self.write_keyword("DATETIMEOFFSET");
24047                        } else {
24048                            self.write_keyword("DATETIME2");
24049                        }
24050                        if let Some(p) = precision {
24051                            self.write(&format!("({})", p));
24052                        }
24053                    }
24054                    Some(DialectType::MySQL) => {
24055                        // MySQL: TIMESTAMP stays as TIMESTAMP in DDL; CAST mapping handled separately
24056                        self.write_keyword("TIMESTAMP");
24057                        if let Some(p) = precision {
24058                            self.write(&format!("({})", p));
24059                        }
24060                    }
24061                    Some(DialectType::Doris) | Some(DialectType::StarRocks) => {
24062                        // Doris/StarRocks: TIMESTAMP -> DATETIME
24063                        self.write_keyword("DATETIME");
24064                        if let Some(p) = precision {
24065                            self.write(&format!("({})", p));
24066                        }
24067                    }
24068                    Some(DialectType::BigQuery) => {
24069                        // BigQuery: TIMESTAMP is always UTC, DATETIME is timezone-naive
24070                        if *timezone {
24071                            self.write_keyword("TIMESTAMP");
24072                        } else {
24073                            self.write_keyword("DATETIME");
24074                        }
24075                    }
24076                    Some(DialectType::DuckDB) => {
24077                        // DuckDB: TIMESTAMPTZ shorthand
24078                        if *timezone {
24079                            self.write_keyword("TIMESTAMPTZ");
24080                        } else {
24081                            self.write_keyword("TIMESTAMP");
24082                            if let Some(p) = precision {
24083                                self.write(&format!("({})", p));
24084                            }
24085                        }
24086                    }
24087                    _ => {
24088                        if *timezone && !self.config.tz_to_with_time_zone {
24089                            // Use TIMESTAMPTZ shorthand when dialect doesn't prefer WITH TIME ZONE
24090                            self.write_keyword("TIMESTAMPTZ");
24091                            if let Some(p) = precision {
24092                                self.write(&format!("({})", p));
24093                            }
24094                        } else {
24095                            self.write_keyword("TIMESTAMP");
24096                            if let Some(p) = precision {
24097                                self.write(&format!("({})", p));
24098                            }
24099                            if *timezone {
24100                                self.write_space();
24101                                self.write_keyword("WITH TIME ZONE");
24102                            }
24103                        }
24104                    }
24105                }
24106            }
24107            DataType::Interval { unit, to } => {
24108                self.write_keyword("INTERVAL");
24109                if let Some(u) = unit {
24110                    self.write_space();
24111                    self.write_keyword(u);
24112                }
24113                // Handle range intervals like DAY TO HOUR
24114                if let Some(t) = to {
24115                    self.write_space();
24116                    self.write_keyword("TO");
24117                    self.write_space();
24118                    self.write_keyword(t);
24119                }
24120            }
24121            DataType::Json => {
24122                // Dialect-specific JSON type mappings
24123                match self.config.dialect {
24124                    Some(DialectType::Oracle) => self.write_keyword("JSON"), // Oracle 21c+
24125                    Some(DialectType::TSQL) => self.write_keyword("NVARCHAR(MAX)"), // No native JSON type
24126                    Some(DialectType::MySQL) => self.write_keyword("JSON"),
24127                    Some(DialectType::Snowflake) => self.write_keyword("VARIANT"),
24128                    _ => self.write_keyword("JSON"),
24129                }
24130            }
24131            DataType::JsonB => {
24132                // JSONB is PostgreSQL specific, but Doris also supports it
24133                match self.config.dialect {
24134                    Some(DialectType::PostgreSQL) => self.write_keyword("JSONB"),
24135                    Some(DialectType::Doris) => self.write_keyword("JSONB"),
24136                    Some(DialectType::Snowflake) => self.write_keyword("VARIANT"),
24137                    Some(DialectType::TSQL) => self.write_keyword("NVARCHAR(MAX)"),
24138                    Some(DialectType::DuckDB) => self.write_keyword("JSON"), // DuckDB maps JSONB to JSON
24139                    _ => self.write_keyword("JSON"), // Fall back to JSON for other dialects
24140                }
24141            }
24142            DataType::Uuid => {
24143                // Dialect-specific UUID type mappings
24144                match self.config.dialect {
24145                    Some(DialectType::TSQL) => self.write_keyword("UNIQUEIDENTIFIER"),
24146                    Some(DialectType::MySQL) => self.write_keyword("CHAR(36)"),
24147                    Some(DialectType::Oracle) => self.write_keyword("RAW(16)"),
24148                    Some(DialectType::BigQuery)
24149                    | Some(DialectType::Spark)
24150                    | Some(DialectType::Databricks) => self.write_keyword("STRING"),
24151                    _ => self.write_keyword("UUID"),
24152                }
24153            }
24154            DataType::Array {
24155                element_type,
24156                dimension,
24157            } => {
24158                // Dialect-specific array syntax
24159                match self.config.dialect {
24160                    Some(DialectType::PostgreSQL)
24161                    | Some(DialectType::Redshift)
24162                    | Some(DialectType::DuckDB) => {
24163                        // PostgreSQL uses TYPE[] or TYPE[N] syntax
24164                        self.generate_data_type(element_type)?;
24165                        if let Some(dim) = dimension {
24166                            self.write(&format!("[{}]", dim));
24167                        } else {
24168                            self.write("[]");
24169                        }
24170                    }
24171                    Some(DialectType::BigQuery) => {
24172                        self.write_keyword("ARRAY<");
24173                        self.generate_data_type(element_type)?;
24174                        self.write(">");
24175                    }
24176                    Some(DialectType::Snowflake)
24177                    | Some(DialectType::Presto)
24178                    | Some(DialectType::Trino)
24179                    | Some(DialectType::ClickHouse) => {
24180                        // These dialects use Array(TYPE) parentheses syntax
24181                        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
24182                            self.write("Array(");
24183                        } else {
24184                            self.write_keyword("ARRAY(");
24185                        }
24186                        self.generate_data_type(element_type)?;
24187                        self.write(")");
24188                    }
24189                    Some(DialectType::TSQL)
24190                    | Some(DialectType::MySQL)
24191                    | Some(DialectType::Oracle) => {
24192                        // These dialects don't have native array types
24193                        // Fall back to JSON or use native workarounds
24194                        match self.config.dialect {
24195                            Some(DialectType::MySQL) => self.write_keyword("JSON"),
24196                            Some(DialectType::TSQL) => self.write_keyword("NVARCHAR(MAX)"),
24197                            _ => self.write_keyword("JSON"),
24198                        }
24199                    }
24200                    _ => {
24201                        // Default: use angle bracket syntax (ARRAY<T>)
24202                        self.write_keyword("ARRAY<");
24203                        self.generate_data_type(element_type)?;
24204                        self.write(">");
24205                    }
24206                }
24207            }
24208            DataType::List { element_type } => {
24209                // Materialize: element_type LIST (postfix syntax)
24210                self.generate_data_type(element_type)?;
24211                self.write_keyword(" LIST");
24212            }
24213            DataType::Map {
24214                key_type,
24215                value_type,
24216            } => {
24217                // Use parentheses for Snowflake and RisingWave, bracket syntax for Materialize, angle brackets for others
24218                match self.config.dialect {
24219                    Some(DialectType::Materialize) => {
24220                        // Materialize: MAP[key_type => value_type]
24221                        self.write_keyword("MAP[");
24222                        self.generate_data_type(key_type)?;
24223                        self.write(" => ");
24224                        self.generate_data_type(value_type)?;
24225                        self.write("]");
24226                    }
24227                    Some(DialectType::Snowflake)
24228                    | Some(DialectType::RisingWave)
24229                    | Some(DialectType::DuckDB)
24230                    | Some(DialectType::Presto)
24231                    | Some(DialectType::Trino)
24232                    | Some(DialectType::Athena) => {
24233                        self.write_keyword("MAP(");
24234                        self.generate_data_type(key_type)?;
24235                        self.write(", ");
24236                        self.generate_data_type(value_type)?;
24237                        self.write(")");
24238                    }
24239                    Some(DialectType::ClickHouse) => {
24240                        // ClickHouse: Map(key_type, value_type) with parenthesized syntax
24241                        // Key types must NOT be wrapped in Nullable
24242                        self.write("Map(");
24243                        self.clickhouse_nullable_depth = -1; // suppress Nullable for key
24244                        self.generate_data_type(key_type)?;
24245                        self.clickhouse_nullable_depth = 0;
24246                        self.write(", ");
24247                        self.generate_data_type(value_type)?;
24248                        self.write(")");
24249                    }
24250                    _ => {
24251                        self.write_keyword("MAP<");
24252                        self.generate_data_type(key_type)?;
24253                        self.write(", ");
24254                        self.generate_data_type(value_type)?;
24255                        self.write(">");
24256                    }
24257                }
24258            }
24259            DataType::Vector {
24260                element_type,
24261                dimension,
24262            } => {
24263                if matches!(self.config.dialect, Some(DialectType::SingleStore)) {
24264                    // SingleStore format: VECTOR(dimension, type_alias)
24265                    self.write_keyword("VECTOR(");
24266                    if let Some(dim) = dimension {
24267                        self.write(&dim.to_string());
24268                    }
24269                    // Map type back to SingleStore alias
24270                    let type_alias = element_type.as_ref().and_then(|et| match et.as_ref() {
24271                        DataType::TinyInt { .. } => Some("I8"),
24272                        DataType::SmallInt { .. } => Some("I16"),
24273                        DataType::Int { .. } => Some("I32"),
24274                        DataType::BigInt { .. } => Some("I64"),
24275                        DataType::Float { .. } => Some("F32"),
24276                        DataType::Double { .. } => Some("F64"),
24277                        _ => None,
24278                    });
24279                    if let Some(alias) = type_alias {
24280                        if dimension.is_some() {
24281                            self.write(", ");
24282                        }
24283                        self.write(alias);
24284                    }
24285                    self.write(")");
24286                } else {
24287                    // Snowflake format: VECTOR(type, dimension)
24288                    self.write_keyword("VECTOR(");
24289                    if let Some(ref et) = element_type {
24290                        self.generate_data_type(et)?;
24291                        if dimension.is_some() {
24292                            self.write(", ");
24293                        }
24294                    }
24295                    if let Some(dim) = dimension {
24296                        self.write(&dim.to_string());
24297                    }
24298                    self.write(")");
24299                }
24300            }
24301            DataType::Object { fields, modifier } => {
24302                self.write_keyword("OBJECT(");
24303                for (i, (name, dt, not_null)) in fields.iter().enumerate() {
24304                    if i > 0 {
24305                        self.write(", ");
24306                    }
24307                    self.write(name);
24308                    self.write(" ");
24309                    self.generate_data_type(dt)?;
24310                    if *not_null {
24311                        self.write_keyword(" NOT NULL");
24312                    }
24313                }
24314                self.write(")");
24315                if let Some(mod_str) = modifier {
24316                    self.write(" ");
24317                    self.write_keyword(mod_str);
24318                }
24319            }
24320            DataType::Struct { fields, nested } => {
24321                // Dialect-specific struct type mappings
24322                match self.config.dialect {
24323                    Some(DialectType::Snowflake) => {
24324                        // Snowflake maps STRUCT to OBJECT
24325                        self.write_keyword("OBJECT(");
24326                        for (i, field) in fields.iter().enumerate() {
24327                            if i > 0 {
24328                                self.write(", ");
24329                            }
24330                            if !field.name.is_empty() {
24331                                self.write(&field.name);
24332                                self.write(" ");
24333                            }
24334                            self.generate_data_type(&field.data_type)?;
24335                        }
24336                        self.write(")");
24337                    }
24338                    Some(DialectType::Presto) | Some(DialectType::Trino) => {
24339                        // Presto/Trino use ROW(name TYPE, ...) syntax
24340                        self.write_keyword("ROW(");
24341                        for (i, field) in fields.iter().enumerate() {
24342                            if i > 0 {
24343                                self.write(", ");
24344                            }
24345                            if !field.name.is_empty() {
24346                                self.write(&field.name);
24347                                self.write(" ");
24348                            }
24349                            self.generate_data_type(&field.data_type)?;
24350                        }
24351                        self.write(")");
24352                    }
24353                    Some(DialectType::DuckDB) => {
24354                        // DuckDB uses parenthesized syntax: STRUCT(name TYPE, ...)
24355                        self.write_keyword("STRUCT(");
24356                        for (i, field) in fields.iter().enumerate() {
24357                            if i > 0 {
24358                                self.write(", ");
24359                            }
24360                            if !field.name.is_empty() {
24361                                self.write(&field.name);
24362                                self.write(" ");
24363                            }
24364                            self.generate_data_type(&field.data_type)?;
24365                        }
24366                        self.write(")");
24367                    }
24368                    Some(DialectType::ClickHouse) => {
24369                        // ClickHouse uses Tuple(name TYPE, ...) for struct types
24370                        self.write("Tuple(");
24371                        for (i, field) in fields.iter().enumerate() {
24372                            if i > 0 {
24373                                self.write(", ");
24374                            }
24375                            if !field.name.is_empty() {
24376                                self.write(&field.name);
24377                                self.write(" ");
24378                            }
24379                            self.generate_data_type(&field.data_type)?;
24380                        }
24381                        self.write(")");
24382                    }
24383                    Some(DialectType::SingleStore) => {
24384                        // SingleStore uses RECORD(name TYPE, ...) for struct types
24385                        self.write_keyword("RECORD(");
24386                        for (i, field) in fields.iter().enumerate() {
24387                            if i > 0 {
24388                                self.write(", ");
24389                            }
24390                            if !field.name.is_empty() {
24391                                self.write(&field.name);
24392                                self.write(" ");
24393                            }
24394                            self.generate_data_type(&field.data_type)?;
24395                        }
24396                        self.write(")");
24397                    }
24398                    _ => {
24399                        // Hive/Spark always use angle bracket syntax: STRUCT<name: TYPE>
24400                        let force_angle_brackets = matches!(
24401                            self.config.dialect,
24402                            Some(DialectType::Hive)
24403                                | Some(DialectType::Spark)
24404                                | Some(DialectType::Databricks)
24405                        );
24406                        if *nested && !force_angle_brackets {
24407                            self.write_keyword("STRUCT(");
24408                            for (i, field) in fields.iter().enumerate() {
24409                                if i > 0 {
24410                                    self.write(", ");
24411                                }
24412                                if !field.name.is_empty() {
24413                                    self.write(&field.name);
24414                                    self.write(" ");
24415                                }
24416                                self.generate_data_type(&field.data_type)?;
24417                            }
24418                            self.write(")");
24419                        } else {
24420                            self.write_keyword("STRUCT<");
24421                            for (i, field) in fields.iter().enumerate() {
24422                                if i > 0 {
24423                                    self.write(", ");
24424                                }
24425                                if !field.name.is_empty() {
24426                                    // Named field: name TYPE (with configurable separator for Hive)
24427                                    self.write(&field.name);
24428                                    self.write(self.config.struct_field_sep);
24429                                }
24430                                // For anonymous fields, just output the type
24431                                self.generate_data_type(&field.data_type)?;
24432                                // Spark/Databricks: Output COMMENT clause if present
24433                                if let Some(comment) = &field.comment {
24434                                    self.write(" COMMENT '");
24435                                    self.write(comment);
24436                                    self.write("'");
24437                                }
24438                                // BigQuery: Output OPTIONS clause if present
24439                                if !field.options.is_empty() {
24440                                    self.write(" ");
24441                                    self.generate_options_clause(&field.options)?;
24442                                }
24443                            }
24444                            self.write(">");
24445                        }
24446                    }
24447                }
24448            }
24449            DataType::Enum {
24450                values,
24451                assignments,
24452            } => {
24453                // DuckDB ENUM type: ENUM('RED', 'GREEN', 'BLUE')
24454                // ClickHouse: Enum('hello' = 1, 'world' = 2)
24455                if self.config.dialect == Some(DialectType::ClickHouse) {
24456                    self.write("Enum(");
24457                } else {
24458                    self.write_keyword("ENUM(");
24459                }
24460                for (i, val) in values.iter().enumerate() {
24461                    if i > 0 {
24462                        self.write(", ");
24463                    }
24464                    self.write("'");
24465                    self.write(val);
24466                    self.write("'");
24467                    if let Some(Some(assignment)) = assignments.get(i) {
24468                        self.write(" = ");
24469                        self.write(assignment);
24470                    }
24471                }
24472                self.write(")");
24473            }
24474            DataType::Set { values } => {
24475                // MySQL SET type: SET('a', 'b', 'c')
24476                self.write_keyword("SET(");
24477                for (i, val) in values.iter().enumerate() {
24478                    if i > 0 {
24479                        self.write(", ");
24480                    }
24481                    self.write("'");
24482                    self.write(val);
24483                    self.write("'");
24484                }
24485                self.write(")");
24486            }
24487            DataType::Union { fields } => {
24488                // DuckDB UNION type: UNION(num INT, str TEXT)
24489                self.write_keyword("UNION(");
24490                for (i, (name, dt)) in fields.iter().enumerate() {
24491                    if i > 0 {
24492                        self.write(", ");
24493                    }
24494                    if !name.is_empty() {
24495                        self.write(name);
24496                        self.write(" ");
24497                    }
24498                    self.generate_data_type(dt)?;
24499                }
24500                self.write(")");
24501            }
24502            DataType::Nullable { inner } => {
24503                // ClickHouse: Nullable(T), other dialects: just the inner type
24504                if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
24505                    self.write("Nullable(");
24506                    // Suppress inner Nullable wrapping to prevent Nullable(Nullable(...))
24507                    let saved_depth = self.clickhouse_nullable_depth;
24508                    self.clickhouse_nullable_depth = -1;
24509                    self.generate_data_type(inner)?;
24510                    self.clickhouse_nullable_depth = saved_depth;
24511                    self.write(")");
24512                } else {
24513                    // Map ClickHouse-specific custom type names to standard types
24514                    match inner.as_ref() {
24515                        DataType::Custom { name } if name.eq_ignore_ascii_case("DATETIME") => {
24516                            self.generate_data_type(&DataType::Timestamp {
24517                                precision: None,
24518                                timezone: false,
24519                            })?;
24520                        }
24521                        _ => {
24522                            self.generate_data_type(inner)?;
24523                        }
24524                    }
24525                }
24526            }
24527            DataType::Custom { name } => {
24528                // Handle dialect-specific type transformations
24529                let name_upper = name.to_ascii_uppercase();
24530                match self.config.dialect {
24531                    Some(DialectType::ClickHouse) => {
24532                        let (base_upper, suffix) = if let Some(idx) = name.find('(') {
24533                            (name_upper[..idx].to_string(), &name[idx..])
24534                        } else {
24535                            (name_upper.clone(), "")
24536                        };
24537                        let mapped = match base_upper.as_str() {
24538                            "DATETIME" | "TIMESTAMPTZ" | "TIMESTAMP" | "TIMESTAMPNTZ"
24539                            | "SMALLDATETIME" | "DATETIME2" => "DateTime",
24540                            "DATETIME64" => "DateTime64",
24541                            "DATE32" => "Date32",
24542                            "INT" => "Int32",
24543                            "MEDIUMINT" => "Int32",
24544                            "INT8" => "Int8",
24545                            "INT16" => "Int16",
24546                            "INT32" => "Int32",
24547                            "INT64" => "Int64",
24548                            "INT128" => "Int128",
24549                            "INT256" => "Int256",
24550                            "UINT8" => "UInt8",
24551                            "UINT16" => "UInt16",
24552                            "UINT32" => "UInt32",
24553                            "UINT64" => "UInt64",
24554                            "UINT128" => "UInt128",
24555                            "UINT256" => "UInt256",
24556                            "FLOAT32" => "Float32",
24557                            "FLOAT64" => "Float64",
24558                            "DECIMAL32" => "Decimal32",
24559                            "DECIMAL64" => "Decimal64",
24560                            "DECIMAL128" => "Decimal128",
24561                            "DECIMAL256" => "Decimal256",
24562                            "ENUM" => "Enum",
24563                            "ENUM8" => "Enum8",
24564                            "ENUM16" => "Enum16",
24565                            "FIXEDSTRING" => "FixedString",
24566                            "NESTED" => "Nested",
24567                            "LOWCARDINALITY" => "LowCardinality",
24568                            "NULLABLE" => "Nullable",
24569                            "IPV4" => "IPv4",
24570                            "IPV6" => "IPv6",
24571                            "POINT" => "Point",
24572                            "RING" => "Ring",
24573                            "LINESTRING" => "LineString",
24574                            "MULTILINESTRING" => "MultiLineString",
24575                            "POLYGON" => "Polygon",
24576                            "MULTIPOLYGON" => "MultiPolygon",
24577                            "AGGREGATEFUNCTION" => "AggregateFunction",
24578                            "SIMPLEAGGREGATEFUNCTION" => "SimpleAggregateFunction",
24579                            "DYNAMIC" => "Dynamic",
24580                            _ => "",
24581                        };
24582                        if mapped.is_empty() {
24583                            self.write(name);
24584                        } else {
24585                            self.write(mapped);
24586                            if matches!(base_upper.as_str(), "ENUM8" | "ENUM16")
24587                                && !suffix.is_empty()
24588                            {
24589                                let escaped_suffix = suffix
24590                                    .replace('\\', "\\\\")
24591                                    .replace('\t', "\\t")
24592                                    .replace('\n', "\\n")
24593                                    .replace('\r', "\\r");
24594                                self.write(&escaped_suffix);
24595                            } else {
24596                                self.write(suffix);
24597                            }
24598                        }
24599                    }
24600                    Some(DialectType::MySQL)
24601                        if name_upper == "TIMESTAMPTZ" || name_upper == "TIMESTAMPLTZ" =>
24602                    {
24603                        // MySQL doesn't support TIMESTAMPTZ/TIMESTAMPLTZ, use TIMESTAMP
24604                        self.write_keyword("TIMESTAMP");
24605                    }
24606                    Some(DialectType::Snowflake) => {
24607                        let (base_upper, suffix) = if let Some(idx) = name.find('(') {
24608                            (name_upper[..idx].to_string(), &name[idx..])
24609                        } else {
24610                            (name_upper.clone(), "")
24611                        };
24612
24613                        match base_upper.as_str() {
24614                            "TIMESTAMPNTZ" | "TIMESTAMP_NTZ" => {
24615                                self.write_keyword("TIMESTAMPNTZ");
24616                                self.write(suffix);
24617                            }
24618                            "TIMESTAMPLTZ" | "TIMESTAMP_LTZ" => {
24619                                self.write_keyword("TIMESTAMPLTZ");
24620                                self.write(suffix);
24621                            }
24622                            "TIMESTAMPTZ" | "TIMESTAMP_TZ" => {
24623                                self.write_keyword("TIMESTAMPTZ");
24624                                self.write(suffix);
24625                            }
24626                            _ => self.write(name),
24627                        }
24628                    }
24629                    Some(DialectType::Fabric) => {
24630                        let (base_upper, args_str) = if let Some(idx) = name.find('(') {
24631                            (name_upper[..idx].to_string(), Some(&name[idx..]))
24632                        } else {
24633                            (name_upper.clone(), None)
24634                        };
24635
24636                        match base_upper.as_str() {
24637                            "NVARCHAR" => {
24638                                self.write_keyword("VARCHAR");
24639                                if let Some(args) = args_str {
24640                                    self.write(args);
24641                                }
24642                            }
24643                            "NCHAR" => {
24644                                self.write_keyword("CHAR");
24645                                if let Some(args) = args_str {
24646                                    self.write(args);
24647                                }
24648                            }
24649                            _ => self.write(name),
24650                        }
24651                    }
24652                    Some(DialectType::TSQL) if name_upper == "VARIANT" => {
24653                        self.write_keyword("SQL_VARIANT");
24654                    }
24655                    Some(DialectType::DuckDB) if name_upper == "DECFLOAT" => {
24656                        self.write_keyword("DECIMAL(38, 5)");
24657                    }
24658                    Some(DialectType::Exasol) => {
24659                        // Exasol type mappings for custom types
24660                        match name_upper.as_str() {
24661                            // Binary types → VARCHAR
24662                            "LONGBLOB" | "MEDIUMBLOB" | "TINYBLOB" => self.write_keyword("VARCHAR"),
24663                            // Text types → VARCHAR (TEXT → LONG VARCHAR is handled by DataType::Text)
24664                            "LONGTEXT" | "MEDIUMTEXT" | "TINYTEXT" => self.write_keyword("VARCHAR"),
24665                            // Integer types
24666                            "MEDIUMINT" => self.write_keyword("INT"),
24667                            // Decimal types → DECIMAL
24668                            "DECIMAL32" | "DECIMAL64" | "DECIMAL128" | "DECIMAL256" => {
24669                                self.write_keyword("DECIMAL")
24670                            }
24671                            // Timestamp types
24672                            "DATETIME" => self.write_keyword("TIMESTAMP"),
24673                            "TIMESTAMPLTZ" => self.write_keyword("TIMESTAMP WITH LOCAL TIME ZONE"),
24674                            _ => self.write(name),
24675                        }
24676                    }
24677                    Some(DialectType::Dremio) => {
24678                        // Dremio type mappings for custom types
24679                        match name_upper.as_str() {
24680                            "TIMESTAMPNTZ" | "DATETIME" => self.write_keyword("TIMESTAMP"),
24681                            "ARRAY" => self.write_keyword("LIST"),
24682                            "NCHAR" => self.write_keyword("VARCHAR"),
24683                            _ => self.write(name),
24684                        }
24685                    }
24686                    // Map dialect-specific custom types to standard SQL types for other dialects
24687                    _ => {
24688                        // Extract base name and args for types with parenthesized args (e.g., DATETIME2(3))
24689                        let (base_upper, _args_str) = if let Some(idx) = name_upper.find('(') {
24690                            (name_upper[..idx].to_string(), Some(&name[idx..]))
24691                        } else {
24692                            (name_upper.clone(), None)
24693                        };
24694
24695                        match base_upper.as_str() {
24696                            "INT64"
24697                                if !matches!(self.config.dialect, Some(DialectType::BigQuery)) =>
24698                            {
24699                                self.write_keyword("BIGINT");
24700                            }
24701                            "FLOAT64"
24702                                if !matches!(self.config.dialect, Some(DialectType::BigQuery)) =>
24703                            {
24704                                self.write_keyword("DOUBLE");
24705                            }
24706                            "BOOL"
24707                                if !matches!(self.config.dialect, Some(DialectType::BigQuery)) =>
24708                            {
24709                                self.write_keyword("BOOLEAN");
24710                            }
24711                            "BYTES"
24712                                if matches!(
24713                                    self.config.dialect,
24714                                    Some(DialectType::Spark)
24715                                        | Some(DialectType::Hive)
24716                                        | Some(DialectType::Databricks)
24717                                ) =>
24718                            {
24719                                self.write_keyword("BINARY");
24720                            }
24721                            "BYTES"
24722                                if !matches!(self.config.dialect, Some(DialectType::BigQuery)) =>
24723                            {
24724                                self.write_keyword("VARBINARY");
24725                            }
24726                            // TSQL DATETIME2/SMALLDATETIME -> TIMESTAMP
24727                            "DATETIME2" | "SMALLDATETIME"
24728                                if !matches!(
24729                                    self.config.dialect,
24730                                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
24731                                ) =>
24732                            {
24733                                // PostgreSQL preserves precision, others don't
24734                                if matches!(
24735                                    self.config.dialect,
24736                                    Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
24737                                ) {
24738                                    self.write_keyword("TIMESTAMP");
24739                                    if let Some(args) = _args_str {
24740                                        self.write(args);
24741                                    }
24742                                } else {
24743                                    self.write_keyword("TIMESTAMP");
24744                                }
24745                            }
24746                            // TSQL DATETIMEOFFSET -> TIMESTAMPTZ
24747                            "DATETIMEOFFSET"
24748                                if !matches!(
24749                                    self.config.dialect,
24750                                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
24751                                ) =>
24752                            {
24753                                if matches!(
24754                                    self.config.dialect,
24755                                    Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
24756                                ) {
24757                                    self.write_keyword("TIMESTAMPTZ");
24758                                    if let Some(args) = _args_str {
24759                                        self.write(args);
24760                                    }
24761                                } else {
24762                                    self.write_keyword("TIMESTAMPTZ");
24763                                }
24764                            }
24765                            // TSQL UNIQUEIDENTIFIER -> UUID or STRING
24766                            "UNIQUEIDENTIFIER"
24767                                if !matches!(
24768                                    self.config.dialect,
24769                                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
24770                                ) =>
24771                            {
24772                                match self.config.dialect {
24773                                    Some(DialectType::Spark)
24774                                    | Some(DialectType::Databricks)
24775                                    | Some(DialectType::Hive) => self.write_keyword("STRING"),
24776                                    _ => self.write_keyword("UUID"),
24777                                }
24778                            }
24779                            // TSQL BIT -> BOOLEAN for most dialects
24780                            "BIT"
24781                                if !matches!(
24782                                    self.config.dialect,
24783                                    Some(DialectType::TSQL)
24784                                        | Some(DialectType::Fabric)
24785                                        | Some(DialectType::PostgreSQL)
24786                                        | Some(DialectType::MySQL)
24787                                        | Some(DialectType::DuckDB)
24788                                ) =>
24789                            {
24790                                self.write_keyword("BOOLEAN");
24791                            }
24792                            // TSQL NVARCHAR -> VARCHAR (with default size 30 for some dialects)
24793                            "NVARCHAR"
24794                                if !matches!(
24795                                    self.config.dialect,
24796                                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
24797                                ) =>
24798                            {
24799                                match self.config.dialect {
24800                                    Some(DialectType::Oracle) => {
24801                                        // Oracle: NVARCHAR -> NVARCHAR2
24802                                        self.write_keyword("NVARCHAR2");
24803                                        if let Some(args) = _args_str {
24804                                            self.write(args);
24805                                        }
24806                                    }
24807                                    Some(DialectType::BigQuery) => {
24808                                        // BigQuery: NVARCHAR -> STRING
24809                                        self.write_keyword("STRING");
24810                                    }
24811                                    Some(DialectType::SQLite) | Some(DialectType::DuckDB) => {
24812                                        self.write_keyword("TEXT");
24813                                        if let Some(args) = _args_str {
24814                                            self.write(args);
24815                                        }
24816                                    }
24817                                    Some(DialectType::Hive) => {
24818                                        // Hive: NVARCHAR -> STRING
24819                                        self.write_keyword("STRING");
24820                                    }
24821                                    Some(DialectType::Spark) | Some(DialectType::Databricks) => {
24822                                        if _args_str.is_some() {
24823                                            self.write_keyword("VARCHAR");
24824                                            self.write(_args_str.unwrap());
24825                                        } else {
24826                                            self.write_keyword("STRING");
24827                                        }
24828                                    }
24829                                    _ => {
24830                                        self.write_keyword("VARCHAR");
24831                                        if let Some(args) = _args_str {
24832                                            self.write(args);
24833                                        }
24834                                    }
24835                                }
24836                            }
24837                            // NCHAR -> CHAR (NCHAR for Oracle/TSQL, STRING for BigQuery/Hive)
24838                            "NCHAR"
24839                                if !matches!(
24840                                    self.config.dialect,
24841                                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
24842                                ) =>
24843                            {
24844                                match self.config.dialect {
24845                                    Some(DialectType::Oracle) => {
24846                                        // Oracle natively supports NCHAR
24847                                        self.write_keyword("NCHAR");
24848                                        if let Some(args) = _args_str {
24849                                            self.write(args);
24850                                        }
24851                                    }
24852                                    Some(DialectType::BigQuery) => {
24853                                        // BigQuery: NCHAR -> STRING
24854                                        self.write_keyword("STRING");
24855                                    }
24856                                    Some(DialectType::Hive) => {
24857                                        // Hive: NCHAR -> STRING
24858                                        self.write_keyword("STRING");
24859                                    }
24860                                    Some(DialectType::SQLite) | Some(DialectType::DuckDB) => {
24861                                        self.write_keyword("TEXT");
24862                                        if let Some(args) = _args_str {
24863                                            self.write(args);
24864                                        }
24865                                    }
24866                                    Some(DialectType::Spark) | Some(DialectType::Databricks) => {
24867                                        if _args_str.is_some() {
24868                                            self.write_keyword("CHAR");
24869                                            self.write(_args_str.unwrap());
24870                                        } else {
24871                                            self.write_keyword("STRING");
24872                                        }
24873                                    }
24874                                    _ => {
24875                                        self.write_keyword("CHAR");
24876                                        if let Some(args) = _args_str {
24877                                            self.write(args);
24878                                        }
24879                                    }
24880                                }
24881                            }
24882                            // MySQL text variant types -> map to appropriate target type
24883                            // For MySQL/SingleStore: keep original name (column definitions), CAST handling is in generate_cast
24884                            "LONGTEXT" | "MEDIUMTEXT" | "TINYTEXT" => match self.config.dialect {
24885                                Some(DialectType::MySQL)
24886                                | Some(DialectType::SingleStore)
24887                                | Some(DialectType::TiDB) => self.write_keyword(&base_upper),
24888                                Some(DialectType::Spark)
24889                                | Some(DialectType::Databricks)
24890                                | Some(DialectType::Hive) => self.write_keyword("TEXT"),
24891                                Some(DialectType::BigQuery) => self.write_keyword("STRING"),
24892                                Some(DialectType::Presto)
24893                                | Some(DialectType::Trino)
24894                                | Some(DialectType::Athena) => self.write_keyword("VARCHAR"),
24895                                Some(DialectType::Snowflake)
24896                                | Some(DialectType::Redshift)
24897                                | Some(DialectType::Dremio) => self.write_keyword("VARCHAR"),
24898                                _ => self.write_keyword("TEXT"),
24899                            },
24900                            // MySQL blob variant types -> map to appropriate target type
24901                            // For MySQL/SingleStore: keep original name (column definitions), CAST handling is in generate_cast
24902                            "LONGBLOB" | "MEDIUMBLOB" | "TINYBLOB" => match self.config.dialect {
24903                                Some(DialectType::MySQL)
24904                                | Some(DialectType::SingleStore)
24905                                | Some(DialectType::TiDB) => self.write_keyword(&base_upper),
24906                                Some(DialectType::Spark)
24907                                | Some(DialectType::Databricks)
24908                                | Some(DialectType::Hive) => self.write_keyword("BLOB"),
24909                                Some(DialectType::DuckDB) => self.write_keyword("VARBINARY"),
24910                                Some(DialectType::BigQuery) => self.write_keyword("BYTES"),
24911                                Some(DialectType::Presto)
24912                                | Some(DialectType::Trino)
24913                                | Some(DialectType::Athena) => self.write_keyword("VARBINARY"),
24914                                Some(DialectType::Snowflake)
24915                                | Some(DialectType::Redshift)
24916                                | Some(DialectType::Dremio) => self.write_keyword("VARBINARY"),
24917                                _ => self.write_keyword("BLOB"),
24918                            },
24919                            // LONGVARCHAR -> TEXT for SQLite, VARCHAR for others
24920                            "LONGVARCHAR" => match self.config.dialect {
24921                                Some(DialectType::SQLite) => self.write_keyword("TEXT"),
24922                                _ => self.write_keyword("VARCHAR"),
24923                            },
24924                            // DATETIME -> TIMESTAMP for most, DATETIME for MySQL/Doris/StarRocks/Snowflake
24925                            "DATETIME" => {
24926                                match self.config.dialect {
24927                                    Some(DialectType::MySQL)
24928                                    | Some(DialectType::Doris)
24929                                    | Some(DialectType::StarRocks)
24930                                    | Some(DialectType::TSQL)
24931                                    | Some(DialectType::Fabric)
24932                                    | Some(DialectType::BigQuery)
24933                                    | Some(DialectType::SQLite)
24934                                    | Some(DialectType::Snowflake) => {
24935                                        self.write_keyword("DATETIME");
24936                                        if let Some(args) = _args_str {
24937                                            self.write(args);
24938                                        }
24939                                    }
24940                                    Some(_) => {
24941                                        // Only map to TIMESTAMP when we have a specific target dialect
24942                                        self.write_keyword("TIMESTAMP");
24943                                        if let Some(args) = _args_str {
24944                                            self.write(args);
24945                                        }
24946                                    }
24947                                    None => {
24948                                        // No dialect - preserve original
24949                                        self.write(name);
24950                                    }
24951                                }
24952                            }
24953                            // VARCHAR2/NVARCHAR2 (Oracle) -> VARCHAR for non-Oracle targets
24954                            "VARCHAR2"
24955                                if !matches!(self.config.dialect, Some(DialectType::Oracle)) =>
24956                            {
24957                                match self.config.dialect {
24958                                    Some(DialectType::DuckDB) | Some(DialectType::SQLite) => {
24959                                        self.write_keyword("TEXT");
24960                                    }
24961                                    Some(DialectType::Hive)
24962                                    | Some(DialectType::Spark)
24963                                    | Some(DialectType::Databricks)
24964                                    | Some(DialectType::BigQuery)
24965                                    | Some(DialectType::ClickHouse)
24966                                    | Some(DialectType::StarRocks)
24967                                    | Some(DialectType::Doris) => {
24968                                        self.write_keyword("STRING");
24969                                    }
24970                                    _ => {
24971                                        self.write_keyword("VARCHAR");
24972                                        if let Some(args) = _args_str {
24973                                            self.write(args);
24974                                        }
24975                                    }
24976                                }
24977                            }
24978                            "NVARCHAR2"
24979                                if !matches!(self.config.dialect, Some(DialectType::Oracle)) =>
24980                            {
24981                                match self.config.dialect {
24982                                    Some(DialectType::DuckDB) | Some(DialectType::SQLite) => {
24983                                        self.write_keyword("TEXT");
24984                                    }
24985                                    Some(DialectType::Hive)
24986                                    | Some(DialectType::Spark)
24987                                    | Some(DialectType::Databricks)
24988                                    | Some(DialectType::BigQuery)
24989                                    | Some(DialectType::ClickHouse)
24990                                    | Some(DialectType::StarRocks)
24991                                    | Some(DialectType::Doris) => {
24992                                        self.write_keyword("STRING");
24993                                    }
24994                                    _ => {
24995                                        self.write_keyword("VARCHAR");
24996                                        if let Some(args) = _args_str {
24997                                            self.write(args);
24998                                        }
24999                                    }
25000                                }
25001                            }
25002                            _ => self.write(name),
25003                        }
25004                    }
25005                }
25006            }
25007            DataType::Geometry { subtype, srid } => {
25008                // Dialect-specific geometry type mappings
25009                match self.config.dialect {
25010                    Some(DialectType::MySQL) => {
25011                        // MySQL uses POINT SRID 4326 syntax for specific types
25012                        if let Some(sub) = subtype {
25013                            self.write_keyword(sub);
25014                            if let Some(s) = srid {
25015                                self.write(" SRID ");
25016                                self.write(&s.to_string());
25017                            }
25018                        } else {
25019                            self.write_keyword("GEOMETRY");
25020                        }
25021                    }
25022                    Some(DialectType::BigQuery) => {
25023                        // BigQuery only supports GEOGRAPHY, not GEOMETRY
25024                        self.write_keyword("GEOGRAPHY");
25025                    }
25026                    Some(DialectType::Teradata) => {
25027                        // Teradata uses ST_GEOMETRY
25028                        self.write_keyword("ST_GEOMETRY");
25029                        if subtype.is_some() || srid.is_some() {
25030                            self.write("(");
25031                            if let Some(sub) = subtype {
25032                                self.write_keyword(sub);
25033                            }
25034                            if let Some(s) = srid {
25035                                if subtype.is_some() {
25036                                    self.write(", ");
25037                                }
25038                                self.write(&s.to_string());
25039                            }
25040                            self.write(")");
25041                        }
25042                    }
25043                    _ => {
25044                        // PostgreSQL, Snowflake, DuckDB use GEOMETRY(subtype, srid) syntax
25045                        self.write_keyword("GEOMETRY");
25046                        if subtype.is_some() || srid.is_some() {
25047                            self.write("(");
25048                            if let Some(sub) = subtype {
25049                                self.write_keyword(sub);
25050                            }
25051                            if let Some(s) = srid {
25052                                if subtype.is_some() {
25053                                    self.write(", ");
25054                                }
25055                                self.write(&s.to_string());
25056                            }
25057                            self.write(")");
25058                        }
25059                    }
25060                }
25061            }
25062            DataType::Geography { subtype, srid } => {
25063                // Dialect-specific geography type mappings
25064                match self.config.dialect {
25065                    Some(DialectType::MySQL) => {
25066                        // MySQL doesn't have native GEOGRAPHY, use GEOMETRY with SRID 4326
25067                        if let Some(sub) = subtype {
25068                            self.write_keyword(sub);
25069                        } else {
25070                            self.write_keyword("GEOMETRY");
25071                        }
25072                        // Geography implies SRID 4326 (WGS84)
25073                        let effective_srid = srid.unwrap_or(4326);
25074                        self.write(" SRID ");
25075                        self.write(&effective_srid.to_string());
25076                    }
25077                    Some(DialectType::BigQuery) => {
25078                        // BigQuery uses simple GEOGRAPHY without parameters
25079                        self.write_keyword("GEOGRAPHY");
25080                    }
25081                    Some(DialectType::Snowflake) => {
25082                        // Snowflake uses GEOGRAPHY without parameters
25083                        self.write_keyword("GEOGRAPHY");
25084                    }
25085                    _ => {
25086                        // PostgreSQL uses GEOGRAPHY(subtype, srid) syntax
25087                        self.write_keyword("GEOGRAPHY");
25088                        if subtype.is_some() || srid.is_some() {
25089                            self.write("(");
25090                            if let Some(sub) = subtype {
25091                                self.write_keyword(sub);
25092                            }
25093                            if let Some(s) = srid {
25094                                if subtype.is_some() {
25095                                    self.write(", ");
25096                                }
25097                                self.write(&s.to_string());
25098                            }
25099                            self.write(")");
25100                        }
25101                    }
25102                }
25103            }
25104            DataType::CharacterSet { name } => {
25105                // For MySQL CONVERT USING - output as CHAR CHARACTER SET name
25106                self.write_keyword("CHAR CHARACTER SET ");
25107                self.write(name);
25108            }
25109            _ => self.write("UNKNOWN"),
25110        }
25111        Ok(())
25112    }
25113
25114    // === Helper methods ===
25115
25116    #[inline]
25117    fn write(&mut self, s: &str) {
25118        self.output.push_str(s);
25119    }
25120
25121    #[inline]
25122    fn write_space(&mut self) {
25123        self.output.push(' ');
25124    }
25125
25126    #[inline]
25127    fn write_keyword(&mut self, keyword: &str) {
25128        if self.config.uppercase_keywords {
25129            self.output.push_str(keyword);
25130        } else {
25131            for b in keyword.bytes() {
25132                self.output.push(b.to_ascii_lowercase() as char);
25133            }
25134        }
25135    }
25136
25137    /// Write a function name respecting the normalize_functions config setting
25138    fn write_func_name(&mut self, name: &str) {
25139        let normalized = self.normalize_func_name(name);
25140        self.output.push_str(normalized.as_ref());
25141    }
25142
25143    /// Convert strptime format string to Exasol format string
25144    /// Exasol TIME_MAPPING (reverse of Python sqlglot):
25145    /// %Y -> YYYY, %y -> YY, %m -> MM, %d -> DD, %H -> HH, %M -> MI, %S -> SS, %a -> DY
25146    fn convert_strptime_to_exasol_format(format: &str) -> String {
25147        let mut result = String::new();
25148        let chars: Vec<char> = format.chars().collect();
25149        let mut i = 0;
25150        while i < chars.len() {
25151            if chars[i] == '%' && i + 1 < chars.len() {
25152                let spec = chars[i + 1];
25153                let exasol_spec = match spec {
25154                    'Y' => "YYYY",
25155                    'y' => "YY",
25156                    'm' => "MM",
25157                    'd' => "DD",
25158                    'H' => "HH",
25159                    'M' => "MI",
25160                    'S' => "SS",
25161                    'a' => "DY",    // abbreviated weekday name
25162                    'A' => "DAY",   // full weekday name
25163                    'b' => "MON",   // abbreviated month name
25164                    'B' => "MONTH", // full month name
25165                    'I' => "H12",   // 12-hour format
25166                    'u' => "ID",    // ISO weekday (1-7)
25167                    'V' => "IW",    // ISO week number
25168                    'G' => "IYYY",  // ISO year
25169                    'W' => "UW",    // Week number (Monday as first day)
25170                    'U' => "UW",    // Week number (Sunday as first day)
25171                    'z' => "Z",     // timezone offset
25172                    _ => {
25173                        // Unknown specifier, keep as-is
25174                        result.push('%');
25175                        result.push(spec);
25176                        i += 2;
25177                        continue;
25178                    }
25179                };
25180                result.push_str(exasol_spec);
25181                i += 2;
25182            } else {
25183                result.push(chars[i]);
25184                i += 1;
25185            }
25186        }
25187        result
25188    }
25189
25190    /// Convert strptime format string to PostgreSQL/Redshift format string
25191    /// PostgreSQL INVERSE_TIME_MAPPING from Python sqlglot:
25192    /// %Y -> YYYY, %y -> YY, %m -> MM, %d -> DD, %H -> HH24, %M -> MI, %S -> SS, %f -> US, etc.
25193    fn convert_strptime_to_postgres_format(format: &str) -> String {
25194        let mut result = String::new();
25195        let chars: Vec<char> = format.chars().collect();
25196        let mut i = 0;
25197        while i < chars.len() {
25198            if chars[i] == '%' && i + 1 < chars.len() {
25199                // Check for %-d, %-m, etc. (non-padded, 3-char sequence)
25200                if chars[i + 1] == '-' && i + 2 < chars.len() {
25201                    let spec = chars[i + 2];
25202                    let pg_spec = match spec {
25203                        'd' => "FMDD",
25204                        'm' => "FMMM",
25205                        'H' => "FMHH24",
25206                        'M' => "FMMI",
25207                        'S' => "FMSS",
25208                        _ => {
25209                            result.push('%');
25210                            result.push('-');
25211                            result.push(spec);
25212                            i += 3;
25213                            continue;
25214                        }
25215                    };
25216                    result.push_str(pg_spec);
25217                    i += 3;
25218                    continue;
25219                }
25220                let spec = chars[i + 1];
25221                let pg_spec = match spec {
25222                    'Y' => "YYYY",
25223                    'y' => "YY",
25224                    'm' => "MM",
25225                    'd' => "DD",
25226                    'H' => "HH24",
25227                    'I' => "HH12",
25228                    'M' => "MI",
25229                    'S' => "SS",
25230                    'f' => "US",      // microseconds
25231                    'u' => "D",       // day of week (1=Monday)
25232                    'j' => "DDD",     // day of year
25233                    'z' => "OF",      // UTC offset
25234                    'Z' => "TZ",      // timezone name
25235                    'A' => "TMDay",   // full weekday name
25236                    'a' => "TMDy",    // abbreviated weekday name
25237                    'b' => "TMMon",   // abbreviated month name
25238                    'B' => "TMMonth", // full month name
25239                    'U' => "WW",      // week number
25240                    _ => {
25241                        // Unknown specifier, keep as-is
25242                        result.push('%');
25243                        result.push(spec);
25244                        i += 2;
25245                        continue;
25246                    }
25247                };
25248                result.push_str(pg_spec);
25249                i += 2;
25250            } else {
25251                result.push(chars[i]);
25252                i += 1;
25253            }
25254        }
25255        result
25256    }
25257
25258    /// Write a LIMIT expression value, evaluating constant expressions if limit_only_literals is set
25259    fn write_limit_expr(&mut self, expr: &Expression) -> Result<()> {
25260        if self.config.limit_only_literals {
25261            if let Some(value) = Self::try_evaluate_constant(expr) {
25262                self.write(&value.to_string());
25263                return Ok(());
25264            }
25265        }
25266        self.generate_expression(expr)
25267    }
25268
25269    /// Format a comment with proper spacing.
25270    /// Converts `/*text*/` to `/* text */` (adding internal spaces if not present).
25271    /// Python SQLGlot normalizes comment format to have spaces inside block comments.
25272    fn write_formatted_comment(&mut self, comment: &str) {
25273        // Normalize all comments to block comment format /* ... */
25274        // This matches Python sqlglot behavior which always outputs block comments
25275        let content = if comment.starts_with("/*") && comment.ends_with("*/") {
25276            // Already block comment - extract inner content
25277            // Preserve internal whitespace, but ensure at least one space padding
25278            &comment[2..comment.len() - 2]
25279        } else if comment.starts_with("--") {
25280            // Line comment - extract content after --
25281            // Preserve internal whitespace (e.g., "--       x" -> "/*       x */")
25282            &comment[2..]
25283        } else {
25284            // Raw content (no delimiters)
25285            comment
25286        };
25287        // Skip empty comments (e.g., bare "--" with no content)
25288        if content.trim().is_empty() {
25289            return;
25290        }
25291        // Escape nested block comment markers to prevent premature closure or unintended nesting.
25292        // This matches Python sqlglot's sanitize_comment behavior.
25293        let sanitized = content.replace("*/", "* /").replace("/*", "/ *");
25294        let content = &sanitized;
25295        // Ensure at least one space after /* and before */
25296        self.output.push_str("/*");
25297        if !content.starts_with(' ') {
25298            self.output.push(' ');
25299        }
25300        self.output.push_str(content);
25301        if !content.ends_with(' ') {
25302            self.output.push(' ');
25303        }
25304        self.output.push_str("*/");
25305    }
25306
25307    /// Escape a raw block content (from dollar-quoted string) for single-quoted output.
25308    /// Escapes single quotes with backslash, and for Snowflake also escapes backslashes.
25309    fn escape_block_for_single_quote(&self, block: &str) -> String {
25310        let escape_backslash = matches!(
25311            self.config.dialect,
25312            Some(crate::dialects::DialectType::Snowflake)
25313        );
25314        let mut escaped = String::with_capacity(block.len() + 4);
25315        for ch in block.chars() {
25316            if ch == '\'' {
25317                escaped.push('\\');
25318                escaped.push('\'');
25319            } else if escape_backslash && ch == '\\' {
25320                escaped.push('\\');
25321                escaped.push('\\');
25322            } else {
25323                escaped.push(ch);
25324            }
25325        }
25326        escaped
25327    }
25328
25329    fn write_newline(&mut self) {
25330        self.output.push('\n');
25331    }
25332
25333    fn write_indent(&mut self) {
25334        for _ in 0..self.indent_level {
25335            self.output.push_str(self.config.indent);
25336        }
25337    }
25338
25339    // === SQLGlot-style pretty printing helpers ===
25340
25341    /// Returns the separator string for pretty printing.
25342    /// Check if the total length of arguments exceeds max_text_width.
25343    /// Used for dynamic line breaking in expressions() formatting.
25344    fn too_wide(&self, args: &[String]) -> bool {
25345        args.iter().map(|s| s.len()).sum::<usize>() > self.config.max_text_width
25346    }
25347
25348    /// Generate an expression to a string using a temporary non-pretty generator.
25349    /// Useful for width calculations before deciding on formatting.
25350    fn generate_to_string(&self, expr: &Expression) -> Result<String> {
25351        let config = GeneratorConfig {
25352            pretty: false,
25353            dialect: self.config.dialect,
25354            ..Default::default()
25355        };
25356        let mut gen = Generator::with_config(config);
25357        gen.generate_expression(expr)?;
25358        Ok(gen.output)
25359    }
25360
25361    /// Writes a clause with a single condition (WHERE, HAVING, QUALIFY).
25362    /// In pretty mode: newline + indented keyword + newline + indented condition
25363    fn write_clause_condition(&mut self, keyword: &str, condition: &Expression) -> Result<()> {
25364        if self.config.pretty {
25365            self.write_newline();
25366            self.write_indent();
25367            self.write_keyword(keyword);
25368            self.write_newline();
25369            self.indent_level += 1;
25370            self.write_indent();
25371            self.generate_expression(condition)?;
25372            self.indent_level -= 1;
25373        } else {
25374            self.write_space();
25375            self.write_keyword(keyword);
25376            self.write_space();
25377            self.generate_expression(condition)?;
25378        }
25379        Ok(())
25380    }
25381
25382    /// Writes a clause with a list of expressions (GROUP BY, DISTRIBUTE BY, CLUSTER BY).
25383    /// In pretty mode: each expression on new line with indentation
25384    fn write_clause_expressions(&mut self, keyword: &str, exprs: &[Expression]) -> Result<()> {
25385        if exprs.is_empty() {
25386            return Ok(());
25387        }
25388
25389        if self.config.pretty {
25390            self.write_newline();
25391            self.write_indent();
25392            self.write_keyword(keyword);
25393            self.write_newline();
25394            self.indent_level += 1;
25395            for (i, expr) in exprs.iter().enumerate() {
25396                if i > 0 {
25397                    self.write(",");
25398                    self.write_newline();
25399                }
25400                self.write_indent();
25401                self.generate_expression(expr)?;
25402            }
25403            self.indent_level -= 1;
25404        } else {
25405            self.write_space();
25406            self.write_keyword(keyword);
25407            self.write_space();
25408            for (i, expr) in exprs.iter().enumerate() {
25409                if i > 0 {
25410                    self.write(", ");
25411                }
25412                self.generate_expression(expr)?;
25413            }
25414        }
25415        Ok(())
25416    }
25417
25418    /// Writes ORDER BY / SORT BY clause with Ordered expressions
25419    fn write_order_clause(&mut self, keyword: &str, orderings: &[Ordered]) -> Result<()> {
25420        if orderings.is_empty() {
25421            return Ok(());
25422        }
25423
25424        if self.config.pretty {
25425            self.write_newline();
25426            self.write_indent();
25427            self.write_keyword(keyword);
25428            self.write_newline();
25429            self.indent_level += 1;
25430            for (i, ordered) in orderings.iter().enumerate() {
25431                if i > 0 {
25432                    self.write(",");
25433                    self.write_newline();
25434                }
25435                self.write_indent();
25436                self.generate_ordered(ordered)?;
25437            }
25438            self.indent_level -= 1;
25439        } else {
25440            self.write_space();
25441            self.write_keyword(keyword);
25442            self.write_space();
25443            for (i, ordered) in orderings.iter().enumerate() {
25444                if i > 0 {
25445                    self.write(", ");
25446                }
25447                self.generate_ordered(ordered)?;
25448            }
25449        }
25450        Ok(())
25451    }
25452
25453    /// Writes WINDOW clause with named window definitions
25454    fn write_window_clause(&mut self, windows: &[NamedWindow]) -> Result<()> {
25455        if windows.is_empty() {
25456            return Ok(());
25457        }
25458
25459        if self.config.pretty {
25460            self.write_newline();
25461            self.write_indent();
25462            self.write_keyword("WINDOW");
25463            self.write_newline();
25464            self.indent_level += 1;
25465            for (i, named_window) in windows.iter().enumerate() {
25466                if i > 0 {
25467                    self.write(",");
25468                    self.write_newline();
25469                }
25470                self.write_indent();
25471                self.generate_identifier(&named_window.name)?;
25472                self.write_space();
25473                self.write_keyword("AS");
25474                self.write(" (");
25475                self.generate_over(&named_window.spec)?;
25476                self.write(")");
25477            }
25478            self.indent_level -= 1;
25479        } else {
25480            self.write_space();
25481            self.write_keyword("WINDOW");
25482            self.write_space();
25483            for (i, named_window) in windows.iter().enumerate() {
25484                if i > 0 {
25485                    self.write(", ");
25486                }
25487                self.generate_identifier(&named_window.name)?;
25488                self.write_space();
25489                self.write_keyword("AS");
25490                self.write(" (");
25491                self.generate_over(&named_window.spec)?;
25492                self.write(")");
25493            }
25494        }
25495        Ok(())
25496    }
25497
25498    // === BATCH-GENERATED STUB METHODS (481 variants) ===
25499    fn generate_ai_agg(&mut self, e: &AIAgg) -> Result<()> {
25500        // AI_AGG(this, expression)
25501        self.write_keyword("AI_AGG");
25502        self.write("(");
25503        self.generate_expression(&e.this)?;
25504        self.write(", ");
25505        self.generate_expression(&e.expression)?;
25506        self.write(")");
25507        Ok(())
25508    }
25509
25510    fn generate_ai_classify(&mut self, e: &AIClassify) -> Result<()> {
25511        // AI_CLASSIFY(input, [categories], [config])
25512        self.write_keyword("AI_CLASSIFY");
25513        self.write("(");
25514        self.generate_expression(&e.this)?;
25515        if let Some(categories) = &e.categories {
25516            self.write(", ");
25517            self.generate_expression(categories)?;
25518        }
25519        if let Some(config) = &e.config {
25520            self.write(", ");
25521            self.generate_expression(config)?;
25522        }
25523        self.write(")");
25524        Ok(())
25525    }
25526
25527    fn generate_add_partition(&mut self, e: &AddPartition) -> Result<()> {
25528        // Python: return f"ADD {exists}{self.sql(expression.this)}{location}"
25529        self.write_keyword("ADD");
25530        self.write_space();
25531        if e.exists {
25532            self.write_keyword("IF NOT EXISTS");
25533            self.write_space();
25534        }
25535        self.generate_expression(&e.this)?;
25536        if let Some(location) = &e.location {
25537            self.write_space();
25538            self.generate_expression(location)?;
25539        }
25540        Ok(())
25541    }
25542
25543    fn generate_algorithm_property(&mut self, e: &AlgorithmProperty) -> Result<()> {
25544        // Python: return f"ALGORITHM={self.sql(expression, 'this')}"
25545        self.write_keyword("ALGORITHM");
25546        self.write("=");
25547        self.generate_expression(&e.this)?;
25548        Ok(())
25549    }
25550
25551    fn generate_aliases(&mut self, e: &Aliases) -> Result<()> {
25552        // Python: return f"{self.sql(expression, 'this')} AS ({self.expressions(expression, flat=True)})"
25553        self.generate_expression(&e.this)?;
25554        self.write_space();
25555        self.write_keyword("AS");
25556        self.write(" (");
25557        for (i, expr) in e.expressions.iter().enumerate() {
25558            if i > 0 {
25559                self.write(", ");
25560            }
25561            self.generate_expression(expr)?;
25562        }
25563        self.write(")");
25564        Ok(())
25565    }
25566
25567    fn generate_allowed_values_property(&mut self, e: &AllowedValuesProperty) -> Result<()> {
25568        // Python: return f"ALLOWED_VALUES {self.expressions(e, flat=True)}"
25569        self.write_keyword("ALLOWED_VALUES");
25570        self.write_space();
25571        for (i, expr) in e.expressions.iter().enumerate() {
25572            if i > 0 {
25573                self.write(", ");
25574            }
25575            self.generate_expression(expr)?;
25576        }
25577        Ok(())
25578    }
25579
25580    fn generate_alter_column(&mut self, e: &AlterColumn) -> Result<()> {
25581        // Python: complex logic based on dtype, default, comment, visible, etc.
25582        self.write_keyword("ALTER COLUMN");
25583        self.write_space();
25584        self.generate_expression(&e.this)?;
25585
25586        if let Some(dtype) = &e.dtype {
25587            self.write_space();
25588            self.write_keyword("SET DATA TYPE");
25589            self.write_space();
25590            self.generate_expression(dtype)?;
25591            if let Some(collate) = &e.collate {
25592                self.write_space();
25593                self.write_keyword("COLLATE");
25594                self.write_space();
25595                self.generate_expression(collate)?;
25596            }
25597            if let Some(using) = &e.using {
25598                self.write_space();
25599                self.write_keyword("USING");
25600                self.write_space();
25601                self.generate_expression(using)?;
25602            }
25603        } else if let Some(default) = &e.default {
25604            self.write_space();
25605            self.write_keyword("SET DEFAULT");
25606            self.write_space();
25607            self.generate_expression(default)?;
25608        } else if let Some(comment) = &e.comment {
25609            self.write_space();
25610            self.write_keyword("COMMENT");
25611            self.write_space();
25612            self.generate_expression(comment)?;
25613        } else if let Some(drop) = &e.drop {
25614            self.write_space();
25615            self.write_keyword("DROP");
25616            self.write_space();
25617            self.generate_expression(drop)?;
25618        } else if let Some(visible) = &e.visible {
25619            self.write_space();
25620            self.generate_expression(visible)?;
25621        } else if let Some(rename_to) = &e.rename_to {
25622            self.write_space();
25623            self.write_keyword("RENAME TO");
25624            self.write_space();
25625            self.generate_expression(rename_to)?;
25626        } else if let Some(allow_null) = &e.allow_null {
25627            self.write_space();
25628            self.generate_expression(allow_null)?;
25629        }
25630        Ok(())
25631    }
25632
25633    fn generate_alter_session(&mut self, e: &AlterSession) -> Result<()> {
25634        // Python: keyword = "UNSET" if expression.args.get("unset") else "SET"; return f"{keyword} {items_sql}"
25635        self.write_keyword("ALTER SESSION");
25636        self.write_space();
25637        if e.unset.is_some() {
25638            self.write_keyword("UNSET");
25639        } else {
25640            self.write_keyword("SET");
25641        }
25642        self.write_space();
25643        for (i, expr) in e.expressions.iter().enumerate() {
25644            if i > 0 {
25645                self.write(", ");
25646            }
25647            self.generate_expression(expr)?;
25648        }
25649        Ok(())
25650    }
25651
25652    fn generate_alter_set(&mut self, e: &AlterSet) -> Result<()> {
25653        // Python (Snowflake): return f"SET{exprs}{file_format}{copy_options}{tag}"
25654        self.write_keyword("SET");
25655
25656        // Generate option (e.g., AUTHORIZATION, LOGGED, UNLOGGED, etc.)
25657        if let Some(opt) = &e.option {
25658            self.write_space();
25659            self.generate_expression(opt)?;
25660        }
25661
25662        // Generate PROPERTIES (for Trino SET PROPERTIES x = y, ...)
25663        // Check if expressions look like property assignments
25664        if !e.expressions.is_empty() {
25665            // Check if this looks like property assignments (for SET PROPERTIES)
25666            let is_properties = e
25667                .expressions
25668                .iter()
25669                .any(|expr| matches!(expr, Expression::Eq(_)));
25670            if is_properties && e.option.is_none() {
25671                self.write_space();
25672                self.write_keyword("PROPERTIES");
25673            }
25674            self.write_space();
25675            for (i, expr) in e.expressions.iter().enumerate() {
25676                if i > 0 {
25677                    self.write(", ");
25678                }
25679                self.generate_expression(expr)?;
25680            }
25681        }
25682
25683        // Generate STAGE_FILE_FORMAT = (...) with space-separated properties
25684        if let Some(file_format) = &e.file_format {
25685            self.write(" ");
25686            self.write_keyword("STAGE_FILE_FORMAT");
25687            self.write(" = (");
25688            self.generate_space_separated_properties(file_format)?;
25689            self.write(")");
25690        }
25691
25692        // Generate STAGE_COPY_OPTIONS = (...) with space-separated properties
25693        if let Some(copy_options) = &e.copy_options {
25694            self.write(" ");
25695            self.write_keyword("STAGE_COPY_OPTIONS");
25696            self.write(" = (");
25697            self.generate_space_separated_properties(copy_options)?;
25698            self.write(")");
25699        }
25700
25701        // Generate TAG ...
25702        if let Some(tag) = &e.tag {
25703            self.write(" ");
25704            self.write_keyword("TAG");
25705            self.write(" ");
25706            self.generate_expression(tag)?;
25707        }
25708
25709        Ok(())
25710    }
25711
25712    /// Generate space-separated properties (for Snowflake STAGE_FILE_FORMAT, etc.)
25713    fn generate_space_separated_properties(&mut self, expr: &Expression) -> Result<()> {
25714        match expr {
25715            Expression::Tuple(t) => {
25716                for (i, prop) in t.expressions.iter().enumerate() {
25717                    if i > 0 {
25718                        self.write(" ");
25719                    }
25720                    self.generate_expression(prop)?;
25721                }
25722            }
25723            _ => {
25724                self.generate_expression(expr)?;
25725            }
25726        }
25727        Ok(())
25728    }
25729
25730    fn generate_alter_sort_key(&mut self, e: &AlterSortKey) -> Result<()> {
25731        // Python: return f"ALTER{compound} SORTKEY {this or expressions}"
25732        self.write_keyword("ALTER");
25733        if e.compound.is_some() {
25734            self.write_space();
25735            self.write_keyword("COMPOUND");
25736        }
25737        self.write_space();
25738        self.write_keyword("SORTKEY");
25739        self.write_space();
25740        if let Some(this) = &e.this {
25741            self.generate_expression(this)?;
25742        } else if !e.expressions.is_empty() {
25743            self.write("(");
25744            for (i, expr) in e.expressions.iter().enumerate() {
25745                if i > 0 {
25746                    self.write(", ");
25747                }
25748                self.generate_expression(expr)?;
25749            }
25750            self.write(")");
25751        }
25752        Ok(())
25753    }
25754
25755    fn generate_analyze(&mut self, e: &Analyze) -> Result<()> {
25756        // Python: return f"ANALYZE{options}{kind}{this}{partition}{mode}{inner_expression}{properties}"
25757        self.write_keyword("ANALYZE");
25758        if !e.options.is_empty() {
25759            self.write_space();
25760            for (i, opt) in e.options.iter().enumerate() {
25761                if i > 0 {
25762                    self.write_space();
25763                }
25764                // Write options as keywords (not identifiers) to avoid quoting reserved words like FULL
25765                if let Expression::Identifier(id) = opt {
25766                    self.write_keyword(&id.name);
25767                } else {
25768                    self.generate_expression(opt)?;
25769                }
25770            }
25771        }
25772        if let Some(kind) = &e.kind {
25773            self.write_space();
25774            self.write_keyword(kind);
25775        }
25776        if let Some(this) = &e.this {
25777            self.write_space();
25778            self.generate_expression(this)?;
25779        }
25780        // Column list: ANALYZE tbl(col1, col2) (PostgreSQL)
25781        if !e.columns.is_empty() {
25782            self.write("(");
25783            for (i, col) in e.columns.iter().enumerate() {
25784                if i > 0 {
25785                    self.write(", ");
25786                }
25787                self.write(col);
25788            }
25789            self.write(")");
25790        }
25791        if let Some(partition) = &e.partition {
25792            self.write_space();
25793            self.generate_expression(partition)?;
25794        }
25795        if let Some(mode) = &e.mode {
25796            self.write_space();
25797            self.generate_expression(mode)?;
25798        }
25799        if let Some(expression) = &e.expression {
25800            self.write_space();
25801            self.generate_expression(expression)?;
25802        }
25803        if !e.properties.is_empty() {
25804            self.write_space();
25805            self.write_keyword(self.config.with_properties_prefix);
25806            self.write(" (");
25807            for (i, prop) in e.properties.iter().enumerate() {
25808                if i > 0 {
25809                    self.write(", ");
25810                }
25811                self.generate_expression(prop)?;
25812            }
25813            self.write(")");
25814        }
25815        Ok(())
25816    }
25817
25818    fn generate_analyze_delete(&mut self, e: &AnalyzeDelete) -> Result<()> {
25819        // Python: return f"DELETE{kind} STATISTICS"
25820        self.write_keyword("DELETE");
25821        if let Some(kind) = &e.kind {
25822            self.write_space();
25823            self.write_keyword(kind);
25824        }
25825        self.write_space();
25826        self.write_keyword("STATISTICS");
25827        Ok(())
25828    }
25829
25830    fn generate_analyze_histogram(&mut self, e: &AnalyzeHistogram) -> Result<()> {
25831        // Python: return f"{this} HISTOGRAM ON {columns}{inner_expression}{update_options}"
25832        // Write `this` (UPDATE or DROP) as keyword to avoid quoting reserved words
25833        if let Expression::Identifier(id) = e.this.as_ref() {
25834            self.write_keyword(&id.name);
25835        } else {
25836            self.generate_expression(&e.this)?;
25837        }
25838        self.write_space();
25839        self.write_keyword("HISTOGRAM ON");
25840        self.write_space();
25841        for (i, expr) in e.expressions.iter().enumerate() {
25842            if i > 0 {
25843                self.write(", ");
25844            }
25845            self.generate_expression(expr)?;
25846        }
25847        if let Some(expression) = &e.expression {
25848            self.write_space();
25849            self.generate_expression(expression)?;
25850        }
25851        if let Some(update_options) = &e.update_options {
25852            self.write_space();
25853            self.generate_expression(update_options)?;
25854            self.write_space();
25855            self.write_keyword("UPDATE");
25856        }
25857        Ok(())
25858    }
25859
25860    fn generate_analyze_list_chained_rows(&mut self, e: &AnalyzeListChainedRows) -> Result<()> {
25861        // Python: return f"LIST CHAINED ROWS{inner_expression}"
25862        self.write_keyword("LIST CHAINED ROWS");
25863        if let Some(expression) = &e.expression {
25864            self.write_space();
25865            self.write_keyword("INTO");
25866            self.write_space();
25867            self.generate_expression(expression)?;
25868        }
25869        Ok(())
25870    }
25871
25872    fn generate_analyze_sample(&mut self, e: &AnalyzeSample) -> Result<()> {
25873        // Python: return f"SAMPLE {sample} {kind}"
25874        self.write_keyword("SAMPLE");
25875        self.write_space();
25876        if let Some(sample) = &e.sample {
25877            self.generate_expression(sample)?;
25878            self.write_space();
25879        }
25880        self.write_keyword(&e.kind);
25881        Ok(())
25882    }
25883
25884    fn generate_analyze_statistics(&mut self, e: &AnalyzeStatistics) -> Result<()> {
25885        // Python: return f"{kind}{option} STATISTICS{this}{columns}"
25886        self.write_keyword(&e.kind);
25887        if let Some(option) = &e.option {
25888            self.write_space();
25889            self.generate_expression(option)?;
25890        }
25891        self.write_space();
25892        self.write_keyword("STATISTICS");
25893        if let Some(this) = &e.this {
25894            self.write_space();
25895            self.generate_expression(this)?;
25896        }
25897        if !e.expressions.is_empty() {
25898            self.write_space();
25899            for (i, expr) in e.expressions.iter().enumerate() {
25900                if i > 0 {
25901                    self.write(", ");
25902                }
25903                self.generate_expression(expr)?;
25904            }
25905        }
25906        Ok(())
25907    }
25908
25909    fn generate_analyze_validate(&mut self, e: &AnalyzeValidate) -> Result<()> {
25910        // Python: return f"VALIDATE {kind}{this}{inner_expression}"
25911        self.write_keyword("VALIDATE");
25912        self.write_space();
25913        self.write_keyword(&e.kind);
25914        if let Some(this) = &e.this {
25915            self.write_space();
25916            // this is a keyword string like "UPDATE", "CASCADE FAST", etc. - write as keywords
25917            if let Expression::Identifier(id) = this.as_ref() {
25918                self.write_keyword(&id.name);
25919            } else {
25920                self.generate_expression(this)?;
25921            }
25922        }
25923        if let Some(expression) = &e.expression {
25924            self.write_space();
25925            self.write_keyword("INTO");
25926            self.write_space();
25927            self.generate_expression(expression)?;
25928        }
25929        Ok(())
25930    }
25931
25932    fn generate_analyze_with(&mut self, e: &AnalyzeWith) -> Result<()> {
25933        // Python: return f"WITH {expressions}"
25934        self.write_keyword("WITH");
25935        self.write_space();
25936        for (i, expr) in e.expressions.iter().enumerate() {
25937            if i > 0 {
25938                self.write(", ");
25939            }
25940            self.generate_expression(expr)?;
25941        }
25942        Ok(())
25943    }
25944
25945    fn generate_anonymous(&mut self, e: &Anonymous) -> Result<()> {
25946        // Anonymous represents a generic function call: FUNC_NAME(args...)
25947        // Python: return self.func(self.sql(expression, "this"), *expression.expressions)
25948        self.generate_expression(&e.this)?;
25949        self.write("(");
25950        for (i, arg) in e.expressions.iter().enumerate() {
25951            if i > 0 {
25952                self.write(", ");
25953            }
25954            self.generate_expression(arg)?;
25955        }
25956        self.write(")");
25957        Ok(())
25958    }
25959
25960    fn generate_anonymous_agg_func(&mut self, e: &AnonymousAggFunc) -> Result<()> {
25961        // Same as Anonymous but for aggregate functions
25962        self.generate_expression(&e.this)?;
25963        self.write("(");
25964        for (i, arg) in e.expressions.iter().enumerate() {
25965            if i > 0 {
25966                self.write(", ");
25967            }
25968            self.generate_expression(arg)?;
25969        }
25970        self.write(")");
25971        Ok(())
25972    }
25973
25974    fn generate_apply(&mut self, e: &Apply) -> Result<()> {
25975        // Python: return f"{this} APPLY({expr})"
25976        self.generate_expression(&e.this)?;
25977        self.write_space();
25978        self.write_keyword("APPLY");
25979        self.write("(");
25980        self.generate_expression(&e.expression)?;
25981        self.write(")");
25982        Ok(())
25983    }
25984
25985    fn generate_approx_percentile_estimate(&mut self, e: &ApproxPercentileEstimate) -> Result<()> {
25986        // APPROX_PERCENTILE_ESTIMATE(this, percentile)
25987        self.write_keyword("APPROX_PERCENTILE_ESTIMATE");
25988        self.write("(");
25989        self.generate_expression(&e.this)?;
25990        if let Some(percentile) = &e.percentile {
25991            self.write(", ");
25992            self.generate_expression(percentile)?;
25993        }
25994        self.write(")");
25995        Ok(())
25996    }
25997
25998    fn generate_approx_quantile(&mut self, e: &ApproxQuantile) -> Result<()> {
25999        // APPROX_QUANTILE(this, quantile[, accuracy][, weight])
26000        self.write_keyword("APPROX_QUANTILE");
26001        self.write("(");
26002        self.generate_expression(&e.this)?;
26003        if let Some(quantile) = &e.quantile {
26004            self.write(", ");
26005            self.generate_expression(quantile)?;
26006        }
26007        if let Some(accuracy) = &e.accuracy {
26008            self.write(", ");
26009            self.generate_expression(accuracy)?;
26010        }
26011        if let Some(weight) = &e.weight {
26012            self.write(", ");
26013            self.generate_expression(weight)?;
26014        }
26015        self.write(")");
26016        Ok(())
26017    }
26018
26019    fn generate_approx_quantiles(&mut self, e: &ApproxQuantiles) -> Result<()> {
26020        // APPROX_QUANTILES(this, expression)
26021        self.write_keyword("APPROX_QUANTILES");
26022        self.write("(");
26023        self.generate_expression(&e.this)?;
26024        if let Some(expression) = &e.expression {
26025            self.write(", ");
26026            self.generate_expression(expression)?;
26027        }
26028        self.write(")");
26029        Ok(())
26030    }
26031
26032    fn generate_approx_top_k(&mut self, e: &ApproxTopK) -> Result<()> {
26033        // APPROX_TOP_K(this[, expression][, counters])
26034        self.write_keyword("APPROX_TOP_K");
26035        self.write("(");
26036        self.generate_expression(&e.this)?;
26037        if let Some(expression) = &e.expression {
26038            self.write(", ");
26039            self.generate_expression(expression)?;
26040        }
26041        if let Some(counters) = &e.counters {
26042            self.write(", ");
26043            self.generate_expression(counters)?;
26044        }
26045        self.write(")");
26046        Ok(())
26047    }
26048
26049    fn generate_approx_top_k_accumulate(&mut self, e: &ApproxTopKAccumulate) -> Result<()> {
26050        // APPROX_TOP_K_ACCUMULATE(this[, expression])
26051        self.write_keyword("APPROX_TOP_K_ACCUMULATE");
26052        self.write("(");
26053        self.generate_expression(&e.this)?;
26054        if let Some(expression) = &e.expression {
26055            self.write(", ");
26056            self.generate_expression(expression)?;
26057        }
26058        self.write(")");
26059        Ok(())
26060    }
26061
26062    fn generate_approx_top_k_combine(&mut self, e: &ApproxTopKCombine) -> Result<()> {
26063        // APPROX_TOP_K_COMBINE(this[, expression])
26064        self.write_keyword("APPROX_TOP_K_COMBINE");
26065        self.write("(");
26066        self.generate_expression(&e.this)?;
26067        if let Some(expression) = &e.expression {
26068            self.write(", ");
26069            self.generate_expression(expression)?;
26070        }
26071        self.write(")");
26072        Ok(())
26073    }
26074
26075    fn generate_approx_top_k_estimate(&mut self, e: &ApproxTopKEstimate) -> Result<()> {
26076        // APPROX_TOP_K_ESTIMATE(this[, expression])
26077        self.write_keyword("APPROX_TOP_K_ESTIMATE");
26078        self.write("(");
26079        self.generate_expression(&e.this)?;
26080        if let Some(expression) = &e.expression {
26081            self.write(", ");
26082            self.generate_expression(expression)?;
26083        }
26084        self.write(")");
26085        Ok(())
26086    }
26087
26088    fn generate_approx_top_sum(&mut self, e: &ApproxTopSum) -> Result<()> {
26089        // APPROX_TOP_SUM(this, expression[, count])
26090        self.write_keyword("APPROX_TOP_SUM");
26091        self.write("(");
26092        self.generate_expression(&e.this)?;
26093        self.write(", ");
26094        self.generate_expression(&e.expression)?;
26095        if let Some(count) = &e.count {
26096            self.write(", ");
26097            self.generate_expression(count)?;
26098        }
26099        self.write(")");
26100        Ok(())
26101    }
26102
26103    fn generate_arg_max(&mut self, e: &ArgMax) -> Result<()> {
26104        // ARG_MAX(this, expression[, count])
26105        self.write_keyword("ARG_MAX");
26106        self.write("(");
26107        self.generate_expression(&e.this)?;
26108        self.write(", ");
26109        self.generate_expression(&e.expression)?;
26110        if let Some(count) = &e.count {
26111            self.write(", ");
26112            self.generate_expression(count)?;
26113        }
26114        self.write(")");
26115        Ok(())
26116    }
26117
26118    fn generate_arg_min(&mut self, e: &ArgMin) -> Result<()> {
26119        // ARG_MIN(this, expression[, count])
26120        self.write_keyword("ARG_MIN");
26121        self.write("(");
26122        self.generate_expression(&e.this)?;
26123        self.write(", ");
26124        self.generate_expression(&e.expression)?;
26125        if let Some(count) = &e.count {
26126            self.write(", ");
26127            self.generate_expression(count)?;
26128        }
26129        self.write(")");
26130        Ok(())
26131    }
26132
26133    fn generate_array_all(&mut self, e: &ArrayAll) -> Result<()> {
26134        // ARRAY_ALL(this, expression)
26135        self.write_keyword("ARRAY_ALL");
26136        self.write("(");
26137        self.generate_expression(&e.this)?;
26138        self.write(", ");
26139        self.generate_expression(&e.expression)?;
26140        self.write(")");
26141        Ok(())
26142    }
26143
26144    fn generate_array_any(&mut self, e: &ArrayAny) -> Result<()> {
26145        // ARRAY_ANY(this, expression) - fallback implementation
26146        self.write_keyword("ARRAY_ANY");
26147        self.write("(");
26148        self.generate_expression(&e.this)?;
26149        self.write(", ");
26150        self.generate_expression(&e.expression)?;
26151        self.write(")");
26152        Ok(())
26153    }
26154
26155    fn generate_array_construct_compact(&mut self, e: &ArrayConstructCompact) -> Result<()> {
26156        // ARRAY_CONSTRUCT_COMPACT(expressions...)
26157        self.write_keyword("ARRAY_CONSTRUCT_COMPACT");
26158        self.write("(");
26159        for (i, expr) in e.expressions.iter().enumerate() {
26160            if i > 0 {
26161                self.write(", ");
26162            }
26163            self.generate_expression(expr)?;
26164        }
26165        self.write(")");
26166        Ok(())
26167    }
26168
26169    fn generate_array_sum(&mut self, e: &ArraySum) -> Result<()> {
26170        // ARRAY_SUM(this[, expression])
26171        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
26172            self.write("arraySum");
26173        } else {
26174            self.write_keyword("ARRAY_SUM");
26175        }
26176        self.write("(");
26177        self.generate_expression(&e.this)?;
26178        if let Some(expression) = &e.expression {
26179            self.write(", ");
26180            self.generate_expression(expression)?;
26181        }
26182        self.write(")");
26183        Ok(())
26184    }
26185
26186    fn generate_at_index(&mut self, e: &AtIndex) -> Result<()> {
26187        // Python: return f"{this} AT {index}"
26188        self.generate_expression(&e.this)?;
26189        self.write_space();
26190        self.write_keyword("AT");
26191        self.write_space();
26192        self.generate_expression(&e.expression)?;
26193        Ok(())
26194    }
26195
26196    fn generate_attach(&mut self, e: &Attach) -> Result<()> {
26197        // Python: return f"ATTACH{exists_sql} {this}{expressions}"
26198        self.write_keyword("ATTACH");
26199        if e.exists {
26200            self.write_space();
26201            self.write_keyword("IF NOT EXISTS");
26202        }
26203        self.write_space();
26204        self.generate_expression(&e.this)?;
26205        if !e.expressions.is_empty() {
26206            self.write(" (");
26207            for (i, expr) in e.expressions.iter().enumerate() {
26208                if i > 0 {
26209                    self.write(", ");
26210                }
26211                self.generate_expression(expr)?;
26212            }
26213            self.write(")");
26214        }
26215        Ok(())
26216    }
26217
26218    fn generate_attach_option(&mut self, e: &AttachOption) -> Result<()> {
26219        // AttachOption: this [expression]
26220        // Python sqlglot: no equals sign, just space-separated
26221        self.generate_expression(&e.this)?;
26222        if let Some(expression) = &e.expression {
26223            self.write_space();
26224            self.generate_expression(expression)?;
26225        }
26226        Ok(())
26227    }
26228
26229    /// Generate the auto_increment keyword and options for a column definition.
26230    /// Different dialects use different syntax: IDENTITY, AUTOINCREMENT, AUTO_INCREMENT,
26231    /// GENERATED AS IDENTITY, etc.
26232    fn generate_auto_increment_keyword(
26233        &mut self,
26234        col: &crate::expressions::ColumnDef,
26235    ) -> Result<()> {
26236        use crate::dialects::DialectType;
26237        if matches!(self.config.dialect, Some(DialectType::Redshift)) {
26238            self.write_keyword("IDENTITY");
26239            if col.auto_increment_start.is_some() || col.auto_increment_increment.is_some() {
26240                self.write("(");
26241                if let Some(ref start) = col.auto_increment_start {
26242                    self.generate_expression(start)?;
26243                } else {
26244                    self.write("0");
26245                }
26246                self.write(", ");
26247                if let Some(ref inc) = col.auto_increment_increment {
26248                    self.generate_expression(inc)?;
26249                } else {
26250                    self.write("1");
26251                }
26252                self.write(")");
26253            }
26254        } else if matches!(
26255            self.config.dialect,
26256            Some(DialectType::Snowflake) | Some(DialectType::SQLite)
26257        ) {
26258            self.write_keyword("AUTOINCREMENT");
26259            if let Some(ref start) = col.auto_increment_start {
26260                self.write_space();
26261                self.write_keyword("START");
26262                self.write_space();
26263                self.generate_expression(start)?;
26264            }
26265            if let Some(ref inc) = col.auto_increment_increment {
26266                self.write_space();
26267                self.write_keyword("INCREMENT");
26268                self.write_space();
26269                self.generate_expression(inc)?;
26270            }
26271            if let Some(order) = col.auto_increment_order {
26272                self.write_space();
26273                if order {
26274                    self.write_keyword("ORDER");
26275                } else {
26276                    self.write_keyword("NOORDER");
26277                }
26278            }
26279        } else if matches!(self.config.dialect, Some(DialectType::PostgreSQL)) {
26280            self.write_keyword("GENERATED BY DEFAULT AS IDENTITY");
26281            if col.auto_increment_start.is_some() || col.auto_increment_increment.is_some() {
26282                self.write(" (");
26283                let mut first = true;
26284                if let Some(ref start) = col.auto_increment_start {
26285                    self.write_keyword("START WITH");
26286                    self.write_space();
26287                    self.generate_expression(start)?;
26288                    first = false;
26289                }
26290                if let Some(ref inc) = col.auto_increment_increment {
26291                    if !first {
26292                        self.write_space();
26293                    }
26294                    self.write_keyword("INCREMENT BY");
26295                    self.write_space();
26296                    self.generate_expression(inc)?;
26297                }
26298                self.write(")");
26299            }
26300        } else if matches!(self.config.dialect, Some(DialectType::Databricks)) {
26301            // IDENTITY(start, increment) -> GENERATED BY DEFAULT AS IDENTITY
26302            // Plain IDENTITY/AUTO_INCREMENT -> GENERATED ALWAYS AS IDENTITY
26303            if col.auto_increment_start.is_some() || col.auto_increment_increment.is_some() {
26304                self.write_keyword("GENERATED BY DEFAULT AS IDENTITY");
26305            } else {
26306                self.write_keyword("GENERATED ALWAYS AS IDENTITY");
26307            }
26308            if col.auto_increment_start.is_some() || col.auto_increment_increment.is_some() {
26309                self.write(" (");
26310                let mut first = true;
26311                if let Some(ref start) = col.auto_increment_start {
26312                    self.write_keyword("START WITH");
26313                    self.write_space();
26314                    self.generate_expression(start)?;
26315                    first = false;
26316                }
26317                if let Some(ref inc) = col.auto_increment_increment {
26318                    if !first {
26319                        self.write_space();
26320                    }
26321                    self.write_keyword("INCREMENT BY");
26322                    self.write_space();
26323                    self.generate_expression(inc)?;
26324                }
26325                self.write(")");
26326            }
26327        } else if matches!(
26328            self.config.dialect,
26329            Some(DialectType::TSQL) | Some(DialectType::Fabric)
26330        ) {
26331            self.write_keyword("IDENTITY");
26332            if col.auto_increment_start.is_some() || col.auto_increment_increment.is_some() {
26333                self.write("(");
26334                if let Some(ref start) = col.auto_increment_start {
26335                    self.generate_expression(start)?;
26336                } else {
26337                    self.write("0");
26338                }
26339                self.write(", ");
26340                if let Some(ref inc) = col.auto_increment_increment {
26341                    self.generate_expression(inc)?;
26342                } else {
26343                    self.write("1");
26344                }
26345                self.write(")");
26346            }
26347        } else {
26348            self.write_keyword("AUTO_INCREMENT");
26349            if let Some(ref start) = col.auto_increment_start {
26350                self.write_space();
26351                self.write_keyword("START");
26352                self.write_space();
26353                self.generate_expression(start)?;
26354            }
26355            if let Some(ref inc) = col.auto_increment_increment {
26356                self.write_space();
26357                self.write_keyword("INCREMENT");
26358                self.write_space();
26359                self.generate_expression(inc)?;
26360            }
26361            if let Some(order) = col.auto_increment_order {
26362                self.write_space();
26363                if order {
26364                    self.write_keyword("ORDER");
26365                } else {
26366                    self.write_keyword("NOORDER");
26367                }
26368            }
26369        }
26370        Ok(())
26371    }
26372
26373    fn generate_auto_increment_property(&mut self, e: &AutoIncrementProperty) -> Result<()> {
26374        // AUTO_INCREMENT=value
26375        self.write_keyword("AUTO_INCREMENT");
26376        self.write("=");
26377        self.generate_expression(&e.this)?;
26378        Ok(())
26379    }
26380
26381    fn generate_auto_refresh_property(&mut self, e: &AutoRefreshProperty) -> Result<()> {
26382        // AUTO_REFRESH=value
26383        self.write_keyword("AUTO_REFRESH");
26384        self.write("=");
26385        self.generate_expression(&e.this)?;
26386        Ok(())
26387    }
26388
26389    fn generate_backup_property(&mut self, e: &BackupProperty) -> Result<()> {
26390        // BACKUP YES|NO (Redshift syntax uses space, not equals)
26391        self.write_keyword("BACKUP");
26392        self.write_space();
26393        self.generate_expression(&e.this)?;
26394        Ok(())
26395    }
26396
26397    fn generate_base64_decode_binary(&mut self, e: &Base64DecodeBinary) -> Result<()> {
26398        // BASE64_DECODE_BINARY(this[, alphabet])
26399        self.write_keyword("BASE64_DECODE_BINARY");
26400        self.write("(");
26401        self.generate_expression(&e.this)?;
26402        if let Some(alphabet) = &e.alphabet {
26403            self.write(", ");
26404            self.generate_expression(alphabet)?;
26405        }
26406        self.write(")");
26407        Ok(())
26408    }
26409
26410    fn generate_base64_decode_string(&mut self, e: &Base64DecodeString) -> Result<()> {
26411        // BASE64_DECODE_STRING(this[, alphabet])
26412        self.write_keyword("BASE64_DECODE_STRING");
26413        self.write("(");
26414        self.generate_expression(&e.this)?;
26415        if let Some(alphabet) = &e.alphabet {
26416            self.write(", ");
26417            self.generate_expression(alphabet)?;
26418        }
26419        self.write(")");
26420        Ok(())
26421    }
26422
26423    fn generate_base64_encode(&mut self, e: &Base64Encode) -> Result<()> {
26424        // BASE64_ENCODE(this[, max_line_length][, alphabet])
26425        self.write_keyword("BASE64_ENCODE");
26426        self.write("(");
26427        self.generate_expression(&e.this)?;
26428        if let Some(max_line_length) = &e.max_line_length {
26429            self.write(", ");
26430            self.generate_expression(max_line_length)?;
26431        }
26432        if let Some(alphabet) = &e.alphabet {
26433            self.write(", ");
26434            self.generate_expression(alphabet)?;
26435        }
26436        self.write(")");
26437        Ok(())
26438    }
26439
26440    fn generate_block_compression_property(&mut self, e: &BlockCompressionProperty) -> Result<()> {
26441        // BLOCKCOMPRESSION=... (complex Teradata property)
26442        self.write_keyword("BLOCKCOMPRESSION");
26443        self.write("=");
26444        if let Some(autotemp) = &e.autotemp {
26445            self.write_keyword("AUTOTEMP");
26446            self.write("(");
26447            self.generate_expression(autotemp)?;
26448            self.write(")");
26449        }
26450        if let Some(always) = &e.always {
26451            self.generate_expression(always)?;
26452        }
26453        if let Some(default) = &e.default {
26454            self.generate_expression(default)?;
26455        }
26456        if let Some(manual) = &e.manual {
26457            self.generate_expression(manual)?;
26458        }
26459        if let Some(never) = &e.never {
26460            self.generate_expression(never)?;
26461        }
26462        Ok(())
26463    }
26464
26465    fn generate_booland(&mut self, e: &Booland) -> Result<()> {
26466        // Python: return f"(({self.sql(expression, 'this')}) AND ({self.sql(expression, 'expression')}))"
26467        self.write("((");
26468        self.generate_expression(&e.this)?;
26469        self.write(") ");
26470        self.write_keyword("AND");
26471        self.write(" (");
26472        self.generate_expression(&e.expression)?;
26473        self.write("))");
26474        Ok(())
26475    }
26476
26477    fn generate_boolor(&mut self, e: &Boolor) -> Result<()> {
26478        // Python: return f"(({self.sql(expression, 'this')}) OR ({self.sql(expression, 'expression')}))"
26479        self.write("((");
26480        self.generate_expression(&e.this)?;
26481        self.write(") ");
26482        self.write_keyword("OR");
26483        self.write(" (");
26484        self.generate_expression(&e.expression)?;
26485        self.write("))");
26486        Ok(())
26487    }
26488
26489    fn generate_build_property(&mut self, e: &BuildProperty) -> Result<()> {
26490        // BUILD value (e.g., BUILD IMMEDIATE, BUILD DEFERRED)
26491        self.write_keyword("BUILD");
26492        self.write_space();
26493        self.generate_expression(&e.this)?;
26494        Ok(())
26495    }
26496
26497    fn generate_byte_string(&mut self, e: &ByteString) -> Result<()> {
26498        // Byte string literal like B'...' or X'...'
26499        self.generate_expression(&e.this)?;
26500        Ok(())
26501    }
26502
26503    fn generate_case_specific_column_constraint(
26504        &mut self,
26505        e: &CaseSpecificColumnConstraint,
26506    ) -> Result<()> {
26507        // CASESPECIFIC or NOT CASESPECIFIC (Teradata)
26508        if e.not_.is_some() {
26509            self.write_keyword("NOT");
26510            self.write_space();
26511        }
26512        self.write_keyword("CASESPECIFIC");
26513        Ok(())
26514    }
26515
26516    fn generate_cast_to_str_type(&mut self, e: &CastToStrType) -> Result<()> {
26517        // Cast to string type (dialect-specific)
26518        self.write_keyword("CAST");
26519        self.write("(");
26520        self.generate_expression(&e.this)?;
26521        if self.config.dialect == Some(DialectType::ClickHouse) {
26522            // ClickHouse: CAST(expr, 'type_string')
26523            self.write(", ");
26524        } else {
26525            self.write_space();
26526            self.write_keyword("AS");
26527            self.write_space();
26528        }
26529        if let Some(to) = &e.to {
26530            self.generate_expression(to)?;
26531        }
26532        self.write(")");
26533        Ok(())
26534    }
26535
26536    fn generate_changes(&mut self, e: &Changes) -> Result<()> {
26537        // CHANGES (INFORMATION => value) AT|BEFORE (...) END (...)
26538        // Python: f"CHANGES ({information}){at_before}{end}"
26539        self.write_keyword("CHANGES");
26540        self.write(" (");
26541        if let Some(information) = &e.information {
26542            self.write_keyword("INFORMATION");
26543            self.write(" => ");
26544            self.generate_expression(information)?;
26545        }
26546        self.write(")");
26547        // at_before and end are HistoricalData expressions that generate their own keywords
26548        if let Some(at_before) = &e.at_before {
26549            self.write(" ");
26550            self.generate_expression(at_before)?;
26551        }
26552        if let Some(end) = &e.end {
26553            self.write(" ");
26554            self.generate_expression(end)?;
26555        }
26556        Ok(())
26557    }
26558
26559    fn generate_character_set_column_constraint(
26560        &mut self,
26561        e: &CharacterSetColumnConstraint,
26562    ) -> Result<()> {
26563        // CHARACTER SET charset_name
26564        self.write_keyword("CHARACTER SET");
26565        self.write_space();
26566        self.generate_expression(&e.this)?;
26567        Ok(())
26568    }
26569
26570    fn generate_character_set_property(&mut self, e: &CharacterSetProperty) -> Result<()> {
26571        // [DEFAULT] CHARACTER SET=value
26572        if e.default.is_some() {
26573            self.write_keyword("DEFAULT");
26574            self.write_space();
26575        }
26576        self.write_keyword("CHARACTER SET");
26577        self.write("=");
26578        self.generate_expression(&e.this)?;
26579        Ok(())
26580    }
26581
26582    fn generate_check_column_constraint(&mut self, e: &CheckColumnConstraint) -> Result<()> {
26583        // Python: return f"CHECK ({self.sql(expression, 'this')}){enforced}"
26584        self.write_keyword("CHECK");
26585        self.write(" (");
26586        self.generate_expression(&e.this)?;
26587        self.write(")");
26588        if e.enforced.is_some() {
26589            self.write_space();
26590            self.write_keyword("ENFORCED");
26591        }
26592        Ok(())
26593    }
26594
26595    fn generate_assume_column_constraint(&mut self, e: &AssumeColumnConstraint) -> Result<()> {
26596        // Python: return f"ASSUME ({self.sql(e, 'this')})"
26597        self.write_keyword("ASSUME");
26598        self.write(" (");
26599        self.generate_expression(&e.this)?;
26600        self.write(")");
26601        Ok(())
26602    }
26603
26604    fn generate_check_json(&mut self, e: &CheckJson) -> Result<()> {
26605        // CHECK_JSON(this)
26606        self.write_keyword("CHECK_JSON");
26607        self.write("(");
26608        self.generate_expression(&e.this)?;
26609        self.write(")");
26610        Ok(())
26611    }
26612
26613    fn generate_check_xml(&mut self, e: &CheckXml) -> Result<()> {
26614        // CHECK_XML(this)
26615        self.write_keyword("CHECK_XML");
26616        self.write("(");
26617        self.generate_expression(&e.this)?;
26618        self.write(")");
26619        Ok(())
26620    }
26621
26622    fn generate_checksum_property(&mut self, e: &ChecksumProperty) -> Result<()> {
26623        // CHECKSUM=[ON|OFF|DEFAULT]
26624        self.write_keyword("CHECKSUM");
26625        self.write("=");
26626        if e.on.is_some() {
26627            self.write_keyword("ON");
26628        } else if e.default.is_some() {
26629            self.write_keyword("DEFAULT");
26630        } else {
26631            self.write_keyword("OFF");
26632        }
26633        Ok(())
26634    }
26635
26636    fn generate_clone(&mut self, e: &Clone) -> Result<()> {
26637        // Python: return f"{shallow}{keyword} {this}"
26638        if e.shallow.is_some() {
26639            self.write_keyword("SHALLOW");
26640            self.write_space();
26641        }
26642        if e.copy.is_some() {
26643            self.write_keyword("COPY");
26644        } else {
26645            self.write_keyword("CLONE");
26646        }
26647        self.write_space();
26648        self.generate_expression(&e.this)?;
26649        Ok(())
26650    }
26651
26652    fn generate_cluster_by(&mut self, e: &ClusterBy) -> Result<()> {
26653        // CLUSTER BY (expressions)
26654        self.write_keyword("CLUSTER BY");
26655        self.write(" (");
26656        for (i, ord) in e.expressions.iter().enumerate() {
26657            if i > 0 {
26658                self.write(", ");
26659            }
26660            self.generate_ordered(ord)?;
26661        }
26662        self.write(")");
26663        Ok(())
26664    }
26665
26666    fn generate_cluster_by_columns_property(&mut self, e: &ClusterByColumnsProperty) -> Result<()> {
26667        // BigQuery table property: CLUSTER BY col1, col2
26668        self.write_keyword("CLUSTER BY");
26669        self.write_space();
26670        for (i, col) in e.columns.iter().enumerate() {
26671            if i > 0 {
26672                self.write(", ");
26673            }
26674            self.generate_identifier(col)?;
26675        }
26676        Ok(())
26677    }
26678
26679    fn generate_clustered_by_property(&mut self, e: &ClusteredByProperty) -> Result<()> {
26680        // Python: return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS"
26681        self.write_keyword("CLUSTERED BY");
26682        self.write(" (");
26683        for (i, expr) in e.expressions.iter().enumerate() {
26684            if i > 0 {
26685                self.write(", ");
26686            }
26687            self.generate_expression(expr)?;
26688        }
26689        self.write(")");
26690        if let Some(sorted_by) = &e.sorted_by {
26691            self.write_space();
26692            self.write_keyword("SORTED BY");
26693            self.write(" (");
26694            // Unwrap Tuple to avoid double parentheses
26695            if let Expression::Tuple(t) = sorted_by.as_ref() {
26696                for (i, expr) in t.expressions.iter().enumerate() {
26697                    if i > 0 {
26698                        self.write(", ");
26699                    }
26700                    self.generate_expression(expr)?;
26701                }
26702            } else {
26703                self.generate_expression(sorted_by)?;
26704            }
26705            self.write(")");
26706        }
26707        if let Some(buckets) = &e.buckets {
26708            self.write_space();
26709            self.write_keyword("INTO");
26710            self.write_space();
26711            self.generate_expression(buckets)?;
26712            self.write_space();
26713            self.write_keyword("BUCKETS");
26714        }
26715        Ok(())
26716    }
26717
26718    fn generate_collate_property(&mut self, e: &CollateProperty) -> Result<()> {
26719        // [DEFAULT] COLLATE [=] value
26720        // BigQuery uses space: DEFAULT COLLATE 'en'
26721        // Others use equals: COLLATE='en'
26722        if e.default.is_some() {
26723            self.write_keyword("DEFAULT");
26724            self.write_space();
26725        }
26726        self.write_keyword("COLLATE");
26727        // BigQuery uses space between COLLATE and value
26728        match self.config.dialect {
26729            Some(DialectType::BigQuery) => self.write_space(),
26730            _ => self.write("="),
26731        }
26732        self.generate_expression(&e.this)?;
26733        Ok(())
26734    }
26735
26736    fn generate_column_constraint(&mut self, e: &ColumnConstraint) -> Result<()> {
26737        // ColumnConstraint is an enum
26738        match e {
26739            ColumnConstraint::NotNull => {
26740                self.write_keyword("NOT NULL");
26741            }
26742            ColumnConstraint::Null => {
26743                self.write_keyword("NULL");
26744            }
26745            ColumnConstraint::Unique => {
26746                self.write_keyword("UNIQUE");
26747            }
26748            ColumnConstraint::PrimaryKey => {
26749                self.write_keyword("PRIMARY KEY");
26750            }
26751            ColumnConstraint::Default(expr) => {
26752                self.write_keyword("DEFAULT");
26753                self.write_space();
26754                self.generate_expression(expr)?;
26755            }
26756            ColumnConstraint::Check(expr) => {
26757                self.write_keyword("CHECK");
26758                self.write(" (");
26759                self.generate_expression(expr)?;
26760                self.write(")");
26761            }
26762            ColumnConstraint::References(fk_ref) => {
26763                if fk_ref.has_foreign_key_keywords {
26764                    self.write_keyword("FOREIGN KEY");
26765                    self.write_space();
26766                }
26767                self.write_keyword("REFERENCES");
26768                self.write_space();
26769                self.generate_table(&fk_ref.table)?;
26770                if !fk_ref.columns.is_empty() {
26771                    self.write(" (");
26772                    for (i, col) in fk_ref.columns.iter().enumerate() {
26773                        if i > 0 {
26774                            self.write(", ");
26775                        }
26776                        self.generate_identifier(col)?;
26777                    }
26778                    self.write(")");
26779                }
26780            }
26781            ColumnConstraint::GeneratedAsIdentity(gen) => {
26782                self.write_keyword("GENERATED");
26783                self.write_space();
26784                if gen.always {
26785                    self.write_keyword("ALWAYS");
26786                } else {
26787                    self.write_keyword("BY DEFAULT");
26788                    if gen.on_null {
26789                        self.write_space();
26790                        self.write_keyword("ON NULL");
26791                    }
26792                }
26793                self.write_space();
26794                self.write_keyword("AS IDENTITY");
26795            }
26796            ColumnConstraint::Collate(collation) => {
26797                self.write_keyword("COLLATE");
26798                self.write_space();
26799                self.generate_identifier(collation)?;
26800            }
26801            ColumnConstraint::Comment(comment) => {
26802                self.write_keyword("COMMENT");
26803                self.write(" '");
26804                self.write(comment);
26805                self.write("'");
26806            }
26807            ColumnConstraint::ComputedColumn(cc) => {
26808                self.generate_computed_column_inline(cc)?;
26809            }
26810            ColumnConstraint::GeneratedAsRow(gar) => {
26811                self.generate_generated_as_row_inline(gar)?;
26812            }
26813            ColumnConstraint::Tags(tags) => {
26814                self.write_keyword("TAG");
26815                self.write(" (");
26816                for (i, expr) in tags.expressions.iter().enumerate() {
26817                    if i > 0 {
26818                        self.write(", ");
26819                    }
26820                    self.generate_expression(expr)?;
26821                }
26822                self.write(")");
26823            }
26824            ColumnConstraint::Path(path_expr) => {
26825                self.write_keyword("PATH");
26826                self.write_space();
26827                self.generate_expression(path_expr)?;
26828            }
26829        }
26830        Ok(())
26831    }
26832
26833    fn generate_column_position(&mut self, e: &ColumnPosition) -> Result<()> {
26834        // ColumnPosition is an enum
26835        match e {
26836            ColumnPosition::First => {
26837                self.write_keyword("FIRST");
26838            }
26839            ColumnPosition::After(ident) => {
26840                self.write_keyword("AFTER");
26841                self.write_space();
26842                self.generate_identifier(ident)?;
26843            }
26844        }
26845        Ok(())
26846    }
26847
26848    fn generate_column_prefix(&mut self, e: &ColumnPrefix) -> Result<()> {
26849        // column(prefix)
26850        self.generate_expression(&e.this)?;
26851        self.write("(");
26852        self.generate_expression(&e.expression)?;
26853        self.write(")");
26854        Ok(())
26855    }
26856
26857    fn generate_columns(&mut self, e: &Columns) -> Result<()> {
26858        // If unpack is true, this came from * COLUMNS(pattern)
26859        // DuckDB syntax: * COLUMNS(c ILIKE '%suffix') or COLUMNS(pattern)
26860        if let Some(ref unpack) = e.unpack {
26861            if let Expression::Boolean(b) = unpack.as_ref() {
26862                if b.value {
26863                    self.write("*");
26864                }
26865            }
26866        }
26867        self.write_keyword("COLUMNS");
26868        self.write("(");
26869        self.generate_expression(&e.this)?;
26870        self.write(")");
26871        Ok(())
26872    }
26873
26874    fn generate_combined_agg_func(&mut self, e: &CombinedAggFunc) -> Result<()> {
26875        // Combined aggregate: FUNC(args) combined
26876        self.generate_expression(&e.this)?;
26877        self.write("(");
26878        for (i, expr) in e.expressions.iter().enumerate() {
26879            if i > 0 {
26880                self.write(", ");
26881            }
26882            self.generate_expression(expr)?;
26883        }
26884        self.write(")");
26885        Ok(())
26886    }
26887
26888    fn generate_combined_parameterized_agg(&mut self, e: &CombinedParameterizedAgg) -> Result<()> {
26889        // Combined parameterized aggregate: FUNC(params)(expressions)
26890        self.generate_expression(&e.this)?;
26891        self.write("(");
26892        for (i, param) in e.params.iter().enumerate() {
26893            if i > 0 {
26894                self.write(", ");
26895            }
26896            self.generate_expression(param)?;
26897        }
26898        self.write(")(");
26899        for (i, expr) in e.expressions.iter().enumerate() {
26900            if i > 0 {
26901                self.write(", ");
26902            }
26903            self.generate_expression(expr)?;
26904        }
26905        self.write(")");
26906        Ok(())
26907    }
26908
26909    fn generate_commit(&mut self, e: &Commit) -> Result<()> {
26910        // COMMIT [TRANSACTION [transaction_name]] [WITH (DELAYED_DURABILITY = ON|OFF)] [AND [NO] CHAIN]
26911        self.write_keyword("COMMIT");
26912
26913        // TSQL always uses COMMIT TRANSACTION
26914        if e.this.is_none()
26915            && matches!(
26916                self.config.dialect,
26917                Some(DialectType::TSQL) | Some(DialectType::Fabric)
26918            )
26919        {
26920            self.write_space();
26921            self.write_keyword("TRANSACTION");
26922        }
26923
26924        // Check if this has TRANSACTION keyword or transaction name
26925        if let Some(this) = &e.this {
26926            // Check if it's just the "TRANSACTION" marker or an actual transaction name
26927            let is_transaction_marker = matches!(
26928                this.as_ref(),
26929                Expression::Identifier(id) if id.name == "TRANSACTION"
26930            );
26931
26932            self.write_space();
26933            self.write_keyword("TRANSACTION");
26934
26935            // If it's a real transaction name, output it
26936            if !is_transaction_marker {
26937                self.write_space();
26938                self.generate_expression(this)?;
26939            }
26940        }
26941
26942        // Output WITH (DELAYED_DURABILITY = ON|OFF) for TSQL
26943        if let Some(durability) = &e.durability {
26944            self.write_space();
26945            self.write_keyword("WITH");
26946            self.write(" (");
26947            self.write_keyword("DELAYED_DURABILITY");
26948            self.write(" = ");
26949            if let Expression::Boolean(BooleanLiteral { value: true }) = durability.as_ref() {
26950                self.write_keyword("ON");
26951            } else {
26952                self.write_keyword("OFF");
26953            }
26954            self.write(")");
26955        }
26956
26957        // Output AND [NO] CHAIN
26958        if let Some(chain) = &e.chain {
26959            self.write_space();
26960            if let Expression::Boolean(BooleanLiteral { value: false }) = chain.as_ref() {
26961                self.write_keyword("AND NO CHAIN");
26962            } else {
26963                self.write_keyword("AND CHAIN");
26964            }
26965        }
26966        Ok(())
26967    }
26968
26969    fn generate_comprehension(&mut self, e: &Comprehension) -> Result<()> {
26970        // Python-style comprehension: [expr FOR var[, pos] IN iterator IF condition]
26971        self.write("[");
26972        self.generate_expression(&e.this)?;
26973        self.write_space();
26974        self.write_keyword("FOR");
26975        self.write_space();
26976        self.generate_expression(&e.expression)?;
26977        // Handle optional position variable (for enumerate-like syntax)
26978        if let Some(pos) = &e.position {
26979            self.write(", ");
26980            self.generate_expression(pos)?;
26981        }
26982        if let Some(iterator) = &e.iterator {
26983            self.write_space();
26984            self.write_keyword("IN");
26985            self.write_space();
26986            self.generate_expression(iterator)?;
26987        }
26988        if let Some(condition) = &e.condition {
26989            self.write_space();
26990            self.write_keyword("IF");
26991            self.write_space();
26992            self.generate_expression(condition)?;
26993        }
26994        self.write("]");
26995        Ok(())
26996    }
26997
26998    fn generate_compress(&mut self, e: &Compress) -> Result<()> {
26999        // COMPRESS(this[, method])
27000        self.write_keyword("COMPRESS");
27001        self.write("(");
27002        self.generate_expression(&e.this)?;
27003        if let Some(method) = &e.method {
27004            self.write(", '");
27005            self.write(method);
27006            self.write("'");
27007        }
27008        self.write(")");
27009        Ok(())
27010    }
27011
27012    fn generate_compress_column_constraint(&mut self, e: &CompressColumnConstraint) -> Result<()> {
27013        // Python: return f"COMPRESS {this}"
27014        self.write_keyword("COMPRESS");
27015        if let Some(this) = &e.this {
27016            self.write_space();
27017            self.generate_expression(this)?;
27018        }
27019        Ok(())
27020    }
27021
27022    fn generate_computed_column_constraint(&mut self, e: &ComputedColumnConstraint) -> Result<()> {
27023        // Python: return f"AS {this}{persisted}"
27024        self.write_keyword("AS");
27025        self.write_space();
27026        self.generate_expression(&e.this)?;
27027        if e.not_null.is_some() {
27028            self.write_space();
27029            self.write_keyword("PERSISTED NOT NULL");
27030        } else if e.persisted.is_some() {
27031            self.write_space();
27032            self.write_keyword("PERSISTED");
27033        }
27034        Ok(())
27035    }
27036
27037    /// Generate a ComputedColumn constraint inline within a column definition.
27038    /// Handles MySQL/PostgreSQL: GENERATED ALWAYS AS (expr) STORED|VIRTUAL
27039    /// Handles TSQL: AS (expr) [PERSISTED] [NOT NULL]
27040    fn generate_computed_column_inline(&mut self, cc: &ComputedColumn) -> Result<()> {
27041        let computed_expr = if matches!(
27042            self.config.dialect,
27043            Some(DialectType::TSQL) | Some(DialectType::Fabric)
27044        ) {
27045            match &*cc.expression {
27046                Expression::Year(y) if !matches!(&y.this, Expression::Cast(c) if matches!(c.to, DataType::Date)) =>
27047                {
27048                    let wrapped = Expression::Cast(Box::new(Cast {
27049                        this: y.this.clone(),
27050                        to: DataType::Date,
27051                        trailing_comments: Vec::new(),
27052                        double_colon_syntax: false,
27053                        format: None,
27054                        default: None,
27055                        inferred_type: None,
27056                    }));
27057                    Expression::Year(Box::new(UnaryFunc::new(wrapped)))
27058                }
27059                Expression::Function(f)
27060                    if f.name.eq_ignore_ascii_case("YEAR")
27061                        && f.args.len() == 1
27062                        && !matches!(&f.args[0], Expression::Cast(c) if matches!(c.to, DataType::Date)) =>
27063                {
27064                    let wrapped = Expression::Cast(Box::new(Cast {
27065                        this: f.args[0].clone(),
27066                        to: DataType::Date,
27067                        trailing_comments: Vec::new(),
27068                        double_colon_syntax: false,
27069                        format: None,
27070                        default: None,
27071                        inferred_type: None,
27072                    }));
27073                    Expression::Function(Box::new(Function::new("YEAR".to_string(), vec![wrapped])))
27074                }
27075                _ => *cc.expression.clone(),
27076            }
27077        } else {
27078            *cc.expression.clone()
27079        };
27080
27081        match cc.persistence_kind.as_deref() {
27082            Some("STORED") | Some("VIRTUAL") => {
27083                // MySQL/PostgreSQL: GENERATED ALWAYS AS (expr) STORED|VIRTUAL
27084                self.write_keyword("GENERATED ALWAYS AS");
27085                self.write(" (");
27086                self.generate_expression(&computed_expr)?;
27087                self.write(")");
27088                self.write_space();
27089                if cc.persisted {
27090                    self.write_keyword("STORED");
27091                } else {
27092                    self.write_keyword("VIRTUAL");
27093                }
27094            }
27095            Some("PERSISTED") => {
27096                // TSQL/SingleStore: AS (expr) PERSISTED [TYPE] [NOT NULL]
27097                self.write_keyword("AS");
27098                self.write(" (");
27099                self.generate_expression(&computed_expr)?;
27100                self.write(")");
27101                self.write_space();
27102                self.write_keyword("PERSISTED");
27103                // Output data type if present (SingleStore: PERSISTED TYPE NOT NULL)
27104                if let Some(ref dt) = cc.data_type {
27105                    self.write_space();
27106                    self.generate_data_type(dt)?;
27107                }
27108                if cc.not_null {
27109                    self.write_space();
27110                    self.write_keyword("NOT NULL");
27111                }
27112            }
27113            _ => {
27114                // Spark/Databricks/Hive: GENERATED ALWAYS AS (expr)
27115                // TSQL computed column without PERSISTED: AS (expr)
27116                if matches!(
27117                    self.config.dialect,
27118                    Some(DialectType::Spark)
27119                        | Some(DialectType::Databricks)
27120                        | Some(DialectType::Hive)
27121                ) {
27122                    self.write_keyword("GENERATED ALWAYS AS");
27123                    self.write(" (");
27124                    self.generate_expression(&computed_expr)?;
27125                    self.write(")");
27126                } else if matches!(
27127                    self.config.dialect,
27128                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
27129                ) {
27130                    self.write_keyword("AS");
27131                    let omit_parens = matches!(computed_expr, Expression::Year(_))
27132                        || matches!(&computed_expr, Expression::Function(f) if f.name.eq_ignore_ascii_case("YEAR"));
27133                    if omit_parens {
27134                        self.write_space();
27135                        self.generate_expression(&computed_expr)?;
27136                    } else {
27137                        self.write(" (");
27138                        self.generate_expression(&computed_expr)?;
27139                        self.write(")");
27140                    }
27141                } else {
27142                    self.write_keyword("AS");
27143                    self.write(" (");
27144                    self.generate_expression(&computed_expr)?;
27145                    self.write(")");
27146                }
27147            }
27148        }
27149        Ok(())
27150    }
27151
27152    /// Generate a GeneratedAsRow constraint inline within a column definition.
27153    /// TSQL temporal: GENERATED ALWAYS AS ROW START|END [HIDDEN]
27154    fn generate_generated_as_row_inline(&mut self, gar: &GeneratedAsRow) -> Result<()> {
27155        self.write_keyword("GENERATED ALWAYS AS ROW ");
27156        if gar.start {
27157            self.write_keyword("START");
27158        } else {
27159            self.write_keyword("END");
27160        }
27161        if gar.hidden {
27162            self.write_space();
27163            self.write_keyword("HIDDEN");
27164        }
27165        Ok(())
27166    }
27167
27168    /// Generate just the SYSTEM_VERSIONING=ON(...) content without WITH() wrapper.
27169    fn generate_system_versioning_content(
27170        &mut self,
27171        e: &WithSystemVersioningProperty,
27172    ) -> Result<()> {
27173        let mut parts = Vec::new();
27174
27175        if let Some(this) = &e.this {
27176            let mut s = String::from("HISTORY_TABLE=");
27177            let mut gen = Generator::with_arc_config(self.config.clone());
27178            gen.generate_expression(this)?;
27179            s.push_str(&gen.output);
27180            parts.push(s);
27181        }
27182
27183        if let Some(data_consistency) = &e.data_consistency {
27184            let mut s = String::from("DATA_CONSISTENCY_CHECK=");
27185            let mut gen = Generator::with_arc_config(self.config.clone());
27186            gen.generate_expression(data_consistency)?;
27187            s.push_str(&gen.output);
27188            parts.push(s);
27189        }
27190
27191        if let Some(retention_period) = &e.retention_period {
27192            let mut s = String::from("HISTORY_RETENTION_PERIOD=");
27193            let mut gen = Generator::with_arc_config(self.config.clone());
27194            gen.generate_expression(retention_period)?;
27195            s.push_str(&gen.output);
27196            parts.push(s);
27197        }
27198
27199        self.write_keyword("SYSTEM_VERSIONING");
27200        self.write("=");
27201
27202        if !parts.is_empty() {
27203            self.write_keyword("ON");
27204            self.write("(");
27205            self.write(&parts.join(", "));
27206            self.write(")");
27207        } else if e.on.is_some() {
27208            self.write_keyword("ON");
27209        } else {
27210            self.write_keyword("OFF");
27211        }
27212
27213        Ok(())
27214    }
27215
27216    fn generate_conditional_insert(&mut self, e: &ConditionalInsert) -> Result<()> {
27217        // Conditional INSERT for multi-table inserts
27218        // Output: [WHEN cond THEN | ELSE] INTO table [(cols)] [VALUES (...)]
27219        if e.else_.is_some() {
27220            self.write_keyword("ELSE");
27221            self.write_space();
27222        } else if let Some(expression) = &e.expression {
27223            self.write_keyword("WHEN");
27224            self.write_space();
27225            self.generate_expression(expression)?;
27226            self.write_space();
27227            self.write_keyword("THEN");
27228            self.write_space();
27229        }
27230
27231        // Handle Insert expression specially - output "INTO table (cols) VALUES (...)"
27232        // without the "INSERT " prefix
27233        if let Expression::Insert(insert) = e.this.as_ref() {
27234            self.write_keyword("INTO");
27235            self.write_space();
27236            self.generate_table(&insert.table)?;
27237
27238            // Optional column list
27239            if !insert.columns.is_empty() {
27240                self.write(" (");
27241                for (i, col) in insert.columns.iter().enumerate() {
27242                    if i > 0 {
27243                        self.write(", ");
27244                    }
27245                    self.generate_identifier(col)?;
27246                }
27247                self.write(")");
27248            }
27249
27250            // Optional VALUES clause
27251            if !insert.values.is_empty() {
27252                self.write_space();
27253                self.write_keyword("VALUES");
27254                for (row_idx, row) in insert.values.iter().enumerate() {
27255                    if row_idx > 0 {
27256                        self.write(", ");
27257                    }
27258                    self.write(" (");
27259                    for (i, val) in row.iter().enumerate() {
27260                        if i > 0 {
27261                            self.write(", ");
27262                        }
27263                        self.generate_expression(val)?;
27264                    }
27265                    self.write(")");
27266                }
27267            }
27268        } else {
27269            // Fallback for non-Insert expressions
27270            self.generate_expression(&e.this)?;
27271        }
27272        Ok(())
27273    }
27274
27275    fn generate_constraint(&mut self, e: &Constraint) -> Result<()> {
27276        // Python: return f"CONSTRAINT {this} {expressions}"
27277        self.write_keyword("CONSTRAINT");
27278        self.write_space();
27279        self.generate_expression(&e.this)?;
27280        if !e.expressions.is_empty() {
27281            self.write_space();
27282            for (i, expr) in e.expressions.iter().enumerate() {
27283                if i > 0 {
27284                    self.write_space();
27285                }
27286                self.generate_expression(expr)?;
27287            }
27288        }
27289        Ok(())
27290    }
27291
27292    fn generate_convert_timezone(&mut self, e: &ConvertTimezone) -> Result<()> {
27293        // CONVERT_TIMEZONE([source_tz,] target_tz, timestamp)
27294        self.write_keyword("CONVERT_TIMEZONE");
27295        self.write("(");
27296        let mut first = true;
27297        if let Some(source_tz) = &e.source_tz {
27298            self.generate_expression(source_tz)?;
27299            first = false;
27300        }
27301        if let Some(target_tz) = &e.target_tz {
27302            if !first {
27303                self.write(", ");
27304            }
27305            self.generate_expression(target_tz)?;
27306            first = false;
27307        }
27308        if let Some(timestamp) = &e.timestamp {
27309            if !first {
27310                self.write(", ");
27311            }
27312            self.generate_expression(timestamp)?;
27313        }
27314        self.write(")");
27315        Ok(())
27316    }
27317
27318    fn generate_convert_to_charset(&mut self, e: &ConvertToCharset) -> Result<()> {
27319        // CONVERT(this USING dest)
27320        self.write_keyword("CONVERT");
27321        self.write("(");
27322        self.generate_expression(&e.this)?;
27323        if let Some(dest) = &e.dest {
27324            self.write_space();
27325            self.write_keyword("USING");
27326            self.write_space();
27327            self.generate_expression(dest)?;
27328        }
27329        self.write(")");
27330        Ok(())
27331    }
27332
27333    fn generate_copy(&mut self, e: &CopyStmt) -> Result<()> {
27334        self.write_keyword("COPY");
27335        if e.is_into {
27336            self.write_space();
27337            self.write_keyword("INTO");
27338        }
27339        self.write_space();
27340
27341        // Generate target table or query (or stage for COPY INTO @stage)
27342        if let Expression::Literal(lit) = &e.this {
27343            if let Literal::String(s) = lit.as_ref() {
27344                if s.starts_with('@') {
27345                    self.write(s);
27346                } else {
27347                    self.generate_expression(&e.this)?;
27348                }
27349            }
27350        } else {
27351            self.generate_expression(&e.this)?;
27352        }
27353
27354        // FROM or TO based on kind
27355        if e.kind {
27356            // kind=true means FROM (loading into table)
27357            if self.config.pretty {
27358                self.write_newline();
27359            } else {
27360                self.write_space();
27361            }
27362            self.write_keyword("FROM");
27363            self.write_space();
27364        } else if !e.files.is_empty() {
27365            // kind=false means TO (exporting)
27366            if self.config.pretty {
27367                self.write_newline();
27368            } else {
27369                self.write_space();
27370            }
27371            self.write_keyword("TO");
27372            self.write_space();
27373        }
27374
27375        // Generate source/destination files
27376        for (i, file) in e.files.iter().enumerate() {
27377            if i > 0 {
27378                self.write_space();
27379            }
27380            // For stage references (strings starting with @), output without quotes
27381            if let Expression::Literal(lit) = file {
27382                if let Literal::String(s) = lit.as_ref() {
27383                    if s.starts_with('@') {
27384                        self.write(s);
27385                    } else {
27386                        self.generate_expression(file)?;
27387                    }
27388                }
27389            } else if let Expression::Identifier(id) = file {
27390                // Backtick-quoted file path (Databricks style: `s3://link`)
27391                if id.quoted {
27392                    self.write("`");
27393                    self.write(&id.name);
27394                    self.write("`");
27395                } else {
27396                    self.generate_expression(file)?;
27397                }
27398            } else {
27399                self.generate_expression(file)?;
27400            }
27401        }
27402
27403        // Generate credentials if present (Snowflake style - not wrapped in WITH)
27404        if !e.with_wrapped {
27405            if let Some(ref creds) = e.credentials {
27406                if let Some(ref storage) = creds.storage {
27407                    if self.config.pretty {
27408                        self.write_newline();
27409                    } else {
27410                        self.write_space();
27411                    }
27412                    self.write_keyword("STORAGE_INTEGRATION");
27413                    self.write(" = ");
27414                    self.write(storage);
27415                }
27416                if creds.credentials.is_empty() {
27417                    // Empty credentials: CREDENTIALS = ()
27418                    if self.config.pretty {
27419                        self.write_newline();
27420                    } else {
27421                        self.write_space();
27422                    }
27423                    self.write_keyword("CREDENTIALS");
27424                    self.write(" = ()");
27425                } else {
27426                    if self.config.pretty {
27427                        self.write_newline();
27428                    } else {
27429                        self.write_space();
27430                    }
27431                    self.write_keyword("CREDENTIALS");
27432                    // Check if this is Redshift-style (single value with empty key)
27433                    // vs Snowflake-style (multiple key=value pairs)
27434                    if creds.credentials.len() == 1 && creds.credentials[0].0.is_empty() {
27435                        // Redshift style: CREDENTIALS 'value'
27436                        self.write(" '");
27437                        self.write(&creds.credentials[0].1);
27438                        self.write("'");
27439                    } else {
27440                        // Snowflake style: CREDENTIALS = (KEY='value' ...)
27441                        self.write(" = (");
27442                        for (i, (k, v)) in creds.credentials.iter().enumerate() {
27443                            if i > 0 {
27444                                self.write_space();
27445                            }
27446                            self.write(k);
27447                            self.write("='");
27448                            self.write(v);
27449                            self.write("'");
27450                        }
27451                        self.write(")");
27452                    }
27453                }
27454                if let Some(ref encryption) = creds.encryption {
27455                    self.write_space();
27456                    self.write_keyword("ENCRYPTION");
27457                    self.write(" = ");
27458                    self.write(encryption);
27459                }
27460            }
27461        }
27462
27463        // Generate parameters
27464        if !e.params.is_empty() {
27465            if e.with_wrapped {
27466                // DuckDB/PostgreSQL/TSQL WITH (...) format
27467                self.write_space();
27468                self.write_keyword("WITH");
27469                self.write(" (");
27470                for (i, param) in e.params.iter().enumerate() {
27471                    if i > 0 {
27472                        self.write(", ");
27473                    }
27474                    self.generate_copy_param_with_format(param)?;
27475                }
27476                self.write(")");
27477            } else {
27478                // Snowflake/Redshift format: KEY = VALUE or KEY VALUE (space separated, no WITH wrapper)
27479                // For Redshift: IAM_ROLE value, CREDENTIALS 'value', REGION 'value', FORMAT type
27480                // For Snowflake: KEY = VALUE
27481                for param in &e.params {
27482                    if self.config.pretty {
27483                        self.write_newline();
27484                    } else {
27485                        self.write_space();
27486                    }
27487                    // Preserve original case of parameter name (important for Redshift COPY options)
27488                    self.write(&param.name);
27489                    if let Some(ref value) = param.value {
27490                        // Use = only if it was present in the original (param.eq)
27491                        if param.eq {
27492                            self.write(" = ");
27493                        } else {
27494                            self.write(" ");
27495                        }
27496                        if !param.values.is_empty() {
27497                            self.write("(");
27498                            for (i, v) in param.values.iter().enumerate() {
27499                                if i > 0 {
27500                                    self.write_space();
27501                                }
27502                                self.generate_copy_nested_param(v)?;
27503                            }
27504                            self.write(")");
27505                        } else {
27506                            // For COPY parameter values, output identifiers without quoting
27507                            self.generate_copy_param_value(value)?;
27508                        }
27509                    } else if !param.values.is_empty() {
27510                        // For varlen options like FORMAT_OPTIONS, COPY_OPTIONS - no = before (
27511                        if param.eq {
27512                            self.write(" = (");
27513                        } else {
27514                            self.write(" (");
27515                        }
27516                        // Determine separator for values inside parentheses:
27517                        // - Snowflake FILE_FORMAT = (TYPE=CSV FIELD_DELIMITER='|') → space-separated (has = before parens)
27518                        // - Databricks FORMAT_OPTIONS ('opt1'='true', 'opt2'='test') → comma-separated (no = before parens)
27519                        // - Simple value lists like FILES = ('file1', 'file2') → comma-separated
27520                        let is_key_value_pairs = param
27521                            .values
27522                            .first()
27523                            .map_or(false, |v| matches!(v, Expression::Eq(_)));
27524                        let sep = if is_key_value_pairs && param.eq {
27525                            " "
27526                        } else {
27527                            ", "
27528                        };
27529                        for (i, v) in param.values.iter().enumerate() {
27530                            if i > 0 {
27531                                self.write(sep);
27532                            }
27533                            self.generate_copy_nested_param(v)?;
27534                        }
27535                        self.write(")");
27536                    }
27537                }
27538            }
27539        }
27540
27541        Ok(())
27542    }
27543
27544    /// Generate a COPY parameter in WITH (...) format
27545    /// Handles both KEY = VALUE (TSQL) and KEY VALUE (DuckDB/PostgreSQL) formats
27546    fn generate_copy_param_with_format(&mut self, param: &CopyParameter) -> Result<()> {
27547        self.write_keyword(&param.name);
27548        if !param.values.is_empty() {
27549            // Nested values: CREDENTIAL = (IDENTITY='...', SECRET='...')
27550            self.write(" = (");
27551            for (i, v) in param.values.iter().enumerate() {
27552                if i > 0 {
27553                    self.write(", ");
27554                }
27555                self.generate_copy_nested_param(v)?;
27556            }
27557            self.write(")");
27558        } else if let Some(ref value) = param.value {
27559            if param.eq {
27560                self.write(" = ");
27561            } else {
27562                self.write(" ");
27563            }
27564            self.generate_expression(value)?;
27565        }
27566        Ok(())
27567    }
27568
27569    /// Generate nested parameter for COPY statements (KEY=VALUE without spaces)
27570    fn generate_copy_nested_param(&mut self, expr: &Expression) -> Result<()> {
27571        match expr {
27572            Expression::Eq(eq) => {
27573                // Generate key
27574                match &eq.left {
27575                    Expression::Column(c) => self.write(&c.name.name),
27576                    _ => self.generate_expression(&eq.left)?,
27577                }
27578                self.write("=");
27579                // Generate value
27580                match &eq.right {
27581                    Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
27582                        let Literal::String(s) = lit.as_ref() else {
27583                            unreachable!()
27584                        };
27585                        self.write("'");
27586                        self.write(s);
27587                        self.write("'");
27588                    }
27589                    Expression::Tuple(t) => {
27590                        // For lists like NULL_IF=('', 'str1')
27591                        self.write("(");
27592                        if self.config.pretty {
27593                            self.write_newline();
27594                            self.indent_level += 1;
27595                            for (i, item) in t.expressions.iter().enumerate() {
27596                                if i > 0 {
27597                                    self.write(", ");
27598                                }
27599                                self.write_indent();
27600                                self.generate_expression(item)?;
27601                            }
27602                            self.write_newline();
27603                            self.indent_level -= 1;
27604                        } else {
27605                            for (i, item) in t.expressions.iter().enumerate() {
27606                                if i > 0 {
27607                                    self.write(", ");
27608                                }
27609                                self.generate_expression(item)?;
27610                            }
27611                        }
27612                        self.write(")");
27613                    }
27614                    _ => self.generate_expression(&eq.right)?,
27615                }
27616                Ok(())
27617            }
27618            Expression::Column(c) => {
27619                // Standalone keyword like COMPRESSION
27620                self.write(&c.name.name);
27621                Ok(())
27622            }
27623            _ => self.generate_expression(expr),
27624        }
27625    }
27626
27627    /// Generate a COPY parameter value, outputting identifiers/columns without quoting
27628    /// This is needed for Redshift-style COPY params like: IAM_ROLE default, FORMAT orc
27629    fn generate_copy_param_value(&mut self, expr: &Expression) -> Result<()> {
27630        match expr {
27631            Expression::Column(c) => {
27632                // Output identifier, preserving quotes if originally quoted
27633                if c.name.quoted {
27634                    self.write("\"");
27635                    self.write(&c.name.name);
27636                    self.write("\"");
27637                } else {
27638                    self.write(&c.name.name);
27639                }
27640                Ok(())
27641            }
27642            Expression::Identifier(id) => {
27643                // Output identifier, preserving quotes if originally quoted
27644                if id.quoted {
27645                    self.write("\"");
27646                    self.write(&id.name);
27647                    self.write("\"");
27648                } else {
27649                    self.write(&id.name);
27650                }
27651                Ok(())
27652            }
27653            Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
27654                let Literal::String(s) = lit.as_ref() else {
27655                    unreachable!()
27656                };
27657                // Output string with quotes
27658                self.write("'");
27659                self.write(s);
27660                self.write("'");
27661                Ok(())
27662            }
27663            _ => self.generate_expression(expr),
27664        }
27665    }
27666
27667    fn generate_copy_parameter(&mut self, e: &CopyParameter) -> Result<()> {
27668        self.write_keyword(&e.name);
27669        if let Some(ref value) = e.value {
27670            if e.eq {
27671                self.write(" = ");
27672            } else {
27673                self.write(" ");
27674            }
27675            self.generate_expression(value)?;
27676        }
27677        if !e.values.is_empty() {
27678            if e.eq {
27679                self.write(" = ");
27680            } else {
27681                self.write(" ");
27682            }
27683            self.write("(");
27684            for (i, v) in e.values.iter().enumerate() {
27685                if i > 0 {
27686                    self.write(", ");
27687                }
27688                self.generate_expression(v)?;
27689            }
27690            self.write(")");
27691        }
27692        Ok(())
27693    }
27694
27695    fn generate_corr(&mut self, e: &Corr) -> Result<()> {
27696        // CORR(this, expression)
27697        self.write_keyword("CORR");
27698        self.write("(");
27699        self.generate_expression(&e.this)?;
27700        self.write(", ");
27701        self.generate_expression(&e.expression)?;
27702        self.write(")");
27703        Ok(())
27704    }
27705
27706    fn generate_cosine_distance(&mut self, e: &CosineDistance) -> Result<()> {
27707        // COSINE_DISTANCE(this, expression)
27708        self.write_keyword("COSINE_DISTANCE");
27709        self.write("(");
27710        self.generate_expression(&e.this)?;
27711        self.write(", ");
27712        self.generate_expression(&e.expression)?;
27713        self.write(")");
27714        Ok(())
27715    }
27716
27717    fn generate_covar_pop(&mut self, e: &CovarPop) -> Result<()> {
27718        // COVAR_POP(this, expression)
27719        self.write_keyword("COVAR_POP");
27720        self.write("(");
27721        self.generate_expression(&e.this)?;
27722        self.write(", ");
27723        self.generate_expression(&e.expression)?;
27724        self.write(")");
27725        Ok(())
27726    }
27727
27728    fn generate_covar_samp(&mut self, e: &CovarSamp) -> Result<()> {
27729        // COVAR_SAMP(this, expression)
27730        self.write_keyword("COVAR_SAMP");
27731        self.write("(");
27732        self.generate_expression(&e.this)?;
27733        self.write(", ");
27734        self.generate_expression(&e.expression)?;
27735        self.write(")");
27736        Ok(())
27737    }
27738
27739    fn generate_credentials(&mut self, e: &Credentials) -> Result<()> {
27740        // CREDENTIALS (key1='value1', key2='value2')
27741        self.write_keyword("CREDENTIALS");
27742        self.write(" (");
27743        for (i, (key, value)) in e.credentials.iter().enumerate() {
27744            if i > 0 {
27745                self.write(", ");
27746            }
27747            self.write(key);
27748            self.write("='");
27749            self.write(value);
27750            self.write("'");
27751        }
27752        self.write(")");
27753        Ok(())
27754    }
27755
27756    fn generate_credentials_property(&mut self, e: &CredentialsProperty) -> Result<()> {
27757        // CREDENTIALS=(expressions)
27758        self.write_keyword("CREDENTIALS");
27759        self.write("=(");
27760        for (i, expr) in e.expressions.iter().enumerate() {
27761            if i > 0 {
27762                self.write(", ");
27763            }
27764            self.generate_expression(expr)?;
27765        }
27766        self.write(")");
27767        Ok(())
27768    }
27769
27770    fn generate_cte(&mut self, e: &Cte) -> Result<()> {
27771        use crate::dialects::DialectType;
27772
27773        // Python: return f"{alias_sql}{key_expressions} AS {materialized or ''}{self.wrap(expression)}"
27774        // Output: alias [(col1, col2, ...)] AS [MATERIALIZED|NOT MATERIALIZED] (subquery)
27775        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) && !e.alias_first {
27776            self.generate_expression(&e.this)?;
27777            self.write_space();
27778            self.write_keyword("AS");
27779            self.write_space();
27780            self.generate_identifier(&e.alias)?;
27781            return Ok(());
27782        }
27783        self.write(&e.alias.name);
27784
27785        // BigQuery doesn't support column aliases in CTE definitions
27786        let skip_cte_columns = matches!(self.config.dialect, Some(DialectType::BigQuery));
27787
27788        if !e.columns.is_empty() && !skip_cte_columns {
27789            self.write("(");
27790            for (i, col) in e.columns.iter().enumerate() {
27791                if i > 0 {
27792                    self.write(", ");
27793                }
27794                self.write(&col.name);
27795            }
27796            self.write(")");
27797        }
27798        // USING KEY (columns) for DuckDB recursive CTEs
27799        if !e.key_expressions.is_empty() {
27800            self.write_space();
27801            self.write_keyword("USING KEY");
27802            self.write(" (");
27803            for (i, key) in e.key_expressions.iter().enumerate() {
27804                if i > 0 {
27805                    self.write(", ");
27806                }
27807                self.write(&key.name);
27808            }
27809            self.write(")");
27810        }
27811        self.write_space();
27812        self.write_keyword("AS");
27813        self.write_space();
27814        if let Some(materialized) = e.materialized {
27815            if materialized {
27816                self.write_keyword("MATERIALIZED");
27817            } else {
27818                self.write_keyword("NOT MATERIALIZED");
27819            }
27820            self.write_space();
27821        }
27822        self.write("(");
27823        self.generate_expression(&e.this)?;
27824        self.write(")");
27825        Ok(())
27826    }
27827
27828    fn generate_cube(&mut self, e: &Cube) -> Result<()> {
27829        // Python: return f"CUBE {self.wrap(expressions)}" if expressions else "WITH CUBE"
27830        if e.expressions.is_empty() {
27831            self.write_keyword("WITH CUBE");
27832        } else {
27833            self.write_keyword("CUBE");
27834            self.write("(");
27835            for (i, expr) in e.expressions.iter().enumerate() {
27836                if i > 0 {
27837                    self.write(", ");
27838                }
27839                self.generate_expression(expr)?;
27840            }
27841            self.write(")");
27842        }
27843        Ok(())
27844    }
27845
27846    fn generate_current_datetime(&mut self, e: &CurrentDatetime) -> Result<()> {
27847        // CURRENT_DATETIME or CURRENT_DATETIME(timezone)
27848        self.write_keyword("CURRENT_DATETIME");
27849        if let Some(this) = &e.this {
27850            self.write("(");
27851            self.generate_expression(this)?;
27852            self.write(")");
27853        }
27854        Ok(())
27855    }
27856
27857    fn generate_current_schema(&mut self, _e: &CurrentSchema) -> Result<()> {
27858        // CURRENT_SCHEMA - no arguments
27859        self.write_keyword("CURRENT_SCHEMA");
27860        Ok(())
27861    }
27862
27863    fn generate_current_schemas(&mut self, e: &CurrentSchemas) -> Result<()> {
27864        // CURRENT_SCHEMAS(include_implicit)
27865        self.write_keyword("CURRENT_SCHEMAS");
27866        self.write("(");
27867        // Snowflake: drop the argument (CURRENT_SCHEMAS() takes no args)
27868        if !matches!(
27869            self.config.dialect,
27870            Some(crate::dialects::DialectType::Snowflake)
27871        ) {
27872            if let Some(this) = &e.this {
27873                self.generate_expression(this)?;
27874            }
27875        }
27876        self.write(")");
27877        Ok(())
27878    }
27879
27880    fn generate_current_user(&mut self, e: &CurrentUser) -> Result<()> {
27881        // CURRENT_USER or CURRENT_USER()
27882        self.write_keyword("CURRENT_USER");
27883        // Some dialects always need parens: Snowflake, Spark, Hive, DuckDB, BigQuery, MySQL, Databricks
27884        let needs_parens = e.this.is_some()
27885            || matches!(
27886                self.config.dialect,
27887                Some(DialectType::Snowflake)
27888                    | Some(DialectType::Spark)
27889                    | Some(DialectType::Hive)
27890                    | Some(DialectType::DuckDB)
27891                    | Some(DialectType::BigQuery)
27892                    | Some(DialectType::MySQL)
27893                    | Some(DialectType::Databricks)
27894            );
27895        if needs_parens {
27896            self.write("()");
27897        }
27898        Ok(())
27899    }
27900
27901    fn generate_d_pipe(&mut self, e: &DPipe) -> Result<()> {
27902        // In Solr, || is OR, not string concatenation (DPIPE_IS_STRING_CONCAT = False)
27903        if self.config.dialect == Some(DialectType::Solr) {
27904            self.generate_expression(&e.this)?;
27905            self.write(" ");
27906            self.write_keyword("OR");
27907            self.write(" ");
27908            self.generate_expression(&e.expression)?;
27909        } else if self.config.dialect == Some(DialectType::MySQL) {
27910            self.generate_mysql_concat_from_dpipe(e)?;
27911        } else {
27912            // String concatenation: this || expression
27913            self.generate_expression(&e.this)?;
27914            self.write(" || ");
27915            self.generate_expression(&e.expression)?;
27916        }
27917        Ok(())
27918    }
27919
27920    fn generate_data_blocksize_property(&mut self, e: &DataBlocksizeProperty) -> Result<()> {
27921        // DATABLOCKSIZE=... (Teradata)
27922        self.write_keyword("DATABLOCKSIZE");
27923        self.write("=");
27924        if let Some(size) = e.size {
27925            self.write(&size.to_string());
27926            if let Some(units) = &e.units {
27927                self.write_space();
27928                self.generate_expression(units)?;
27929            }
27930        } else if e.minimum.is_some() {
27931            self.write_keyword("MINIMUM");
27932        } else if e.maximum.is_some() {
27933            self.write_keyword("MAXIMUM");
27934        } else if e.default.is_some() {
27935            self.write_keyword("DEFAULT");
27936        }
27937        Ok(())
27938    }
27939
27940    fn generate_data_deletion_property(&mut self, e: &DataDeletionProperty) -> Result<()> {
27941        // DATA_DELETION=ON or DATA_DELETION=OFF or DATA_DELETION=ON(FILTER_COLUMN=col, RETENTION_PERIOD=...)
27942        self.write_keyword("DATA_DELETION");
27943        self.write("=");
27944
27945        let is_on = matches!(&*e.on, Expression::Boolean(BooleanLiteral { value: true }));
27946        let has_options = e.filter_column.is_some() || e.retention_period.is_some();
27947
27948        if is_on {
27949            self.write_keyword("ON");
27950            if has_options {
27951                self.write("(");
27952                let mut first = true;
27953                if let Some(filter_column) = &e.filter_column {
27954                    self.write_keyword("FILTER_COLUMN");
27955                    self.write("=");
27956                    self.generate_expression(filter_column)?;
27957                    first = false;
27958                }
27959                if let Some(retention_period) = &e.retention_period {
27960                    if !first {
27961                        self.write(", ");
27962                    }
27963                    self.write_keyword("RETENTION_PERIOD");
27964                    self.write("=");
27965                    self.generate_expression(retention_period)?;
27966                }
27967                self.write(")");
27968            }
27969        } else {
27970            self.write_keyword("OFF");
27971        }
27972        Ok(())
27973    }
27974
27975    /// Generate a Date function expression
27976    /// For Exasol: {d'value'} -> TO_DATE('value')
27977    /// For other dialects: DATE('value')
27978    fn generate_date_func(&mut self, e: &UnaryFunc) -> Result<()> {
27979        use crate::dialects::DialectType;
27980        use crate::expressions::Literal;
27981
27982        match self.config.dialect {
27983            // Exasol uses TO_DATE for Date expressions
27984            Some(DialectType::Exasol) => {
27985                self.write_keyword("TO_DATE");
27986                self.write("(");
27987                // Extract the string value from the expression if it's a string literal
27988                match &e.this {
27989                    Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
27990                        let Literal::String(s) = lit.as_ref() else {
27991                            unreachable!()
27992                        };
27993                        self.write("'");
27994                        self.write(s);
27995                        self.write("'");
27996                    }
27997                    _ => {
27998                        self.generate_expression(&e.this)?;
27999                    }
28000                }
28001                self.write(")");
28002            }
28003            // Standard: DATE(value)
28004            _ => {
28005                self.write_keyword("DATE");
28006                self.write("(");
28007                self.generate_expression(&e.this)?;
28008                self.write(")");
28009            }
28010        }
28011        Ok(())
28012    }
28013
28014    fn generate_date_bin(&mut self, e: &DateBin) -> Result<()> {
28015        // DATE_BIN(interval, timestamp[, origin])
28016        self.write_keyword("DATE_BIN");
28017        self.write("(");
28018        self.generate_expression(&e.this)?;
28019        self.write(", ");
28020        self.generate_expression(&e.expression)?;
28021        if let Some(origin) = &e.origin {
28022            self.write(", ");
28023            self.generate_expression(origin)?;
28024        }
28025        self.write(")");
28026        Ok(())
28027    }
28028
28029    fn generate_date_format_column_constraint(
28030        &mut self,
28031        e: &DateFormatColumnConstraint,
28032    ) -> Result<()> {
28033        // FORMAT 'format_string' (Teradata)
28034        self.write_keyword("FORMAT");
28035        self.write_space();
28036        self.generate_expression(&e.this)?;
28037        Ok(())
28038    }
28039
28040    fn generate_date_from_parts(&mut self, e: &DateFromParts) -> Result<()> {
28041        // DATE_FROM_PARTS(year, month, day) or DATEFROMPARTS(year, month, day)
28042        self.write_keyword("DATE_FROM_PARTS");
28043        self.write("(");
28044        let mut first = true;
28045        if let Some(year) = &e.year {
28046            self.generate_expression(year)?;
28047            first = false;
28048        }
28049        if let Some(month) = &e.month {
28050            if !first {
28051                self.write(", ");
28052            }
28053            self.generate_expression(month)?;
28054            first = false;
28055        }
28056        if let Some(day) = &e.day {
28057            if !first {
28058                self.write(", ");
28059            }
28060            self.generate_expression(day)?;
28061        }
28062        self.write(")");
28063        Ok(())
28064    }
28065
28066    fn generate_datetime(&mut self, e: &Datetime) -> Result<()> {
28067        // DATETIME(this) or DATETIME(this, expression)
28068        self.write_keyword("DATETIME");
28069        self.write("(");
28070        self.generate_expression(&e.this)?;
28071        if let Some(expr) = &e.expression {
28072            self.write(", ");
28073            self.generate_expression(expr)?;
28074        }
28075        self.write(")");
28076        Ok(())
28077    }
28078
28079    fn generate_datetime_add(&mut self, e: &DatetimeAdd) -> Result<()> {
28080        // DATETIME_ADD(this, expression, unit)
28081        self.write_keyword("DATETIME_ADD");
28082        self.write("(");
28083        self.generate_expression(&e.this)?;
28084        self.write(", ");
28085        self.generate_expression(&e.expression)?;
28086        if let Some(unit) = &e.unit {
28087            self.write(", ");
28088            self.write_keyword(unit);
28089        }
28090        self.write(")");
28091        Ok(())
28092    }
28093
28094    fn generate_datetime_diff(&mut self, e: &DatetimeDiff) -> Result<()> {
28095        // DATETIME_DIFF(this, expression, unit)
28096        self.write_keyword("DATETIME_DIFF");
28097        self.write("(");
28098        self.generate_expression(&e.this)?;
28099        self.write(", ");
28100        self.generate_expression(&e.expression)?;
28101        if let Some(unit) = &e.unit {
28102            self.write(", ");
28103            self.write_keyword(unit);
28104        }
28105        self.write(")");
28106        Ok(())
28107    }
28108
28109    fn generate_datetime_sub(&mut self, e: &DatetimeSub) -> Result<()> {
28110        // DATETIME_SUB(this, expression, unit)
28111        self.write_keyword("DATETIME_SUB");
28112        self.write("(");
28113        self.generate_expression(&e.this)?;
28114        self.write(", ");
28115        self.generate_expression(&e.expression)?;
28116        if let Some(unit) = &e.unit {
28117            self.write(", ");
28118            self.write_keyword(unit);
28119        }
28120        self.write(")");
28121        Ok(())
28122    }
28123
28124    fn generate_datetime_trunc(&mut self, e: &DatetimeTrunc) -> Result<()> {
28125        // DATETIME_TRUNC(this, unit, zone)
28126        self.write_keyword("DATETIME_TRUNC");
28127        self.write("(");
28128        self.generate_expression(&e.this)?;
28129        self.write(", ");
28130        self.write_keyword(&e.unit);
28131        if let Some(zone) = &e.zone {
28132            self.write(", ");
28133            self.generate_expression(zone)?;
28134        }
28135        self.write(")");
28136        Ok(())
28137    }
28138
28139    fn generate_dayname(&mut self, e: &Dayname) -> Result<()> {
28140        // DAYNAME(this)
28141        self.write_keyword("DAYNAME");
28142        self.write("(");
28143        self.generate_expression(&e.this)?;
28144        self.write(")");
28145        Ok(())
28146    }
28147
28148    fn generate_declare(&mut self, e: &Declare) -> Result<()> {
28149        // DECLARE [OR REPLACE] var1 AS type1, var2 AS type2, ...
28150        self.write_keyword("DECLARE");
28151        self.write_space();
28152        if e.replace {
28153            self.write_keyword("OR");
28154            self.write_space();
28155            self.write_keyword("REPLACE");
28156            self.write_space();
28157        }
28158        for (i, expr) in e.expressions.iter().enumerate() {
28159            if i > 0 {
28160                self.write(", ");
28161            }
28162            self.generate_expression(expr)?;
28163        }
28164        Ok(())
28165    }
28166
28167    fn generate_declare_item(&mut self, e: &DeclareItem) -> Result<()> {
28168        use crate::dialects::DialectType;
28169
28170        // variable TYPE [DEFAULT default]
28171        self.generate_expression(&e.this)?;
28172        // BigQuery multi-variable: DECLARE X, Y, Z INT64
28173        for name in &e.additional_names {
28174            self.write(", ");
28175            self.generate_expression(name)?;
28176        }
28177        if let Some(kind) = &e.kind {
28178            self.write_space();
28179            // BigQuery uses: DECLARE x INT64 DEFAULT value (no AS)
28180            // TSQL: Always includes AS (normalization)
28181            // Others: Include AS if present in original
28182            match self.config.dialect {
28183                Some(DialectType::BigQuery) => {
28184                    self.write(kind);
28185                }
28186                Some(DialectType::TSQL) => {
28187                    // TSQL DECLARE: no AS keyword (sqlglot convention)
28188                    // Normalize INT to INTEGER for simple declarations
28189                    // Complex TABLE declarations (with CLUSTERED/INDEX) are preserved as-is
28190                    let is_complex_table = kind.starts_with("TABLE")
28191                        && (kind.contains("CLUSTERED") || kind.contains("INDEX"));
28192                    if is_complex_table {
28193                        self.write(kind);
28194                    } else if kind == "INT" {
28195                        self.write("INTEGER");
28196                    } else if kind.starts_with("TABLE") {
28197                        // Normalize INT to INTEGER inside simple TABLE column definitions
28198                        let normalized = kind
28199                            .replace(" INT ", " INTEGER ")
28200                            .replace(" INT,", " INTEGER,")
28201                            .replace(" INT)", " INTEGER)")
28202                            .replace("(INT ", "(INTEGER ");
28203                        self.write(&normalized);
28204                    } else {
28205                        self.write(kind);
28206                    }
28207                }
28208                _ => {
28209                    if e.has_as {
28210                        self.write_keyword("AS");
28211                        self.write_space();
28212                    }
28213                    self.write(kind);
28214                }
28215            }
28216        }
28217        if let Some(default) = &e.default {
28218            // BigQuery uses DEFAULT, others use =
28219            match self.config.dialect {
28220                Some(DialectType::BigQuery) => {
28221                    self.write_space();
28222                    self.write_keyword("DEFAULT");
28223                    self.write_space();
28224                }
28225                _ => {
28226                    self.write(" = ");
28227                }
28228            }
28229            self.generate_expression(default)?;
28230        }
28231        Ok(())
28232    }
28233
28234    fn generate_decode_case(&mut self, e: &DecodeCase) -> Result<()> {
28235        // DECODE(expr, search1, result1, search2, result2, ..., default)
28236        self.write_keyword("DECODE");
28237        self.write("(");
28238        for (i, expr) in e.expressions.iter().enumerate() {
28239            if i > 0 {
28240                self.write(", ");
28241            }
28242            self.generate_expression(expr)?;
28243        }
28244        self.write(")");
28245        Ok(())
28246    }
28247
28248    fn generate_decompress_binary(&mut self, e: &DecompressBinary) -> Result<()> {
28249        // DECOMPRESS(expr, 'method')
28250        self.write_keyword("DECOMPRESS");
28251        self.write("(");
28252        self.generate_expression(&e.this)?;
28253        self.write(", '");
28254        self.write(&e.method);
28255        self.write("')");
28256        Ok(())
28257    }
28258
28259    fn generate_decompress_string(&mut self, e: &DecompressString) -> Result<()> {
28260        // DECOMPRESS(expr, 'method')
28261        self.write_keyword("DECOMPRESS");
28262        self.write("(");
28263        self.generate_expression(&e.this)?;
28264        self.write(", '");
28265        self.write(&e.method);
28266        self.write("')");
28267        Ok(())
28268    }
28269
28270    fn generate_decrypt(&mut self, e: &Decrypt) -> Result<()> {
28271        // DECRYPT(value, passphrase [, aad [, algorithm]])
28272        self.write_keyword("DECRYPT");
28273        self.write("(");
28274        self.generate_expression(&e.this)?;
28275        if let Some(passphrase) = &e.passphrase {
28276            self.write(", ");
28277            self.generate_expression(passphrase)?;
28278        }
28279        if let Some(aad) = &e.aad {
28280            self.write(", ");
28281            self.generate_expression(aad)?;
28282        }
28283        if let Some(method) = &e.encryption_method {
28284            self.write(", ");
28285            self.generate_expression(method)?;
28286        }
28287        self.write(")");
28288        Ok(())
28289    }
28290
28291    fn generate_decrypt_raw(&mut self, e: &DecryptRaw) -> Result<()> {
28292        // DECRYPT_RAW(value, key [, iv [, aad [, algorithm]]])
28293        self.write_keyword("DECRYPT_RAW");
28294        self.write("(");
28295        self.generate_expression(&e.this)?;
28296        if let Some(key) = &e.key {
28297            self.write(", ");
28298            self.generate_expression(key)?;
28299        }
28300        if let Some(iv) = &e.iv {
28301            self.write(", ");
28302            self.generate_expression(iv)?;
28303        }
28304        if let Some(aad) = &e.aad {
28305            self.write(", ");
28306            self.generate_expression(aad)?;
28307        }
28308        if let Some(method) = &e.encryption_method {
28309            self.write(", ");
28310            self.generate_expression(method)?;
28311        }
28312        self.write(")");
28313        Ok(())
28314    }
28315
28316    fn generate_definer_property(&mut self, e: &DefinerProperty) -> Result<()> {
28317        // DEFINER = user
28318        self.write_keyword("DEFINER");
28319        self.write(" = ");
28320        self.generate_expression(&e.this)?;
28321        Ok(())
28322    }
28323
28324    fn generate_detach(&mut self, e: &Detach) -> Result<()> {
28325        // Python: DETACH[DATABASE IF EXISTS] this
28326        self.write_keyword("DETACH");
28327        if e.exists {
28328            self.write_keyword(" DATABASE IF EXISTS");
28329        }
28330        self.write_space();
28331        self.generate_expression(&e.this)?;
28332        Ok(())
28333    }
28334
28335    fn generate_dict_property(&mut self, e: &DictProperty) -> Result<()> {
28336        let property_name = match e.this.as_ref() {
28337            Expression::Identifier(id) => id.name.as_str(),
28338            Expression::Var(v) => v.this.as_str(),
28339            _ => "DICTIONARY",
28340        };
28341        self.write_keyword(property_name);
28342        self.write("(");
28343        self.write(&e.kind);
28344        if let Some(settings) = &e.settings {
28345            self.write("(");
28346            if let Expression::Tuple(t) = settings.as_ref() {
28347                if self.config.pretty && !t.expressions.is_empty() {
28348                    self.write_newline();
28349                    self.indent_level += 1;
28350                    for (i, pair) in t.expressions.iter().enumerate() {
28351                        if i > 0 {
28352                            self.write(",");
28353                            self.write_newline();
28354                        }
28355                        self.write_indent();
28356                        if let Expression::Tuple(pair_tuple) = pair {
28357                            if let Some(k) = pair_tuple.expressions.first() {
28358                                self.generate_expression(k)?;
28359                            }
28360                            if let Some(v) = pair_tuple.expressions.get(1) {
28361                                self.write(" ");
28362                                self.generate_expression(v)?;
28363                            }
28364                        } else {
28365                            self.generate_expression(pair)?;
28366                        }
28367                    }
28368                    self.indent_level -= 1;
28369                    self.write_newline();
28370                    self.write_indent();
28371                } else {
28372                    for (i, pair) in t.expressions.iter().enumerate() {
28373                        if i > 0 {
28374                            // ClickHouse dict properties are space-separated, not comma-separated
28375                            self.write(" ");
28376                        }
28377                        if let Expression::Tuple(pair_tuple) = pair {
28378                            if let Some(k) = pair_tuple.expressions.first() {
28379                                self.generate_expression(k)?;
28380                            }
28381                            if let Some(v) = pair_tuple.expressions.get(1) {
28382                                self.write(" ");
28383                                self.generate_expression(v)?;
28384                            }
28385                        } else {
28386                            self.generate_expression(pair)?;
28387                        }
28388                    }
28389                }
28390            } else {
28391                self.generate_expression(settings)?;
28392            }
28393            self.write(")");
28394        } else {
28395            // No settings but kind had parens (e.g., SOURCE(NULL()), LAYOUT(FLAT()))
28396            self.write("()");
28397        }
28398        self.write(")");
28399        Ok(())
28400    }
28401
28402    fn generate_dict_range(&mut self, e: &DictRange) -> Result<()> {
28403        let property_name = match e.this.as_ref() {
28404            Expression::Identifier(id) => id.name.as_str(),
28405            Expression::Var(v) => v.this.as_str(),
28406            _ => "RANGE",
28407        };
28408        self.write_keyword(property_name);
28409        self.write("(");
28410        if let Some(min) = &e.min {
28411            self.write_keyword("MIN");
28412            self.write_space();
28413            self.generate_expression(min)?;
28414        }
28415        if let Some(max) = &e.max {
28416            self.write_space();
28417            self.write_keyword("MAX");
28418            self.write_space();
28419            self.generate_expression(max)?;
28420        }
28421        self.write(")");
28422        Ok(())
28423    }
28424
28425    fn generate_directory(&mut self, e: &Directory) -> Result<()> {
28426        // Python: {local}DIRECTORY {this}{row_format}
28427        if e.local.is_some() {
28428            self.write_keyword("LOCAL ");
28429        }
28430        self.write_keyword("DIRECTORY");
28431        self.write_space();
28432        self.generate_expression(&e.this)?;
28433        if let Some(row_format) = &e.row_format {
28434            self.write_space();
28435            self.generate_expression(row_format)?;
28436        }
28437        Ok(())
28438    }
28439
28440    fn generate_dist_key_property(&mut self, e: &DistKeyProperty) -> Result<()> {
28441        // Redshift: DISTKEY(column)
28442        self.write_keyword("DISTKEY");
28443        self.write("(");
28444        self.generate_expression(&e.this)?;
28445        self.write(")");
28446        Ok(())
28447    }
28448
28449    fn generate_dist_style_property(&mut self, e: &DistStyleProperty) -> Result<()> {
28450        // Redshift: DISTSTYLE KEY|ALL|EVEN|AUTO
28451        self.write_keyword("DISTSTYLE");
28452        self.write_space();
28453        self.generate_expression(&e.this)?;
28454        Ok(())
28455    }
28456
28457    fn generate_distribute_by(&mut self, e: &DistributeBy) -> Result<()> {
28458        // Python: "DISTRIBUTE BY" expressions
28459        self.write_keyword("DISTRIBUTE BY");
28460        self.write_space();
28461        for (i, expr) in e.expressions.iter().enumerate() {
28462            if i > 0 {
28463                self.write(", ");
28464            }
28465            self.generate_expression(expr)?;
28466        }
28467        Ok(())
28468    }
28469
28470    fn generate_distributed_by_property(&mut self, e: &DistributedByProperty) -> Result<()> {
28471        // Python: DISTRIBUTED BY kind (expressions) BUCKETS buckets order
28472        self.write_keyword("DISTRIBUTED BY");
28473        self.write_space();
28474        self.write(&e.kind);
28475        if !e.expressions.is_empty() {
28476            self.write(" (");
28477            for (i, expr) in e.expressions.iter().enumerate() {
28478                if i > 0 {
28479                    self.write(", ");
28480                }
28481                self.generate_expression(expr)?;
28482            }
28483            self.write(")");
28484        }
28485        if let Some(buckets) = &e.buckets {
28486            self.write_space();
28487            self.write_keyword("BUCKETS");
28488            self.write_space();
28489            self.generate_expression(buckets)?;
28490        }
28491        if let Some(order) = &e.order {
28492            self.write_space();
28493            self.generate_expression(order)?;
28494        }
28495        Ok(())
28496    }
28497
28498    fn generate_dot_product(&mut self, e: &DotProduct) -> Result<()> {
28499        // DOT_PRODUCT(vector1, vector2)
28500        self.write_keyword("DOT_PRODUCT");
28501        self.write("(");
28502        self.generate_expression(&e.this)?;
28503        self.write(", ");
28504        self.generate_expression(&e.expression)?;
28505        self.write(")");
28506        Ok(())
28507    }
28508
28509    fn generate_drop_partition(&mut self, e: &DropPartition) -> Result<()> {
28510        // Python: DROP{IF EXISTS }expressions
28511        self.write_keyword("DROP");
28512        if e.exists {
28513            self.write_keyword(" IF EXISTS ");
28514        } else {
28515            self.write_space();
28516        }
28517        for (i, expr) in e.expressions.iter().enumerate() {
28518            if i > 0 {
28519                self.write(", ");
28520            }
28521            self.generate_expression(expr)?;
28522        }
28523        Ok(())
28524    }
28525
28526    fn generate_duplicate_key_property(&mut self, e: &DuplicateKeyProperty) -> Result<()> {
28527        // Python: DUPLICATE KEY (expressions)
28528        self.write_keyword("DUPLICATE KEY");
28529        self.write(" (");
28530        for (i, expr) in e.expressions.iter().enumerate() {
28531            if i > 0 {
28532                self.write(", ");
28533            }
28534            self.generate_expression(expr)?;
28535        }
28536        self.write(")");
28537        Ok(())
28538    }
28539
28540    fn generate_elt(&mut self, e: &Elt) -> Result<()> {
28541        // ELT(index, str1, str2, ...)
28542        self.write_keyword("ELT");
28543        self.write("(");
28544        self.generate_expression(&e.this)?;
28545        for expr in &e.expressions {
28546            self.write(", ");
28547            self.generate_expression(expr)?;
28548        }
28549        self.write(")");
28550        Ok(())
28551    }
28552
28553    fn generate_encode(&mut self, e: &Encode) -> Result<()> {
28554        // ENCODE(string, charset)
28555        self.write_keyword("ENCODE");
28556        self.write("(");
28557        self.generate_expression(&e.this)?;
28558        if let Some(charset) = &e.charset {
28559            self.write(", ");
28560            self.generate_expression(charset)?;
28561        }
28562        self.write(")");
28563        Ok(())
28564    }
28565
28566    fn generate_encode_property(&mut self, e: &EncodeProperty) -> Result<()> {
28567        // Python: [KEY ]ENCODE this [properties]
28568        if e.key.is_some() {
28569            self.write_keyword("KEY ");
28570        }
28571        self.write_keyword("ENCODE");
28572        self.write_space();
28573        self.generate_expression(&e.this)?;
28574        if !e.properties.is_empty() {
28575            self.write(" (");
28576            for (i, prop) in e.properties.iter().enumerate() {
28577                if i > 0 {
28578                    self.write(", ");
28579                }
28580                self.generate_expression(prop)?;
28581            }
28582            self.write(")");
28583        }
28584        Ok(())
28585    }
28586
28587    fn generate_encrypt(&mut self, e: &Encrypt) -> Result<()> {
28588        // ENCRYPT(value, passphrase [, aad [, algorithm]])
28589        self.write_keyword("ENCRYPT");
28590        self.write("(");
28591        self.generate_expression(&e.this)?;
28592        if let Some(passphrase) = &e.passphrase {
28593            self.write(", ");
28594            self.generate_expression(passphrase)?;
28595        }
28596        if let Some(aad) = &e.aad {
28597            self.write(", ");
28598            self.generate_expression(aad)?;
28599        }
28600        if let Some(method) = &e.encryption_method {
28601            self.write(", ");
28602            self.generate_expression(method)?;
28603        }
28604        self.write(")");
28605        Ok(())
28606    }
28607
28608    fn generate_encrypt_raw(&mut self, e: &EncryptRaw) -> Result<()> {
28609        // ENCRYPT_RAW(value, key [, iv [, aad [, algorithm]]])
28610        self.write_keyword("ENCRYPT_RAW");
28611        self.write("(");
28612        self.generate_expression(&e.this)?;
28613        if let Some(key) = &e.key {
28614            self.write(", ");
28615            self.generate_expression(key)?;
28616        }
28617        if let Some(iv) = &e.iv {
28618            self.write(", ");
28619            self.generate_expression(iv)?;
28620        }
28621        if let Some(aad) = &e.aad {
28622            self.write(", ");
28623            self.generate_expression(aad)?;
28624        }
28625        if let Some(method) = &e.encryption_method {
28626            self.write(", ");
28627            self.generate_expression(method)?;
28628        }
28629        self.write(")");
28630        Ok(())
28631    }
28632
28633    fn generate_engine_property(&mut self, e: &EngineProperty) -> Result<()> {
28634        // MySQL: ENGINE = InnoDB
28635        self.write_keyword("ENGINE");
28636        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
28637            self.write("=");
28638        } else {
28639            self.write(" = ");
28640        }
28641        self.generate_expression(&e.this)?;
28642        Ok(())
28643    }
28644
28645    fn generate_enviroment_property(&mut self, e: &EnviromentProperty) -> Result<()> {
28646        // ENVIRONMENT (expressions)
28647        self.write_keyword("ENVIRONMENT");
28648        self.write(" (");
28649        for (i, expr) in e.expressions.iter().enumerate() {
28650            if i > 0 {
28651                self.write(", ");
28652            }
28653            self.generate_expression(expr)?;
28654        }
28655        self.write(")");
28656        Ok(())
28657    }
28658
28659    fn generate_ephemeral_column_constraint(
28660        &mut self,
28661        e: &EphemeralColumnConstraint,
28662    ) -> Result<()> {
28663        // MySQL: EPHEMERAL [expr]
28664        self.write_keyword("EPHEMERAL");
28665        if let Some(this) = &e.this {
28666            self.write_space();
28667            self.generate_expression(this)?;
28668        }
28669        Ok(())
28670    }
28671
28672    fn generate_equal_null(&mut self, e: &EqualNull) -> Result<()> {
28673        // Snowflake: EQUAL_NULL(a, b)
28674        self.write_keyword("EQUAL_NULL");
28675        self.write("(");
28676        self.generate_expression(&e.this)?;
28677        self.write(", ");
28678        self.generate_expression(&e.expression)?;
28679        self.write(")");
28680        Ok(())
28681    }
28682
28683    fn generate_euclidean_distance(&mut self, e: &EuclideanDistance) -> Result<()> {
28684        use crate::dialects::DialectType;
28685
28686        // PostgreSQL uses <-> operator syntax
28687        match self.config.dialect {
28688            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift) => {
28689                self.generate_expression(&e.this)?;
28690                self.write(" <-> ");
28691                self.generate_expression(&e.expression)?;
28692            }
28693            _ => {
28694                // Other dialects use EUCLIDEAN_DISTANCE function
28695                self.write_keyword("EUCLIDEAN_DISTANCE");
28696                self.write("(");
28697                self.generate_expression(&e.this)?;
28698                self.write(", ");
28699                self.generate_expression(&e.expression)?;
28700                self.write(")");
28701            }
28702        }
28703        Ok(())
28704    }
28705
28706    fn generate_execute_as_property(&mut self, e: &ExecuteAsProperty) -> Result<()> {
28707        // EXECUTE AS CALLER|OWNER|user
28708        self.write_keyword("EXECUTE AS");
28709        self.write_space();
28710        self.generate_expression(&e.this)?;
28711        Ok(())
28712    }
28713
28714    fn generate_export(&mut self, e: &Export) -> Result<()> {
28715        // BigQuery: EXPORT DATA [WITH CONNECTION connection] OPTIONS (...) AS query
28716        self.write_keyword("EXPORT DATA");
28717        if let Some(connection) = &e.connection {
28718            self.write_space();
28719            self.write_keyword("WITH CONNECTION");
28720            self.write_space();
28721            self.generate_expression(connection)?;
28722        }
28723        if !e.options.is_empty() {
28724            self.write_space();
28725            self.generate_options_clause(&e.options)?;
28726        }
28727        self.write_space();
28728        self.write_keyword("AS");
28729        self.write_space();
28730        self.generate_expression(&e.this)?;
28731        Ok(())
28732    }
28733
28734    fn generate_external_property(&mut self, e: &ExternalProperty) -> Result<()> {
28735        // EXTERNAL [this]
28736        self.write_keyword("EXTERNAL");
28737        if let Some(this) = &e.this {
28738            self.write_space();
28739            self.generate_expression(this)?;
28740        }
28741        Ok(())
28742    }
28743
28744    fn generate_fallback_property(&mut self, e: &FallbackProperty) -> Result<()> {
28745        // Python: {no}FALLBACK{protection}
28746        if e.no.is_some() {
28747            self.write_keyword("NO ");
28748        }
28749        self.write_keyword("FALLBACK");
28750        if e.protection.is_some() {
28751            self.write_keyword(" PROTECTION");
28752        }
28753        Ok(())
28754    }
28755
28756    fn generate_farm_fingerprint(&mut self, e: &FarmFingerprint) -> Result<()> {
28757        // BigQuery: FARM_FINGERPRINT(value)
28758        self.write_keyword("FARM_FINGERPRINT");
28759        self.write("(");
28760        for (i, expr) in e.expressions.iter().enumerate() {
28761            if i > 0 {
28762                self.write(", ");
28763            }
28764            self.generate_expression(expr)?;
28765        }
28766        self.write(")");
28767        Ok(())
28768    }
28769
28770    fn generate_features_at_time(&mut self, e: &FeaturesAtTime) -> Result<()> {
28771        // BigQuery ML: FEATURES_AT_TIME(feature_view, time, [num_rows], [ignore_feature_nulls])
28772        self.write_keyword("FEATURES_AT_TIME");
28773        self.write("(");
28774        self.generate_expression(&e.this)?;
28775        if let Some(time) = &e.time {
28776            self.write(", ");
28777            self.generate_expression(time)?;
28778        }
28779        if let Some(num_rows) = &e.num_rows {
28780            self.write(", ");
28781            self.generate_expression(num_rows)?;
28782        }
28783        if let Some(ignore_nulls) = &e.ignore_feature_nulls {
28784            self.write(", ");
28785            self.generate_expression(ignore_nulls)?;
28786        }
28787        self.write(")");
28788        Ok(())
28789    }
28790
28791    fn generate_fetch(&mut self, e: &Fetch) -> Result<()> {
28792        // For dialects that prefer LIMIT, convert simple FETCH to LIMIT
28793        let use_limit = !e.percent
28794            && !e.with_ties
28795            && e.count.is_some()
28796            && matches!(
28797                self.config.dialect,
28798                Some(DialectType::Spark)
28799                    | Some(DialectType::Hive)
28800                    | Some(DialectType::DuckDB)
28801                    | Some(DialectType::SQLite)
28802                    | Some(DialectType::MySQL)
28803                    | Some(DialectType::BigQuery)
28804                    | Some(DialectType::Databricks)
28805                    | Some(DialectType::StarRocks)
28806                    | Some(DialectType::Doris)
28807                    | Some(DialectType::Athena)
28808                    | Some(DialectType::ClickHouse)
28809            );
28810
28811        if use_limit {
28812            self.write_keyword("LIMIT");
28813            self.write_space();
28814            self.generate_expression(e.count.as_ref().unwrap())?;
28815            return Ok(());
28816        }
28817
28818        // Python: FETCH direction count limit_options
28819        self.write_keyword("FETCH");
28820        if !e.direction.is_empty() {
28821            self.write_space();
28822            self.write_keyword(&e.direction);
28823        }
28824        if let Some(count) = &e.count {
28825            self.write_space();
28826            self.generate_expression(count)?;
28827        }
28828        // Generate PERCENT, ROWS, WITH TIES/ONLY
28829        if e.percent {
28830            self.write_keyword(" PERCENT");
28831        }
28832        if e.rows {
28833            self.write_keyword(" ROWS");
28834        }
28835        if e.with_ties {
28836            self.write_keyword(" WITH TIES");
28837        } else if e.rows {
28838            self.write_keyword(" ONLY");
28839        } else {
28840            self.write_keyword(" ROWS ONLY");
28841        }
28842        Ok(())
28843    }
28844
28845    fn generate_file_format_property(&mut self, e: &FileFormatProperty) -> Result<()> {
28846        // For Hive format: STORED AS this or STORED AS INPUTFORMAT x OUTPUTFORMAT y
28847        // For Spark/Databricks without hive_format: USING this
28848        // For Snowflake/others: FILE_FORMAT = this or FILE_FORMAT = (expressions)
28849        if e.hive_format.is_some() {
28850            // Hive format: STORED AS ...
28851            self.write_keyword("STORED AS");
28852            self.write_space();
28853            if let Some(this) = &e.this {
28854                // Uppercase the format name (e.g., parquet -> PARQUET)
28855                if let Expression::Identifier(id) = this.as_ref() {
28856                    self.write_keyword(&id.name.to_ascii_uppercase());
28857                } else {
28858                    self.generate_expression(this)?;
28859                }
28860            }
28861        } else if matches!(self.config.dialect, Some(DialectType::Hive)) {
28862            // Hive: STORED AS format
28863            self.write_keyword("STORED AS");
28864            self.write_space();
28865            if let Some(this) = &e.this {
28866                if let Expression::Identifier(id) = this.as_ref() {
28867                    self.write_keyword(&id.name.to_ascii_uppercase());
28868                } else {
28869                    self.generate_expression(this)?;
28870                }
28871            }
28872        } else if matches!(
28873            self.config.dialect,
28874            Some(DialectType::Spark) | Some(DialectType::Databricks)
28875        ) {
28876            // Spark/Databricks: USING format (e.g., USING DELTA)
28877            self.write_keyword("USING");
28878            self.write_space();
28879            if let Some(this) = &e.this {
28880                self.generate_expression(this)?;
28881            }
28882        } else {
28883            // Snowflake/standard format
28884            self.write_keyword("FILE_FORMAT");
28885            self.write(" = ");
28886            if let Some(this) = &e.this {
28887                self.generate_expression(this)?;
28888            } else if !e.expressions.is_empty() {
28889                self.write("(");
28890                for (i, expr) in e.expressions.iter().enumerate() {
28891                    if i > 0 {
28892                        self.write(", ");
28893                    }
28894                    self.generate_expression(expr)?;
28895                }
28896                self.write(")");
28897            }
28898        }
28899        Ok(())
28900    }
28901
28902    fn generate_filter(&mut self, e: &Filter) -> Result<()> {
28903        // agg_func FILTER(WHERE condition)
28904        self.generate_expression(&e.this)?;
28905        self.write_space();
28906        self.write_keyword("FILTER");
28907        self.write("(");
28908        self.write_keyword("WHERE");
28909        self.write_space();
28910        self.generate_expression(&e.expression)?;
28911        self.write(")");
28912        Ok(())
28913    }
28914
28915    fn generate_float64(&mut self, e: &Float64) -> Result<()> {
28916        // FLOAT64(this) or FLOAT64(this, expression)
28917        self.write_keyword("FLOAT64");
28918        self.write("(");
28919        self.generate_expression(&e.this)?;
28920        if let Some(expr) = &e.expression {
28921            self.write(", ");
28922            self.generate_expression(expr)?;
28923        }
28924        self.write(")");
28925        Ok(())
28926    }
28927
28928    fn generate_for_in(&mut self, e: &ForIn) -> Result<()> {
28929        // FOR this DO expression
28930        self.write_keyword("FOR");
28931        self.write_space();
28932        self.generate_expression(&e.this)?;
28933        self.write_space();
28934        self.write_keyword("DO");
28935        self.write_space();
28936        self.generate_expression(&e.expression)?;
28937        Ok(())
28938    }
28939
28940    fn generate_foreign_key(&mut self, e: &ForeignKey) -> Result<()> {
28941        // FOREIGN KEY (cols) REFERENCES table(cols) ON DELETE action ON UPDATE action
28942        self.write_keyword("FOREIGN KEY");
28943        if !e.expressions.is_empty() {
28944            self.write(" (");
28945            for (i, expr) in e.expressions.iter().enumerate() {
28946                if i > 0 {
28947                    self.write(", ");
28948                }
28949                self.generate_expression(expr)?;
28950            }
28951            self.write(")");
28952        }
28953        if let Some(reference) = &e.reference {
28954            self.write_space();
28955            self.generate_expression(reference)?;
28956        }
28957        if let Some(delete) = &e.delete {
28958            self.write_space();
28959            self.write_keyword("ON DELETE");
28960            self.write_space();
28961            self.generate_expression(delete)?;
28962        }
28963        if let Some(update) = &e.update {
28964            self.write_space();
28965            self.write_keyword("ON UPDATE");
28966            self.write_space();
28967            self.generate_expression(update)?;
28968        }
28969        if !e.options.is_empty() {
28970            self.write_space();
28971            for (i, opt) in e.options.iter().enumerate() {
28972                if i > 0 {
28973                    self.write_space();
28974                }
28975                self.generate_expression(opt)?;
28976            }
28977        }
28978        Ok(())
28979    }
28980
28981    fn generate_format(&mut self, e: &Format) -> Result<()> {
28982        // FORMAT(this, expressions...)
28983        self.write_keyword("FORMAT");
28984        self.write("(");
28985        self.generate_expression(&e.this)?;
28986        for expr in &e.expressions {
28987            self.write(", ");
28988            self.generate_expression(expr)?;
28989        }
28990        self.write(")");
28991        Ok(())
28992    }
28993
28994    fn generate_format_phrase(&mut self, e: &FormatPhrase) -> Result<()> {
28995        // Teradata: column (FORMAT 'format_string')
28996        self.generate_expression(&e.this)?;
28997        self.write(" (");
28998        self.write_keyword("FORMAT");
28999        self.write(" '");
29000        self.write(&e.format);
29001        self.write("')");
29002        Ok(())
29003    }
29004
29005    fn generate_freespace_property(&mut self, e: &FreespaceProperty) -> Result<()> {
29006        // Python: FREESPACE=this[PERCENT]
29007        self.write_keyword("FREESPACE");
29008        self.write("=");
29009        self.generate_expression(&e.this)?;
29010        if e.percent.is_some() {
29011            self.write_keyword(" PERCENT");
29012        }
29013        Ok(())
29014    }
29015
29016    fn generate_from(&mut self, e: &From) -> Result<()> {
29017        // Python: return f"{self.seg('FROM')} {self.sql(expression, 'this')}"
29018        self.write_keyword("FROM");
29019        self.write_space();
29020
29021        // BigQuery, Hive, Spark, Databricks, SQLite, and ClickHouse prefer explicit CROSS JOIN over comma syntax
29022        // But keep commas when TABLESAMPLE is present
29023        // Also keep commas when the source dialect is Generic/None and target is one of these dialects
29024        use crate::dialects::DialectType;
29025        let has_tablesample = e
29026            .expressions
29027            .iter()
29028            .any(|expr| matches!(expr, Expression::TableSample(_)));
29029        let is_cross_join_dialect = matches!(
29030            self.config.dialect,
29031            Some(DialectType::BigQuery)
29032                | Some(DialectType::Hive)
29033                | Some(DialectType::Spark)
29034                | Some(DialectType::Databricks)
29035                | Some(DialectType::SQLite)
29036                | Some(DialectType::ClickHouse)
29037        );
29038        let source_is_same_as_target2 = self.config.source_dialect.is_some()
29039            && self.config.source_dialect == self.config.dialect;
29040        let source_is_cross_join_dialect2 = matches!(
29041            self.config.source_dialect,
29042            Some(DialectType::BigQuery)
29043                | Some(DialectType::Hive)
29044                | Some(DialectType::Spark)
29045                | Some(DialectType::Databricks)
29046                | Some(DialectType::SQLite)
29047                | Some(DialectType::ClickHouse)
29048        );
29049        let use_cross_join = !has_tablesample
29050            && is_cross_join_dialect
29051            && (source_is_same_as_target2
29052                || source_is_cross_join_dialect2
29053                || self.config.source_dialect.is_none());
29054
29055        // Snowflake wraps standalone VALUES in FROM clause with parentheses
29056        let wrap_values_in_parens = matches!(self.config.dialect, Some(DialectType::Snowflake));
29057
29058        for (i, expr) in e.expressions.iter().enumerate() {
29059            if i > 0 {
29060                if use_cross_join {
29061                    self.write(" CROSS JOIN ");
29062                } else {
29063                    self.write(", ");
29064                }
29065            }
29066            if wrap_values_in_parens && matches!(expr, Expression::Values(_)) {
29067                self.write("(");
29068                self.generate_expression(expr)?;
29069                self.write(")");
29070            } else {
29071                self.generate_expression(expr)?;
29072            }
29073            // Output leading comments that were on the table name before FROM
29074            // (e.g., FROM \n/* comment */\n tbl PIVOT(...) -> ... PIVOT(...) /* comment */)
29075            let leading = Self::extract_table_leading_comments(expr);
29076            for comment in &leading {
29077                self.write_space();
29078                self.write_formatted_comment(comment);
29079            }
29080        }
29081        Ok(())
29082    }
29083
29084    /// Extract leading_comments from a table expression (possibly wrapped in PIVOT/UNPIVOT)
29085    fn extract_table_leading_comments(expr: &Expression) -> Vec<String> {
29086        match expr {
29087            Expression::Table(t) => t.leading_comments.clone(),
29088            Expression::Pivot(p) => {
29089                if let Expression::Table(t) = &p.this {
29090                    t.leading_comments.clone()
29091                } else {
29092                    Vec::new()
29093                }
29094            }
29095            _ => Vec::new(),
29096        }
29097    }
29098
29099    fn generate_from_base(&mut self, e: &FromBase) -> Result<()> {
29100        // FROM_BASE(this, expression) - convert from base N
29101        self.write_keyword("FROM_BASE");
29102        self.write("(");
29103        self.generate_expression(&e.this)?;
29104        self.write(", ");
29105        self.generate_expression(&e.expression)?;
29106        self.write(")");
29107        Ok(())
29108    }
29109
29110    fn generate_from_time_zone(&mut self, e: &FromTimeZone) -> Result<()> {
29111        // this AT TIME ZONE zone AT TIME ZONE 'UTC'
29112        self.generate_expression(&e.this)?;
29113        if let Some(zone) = &e.zone {
29114            self.write_space();
29115            self.write_keyword("AT TIME ZONE");
29116            self.write_space();
29117            self.generate_expression(zone)?;
29118            self.write_space();
29119            self.write_keyword("AT TIME ZONE");
29120            self.write(" 'UTC'");
29121        }
29122        Ok(())
29123    }
29124
29125    fn generate_gap_fill(&mut self, e: &GapFill) -> Result<()> {
29126        // GAP_FILL(this, ts_column, bucket_width, ...)
29127        self.write_keyword("GAP_FILL");
29128        self.write("(");
29129        self.generate_expression(&e.this)?;
29130        if let Some(ts_column) = &e.ts_column {
29131            self.write(", ");
29132            self.generate_expression(ts_column)?;
29133        }
29134        if let Some(bucket_width) = &e.bucket_width {
29135            self.write(", ");
29136            self.generate_expression(bucket_width)?;
29137        }
29138        if let Some(partitioning_columns) = &e.partitioning_columns {
29139            self.write(", ");
29140            self.generate_expression(partitioning_columns)?;
29141        }
29142        if let Some(value_columns) = &e.value_columns {
29143            self.write(", ");
29144            self.generate_expression(value_columns)?;
29145        }
29146        self.write(")");
29147        Ok(())
29148    }
29149
29150    fn generate_generate_date_array(&mut self, e: &GenerateDateArray) -> Result<()> {
29151        // GENERATE_DATE_ARRAY(start, end, step)
29152        self.write_keyword("GENERATE_DATE_ARRAY");
29153        self.write("(");
29154        let mut first = true;
29155        if let Some(start) = &e.start {
29156            self.generate_expression(start)?;
29157            first = false;
29158        }
29159        if let Some(end) = &e.end {
29160            if !first {
29161                self.write(", ");
29162            }
29163            self.generate_expression(end)?;
29164            first = false;
29165        }
29166        if let Some(step) = &e.step {
29167            if !first {
29168                self.write(", ");
29169            }
29170            self.generate_expression(step)?;
29171        }
29172        self.write(")");
29173        Ok(())
29174    }
29175
29176    fn generate_generate_embedding(&mut self, e: &GenerateEmbedding) -> Result<()> {
29177        // ML.GENERATE_EMBEDDING(model, content, params)
29178        self.write_keyword("ML.GENERATE_EMBEDDING");
29179        self.write("(");
29180        self.generate_expression(&e.this)?;
29181        self.write(", ");
29182        self.generate_expression(&e.expression)?;
29183        if let Some(params) = &e.params_struct {
29184            self.write(", ");
29185            self.generate_expression(params)?;
29186        }
29187        self.write(")");
29188        Ok(())
29189    }
29190
29191    fn generate_generate_series(&mut self, e: &GenerateSeries) -> Result<()> {
29192        // Dialect-specific function name
29193        let fn_name = match self.config.dialect {
29194            Some(DialectType::Presto)
29195            | Some(DialectType::Trino)
29196            | Some(DialectType::Athena)
29197            | Some(DialectType::Spark)
29198            | Some(DialectType::Databricks)
29199            | Some(DialectType::Hive) => "SEQUENCE",
29200            _ => "GENERATE_SERIES",
29201        };
29202        self.write_keyword(fn_name);
29203        self.write("(");
29204        let mut first = true;
29205        if let Some(start) = &e.start {
29206            self.generate_expression(start)?;
29207            first = false;
29208        }
29209        if let Some(end) = &e.end {
29210            if !first {
29211                self.write(", ");
29212            }
29213            self.generate_expression(end)?;
29214            first = false;
29215        }
29216        if let Some(step) = &e.step {
29217            if !first {
29218                self.write(", ");
29219            }
29220            // For Presto/Trino: convert WEEK intervals to DAY multiples
29221            // e.g., INTERVAL '1' WEEK -> (1 * INTERVAL '7' DAY)
29222            if matches!(
29223                self.config.dialect,
29224                Some(DialectType::Presto) | Some(DialectType::Trino) | Some(DialectType::Athena)
29225            ) {
29226                if let Some(converted) = self.convert_week_interval_to_day(step) {
29227                    self.generate_expression(&converted)?;
29228                } else {
29229                    self.generate_expression(step)?;
29230                }
29231            } else {
29232                self.generate_expression(step)?;
29233            }
29234        }
29235        self.write(")");
29236        Ok(())
29237    }
29238
29239    /// Convert a WEEK interval to a DAY-based multiplication expression for Presto/Trino.
29240    /// INTERVAL N WEEK -> (N * INTERVAL '7' DAY)
29241    fn convert_week_interval_to_day(&self, expr: &Expression) -> Option<Expression> {
29242        use crate::expressions::*;
29243        if let Expression::Interval(ref iv) = expr {
29244            // Check for structured WEEK unit
29245            let (is_week, count_str) = if let Some(IntervalUnitSpec::Simple {
29246                unit: IntervalUnit::Week,
29247                ..
29248            }) = &iv.unit
29249            {
29250                // Value is in iv.this
29251                let count = match &iv.this {
29252                    Some(Expression::Literal(lit)) => match lit.as_ref() {
29253                        Literal::String(s) | Literal::Number(s) => s.clone(),
29254                        _ => return None,
29255                    },
29256                    _ => return None,
29257                };
29258                (true, count)
29259            } else if iv.unit.is_none() {
29260                // Check for string-encoded interval like "1 WEEK"
29261                if let Some(Expression::Literal(lit)) = &iv.this {
29262                    if let Literal::String(s) = lit.as_ref() {
29263                        let parts: Vec<&str> = s.trim().splitn(2, char::is_whitespace).collect();
29264                        if parts.len() == 2 && parts[1].eq_ignore_ascii_case("WEEK") {
29265                            (true, parts[0].to_string())
29266                        } else {
29267                            (false, String::new())
29268                        }
29269                    } else {
29270                        (false, String::new())
29271                    }
29272                } else {
29273                    (false, String::new())
29274                }
29275            } else {
29276                (false, String::new())
29277            };
29278
29279            if is_week {
29280                // Build: (N * INTERVAL '7' DAY)
29281                let count_expr = Expression::Literal(Box::new(Literal::Number(count_str)));
29282                let day_interval = Expression::Interval(Box::new(Interval {
29283                    this: Some(Expression::Literal(Box::new(Literal::String(
29284                        "7".to_string(),
29285                    )))),
29286                    unit: Some(IntervalUnitSpec::Simple {
29287                        unit: IntervalUnit::Day,
29288                        use_plural: false,
29289                    }),
29290                }));
29291                let mul = Expression::Mul(Box::new(BinaryOp {
29292                    left: count_expr,
29293                    right: day_interval,
29294                    left_comments: vec![],
29295                    operator_comments: vec![],
29296                    trailing_comments: vec![],
29297                    inferred_type: None,
29298                }));
29299                return Some(Expression::Paren(Box::new(Paren {
29300                    this: mul,
29301                    trailing_comments: vec![],
29302                })));
29303            }
29304        }
29305        None
29306    }
29307
29308    fn generate_generate_timestamp_array(&mut self, e: &GenerateTimestampArray) -> Result<()> {
29309        // GENERATE_TIMESTAMP_ARRAY(start, end, step)
29310        self.write_keyword("GENERATE_TIMESTAMP_ARRAY");
29311        self.write("(");
29312        let mut first = true;
29313        if let Some(start) = &e.start {
29314            self.generate_expression(start)?;
29315            first = false;
29316        }
29317        if let Some(end) = &e.end {
29318            if !first {
29319                self.write(", ");
29320            }
29321            self.generate_expression(end)?;
29322            first = false;
29323        }
29324        if let Some(step) = &e.step {
29325            if !first {
29326                self.write(", ");
29327            }
29328            self.generate_expression(step)?;
29329        }
29330        self.write(")");
29331        Ok(())
29332    }
29333
29334    fn generate_generated_as_identity_column_constraint(
29335        &mut self,
29336        e: &GeneratedAsIdentityColumnConstraint,
29337    ) -> Result<()> {
29338        use crate::dialects::DialectType;
29339
29340        // For Snowflake, use AUTOINCREMENT START x INCREMENT y syntax
29341        if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
29342            self.write_keyword("AUTOINCREMENT");
29343            if let Some(start) = &e.start {
29344                self.write_keyword(" START ");
29345                self.generate_expression(start)?;
29346            }
29347            if let Some(increment) = &e.increment {
29348                self.write_keyword(" INCREMENT ");
29349                self.generate_expression(increment)?;
29350            }
29351            return Ok(());
29352        }
29353
29354        // Python: GENERATED [ALWAYS|BY DEFAULT [ON NULL]] AS IDENTITY [(start, increment, ...)]
29355        self.write_keyword("GENERATED");
29356        if let Some(this) = &e.this {
29357            // Check if it's a truthy boolean expression
29358            if let Expression::Boolean(b) = this.as_ref() {
29359                if b.value {
29360                    self.write_keyword(" ALWAYS");
29361                } else {
29362                    self.write_keyword(" BY DEFAULT");
29363                    if e.on_null.is_some() {
29364                        self.write_keyword(" ON NULL");
29365                    }
29366                }
29367            } else {
29368                self.write_keyword(" ALWAYS");
29369            }
29370        }
29371        self.write_keyword(" AS IDENTITY");
29372        // Add sequence options if any
29373        let has_options = e.start.is_some()
29374            || e.increment.is_some()
29375            || e.minvalue.is_some()
29376            || e.maxvalue.is_some();
29377        if has_options {
29378            self.write(" (");
29379            let mut first = true;
29380            if let Some(start) = &e.start {
29381                self.write_keyword("START WITH ");
29382                self.generate_expression(start)?;
29383                first = false;
29384            }
29385            if let Some(increment) = &e.increment {
29386                if !first {
29387                    self.write(" ");
29388                }
29389                self.write_keyword("INCREMENT BY ");
29390                self.generate_expression(increment)?;
29391                first = false;
29392            }
29393            if let Some(minvalue) = &e.minvalue {
29394                if !first {
29395                    self.write(" ");
29396                }
29397                self.write_keyword("MINVALUE ");
29398                self.generate_expression(minvalue)?;
29399                first = false;
29400            }
29401            if let Some(maxvalue) = &e.maxvalue {
29402                if !first {
29403                    self.write(" ");
29404                }
29405                self.write_keyword("MAXVALUE ");
29406                self.generate_expression(maxvalue)?;
29407            }
29408            self.write(")");
29409        }
29410        Ok(())
29411    }
29412
29413    fn generate_generated_as_row_column_constraint(
29414        &mut self,
29415        e: &GeneratedAsRowColumnConstraint,
29416    ) -> Result<()> {
29417        // Python: GENERATED ALWAYS AS ROW START|END [HIDDEN]
29418        self.write_keyword("GENERATED ALWAYS AS ROW ");
29419        if e.start.is_some() {
29420            self.write_keyword("START");
29421        } else {
29422            self.write_keyword("END");
29423        }
29424        if e.hidden.is_some() {
29425            self.write_keyword(" HIDDEN");
29426        }
29427        Ok(())
29428    }
29429
29430    fn generate_get(&mut self, e: &Get) -> Result<()> {
29431        // GET this target properties
29432        self.write_keyword("GET");
29433        self.write_space();
29434        self.generate_expression(&e.this)?;
29435        if let Some(target) = &e.target {
29436            self.write_space();
29437            self.generate_expression(target)?;
29438        }
29439        for prop in &e.properties {
29440            self.write_space();
29441            self.generate_expression(prop)?;
29442        }
29443        Ok(())
29444    }
29445
29446    fn generate_get_extract(&mut self, e: &GetExtract) -> Result<()> {
29447        // GetExtract generates bracket access: this[expression]
29448        self.generate_expression(&e.this)?;
29449        self.write("[");
29450        self.generate_expression(&e.expression)?;
29451        self.write("]");
29452        Ok(())
29453    }
29454
29455    fn generate_getbit(&mut self, e: &Getbit) -> Result<()> {
29456        // GETBIT(this, expression) or GET_BIT(this, expression)
29457        self.write_keyword("GETBIT");
29458        self.write("(");
29459        self.generate_expression(&e.this)?;
29460        self.write(", ");
29461        self.generate_expression(&e.expression)?;
29462        self.write(")");
29463        Ok(())
29464    }
29465
29466    fn generate_grant_principal(&mut self, e: &GrantPrincipal) -> Result<()> {
29467        // [ROLE|GROUP|SHARE] name (e.g., "ROLE admin", "GROUP qa_users", "SHARE s1", or just "user1")
29468        if e.is_role {
29469            self.write_keyword("ROLE");
29470            self.write_space();
29471        } else if e.is_group {
29472            self.write_keyword("GROUP");
29473            self.write_space();
29474        } else if e.is_share {
29475            self.write_keyword("SHARE");
29476            self.write_space();
29477        }
29478        self.write(&e.name.name);
29479        Ok(())
29480    }
29481
29482    fn generate_grant_privilege(&mut self, e: &GrantPrivilege) -> Result<()> {
29483        // privilege(columns) or just privilege
29484        self.generate_expression(&e.this)?;
29485        if !e.expressions.is_empty() {
29486            self.write("(");
29487            for (i, expr) in e.expressions.iter().enumerate() {
29488                if i > 0 {
29489                    self.write(", ");
29490                }
29491                self.generate_expression(expr)?;
29492            }
29493            self.write(")");
29494        }
29495        Ok(())
29496    }
29497
29498    fn generate_group(&mut self, e: &Group) -> Result<()> {
29499        // Python handles GROUP BY ALL/DISTINCT modifiers and grouping expressions
29500        self.write_keyword("GROUP BY");
29501        // Handle ALL/DISTINCT modifier: Some(true) = ALL, Some(false) = DISTINCT
29502        match e.all {
29503            Some(true) => {
29504                self.write_space();
29505                self.write_keyword("ALL");
29506            }
29507            Some(false) => {
29508                self.write_space();
29509                self.write_keyword("DISTINCT");
29510            }
29511            None => {}
29512        }
29513        if !e.expressions.is_empty() {
29514            self.write_space();
29515            for (i, expr) in e.expressions.iter().enumerate() {
29516                if i > 0 {
29517                    self.write(", ");
29518                }
29519                self.generate_expression(expr)?;
29520            }
29521        }
29522        // Handle CUBE, ROLLUP, GROUPING SETS
29523        if let Some(cube) = &e.cube {
29524            if !e.expressions.is_empty() {
29525                self.write(", ");
29526            } else {
29527                self.write_space();
29528            }
29529            self.generate_expression(cube)?;
29530        }
29531        if let Some(rollup) = &e.rollup {
29532            if !e.expressions.is_empty() || e.cube.is_some() {
29533                self.write(", ");
29534            } else {
29535                self.write_space();
29536            }
29537            self.generate_expression(rollup)?;
29538        }
29539        if let Some(grouping_sets) = &e.grouping_sets {
29540            if !e.expressions.is_empty() || e.cube.is_some() || e.rollup.is_some() {
29541                self.write(", ");
29542            } else {
29543                self.write_space();
29544            }
29545            self.generate_expression(grouping_sets)?;
29546        }
29547        if let Some(totals) = &e.totals {
29548            self.write_space();
29549            self.write_keyword("WITH TOTALS");
29550            self.generate_expression(totals)?;
29551        }
29552        Ok(())
29553    }
29554
29555    fn generate_group_by(&mut self, e: &GroupBy) -> Result<()> {
29556        // GROUP BY expressions
29557        self.write_keyword("GROUP BY");
29558        // Handle ALL/DISTINCT modifier: Some(true) = ALL, Some(false) = DISTINCT
29559        match e.all {
29560            Some(true) => {
29561                self.write_space();
29562                self.write_keyword("ALL");
29563            }
29564            Some(false) => {
29565                self.write_space();
29566                self.write_keyword("DISTINCT");
29567            }
29568            None => {}
29569        }
29570
29571        // Check for trailing WITH CUBE or WITH ROLLUP (Hive/MySQL syntax)
29572        // These are represented as Cube/Rollup expressions with empty expressions at the end
29573        let mut trailing_cube = false;
29574        let mut trailing_rollup = false;
29575        let mut regular_expressions: Vec<&Expression> = Vec::new();
29576
29577        for expr in &e.expressions {
29578            match expr {
29579                Expression::Cube(c) if c.expressions.is_empty() => {
29580                    trailing_cube = true;
29581                }
29582                Expression::Rollup(r) if r.expressions.is_empty() => {
29583                    trailing_rollup = true;
29584                }
29585                _ => {
29586                    regular_expressions.push(expr);
29587                }
29588            }
29589        }
29590
29591        // In pretty mode, put columns on separate lines
29592        if self.config.pretty {
29593            self.write_newline();
29594            self.indent_level += 1;
29595            for (i, expr) in regular_expressions.iter().enumerate() {
29596                if i > 0 {
29597                    self.write(",");
29598                    self.write_newline();
29599                }
29600                self.write_indent();
29601                self.generate_expression(expr)?;
29602            }
29603            self.indent_level -= 1;
29604        } else {
29605            self.write_space();
29606            for (i, expr) in regular_expressions.iter().enumerate() {
29607                if i > 0 {
29608                    self.write(", ");
29609                }
29610                self.generate_expression(expr)?;
29611            }
29612        }
29613
29614        // Output trailing WITH CUBE or WITH ROLLUP
29615        if trailing_cube {
29616            self.write_space();
29617            self.write_keyword("WITH CUBE");
29618        } else if trailing_rollup {
29619            self.write_space();
29620            self.write_keyword("WITH ROLLUP");
29621        }
29622
29623        // ClickHouse: WITH TOTALS
29624        if e.totals {
29625            self.write_space();
29626            self.write_keyword("WITH TOTALS");
29627        }
29628
29629        Ok(())
29630    }
29631
29632    fn generate_grouping(&mut self, e: &Grouping) -> Result<()> {
29633        // GROUPING(col1, col2, ...)
29634        self.write_keyword("GROUPING");
29635        self.write("(");
29636        for (i, expr) in e.expressions.iter().enumerate() {
29637            if i > 0 {
29638                self.write(", ");
29639            }
29640            self.generate_expression(expr)?;
29641        }
29642        self.write(")");
29643        Ok(())
29644    }
29645
29646    fn generate_grouping_id(&mut self, e: &GroupingId) -> Result<()> {
29647        // GROUPING_ID(col1, col2, ...)
29648        self.write_keyword("GROUPING_ID");
29649        self.write("(");
29650        for (i, expr) in e.expressions.iter().enumerate() {
29651            if i > 0 {
29652                self.write(", ");
29653            }
29654            self.generate_expression(expr)?;
29655        }
29656        self.write(")");
29657        Ok(())
29658    }
29659
29660    fn generate_grouping_sets(&mut self, e: &GroupingSets) -> Result<()> {
29661        // Python: return f"GROUPING SETS {self.wrap(grouping_sets)}"
29662        self.write_keyword("GROUPING SETS");
29663        self.write(" (");
29664        for (i, expr) in e.expressions.iter().enumerate() {
29665            if i > 0 {
29666                self.write(", ");
29667            }
29668            self.generate_expression(expr)?;
29669        }
29670        self.write(")");
29671        Ok(())
29672    }
29673
29674    fn generate_hash_agg(&mut self, e: &HashAgg) -> Result<()> {
29675        // HASH_AGG(this, expressions...)
29676        self.write_keyword("HASH_AGG");
29677        self.write("(");
29678        self.generate_expression(&e.this)?;
29679        for expr in &e.expressions {
29680            self.write(", ");
29681            self.generate_expression(expr)?;
29682        }
29683        self.write(")");
29684        Ok(())
29685    }
29686
29687    fn generate_having(&mut self, e: &Having) -> Result<()> {
29688        // Python: return f"{self.seg('HAVING')}{self.sep()}{this}"
29689        self.write_keyword("HAVING");
29690        self.write_space();
29691        self.generate_expression(&e.this)?;
29692        Ok(())
29693    }
29694
29695    fn generate_having_max(&mut self, e: &HavingMax) -> Result<()> {
29696        // Python: this HAVING MAX|MIN expression
29697        self.generate_expression(&e.this)?;
29698        self.write_space();
29699        self.write_keyword("HAVING");
29700        self.write_space();
29701        if e.max.is_some() {
29702            self.write_keyword("MAX");
29703        } else {
29704            self.write_keyword("MIN");
29705        }
29706        self.write_space();
29707        self.generate_expression(&e.expression)?;
29708        Ok(())
29709    }
29710
29711    fn generate_heredoc(&mut self, e: &Heredoc) -> Result<()> {
29712        use crate::dialects::DialectType;
29713        // DuckDB: convert dollar-tagged strings to single-quoted
29714        if matches!(self.config.dialect, Some(DialectType::DuckDB)) {
29715            // Extract the string content and output as single-quoted
29716            if let Expression::Literal(ref lit) = *e.this {
29717                if let Literal::String(ref s) = lit.as_ref() {
29718                    return self.generate_string_literal(s);
29719                }
29720            }
29721        }
29722        // PostgreSQL: preserve dollar-quoting
29723        if matches!(
29724            self.config.dialect,
29725            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
29726        ) {
29727            self.write("$");
29728            if let Some(tag) = &e.tag {
29729                self.generate_expression(tag)?;
29730            }
29731            self.write("$");
29732            self.generate_expression(&e.this)?;
29733            self.write("$");
29734            if let Some(tag) = &e.tag {
29735                self.generate_expression(tag)?;
29736            }
29737            self.write("$");
29738            return Ok(());
29739        }
29740        // Default: output as dollar-tagged
29741        self.write("$");
29742        if let Some(tag) = &e.tag {
29743            self.generate_expression(tag)?;
29744        }
29745        self.write("$");
29746        self.generate_expression(&e.this)?;
29747        self.write("$");
29748        if let Some(tag) = &e.tag {
29749            self.generate_expression(tag)?;
29750        }
29751        self.write("$");
29752        Ok(())
29753    }
29754
29755    fn generate_hex_encode(&mut self, e: &HexEncode) -> Result<()> {
29756        // HEX_ENCODE(this)
29757        self.write_keyword("HEX_ENCODE");
29758        self.write("(");
29759        self.generate_expression(&e.this)?;
29760        self.write(")");
29761        Ok(())
29762    }
29763
29764    fn generate_historical_data(&mut self, e: &HistoricalData) -> Result<()> {
29765        // Python: this (kind => expression)
29766        // Write the keyword (AT/BEFORE/END) directly to avoid quoting it as a reserved word
29767        match e.this.as_ref() {
29768            Expression::Identifier(id) => self.write(&id.name),
29769            other => self.generate_expression(other)?,
29770        }
29771        self.write(" (");
29772        self.write(&e.kind);
29773        self.write(" => ");
29774        self.generate_expression(&e.expression)?;
29775        self.write(")");
29776        Ok(())
29777    }
29778
29779    fn generate_hll(&mut self, e: &Hll) -> Result<()> {
29780        // HLL(this, expressions...)
29781        self.write_keyword("HLL");
29782        self.write("(");
29783        self.generate_expression(&e.this)?;
29784        for expr in &e.expressions {
29785            self.write(", ");
29786            self.generate_expression(expr)?;
29787        }
29788        self.write(")");
29789        Ok(())
29790    }
29791
29792    fn generate_in_out_column_constraint(&mut self, e: &InOutColumnConstraint) -> Result<()> {
29793        // Python: IN|OUT|IN OUT
29794        if e.input_.is_some() && e.output.is_some() {
29795            self.write_keyword("IN OUT");
29796        } else if e.input_.is_some() {
29797            self.write_keyword("IN");
29798        } else if e.output.is_some() {
29799            self.write_keyword("OUT");
29800        }
29801        Ok(())
29802    }
29803
29804    fn generate_include_property(&mut self, e: &IncludeProperty) -> Result<()> {
29805        // Python: INCLUDE this [column_def] [AS alias]
29806        self.write_keyword("INCLUDE");
29807        self.write_space();
29808        self.generate_expression(&e.this)?;
29809        if let Some(column_def) = &e.column_def {
29810            self.write_space();
29811            self.generate_expression(column_def)?;
29812        }
29813        if let Some(alias) = &e.alias {
29814            self.write_space();
29815            self.write_keyword("AS");
29816            self.write_space();
29817            self.write(alias);
29818        }
29819        Ok(())
29820    }
29821
29822    fn generate_index(&mut self, e: &Index) -> Result<()> {
29823        // [UNIQUE] [PRIMARY] [AMP] INDEX [name] [ON table] (params)
29824        if e.unique {
29825            self.write_keyword("UNIQUE");
29826            self.write_space();
29827        }
29828        if e.primary.is_some() {
29829            self.write_keyword("PRIMARY");
29830            self.write_space();
29831        }
29832        if e.amp.is_some() {
29833            self.write_keyword("AMP");
29834            self.write_space();
29835        }
29836        if e.table.is_none() {
29837            self.write_keyword("INDEX");
29838            self.write_space();
29839        }
29840        if let Some(name) = &e.this {
29841            self.generate_expression(name)?;
29842            self.write_space();
29843        }
29844        if let Some(table) = &e.table {
29845            self.write_keyword("ON");
29846            self.write_space();
29847            self.generate_expression(table)?;
29848        }
29849        if !e.params.is_empty() {
29850            self.write("(");
29851            for (i, param) in e.params.iter().enumerate() {
29852                if i > 0 {
29853                    self.write(", ");
29854                }
29855                self.generate_expression(param)?;
29856            }
29857            self.write(")");
29858        }
29859        Ok(())
29860    }
29861
29862    fn generate_index_column_constraint(&mut self, e: &IndexColumnConstraint) -> Result<()> {
29863        // Python: kind INDEX [this] [USING index_type] (expressions) [options]
29864        if let Some(kind) = &e.kind {
29865            self.write(kind);
29866            self.write_space();
29867        }
29868        self.write_keyword("INDEX");
29869        if let Some(this) = &e.this {
29870            self.write_space();
29871            self.generate_expression(this)?;
29872        }
29873        if let Some(index_type) = &e.index_type {
29874            self.write_space();
29875            self.write_keyword("USING");
29876            self.write_space();
29877            self.generate_expression(index_type)?;
29878        }
29879        if !e.expressions.is_empty() {
29880            self.write(" (");
29881            for (i, expr) in e.expressions.iter().enumerate() {
29882                if i > 0 {
29883                    self.write(", ");
29884                }
29885                self.generate_expression(expr)?;
29886            }
29887            self.write(")");
29888        }
29889        for opt in &e.options {
29890            self.write_space();
29891            self.generate_expression(opt)?;
29892        }
29893        Ok(())
29894    }
29895
29896    fn generate_index_constraint_option(&mut self, e: &IndexConstraintOption) -> Result<()> {
29897        // Python: KEY_BLOCK_SIZE = x | USING x | WITH PARSER x | COMMENT x | visible | engine_attr | secondary_engine_attr
29898        if let Some(key_block_size) = &e.key_block_size {
29899            self.write_keyword("KEY_BLOCK_SIZE");
29900            self.write(" = ");
29901            self.generate_expression(key_block_size)?;
29902        } else if let Some(using) = &e.using {
29903            self.write_keyword("USING");
29904            self.write_space();
29905            self.generate_expression(using)?;
29906        } else if let Some(parser) = &e.parser {
29907            self.write_keyword("WITH PARSER");
29908            self.write_space();
29909            self.generate_expression(parser)?;
29910        } else if let Some(comment) = &e.comment {
29911            self.write_keyword("COMMENT");
29912            self.write_space();
29913            self.generate_expression(comment)?;
29914        } else if let Some(visible) = &e.visible {
29915            self.generate_expression(visible)?;
29916        } else if let Some(engine_attr) = &e.engine_attr {
29917            self.write_keyword("ENGINE_ATTRIBUTE");
29918            self.write(" = ");
29919            self.generate_expression(engine_attr)?;
29920        } else if let Some(secondary_engine_attr) = &e.secondary_engine_attr {
29921            self.write_keyword("SECONDARY_ENGINE_ATTRIBUTE");
29922            self.write(" = ");
29923            self.generate_expression(secondary_engine_attr)?;
29924        }
29925        Ok(())
29926    }
29927
29928    fn generate_index_parameters(&mut self, e: &IndexParameters) -> Result<()> {
29929        // Python: [USING using] (columns) [PARTITION BY partition_by] [where] [INCLUDE (include)] [WITH (with_storage)] [USING INDEX TABLESPACE tablespace]
29930        if let Some(using) = &e.using {
29931            self.write_keyword("USING");
29932            self.write_space();
29933            self.generate_expression(using)?;
29934        }
29935        if !e.columns.is_empty() {
29936            self.write("(");
29937            for (i, col) in e.columns.iter().enumerate() {
29938                if i > 0 {
29939                    self.write(", ");
29940                }
29941                self.generate_expression(col)?;
29942            }
29943            self.write(")");
29944        }
29945        if let Some(partition_by) = &e.partition_by {
29946            self.write_space();
29947            self.write_keyword("PARTITION BY");
29948            self.write_space();
29949            self.generate_expression(partition_by)?;
29950        }
29951        if let Some(where_) = &e.where_ {
29952            self.write_space();
29953            self.generate_expression(where_)?;
29954        }
29955        if let Some(include) = &e.include {
29956            self.write_space();
29957            self.write_keyword("INCLUDE");
29958            self.write(" (");
29959            self.generate_expression(include)?;
29960            self.write(")");
29961        }
29962        if let Some(with_storage) = &e.with_storage {
29963            self.write_space();
29964            self.write_keyword("WITH");
29965            self.write(" (");
29966            self.generate_expression(with_storage)?;
29967            self.write(")");
29968        }
29969        if let Some(tablespace) = &e.tablespace {
29970            self.write_space();
29971            self.write_keyword("USING INDEX TABLESPACE");
29972            self.write_space();
29973            self.generate_expression(tablespace)?;
29974        }
29975        Ok(())
29976    }
29977
29978    fn generate_index_table_hint(&mut self, e: &IndexTableHint) -> Result<()> {
29979        // Python: this INDEX [FOR target] (expressions)
29980        // Write hint type (USE/IGNORE/FORCE) as keyword, not through generate_expression
29981        // to avoid quoting reserved keywords like IGNORE, FORCE, JOIN
29982        if let Expression::Identifier(id) = &*e.this {
29983            self.write_keyword(&id.name);
29984        } else {
29985            self.generate_expression(&e.this)?;
29986        }
29987        self.write_space();
29988        self.write_keyword("INDEX");
29989        if let Some(target) = &e.target {
29990            self.write_space();
29991            self.write_keyword("FOR");
29992            self.write_space();
29993            if let Expression::Identifier(id) = &**target {
29994                self.write_keyword(&id.name);
29995            } else {
29996                self.generate_expression(target)?;
29997            }
29998        }
29999        // Always output parentheses (even if empty, e.g. USE INDEX ())
30000        self.write(" (");
30001        for (i, expr) in e.expressions.iter().enumerate() {
30002            if i > 0 {
30003                self.write(", ");
30004            }
30005            self.generate_expression(expr)?;
30006        }
30007        self.write(")");
30008        Ok(())
30009    }
30010
30011    fn generate_inherits_property(&mut self, e: &InheritsProperty) -> Result<()> {
30012        // INHERITS (table1, table2, ...)
30013        self.write_keyword("INHERITS");
30014        self.write(" (");
30015        for (i, expr) in e.expressions.iter().enumerate() {
30016            if i > 0 {
30017                self.write(", ");
30018            }
30019            self.generate_expression(expr)?;
30020        }
30021        self.write(")");
30022        Ok(())
30023    }
30024
30025    fn generate_input_model_property(&mut self, e: &InputModelProperty) -> Result<()> {
30026        // INPUT(model)
30027        self.write_keyword("INPUT");
30028        self.write("(");
30029        self.generate_expression(&e.this)?;
30030        self.write(")");
30031        Ok(())
30032    }
30033
30034    fn generate_input_output_format(&mut self, e: &InputOutputFormat) -> Result<()> {
30035        // Python: INPUTFORMAT input_format OUTPUTFORMAT output_format
30036        if let Some(input_format) = &e.input_format {
30037            self.write_keyword("INPUTFORMAT");
30038            self.write_space();
30039            self.generate_expression(input_format)?;
30040        }
30041        if let Some(output_format) = &e.output_format {
30042            if e.input_format.is_some() {
30043                self.write(" ");
30044            }
30045            self.write_keyword("OUTPUTFORMAT");
30046            self.write_space();
30047            self.generate_expression(output_format)?;
30048        }
30049        Ok(())
30050    }
30051
30052    fn generate_install(&mut self, e: &Install) -> Result<()> {
30053        // [FORCE] INSTALL extension [FROM source]
30054        if e.force.is_some() {
30055            self.write_keyword("FORCE");
30056            self.write_space();
30057        }
30058        self.write_keyword("INSTALL");
30059        self.write_space();
30060        self.generate_expression(&e.this)?;
30061        if let Some(from) = &e.from_ {
30062            self.write_space();
30063            self.write_keyword("FROM");
30064            self.write_space();
30065            self.generate_expression(from)?;
30066        }
30067        Ok(())
30068    }
30069
30070    fn generate_interval_op(&mut self, e: &IntervalOp) -> Result<()> {
30071        // INTERVAL 'expression' unit
30072        self.write_keyword("INTERVAL");
30073        self.write_space();
30074        // When a unit is specified and the expression is a number,
30075        self.generate_expression(&e.expression)?;
30076        if let Some(unit) = &e.unit {
30077            self.write_space();
30078            self.write(unit);
30079        }
30080        Ok(())
30081    }
30082
30083    fn generate_interval_span(&mut self, e: &IntervalSpan) -> Result<()> {
30084        // unit TO unit (e.g., HOUR TO SECOND)
30085        self.write(&format!("{:?}", e.this).to_ascii_uppercase());
30086        self.write_space();
30087        self.write_keyword("TO");
30088        self.write_space();
30089        self.write(&format!("{:?}", e.expression).to_ascii_uppercase());
30090        Ok(())
30091    }
30092
30093    fn generate_into_clause(&mut self, e: &IntoClause) -> Result<()> {
30094        // INTO [TEMPORARY|UNLOGGED] table
30095        self.write_keyword("INTO");
30096        if e.temporary {
30097            self.write_keyword(" TEMPORARY");
30098        }
30099        if e.unlogged.is_some() {
30100            self.write_keyword(" UNLOGGED");
30101        }
30102        if let Some(this) = &e.this {
30103            self.write_space();
30104            self.generate_expression(this)?;
30105        }
30106        if !e.expressions.is_empty() {
30107            self.write(" (");
30108            for (i, expr) in e.expressions.iter().enumerate() {
30109                if i > 0 {
30110                    self.write(", ");
30111                }
30112                self.generate_expression(expr)?;
30113            }
30114            self.write(")");
30115        }
30116        Ok(())
30117    }
30118
30119    fn generate_introducer(&mut self, e: &Introducer) -> Result<()> {
30120        // Python: this expression (e.g., _utf8 'string')
30121        self.generate_expression(&e.this)?;
30122        self.write_space();
30123        self.generate_expression(&e.expression)?;
30124        Ok(())
30125    }
30126
30127    fn generate_isolated_loading_property(&mut self, e: &IsolatedLoadingProperty) -> Result<()> {
30128        // Python: WITH [NO] [CONCURRENT] ISOLATED LOADING [target]
30129        self.write_keyword("WITH");
30130        if e.no.is_some() {
30131            self.write_keyword(" NO");
30132        }
30133        if e.concurrent.is_some() {
30134            self.write_keyword(" CONCURRENT");
30135        }
30136        self.write_keyword(" ISOLATED LOADING");
30137        if let Some(target) = &e.target {
30138            self.write_space();
30139            self.generate_expression(target)?;
30140        }
30141        Ok(())
30142    }
30143
30144    fn generate_json(&mut self, e: &JSON) -> Result<()> {
30145        // Python: JSON [this] [WITHOUT|WITH] [UNIQUE KEYS]
30146        self.write_keyword("JSON");
30147        if let Some(this) = &e.this {
30148            self.write_space();
30149            self.generate_expression(this)?;
30150        }
30151        if let Some(with_) = &e.with_ {
30152            // Check if it's a truthy boolean
30153            if let Expression::Boolean(b) = with_.as_ref() {
30154                if b.value {
30155                    self.write_keyword(" WITH");
30156                } else {
30157                    self.write_keyword(" WITHOUT");
30158                }
30159            }
30160        }
30161        if e.unique {
30162            self.write_keyword(" UNIQUE KEYS");
30163        }
30164        Ok(())
30165    }
30166
30167    fn generate_json_array(&mut self, e: &JSONArray) -> Result<()> {
30168        // Python: return self.func("JSON_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})")
30169        self.write_keyword("JSON_ARRAY");
30170        self.write("(");
30171        for (i, expr) in e.expressions.iter().enumerate() {
30172            if i > 0 {
30173                self.write(", ");
30174            }
30175            self.generate_expression(expr)?;
30176        }
30177        if let Some(null_handling) = &e.null_handling {
30178            self.write_space();
30179            self.generate_expression(null_handling)?;
30180        }
30181        if let Some(return_type) = &e.return_type {
30182            self.write_space();
30183            self.write_keyword("RETURNING");
30184            self.write_space();
30185            self.generate_expression(return_type)?;
30186        }
30187        if e.strict.is_some() {
30188            self.write_space();
30189            self.write_keyword("STRICT");
30190        }
30191        self.write(")");
30192        Ok(())
30193    }
30194
30195    fn generate_json_array_agg_struct(&mut self, e: &JSONArrayAgg) -> Result<()> {
30196        // JSON_ARRAYAGG(this [ORDER BY ...] [NULL ON NULL | ABSENT ON NULL] [RETURNING type] [STRICT])
30197        self.write_keyword("JSON_ARRAYAGG");
30198        self.write("(");
30199        self.generate_expression(&e.this)?;
30200        if let Some(order) = &e.order {
30201            self.write_space();
30202            // Order is stored as an OrderBy expression
30203            if let Expression::OrderBy(ob) = order.as_ref() {
30204                self.write_keyword("ORDER BY");
30205                self.write_space();
30206                for (i, ord) in ob.expressions.iter().enumerate() {
30207                    if i > 0 {
30208                        self.write(", ");
30209                    }
30210                    self.generate_ordered(ord)?;
30211                }
30212            } else {
30213                // Fallback: generate the expression directly
30214                self.generate_expression(order)?;
30215            }
30216        }
30217        if let Some(null_handling) = &e.null_handling {
30218            self.write_space();
30219            self.generate_expression(null_handling)?;
30220        }
30221        if let Some(return_type) = &e.return_type {
30222            self.write_space();
30223            self.write_keyword("RETURNING");
30224            self.write_space();
30225            self.generate_expression(return_type)?;
30226        }
30227        if e.strict.is_some() {
30228            self.write_space();
30229            self.write_keyword("STRICT");
30230        }
30231        self.write(")");
30232        Ok(())
30233    }
30234
30235    fn generate_json_object_agg_struct(&mut self, e: &JSONObjectAgg) -> Result<()> {
30236        // JSON_OBJECTAGG(key: value [NULL ON NULL | ABSENT ON NULL] [WITH UNIQUE KEYS] [RETURNING type])
30237        self.write_keyword("JSON_OBJECTAGG");
30238        self.write("(");
30239        for (i, expr) in e.expressions.iter().enumerate() {
30240            if i > 0 {
30241                self.write(", ");
30242            }
30243            self.generate_expression(expr)?;
30244        }
30245        if let Some(null_handling) = &e.null_handling {
30246            self.write_space();
30247            self.generate_expression(null_handling)?;
30248        }
30249        if let Some(unique_keys) = &e.unique_keys {
30250            self.write_space();
30251            if let Expression::Boolean(b) = unique_keys.as_ref() {
30252                if b.value {
30253                    self.write_keyword("WITH UNIQUE KEYS");
30254                } else {
30255                    self.write_keyword("WITHOUT UNIQUE KEYS");
30256                }
30257            }
30258        }
30259        if let Some(return_type) = &e.return_type {
30260            self.write_space();
30261            self.write_keyword("RETURNING");
30262            self.write_space();
30263            self.generate_expression(return_type)?;
30264        }
30265        self.write(")");
30266        Ok(())
30267    }
30268
30269    fn generate_json_array_append(&mut self, e: &JSONArrayAppend) -> Result<()> {
30270        // JSON_ARRAY_APPEND(this, path, value, ...)
30271        self.write_keyword("JSON_ARRAY_APPEND");
30272        self.write("(");
30273        self.generate_expression(&e.this)?;
30274        for expr in &e.expressions {
30275            self.write(", ");
30276            self.generate_expression(expr)?;
30277        }
30278        self.write(")");
30279        Ok(())
30280    }
30281
30282    fn generate_json_array_contains(&mut self, e: &JSONArrayContains) -> Result<()> {
30283        // JSON_ARRAY_CONTAINS(this, expression)
30284        self.write_keyword("JSON_ARRAY_CONTAINS");
30285        self.write("(");
30286        self.generate_expression(&e.this)?;
30287        self.write(", ");
30288        self.generate_expression(&e.expression)?;
30289        self.write(")");
30290        Ok(())
30291    }
30292
30293    fn generate_json_array_insert(&mut self, e: &JSONArrayInsert) -> Result<()> {
30294        // JSON_ARRAY_INSERT(this, path, value, ...)
30295        self.write_keyword("JSON_ARRAY_INSERT");
30296        self.write("(");
30297        self.generate_expression(&e.this)?;
30298        for expr in &e.expressions {
30299            self.write(", ");
30300            self.generate_expression(expr)?;
30301        }
30302        self.write(")");
30303        Ok(())
30304    }
30305
30306    fn generate_jsonb_exists(&mut self, e: &JSONBExists) -> Result<()> {
30307        // JSONB_EXISTS(this, path)
30308        self.write_keyword("JSONB_EXISTS");
30309        self.write("(");
30310        self.generate_expression(&e.this)?;
30311        if let Some(path) = &e.path {
30312            self.write(", ");
30313            self.generate_expression(path)?;
30314        }
30315        self.write(")");
30316        Ok(())
30317    }
30318
30319    fn generate_jsonb_extract_scalar(&mut self, e: &JSONBExtractScalar) -> Result<()> {
30320        // JSONB_EXTRACT_SCALAR(this, expression)
30321        self.write_keyword("JSONB_EXTRACT_SCALAR");
30322        self.write("(");
30323        self.generate_expression(&e.this)?;
30324        self.write(", ");
30325        self.generate_expression(&e.expression)?;
30326        self.write(")");
30327        Ok(())
30328    }
30329
30330    fn generate_jsonb_object_agg(&mut self, e: &JSONBObjectAgg) -> Result<()> {
30331        // JSONB_OBJECT_AGG(this, expression)
30332        self.write_keyword("JSONB_OBJECT_AGG");
30333        self.write("(");
30334        self.generate_expression(&e.this)?;
30335        self.write(", ");
30336        self.generate_expression(&e.expression)?;
30337        self.write(")");
30338        Ok(())
30339    }
30340
30341    fn generate_json_column_def(&mut self, e: &JSONColumnDef) -> Result<()> {
30342        // Python: NESTED PATH path schema | this kind PATH path [FOR ORDINALITY]
30343        if let Some(nested_schema) = &e.nested_schema {
30344            self.write_keyword("NESTED");
30345            if let Some(path) = &e.path {
30346                self.write_space();
30347                self.write_keyword("PATH");
30348                self.write_space();
30349                self.generate_expression(path)?;
30350            }
30351            self.write_space();
30352            self.generate_expression(nested_schema)?;
30353        } else {
30354            if let Some(this) = &e.this {
30355                self.generate_expression(this)?;
30356            }
30357            if let Some(kind) = &e.kind {
30358                self.write_space();
30359                self.write(kind);
30360            }
30361            if e.format_json {
30362                self.write_space();
30363                self.write_keyword("FORMAT JSON");
30364            }
30365            if let Some(path) = &e.path {
30366                self.write_space();
30367                self.write_keyword("PATH");
30368                self.write_space();
30369                self.generate_expression(path)?;
30370            }
30371            if e.ordinality.is_some() {
30372                self.write_keyword(" FOR ORDINALITY");
30373            }
30374        }
30375        Ok(())
30376    }
30377
30378    fn generate_json_exists(&mut self, e: &JSONExists) -> Result<()> {
30379        // JSON_EXISTS(this, path PASSING vars ON ERROR/EMPTY condition)
30380        self.write_keyword("JSON_EXISTS");
30381        self.write("(");
30382        self.generate_expression(&e.this)?;
30383        if let Some(path) = &e.path {
30384            self.write(", ");
30385            self.generate_expression(path)?;
30386        }
30387        if let Some(passing) = &e.passing {
30388            self.write_space();
30389            self.write_keyword("PASSING");
30390            self.write_space();
30391            self.generate_expression(passing)?;
30392        }
30393        if let Some(on_condition) = &e.on_condition {
30394            self.write_space();
30395            self.generate_expression(on_condition)?;
30396        }
30397        self.write(")");
30398        Ok(())
30399    }
30400
30401    fn generate_json_cast(&mut self, e: &JSONCast) -> Result<()> {
30402        self.generate_expression(&e.this)?;
30403        self.write(".:");
30404        // If the data type has nested type parameters (like Array(JSON), Map(String, Int)),
30405        // wrap the entire type string in double quotes.
30406        // This matches Python sqlglot's ClickHouse _json_cast_sql behavior.
30407        if Self::data_type_has_nested_expressions(&e.to) {
30408            // Generate the data type to a temporary string buffer, then wrap in quotes
30409            let saved = std::mem::take(&mut self.output);
30410            self.generate_data_type(&e.to)?;
30411            let type_sql = std::mem::replace(&mut self.output, saved);
30412            self.write("\"");
30413            self.write(&type_sql);
30414            self.write("\"");
30415        } else {
30416            self.generate_data_type(&e.to)?;
30417        }
30418        Ok(())
30419    }
30420
30421    /// Check if a DataType has nested type expressions (sub-types).
30422    /// This corresponds to Python sqlglot's `to.expressions` being non-empty.
30423    fn data_type_has_nested_expressions(dt: &DataType) -> bool {
30424        matches!(
30425            dt,
30426            DataType::Array { .. } | DataType::Map { .. } | DataType::Struct { .. }
30427        )
30428    }
30429
30430    fn generate_json_extract_array(&mut self, e: &JSONExtractArray) -> Result<()> {
30431        // JSON_EXTRACT_ARRAY(this, expression)
30432        self.write_keyword("JSON_EXTRACT_ARRAY");
30433        self.write("(");
30434        self.generate_expression(&e.this)?;
30435        if let Some(expr) = &e.expression {
30436            self.write(", ");
30437            self.generate_expression(expr)?;
30438        }
30439        self.write(")");
30440        Ok(())
30441    }
30442
30443    fn generate_json_extract_quote(&mut self, e: &JSONExtractQuote) -> Result<()> {
30444        // Snowflake: KEEP [OMIT] QUOTES [SCALAR_ONLY] for JSON extraction
30445        if let Some(option) = &e.option {
30446            self.generate_expression(option)?;
30447            self.write_space();
30448        }
30449        self.write_keyword("QUOTES");
30450        if e.scalar.is_some() {
30451            self.write_keyword(" SCALAR_ONLY");
30452        }
30453        Ok(())
30454    }
30455
30456    fn generate_json_extract_scalar(&mut self, e: &JSONExtractScalar) -> Result<()> {
30457        // JSON_EXTRACT_SCALAR(this, expression)
30458        self.write_keyword("JSON_EXTRACT_SCALAR");
30459        self.write("(");
30460        self.generate_expression(&e.this)?;
30461        self.write(", ");
30462        self.generate_expression(&e.expression)?;
30463        self.write(")");
30464        Ok(())
30465    }
30466
30467    fn generate_json_extract_path(&mut self, e: &JSONExtract) -> Result<()> {
30468        // For variant_extract (Snowflake/Databricks colon syntax like a:field)
30469        // Databricks uses col:path syntax, Snowflake uses GET_PATH(col, 'path')
30470        // Otherwise output JSON_EXTRACT(this, expression)
30471        if e.variant_extract.is_some() {
30472            use crate::dialects::DialectType;
30473            if matches!(self.config.dialect, Some(DialectType::Databricks)) {
30474                // Databricks: output col:path syntax (e.g., c1:price, c1:price.foo, c1:price.bar[1])
30475                // Keys that are not safe identifiers (contain hyphens, spaces, etc.) must use
30476                // bracket notation: c:["x-y"] instead of c:x-y
30477                self.generate_expression(&e.this)?;
30478                self.write(":");
30479                match e.expression.as_ref() {
30480                    Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
30481                        let Literal::String(s) = lit.as_ref() else {
30482                            unreachable!()
30483                        };
30484                        self.write_databricks_json_path(s);
30485                    }
30486                    _ => {
30487                        // Fallback: generate as-is (shouldn't happen in typical cases)
30488                        self.generate_expression(&e.expression)?;
30489                    }
30490                }
30491            } else {
30492                // Snowflake and others: use GET_PATH(col, 'path')
30493                self.write_keyword("GET_PATH");
30494                self.write("(");
30495                self.generate_expression(&e.this)?;
30496                self.write(", ");
30497                self.generate_expression(&e.expression)?;
30498                self.write(")");
30499            }
30500        } else {
30501            self.write_keyword("JSON_EXTRACT");
30502            self.write("(");
30503            self.generate_expression(&e.this)?;
30504            self.write(", ");
30505            self.generate_expression(&e.expression)?;
30506            for expr in &e.expressions {
30507                self.write(", ");
30508                self.generate_expression(expr)?;
30509            }
30510            self.write(")");
30511        }
30512        Ok(())
30513    }
30514
30515    /// Write a Databricks JSON colon-path, using bracket notation for keys
30516    /// that are not safe identifiers (e.g., contain hyphens, spaces, etc.)
30517    /// Safe identifier regex: ^[_a-zA-Z]\w*$
30518    fn write_databricks_json_path(&mut self, path: &str) {
30519        // If the path already starts with bracket notation (e.g., '["fr\'uit"]'),
30520        // it was already formatted by the parser - output as-is
30521        if path.starts_with("[\"") || path.starts_with("['") {
30522            self.write(path);
30523            return;
30524        }
30525        // Split the path into segments at '.' boundaries, but preserve bracket subscripts
30526        // e.g., "price.items[0].name" -> ["price", "items[0]", "name"]
30527        // e.g., "x-y" -> ["x-y"]
30528        let mut first = true;
30529        for segment in path.split('.') {
30530            if !first {
30531                self.write(".");
30532            }
30533            first = false;
30534            // Check if there's a bracket subscript in this segment: "items[0]"
30535            if let Some(bracket_pos) = segment.find('[') {
30536                let key = &segment[..bracket_pos];
30537                let subscript = &segment[bracket_pos..];
30538                if key.is_empty() {
30539                    // Bracket notation at start of segment (e.g., already formatted)
30540                    self.write(segment);
30541                } else if Self::is_safe_json_path_key(key) {
30542                    self.write(key);
30543                    self.write(subscript);
30544                } else {
30545                    self.write("[\"");
30546                    self.write(key);
30547                    self.write("\"]");
30548                    self.write(subscript);
30549                }
30550            } else if Self::is_safe_json_path_key(segment) {
30551                self.write(segment);
30552            } else {
30553                self.write("[\"");
30554                self.write(segment);
30555                self.write("\"]");
30556            }
30557        }
30558    }
30559
30560    /// Check if a JSON path key is a safe identifier that doesn't need bracket quoting.
30561    /// Matches Python sqlglot's SAFE_IDENTIFIER_RE: ^[_a-zA-Z]\w*$
30562    fn is_safe_json_path_key(key: &str) -> bool {
30563        if key.is_empty() {
30564            return false;
30565        }
30566        let mut chars = key.chars();
30567        let first = chars.next().unwrap();
30568        if first != '_' && !first.is_ascii_alphabetic() {
30569            return false;
30570        }
30571        chars.all(|c| c == '_' || c.is_ascii_alphanumeric())
30572    }
30573
30574    fn generate_json_format(&mut self, e: &JSONFormat) -> Result<()> {
30575        // Output: {expr} FORMAT JSON
30576        // This wraps an expression with FORMAT JSON suffix (Oracle JSON function syntax)
30577        if let Some(this) = &e.this {
30578            self.generate_expression(this)?;
30579            self.write_space();
30580        }
30581        self.write_keyword("FORMAT JSON");
30582        Ok(())
30583    }
30584
30585    fn generate_json_key_value(&mut self, e: &JSONKeyValue) -> Result<()> {
30586        // key: value (for JSON objects)
30587        self.generate_expression(&e.this)?;
30588        self.write(": ");
30589        self.generate_expression(&e.expression)?;
30590        Ok(())
30591    }
30592
30593    fn generate_json_keys(&mut self, e: &JSONKeys) -> Result<()> {
30594        // JSON_KEYS(this, expression, expressions...)
30595        self.write_keyword("JSON_KEYS");
30596        self.write("(");
30597        self.generate_expression(&e.this)?;
30598        if let Some(expr) = &e.expression {
30599            self.write(", ");
30600            self.generate_expression(expr)?;
30601        }
30602        for expr in &e.expressions {
30603            self.write(", ");
30604            self.generate_expression(expr)?;
30605        }
30606        self.write(")");
30607        Ok(())
30608    }
30609
30610    fn generate_json_keys_at_depth(&mut self, e: &JSONKeysAtDepth) -> Result<()> {
30611        // JSON_KEYS(this, expression)
30612        self.write_keyword("JSON_KEYS");
30613        self.write("(");
30614        self.generate_expression(&e.this)?;
30615        if let Some(expr) = &e.expression {
30616            self.write(", ");
30617            self.generate_expression(expr)?;
30618        }
30619        self.write(")");
30620        Ok(())
30621    }
30622
30623    fn generate_json_path_expr(&mut self, e: &JSONPath) -> Result<()> {
30624        // JSONPath expression: generates a quoted path like '$.foo' or '$[0]'
30625        // The path components are concatenated without spaces
30626        let mut path_str = String::new();
30627        for expr in &e.expressions {
30628            match expr {
30629                Expression::JSONPathRoot(_) => {
30630                    path_str.push('$');
30631                }
30632                Expression::JSONPathKey(k) => {
30633                    // .key or ."key" (quote if key has special characters)
30634                    if let Expression::Literal(lit) = k.this.as_ref() {
30635                        if let crate::expressions::Literal::String(s) = lit.as_ref() {
30636                            path_str.push('.');
30637                            // Quote the key if it contains non-alphanumeric characters (hyphens, spaces, etc.)
30638                            let needs_quoting = s.chars().any(|c| !c.is_alphanumeric() && c != '_');
30639                            if needs_quoting {
30640                                path_str.push('"');
30641                                path_str.push_str(s);
30642                                path_str.push('"');
30643                            } else {
30644                                path_str.push_str(s);
30645                            }
30646                        }
30647                    }
30648                }
30649                Expression::JSONPathSubscript(s) => {
30650                    // [index]
30651                    if let Expression::Literal(lit) = s.this.as_ref() {
30652                        if let crate::expressions::Literal::Number(n) = lit.as_ref() {
30653                            path_str.push('[');
30654                            path_str.push_str(n);
30655                            path_str.push(']');
30656                        }
30657                    }
30658                }
30659                _ => {
30660                    // For other path parts, try to generate them
30661                    let mut temp_gen = Self::with_arc_config(self.config.clone());
30662                    temp_gen.generate_expression(expr)?;
30663                    path_str.push_str(&temp_gen.output);
30664                }
30665            }
30666        }
30667        // Output as quoted string
30668        self.write("'");
30669        self.write(&path_str);
30670        self.write("'");
30671        Ok(())
30672    }
30673
30674    fn generate_json_path_filter(&mut self, e: &JSONPathFilter) -> Result<()> {
30675        // JSON path filter: ?(predicate)
30676        self.write("?(");
30677        self.generate_expression(&e.this)?;
30678        self.write(")");
30679        Ok(())
30680    }
30681
30682    fn generate_json_path_key(&mut self, e: &JSONPathKey) -> Result<()> {
30683        // JSON path key: .key or ["key"]
30684        self.write(".");
30685        self.generate_expression(&e.this)?;
30686        Ok(())
30687    }
30688
30689    fn generate_json_path_recursive(&mut self, e: &JSONPathRecursive) -> Result<()> {
30690        // JSON path recursive descent: ..
30691        self.write("..");
30692        if let Some(this) = &e.this {
30693            self.generate_expression(this)?;
30694        }
30695        Ok(())
30696    }
30697
30698    fn generate_json_path_root(&mut self) -> Result<()> {
30699        // JSON path root: $
30700        self.write("$");
30701        Ok(())
30702    }
30703
30704    fn generate_json_path_script(&mut self, e: &JSONPathScript) -> Result<()> {
30705        // JSON path script: (expression)
30706        self.write("(");
30707        self.generate_expression(&e.this)?;
30708        self.write(")");
30709        Ok(())
30710    }
30711
30712    fn generate_json_path_selector(&mut self, e: &JSONPathSelector) -> Result<()> {
30713        // JSON path selector: *
30714        self.generate_expression(&e.this)?;
30715        Ok(())
30716    }
30717
30718    fn generate_json_path_slice(&mut self, e: &JSONPathSlice) -> Result<()> {
30719        // JSON path slice: [start:end:step]
30720        self.write("[");
30721        if let Some(start) = &e.start {
30722            self.generate_expression(start)?;
30723        }
30724        self.write(":");
30725        if let Some(end) = &e.end {
30726            self.generate_expression(end)?;
30727        }
30728        if let Some(step) = &e.step {
30729            self.write(":");
30730            self.generate_expression(step)?;
30731        }
30732        self.write("]");
30733        Ok(())
30734    }
30735
30736    fn generate_json_path_subscript(&mut self, e: &JSONPathSubscript) -> Result<()> {
30737        // JSON path subscript: [index] or [*]
30738        self.write("[");
30739        self.generate_expression(&e.this)?;
30740        self.write("]");
30741        Ok(())
30742    }
30743
30744    fn generate_json_path_union(&mut self, e: &JSONPathUnion) -> Result<()> {
30745        // JSON path union: [key1, key2, ...]
30746        self.write("[");
30747        for (i, expr) in e.expressions.iter().enumerate() {
30748            if i > 0 {
30749                self.write(", ");
30750            }
30751            self.generate_expression(expr)?;
30752        }
30753        self.write("]");
30754        Ok(())
30755    }
30756
30757    fn generate_json_remove(&mut self, e: &JSONRemove) -> Result<()> {
30758        // JSON_REMOVE(this, path1, path2, ...)
30759        self.write_keyword("JSON_REMOVE");
30760        self.write("(");
30761        self.generate_expression(&e.this)?;
30762        for expr in &e.expressions {
30763            self.write(", ");
30764            self.generate_expression(expr)?;
30765        }
30766        self.write(")");
30767        Ok(())
30768    }
30769
30770    fn generate_json_schema(&mut self, e: &JSONSchema) -> Result<()> {
30771        // COLUMNS(col1 type, col2 type, ...)
30772        // When pretty printing and content is too wide, format with each column on a separate line
30773        self.write_keyword("COLUMNS");
30774        self.write("(");
30775
30776        if self.config.pretty && !e.expressions.is_empty() {
30777            // First, generate all expressions into strings to check width
30778            let mut expr_strings: Vec<String> = Vec::with_capacity(e.expressions.len());
30779            for expr in &e.expressions {
30780                let mut temp_gen = Generator::with_arc_config(self.config.clone());
30781                temp_gen.generate_expression(expr)?;
30782                expr_strings.push(temp_gen.output);
30783            }
30784
30785            // Check if total width exceeds max_text_width
30786            if self.too_wide(&expr_strings) {
30787                // Pretty print: each column on its own line
30788                self.write_newline();
30789                self.indent_level += 1;
30790                for (i, expr_str) in expr_strings.iter().enumerate() {
30791                    if i > 0 {
30792                        self.write(",");
30793                        self.write_newline();
30794                    }
30795                    self.write_indent();
30796                    self.write(expr_str);
30797                }
30798                self.write_newline();
30799                self.indent_level -= 1;
30800                self.write_indent();
30801            } else {
30802                // Compact: all on one line
30803                for (i, expr_str) in expr_strings.iter().enumerate() {
30804                    if i > 0 {
30805                        self.write(", ");
30806                    }
30807                    self.write(expr_str);
30808                }
30809            }
30810        } else {
30811            // Non-pretty mode: compact format
30812            for (i, expr) in e.expressions.iter().enumerate() {
30813                if i > 0 {
30814                    self.write(", ");
30815                }
30816                self.generate_expression(expr)?;
30817            }
30818        }
30819        self.write(")");
30820        Ok(())
30821    }
30822
30823    fn generate_json_set(&mut self, e: &JSONSet) -> Result<()> {
30824        // JSON_SET(this, path, value, ...)
30825        self.write_keyword("JSON_SET");
30826        self.write("(");
30827        self.generate_expression(&e.this)?;
30828        for expr in &e.expressions {
30829            self.write(", ");
30830            self.generate_expression(expr)?;
30831        }
30832        self.write(")");
30833        Ok(())
30834    }
30835
30836    fn generate_json_strip_nulls(&mut self, e: &JSONStripNulls) -> Result<()> {
30837        // JSON_STRIP_NULLS(this, expression)
30838        self.write_keyword("JSON_STRIP_NULLS");
30839        self.write("(");
30840        self.generate_expression(&e.this)?;
30841        if let Some(expr) = &e.expression {
30842            self.write(", ");
30843            self.generate_expression(expr)?;
30844        }
30845        self.write(")");
30846        Ok(())
30847    }
30848
30849    fn generate_json_table(&mut self, e: &JSONTable) -> Result<()> {
30850        // JSON_TABLE(this, path [error_handling] [empty_handling] schema)
30851        self.write_keyword("JSON_TABLE");
30852        self.write("(");
30853        self.generate_expression(&e.this)?;
30854        if let Some(path) = &e.path {
30855            self.write(", ");
30856            self.generate_expression(path)?;
30857        }
30858        if let Some(error_handling) = &e.error_handling {
30859            self.write_space();
30860            self.generate_expression(error_handling)?;
30861        }
30862        if let Some(empty_handling) = &e.empty_handling {
30863            self.write_space();
30864            self.generate_expression(empty_handling)?;
30865        }
30866        if let Some(schema) = &e.schema {
30867            self.write_space();
30868            self.generate_expression(schema)?;
30869        }
30870        self.write(")");
30871        Ok(())
30872    }
30873
30874    fn generate_json_type(&mut self, e: &JSONType) -> Result<()> {
30875        // JSON_TYPE(this)
30876        self.write_keyword("JSON_TYPE");
30877        self.write("(");
30878        self.generate_expression(&e.this)?;
30879        self.write(")");
30880        Ok(())
30881    }
30882
30883    fn generate_json_value(&mut self, e: &JSONValue) -> Result<()> {
30884        // JSON_VALUE(this, path RETURNING type ON condition)
30885        self.write_keyword("JSON_VALUE");
30886        self.write("(");
30887        self.generate_expression(&e.this)?;
30888        if let Some(path) = &e.path {
30889            self.write(", ");
30890            self.generate_expression(path)?;
30891        }
30892        if let Some(returning) = &e.returning {
30893            self.write_space();
30894            self.write_keyword("RETURNING");
30895            self.write_space();
30896            self.generate_expression(returning)?;
30897        }
30898        if let Some(on_condition) = &e.on_condition {
30899            self.write_space();
30900            self.generate_expression(on_condition)?;
30901        }
30902        self.write(")");
30903        Ok(())
30904    }
30905
30906    fn generate_json_value_array(&mut self, e: &JSONValueArray) -> Result<()> {
30907        // JSON_VALUE_ARRAY(this)
30908        self.write_keyword("JSON_VALUE_ARRAY");
30909        self.write("(");
30910        self.generate_expression(&e.this)?;
30911        self.write(")");
30912        Ok(())
30913    }
30914
30915    fn generate_jarowinkler_similarity(&mut self, e: &JarowinklerSimilarity) -> Result<()> {
30916        // JAROWINKLER_SIMILARITY(str1, str2)
30917        self.write_keyword("JAROWINKLER_SIMILARITY");
30918        self.write("(");
30919        self.generate_expression(&e.this)?;
30920        self.write(", ");
30921        self.generate_expression(&e.expression)?;
30922        self.write(")");
30923        Ok(())
30924    }
30925
30926    fn generate_join_hint(&mut self, e: &JoinHint) -> Result<()> {
30927        // Python: this(expressions)
30928        self.generate_expression(&e.this)?;
30929        self.write("(");
30930        for (i, expr) in e.expressions.iter().enumerate() {
30931            if i > 0 {
30932                self.write(", ");
30933            }
30934            self.generate_expression(expr)?;
30935        }
30936        self.write(")");
30937        Ok(())
30938    }
30939
30940    fn generate_journal_property(&mut self, e: &JournalProperty) -> Result<()> {
30941        // Python: {no}{local}{dual}{before}{after}JOURNAL
30942        if e.no.is_some() {
30943            self.write_keyword("NO ");
30944        }
30945        if let Some(local) = &e.local {
30946            self.generate_expression(local)?;
30947            self.write_space();
30948        }
30949        if e.dual.is_some() {
30950            self.write_keyword("DUAL ");
30951        }
30952        if e.before.is_some() {
30953            self.write_keyword("BEFORE ");
30954        }
30955        if e.after.is_some() {
30956            self.write_keyword("AFTER ");
30957        }
30958        self.write_keyword("JOURNAL");
30959        Ok(())
30960    }
30961
30962    fn generate_language_property(&mut self, e: &LanguageProperty) -> Result<()> {
30963        // LANGUAGE language_name
30964        self.write_keyword("LANGUAGE");
30965        self.write_space();
30966        self.generate_expression(&e.this)?;
30967        Ok(())
30968    }
30969
30970    fn generate_lateral(&mut self, e: &Lateral) -> Result<()> {
30971        // Python: handles LATERAL VIEW (Hive/Spark) and regular LATERAL
30972        if e.view.is_some() {
30973            // LATERAL VIEW [OUTER] expression [alias] [AS columns]
30974            self.write_keyword("LATERAL VIEW");
30975            if e.outer.is_some() {
30976                self.write_space();
30977                self.write_keyword("OUTER");
30978            }
30979            self.write_space();
30980            self.generate_expression(&e.this)?;
30981            if let Some(alias) = &e.alias {
30982                self.write_space();
30983                self.write(alias);
30984            }
30985        } else {
30986            // LATERAL subquery/function [WITH ORDINALITY] [AS alias(columns)]
30987            self.write_keyword("LATERAL");
30988            self.write_space();
30989            self.generate_expression(&e.this)?;
30990            if e.ordinality.is_some() {
30991                self.write_space();
30992                self.write_keyword("WITH ORDINALITY");
30993            }
30994            if let Some(alias) = &e.alias {
30995                self.write_space();
30996                self.write_keyword("AS");
30997                self.write_space();
30998                self.write(alias);
30999                if !e.column_aliases.is_empty() {
31000                    self.write("(");
31001                    for (i, col) in e.column_aliases.iter().enumerate() {
31002                        if i > 0 {
31003                            self.write(", ");
31004                        }
31005                        self.write(col);
31006                    }
31007                    self.write(")");
31008                }
31009            }
31010        }
31011        Ok(())
31012    }
31013
31014    fn generate_like_property(&mut self, e: &LikeProperty) -> Result<()> {
31015        // Python: LIKE this [options]
31016        self.write_keyword("LIKE");
31017        self.write_space();
31018        self.generate_expression(&e.this)?;
31019        for expr in &e.expressions {
31020            self.write_space();
31021            self.generate_expression(expr)?;
31022        }
31023        Ok(())
31024    }
31025
31026    fn generate_limit(&mut self, e: &Limit) -> Result<()> {
31027        self.write_keyword("LIMIT");
31028        self.write_space();
31029        self.write_limit_expr(&e.this)?;
31030        if e.percent {
31031            self.write_space();
31032            self.write_keyword("PERCENT");
31033        }
31034        // Emit any comments that were captured from before the LIMIT keyword
31035        for comment in &e.comments {
31036            self.write(" ");
31037            self.write_formatted_comment(comment);
31038        }
31039        Ok(())
31040    }
31041
31042    fn generate_limit_options(&mut self, e: &LimitOptions) -> Result<()> {
31043        // Python: [PERCENT][ROWS][WITH TIES|ONLY]
31044        if e.percent.is_some() {
31045            self.write_keyword(" PERCENT");
31046        }
31047        if e.rows.is_some() {
31048            self.write_keyword(" ROWS");
31049        }
31050        if e.with_ties.is_some() {
31051            self.write_keyword(" WITH TIES");
31052        } else if e.rows.is_some() {
31053            self.write_keyword(" ONLY");
31054        }
31055        Ok(())
31056    }
31057
31058    fn generate_list(&mut self, e: &List) -> Result<()> {
31059        use crate::dialects::DialectType;
31060        let is_materialize = matches!(self.config.dialect, Some(DialectType::Materialize));
31061
31062        // Check if this is a subquery-based list (LIST(SELECT ...))
31063        if e.expressions.len() == 1 {
31064            if let Expression::Select(_) = &e.expressions[0] {
31065                self.write_keyword("LIST");
31066                self.write("(");
31067                self.generate_expression(&e.expressions[0])?;
31068                self.write(")");
31069                return Ok(());
31070            }
31071        }
31072
31073        // For Materialize, output as LIST[expr, expr, ...]
31074        if is_materialize {
31075            self.write_keyword("LIST");
31076            self.write("[");
31077            for (i, expr) in e.expressions.iter().enumerate() {
31078                if i > 0 {
31079                    self.write(", ");
31080                }
31081                self.generate_expression(expr)?;
31082            }
31083            self.write("]");
31084        } else {
31085            // For other dialects, output as LIST(expr, expr, ...)
31086            self.write_keyword("LIST");
31087            self.write("(");
31088            for (i, expr) in e.expressions.iter().enumerate() {
31089                if i > 0 {
31090                    self.write(", ");
31091                }
31092                self.generate_expression(expr)?;
31093            }
31094            self.write(")");
31095        }
31096        Ok(())
31097    }
31098
31099    fn generate_tomap(&mut self, e: &ToMap) -> Result<()> {
31100        // Check if this is a subquery-based map (MAP(SELECT ...))
31101        if let Expression::Select(_) = &*e.this {
31102            self.write_keyword("MAP");
31103            self.write("(");
31104            self.generate_expression(&e.this)?;
31105            self.write(")");
31106            return Ok(());
31107        }
31108
31109        let is_duckdb = matches!(self.config.dialect, Some(DialectType::DuckDB));
31110
31111        // For Struct-based map: DuckDB uses MAP {'key': value}, Materialize uses MAP['key' => value]
31112        self.write_keyword("MAP");
31113        if is_duckdb {
31114            self.write(" {");
31115        } else {
31116            self.write("[");
31117        }
31118        if let Expression::Struct(s) = &*e.this {
31119            for (i, (_, expr)) in s.fields.iter().enumerate() {
31120                if i > 0 {
31121                    self.write(", ");
31122                }
31123                if let Expression::PropertyEQ(op) = expr {
31124                    self.generate_expression(&op.left)?;
31125                    if is_duckdb {
31126                        self.write(": ");
31127                    } else {
31128                        self.write(" => ");
31129                    }
31130                    self.generate_expression(&op.right)?;
31131                } else {
31132                    self.generate_expression(expr)?;
31133                }
31134            }
31135        }
31136        if is_duckdb {
31137            self.write("}");
31138        } else {
31139            self.write("]");
31140        }
31141        Ok(())
31142    }
31143
31144    fn generate_localtime(&mut self, e: &Localtime) -> Result<()> {
31145        // Python: LOCALTIME or LOCALTIME(precision)
31146        self.write_keyword("LOCALTIME");
31147        if let Some(precision) = &e.this {
31148            self.write("(");
31149            self.generate_expression(precision)?;
31150            self.write(")");
31151        }
31152        Ok(())
31153    }
31154
31155    fn generate_localtimestamp(&mut self, e: &Localtimestamp) -> Result<()> {
31156        // Python: LOCALTIMESTAMP or LOCALTIMESTAMP(precision)
31157        self.write_keyword("LOCALTIMESTAMP");
31158        if let Some(precision) = &e.this {
31159            self.write("(");
31160            self.generate_expression(precision)?;
31161            self.write(")");
31162        }
31163        Ok(())
31164    }
31165
31166    fn generate_location_property(&mut self, e: &LocationProperty) -> Result<()> {
31167        // LOCATION 'path'
31168        self.write_keyword("LOCATION");
31169        self.write_space();
31170        self.generate_expression(&e.this)?;
31171        Ok(())
31172    }
31173
31174    fn generate_lock(&mut self, e: &Lock) -> Result<()> {
31175        // Python: FOR UPDATE|FOR SHARE [OF tables] [NOWAIT|WAIT n]
31176        if e.update.is_some() {
31177            if e.key.is_some() {
31178                self.write_keyword("FOR NO KEY UPDATE");
31179            } else {
31180                self.write_keyword("FOR UPDATE");
31181            }
31182        } else {
31183            if e.key.is_some() {
31184                self.write_keyword("FOR KEY SHARE");
31185            } else {
31186                self.write_keyword("FOR SHARE");
31187            }
31188        }
31189        if !e.expressions.is_empty() {
31190            self.write_keyword(" OF ");
31191            for (i, expr) in e.expressions.iter().enumerate() {
31192                if i > 0 {
31193                    self.write(", ");
31194                }
31195                self.generate_expression(expr)?;
31196            }
31197        }
31198        // Handle wait option following Python sqlglot convention:
31199        // - Boolean(true) -> NOWAIT
31200        // - Boolean(false) -> SKIP LOCKED
31201        // - Literal (number) -> WAIT n
31202        if let Some(wait) = &e.wait {
31203            match wait.as_ref() {
31204                Expression::Boolean(b) => {
31205                    if b.value {
31206                        self.write_keyword(" NOWAIT");
31207                    } else {
31208                        self.write_keyword(" SKIP LOCKED");
31209                    }
31210                }
31211                _ => {
31212                    // It's a literal (number), output WAIT n
31213                    self.write_keyword(" WAIT ");
31214                    self.generate_expression(wait)?;
31215                }
31216            }
31217        }
31218        Ok(())
31219    }
31220
31221    fn generate_lock_property(&mut self, e: &LockProperty) -> Result<()> {
31222        // LOCK property
31223        self.write_keyword("LOCK");
31224        self.write_space();
31225        self.generate_expression(&e.this)?;
31226        Ok(())
31227    }
31228
31229    fn generate_locking_property(&mut self, e: &LockingProperty) -> Result<()> {
31230        // Python: LOCKING kind [this] [for_or_in] lock_type [OVERRIDE]
31231        self.write_keyword("LOCKING");
31232        self.write_space();
31233        self.write(&e.kind);
31234        if let Some(this) = &e.this {
31235            self.write_space();
31236            self.generate_expression(this)?;
31237        }
31238        if let Some(for_or_in) = &e.for_or_in {
31239            self.write_space();
31240            self.generate_expression(for_or_in)?;
31241        }
31242        if let Some(lock_type) = &e.lock_type {
31243            self.write_space();
31244            self.generate_expression(lock_type)?;
31245        }
31246        if e.override_.is_some() {
31247            self.write_keyword(" OVERRIDE");
31248        }
31249        Ok(())
31250    }
31251
31252    fn generate_locking_statement(&mut self, e: &LockingStatement) -> Result<()> {
31253        // this expression
31254        self.generate_expression(&e.this)?;
31255        self.write_space();
31256        self.generate_expression(&e.expression)?;
31257        Ok(())
31258    }
31259
31260    fn generate_log_property(&mut self, e: &LogProperty) -> Result<()> {
31261        // [NO] LOG
31262        if e.no.is_some() {
31263            self.write_keyword("NO ");
31264        }
31265        self.write_keyword("LOG");
31266        Ok(())
31267    }
31268
31269    fn generate_md5_digest(&mut self, e: &MD5Digest) -> Result<()> {
31270        // MD5(this, expressions...)
31271        self.write_keyword("MD5");
31272        self.write("(");
31273        self.generate_expression(&e.this)?;
31274        for expr in &e.expressions {
31275            self.write(", ");
31276            self.generate_expression(expr)?;
31277        }
31278        self.write(")");
31279        Ok(())
31280    }
31281
31282    fn generate_ml_forecast(&mut self, e: &MLForecast) -> Result<()> {
31283        // ML.FORECAST(model, [params])
31284        self.write_keyword("ML.FORECAST");
31285        self.write("(");
31286        self.generate_expression(&e.this)?;
31287        if let Some(expression) = &e.expression {
31288            self.write(", ");
31289            self.generate_expression(expression)?;
31290        }
31291        if let Some(params) = &e.params_struct {
31292            self.write(", ");
31293            self.generate_expression(params)?;
31294        }
31295        self.write(")");
31296        Ok(())
31297    }
31298
31299    fn generate_ml_translate(&mut self, e: &MLTranslate) -> Result<()> {
31300        // ML.TRANSLATE(model, input, [params])
31301        self.write_keyword("ML.TRANSLATE");
31302        self.write("(");
31303        self.generate_expression(&e.this)?;
31304        self.write(", ");
31305        self.generate_expression(&e.expression)?;
31306        if let Some(params) = &e.params_struct {
31307            self.write(", ");
31308            self.generate_expression(params)?;
31309        }
31310        self.write(")");
31311        Ok(())
31312    }
31313
31314    fn generate_make_interval(&mut self, e: &MakeInterval) -> Result<()> {
31315        // MAKE_INTERVAL(years => x, months => y, ...)
31316        self.write_keyword("MAKE_INTERVAL");
31317        self.write("(");
31318        let mut first = true;
31319        if let Some(year) = &e.year {
31320            self.write("years => ");
31321            self.generate_expression(year)?;
31322            first = false;
31323        }
31324        if let Some(month) = &e.month {
31325            if !first {
31326                self.write(", ");
31327            }
31328            self.write("months => ");
31329            self.generate_expression(month)?;
31330            first = false;
31331        }
31332        if let Some(week) = &e.week {
31333            if !first {
31334                self.write(", ");
31335            }
31336            self.write("weeks => ");
31337            self.generate_expression(week)?;
31338            first = false;
31339        }
31340        if let Some(day) = &e.day {
31341            if !first {
31342                self.write(", ");
31343            }
31344            self.write("days => ");
31345            self.generate_expression(day)?;
31346            first = false;
31347        }
31348        if let Some(hour) = &e.hour {
31349            if !first {
31350                self.write(", ");
31351            }
31352            self.write("hours => ");
31353            self.generate_expression(hour)?;
31354            first = false;
31355        }
31356        if let Some(minute) = &e.minute {
31357            if !first {
31358                self.write(", ");
31359            }
31360            self.write("mins => ");
31361            self.generate_expression(minute)?;
31362            first = false;
31363        }
31364        if let Some(second) = &e.second {
31365            if !first {
31366                self.write(", ");
31367            }
31368            self.write("secs => ");
31369            self.generate_expression(second)?;
31370        }
31371        self.write(")");
31372        Ok(())
31373    }
31374
31375    fn generate_manhattan_distance(&mut self, e: &ManhattanDistance) -> Result<()> {
31376        // MANHATTAN_DISTANCE(vector1, vector2)
31377        self.write_keyword("MANHATTAN_DISTANCE");
31378        self.write("(");
31379        self.generate_expression(&e.this)?;
31380        self.write(", ");
31381        self.generate_expression(&e.expression)?;
31382        self.write(")");
31383        Ok(())
31384    }
31385
31386    fn generate_map(&mut self, e: &Map) -> Result<()> {
31387        // MAP(key1, value1, key2, value2, ...)
31388        self.write_keyword("MAP");
31389        self.write("(");
31390        for (i, (key, value)) in e.keys.iter().zip(e.values.iter()).enumerate() {
31391            if i > 0 {
31392                self.write(", ");
31393            }
31394            self.generate_expression(key)?;
31395            self.write(", ");
31396            self.generate_expression(value)?;
31397        }
31398        self.write(")");
31399        Ok(())
31400    }
31401
31402    fn generate_map_cat(&mut self, e: &MapCat) -> Result<()> {
31403        // MAP_CAT(map1, map2)
31404        self.write_keyword("MAP_CAT");
31405        self.write("(");
31406        self.generate_expression(&e.this)?;
31407        self.write(", ");
31408        self.generate_expression(&e.expression)?;
31409        self.write(")");
31410        Ok(())
31411    }
31412
31413    fn generate_map_delete(&mut self, e: &MapDelete) -> Result<()> {
31414        // MAP_DELETE(map, key1, key2, ...)
31415        self.write_keyword("MAP_DELETE");
31416        self.write("(");
31417        self.generate_expression(&e.this)?;
31418        for expr in &e.expressions {
31419            self.write(", ");
31420            self.generate_expression(expr)?;
31421        }
31422        self.write(")");
31423        Ok(())
31424    }
31425
31426    fn generate_map_insert(&mut self, e: &MapInsert) -> Result<()> {
31427        // MAP_INSERT(map, key, value, [update_flag])
31428        self.write_keyword("MAP_INSERT");
31429        self.write("(");
31430        self.generate_expression(&e.this)?;
31431        if let Some(key) = &e.key {
31432            self.write(", ");
31433            self.generate_expression(key)?;
31434        }
31435        if let Some(value) = &e.value {
31436            self.write(", ");
31437            self.generate_expression(value)?;
31438        }
31439        if let Some(update_flag) = &e.update_flag {
31440            self.write(", ");
31441            self.generate_expression(update_flag)?;
31442        }
31443        self.write(")");
31444        Ok(())
31445    }
31446
31447    fn generate_map_pick(&mut self, e: &MapPick) -> Result<()> {
31448        // MAP_PICK(map, key1, key2, ...)
31449        self.write_keyword("MAP_PICK");
31450        self.write("(");
31451        self.generate_expression(&e.this)?;
31452        for expr in &e.expressions {
31453            self.write(", ");
31454            self.generate_expression(expr)?;
31455        }
31456        self.write(")");
31457        Ok(())
31458    }
31459
31460    fn generate_masking_policy_column_constraint(
31461        &mut self,
31462        e: &MaskingPolicyColumnConstraint,
31463    ) -> Result<()> {
31464        // Python: MASKING POLICY name [USING (cols)]
31465        self.write_keyword("MASKING POLICY");
31466        self.write_space();
31467        self.generate_expression(&e.this)?;
31468        if !e.expressions.is_empty() {
31469            self.write_keyword(" USING");
31470            self.write(" (");
31471            for (i, expr) in e.expressions.iter().enumerate() {
31472                if i > 0 {
31473                    self.write(", ");
31474                }
31475                self.generate_expression(expr)?;
31476            }
31477            self.write(")");
31478        }
31479        Ok(())
31480    }
31481
31482    fn generate_match_against(&mut self, e: &MatchAgainst) -> Result<()> {
31483        if matches!(
31484            self.config.dialect,
31485            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
31486        ) {
31487            if e.expressions.len() > 1 {
31488                self.write("(");
31489            }
31490            for (i, expr) in e.expressions.iter().enumerate() {
31491                if i > 0 {
31492                    self.write_keyword(" OR ");
31493                }
31494                self.generate_expression(expr)?;
31495                self.write_space();
31496                self.write("@@");
31497                self.write_space();
31498                self.generate_expression(&e.this)?;
31499            }
31500            if e.expressions.len() > 1 {
31501                self.write(")");
31502            }
31503            return Ok(());
31504        }
31505
31506        // MATCH(columns) AGAINST(expr [modifier])
31507        self.write_keyword("MATCH");
31508        self.write("(");
31509        for (i, expr) in e.expressions.iter().enumerate() {
31510            if i > 0 {
31511                self.write(", ");
31512            }
31513            self.generate_expression(expr)?;
31514        }
31515        self.write(")");
31516        self.write_keyword(" AGAINST");
31517        self.write("(");
31518        self.generate_expression(&e.this)?;
31519        if let Some(modifier) = &e.modifier {
31520            self.write_space();
31521            self.generate_expression(modifier)?;
31522        }
31523        self.write(")");
31524        Ok(())
31525    }
31526
31527    fn generate_match_recognize_measure(&mut self, e: &MatchRecognizeMeasure) -> Result<()> {
31528        // Python: [window_frame] this
31529        if let Some(window_frame) = &e.window_frame {
31530            self.write(&format!("{:?}", window_frame).to_ascii_uppercase());
31531            self.write_space();
31532        }
31533        self.generate_expression(&e.this)?;
31534        Ok(())
31535    }
31536
31537    fn generate_materialized_property(&mut self, e: &MaterializedProperty) -> Result<()> {
31538        // MATERIALIZED [this]
31539        self.write_keyword("MATERIALIZED");
31540        if let Some(this) = &e.this {
31541            self.write_space();
31542            self.generate_expression(this)?;
31543        }
31544        Ok(())
31545    }
31546
31547    fn generate_merge(&mut self, e: &Merge) -> Result<()> {
31548        // MERGE INTO target USING source ON condition WHEN ...
31549        // DuckDB variant: MERGE INTO target USING source USING (key_columns) WHEN ...
31550        if let Some(with_) = &e.with_ {
31551            if let Expression::With(with_clause) = with_.as_ref() {
31552                self.generate_with(with_clause)?;
31553                self.write_space();
31554            } else {
31555                self.generate_expression(with_)?;
31556                self.write_space();
31557            }
31558        }
31559        self.write_keyword("MERGE INTO");
31560        self.write_space();
31561        if matches!(self.config.dialect, Some(crate::DialectType::Oracle)) {
31562            if let Expression::Alias(alias) = e.this.as_ref() {
31563                self.generate_expression(&alias.this)?;
31564                self.write_space();
31565                self.generate_identifier(&alias.alias)?;
31566            } else {
31567                self.generate_expression(&e.this)?;
31568            }
31569        } else {
31570            self.generate_expression(&e.this)?;
31571        }
31572
31573        // USING clause - newline before in pretty mode
31574        if self.config.pretty {
31575            self.write_newline();
31576            self.write_indent();
31577        } else {
31578            self.write_space();
31579        }
31580        self.write_keyword("USING");
31581        self.write_space();
31582        self.generate_expression(&e.using)?;
31583
31584        // ON clause - newline before in pretty mode
31585        if let Some(on) = &e.on {
31586            if self.config.pretty {
31587                self.write_newline();
31588                self.write_indent();
31589            } else {
31590                self.write_space();
31591            }
31592            self.write_keyword("ON");
31593            self.write_space();
31594            self.generate_expression(on)?;
31595        }
31596        // DuckDB USING (key_columns) clause
31597        if let Some(using_cond) = &e.using_cond {
31598            self.write_space();
31599            self.write_keyword("USING");
31600            self.write_space();
31601            self.write("(");
31602            // using_cond is a Tuple containing the column identifiers
31603            if let Expression::Tuple(tuple) = using_cond.as_ref() {
31604                for (i, col) in tuple.expressions.iter().enumerate() {
31605                    if i > 0 {
31606                        self.write(", ");
31607                    }
31608                    self.generate_expression(col)?;
31609                }
31610            } else {
31611                self.generate_expression(using_cond)?;
31612            }
31613            self.write(")");
31614        }
31615        // For PostgreSQL dialect, extract target table name/alias to strip from UPDATE SET
31616        let saved_merge_strip = std::mem::take(&mut self.merge_strip_qualifiers);
31617        if matches!(
31618            self.config.dialect,
31619            Some(crate::DialectType::PostgreSQL)
31620                | Some(crate::DialectType::Redshift)
31621                | Some(crate::DialectType::Trino)
31622                | Some(crate::DialectType::Presto)
31623                | Some(crate::DialectType::Athena)
31624        ) {
31625            let mut names = Vec::new();
31626            match e.this.as_ref() {
31627                Expression::Alias(a) => {
31628                    // e.g., "x AS z" -> strip both "x" and "z"
31629                    if let Expression::Table(t) = &a.this {
31630                        names.push(t.name.name.clone());
31631                    } else if let Expression::Identifier(id) = &a.this {
31632                        names.push(id.name.clone());
31633                    }
31634                    names.push(a.alias.name.clone());
31635                }
31636                Expression::Table(t) => {
31637                    names.push(t.name.name.clone());
31638                }
31639                Expression::Identifier(id) => {
31640                    names.push(id.name.clone());
31641                }
31642                _ => {}
31643            }
31644            self.merge_strip_qualifiers = names;
31645        }
31646
31647        // WHEN clauses - newline before each in pretty mode
31648        if let Some(whens) = &e.whens {
31649            if self.config.pretty {
31650                self.write_newline();
31651                self.write_indent();
31652            } else {
31653                self.write_space();
31654            }
31655            self.generate_expression(whens)?;
31656        }
31657
31658        // Restore merge_strip_qualifiers
31659        self.merge_strip_qualifiers = saved_merge_strip;
31660
31661        // OUTPUT/RETURNING clause - newline before in pretty mode
31662        if let Some(returning) = &e.returning {
31663            if self.config.pretty {
31664                self.write_newline();
31665                self.write_indent();
31666            } else {
31667                self.write_space();
31668            }
31669            self.generate_expression(returning)?;
31670        }
31671        Ok(())
31672    }
31673
31674    fn generate_merge_block_ratio_property(&mut self, e: &MergeBlockRatioProperty) -> Result<()> {
31675        // Python: NO MERGEBLOCKRATIO | DEFAULT MERGEBLOCKRATIO | MERGEBLOCKRATIO=this [PERCENT]
31676        if e.no.is_some() {
31677            self.write_keyword("NO MERGEBLOCKRATIO");
31678        } else if e.default.is_some() {
31679            self.write_keyword("DEFAULT MERGEBLOCKRATIO");
31680        } else {
31681            self.write_keyword("MERGEBLOCKRATIO");
31682            self.write("=");
31683            if let Some(this) = &e.this {
31684                self.generate_expression(this)?;
31685            }
31686            if e.percent.is_some() {
31687                self.write_keyword(" PERCENT");
31688            }
31689        }
31690        Ok(())
31691    }
31692
31693    fn generate_merge_tree_ttl(&mut self, e: &MergeTreeTTL) -> Result<()> {
31694        // TTL expressions [WHERE where] [GROUP BY group] [SET aggregates]
31695        self.write_keyword("TTL");
31696        let pretty_clickhouse = self.config.pretty
31697            && matches!(
31698                self.config.dialect,
31699                Some(crate::dialects::DialectType::ClickHouse)
31700            );
31701
31702        if pretty_clickhouse {
31703            self.write_newline();
31704            self.indent_level += 1;
31705            for (i, expr) in e.expressions.iter().enumerate() {
31706                if i > 0 {
31707                    self.write(",");
31708                    self.write_newline();
31709                }
31710                self.write_indent();
31711                self.generate_expression(expr)?;
31712            }
31713            self.indent_level -= 1;
31714        } else {
31715            self.write_space();
31716            for (i, expr) in e.expressions.iter().enumerate() {
31717                if i > 0 {
31718                    self.write(", ");
31719                }
31720                self.generate_expression(expr)?;
31721            }
31722        }
31723
31724        if let Some(where_) = &e.where_ {
31725            if pretty_clickhouse {
31726                self.write_newline();
31727                if let Expression::Where(w) = where_.as_ref() {
31728                    self.write_indent();
31729                    self.write_keyword("WHERE");
31730                    self.write_newline();
31731                    self.indent_level += 1;
31732                    self.write_indent();
31733                    self.generate_expression(&w.this)?;
31734                    self.indent_level -= 1;
31735                } else {
31736                    self.write_indent();
31737                    self.generate_expression(where_)?;
31738                }
31739            } else {
31740                self.write_space();
31741                self.generate_expression(where_)?;
31742            }
31743        }
31744        if let Some(group) = &e.group {
31745            if pretty_clickhouse {
31746                self.write_newline();
31747                if let Expression::Group(g) = group.as_ref() {
31748                    self.write_indent();
31749                    self.write_keyword("GROUP BY");
31750                    self.write_newline();
31751                    self.indent_level += 1;
31752                    for (i, expr) in g.expressions.iter().enumerate() {
31753                        if i > 0 {
31754                            self.write(",");
31755                            self.write_newline();
31756                        }
31757                        self.write_indent();
31758                        self.generate_expression(expr)?;
31759                    }
31760                    self.indent_level -= 1;
31761                } else {
31762                    self.write_indent();
31763                    self.generate_expression(group)?;
31764                }
31765            } else {
31766                self.write_space();
31767                self.generate_expression(group)?;
31768            }
31769        }
31770        if let Some(aggregates) = &e.aggregates {
31771            if pretty_clickhouse {
31772                self.write_newline();
31773                self.write_indent();
31774                self.write_keyword("SET");
31775                self.write_newline();
31776                self.indent_level += 1;
31777                if let Expression::Tuple(t) = aggregates.as_ref() {
31778                    for (i, agg) in t.expressions.iter().enumerate() {
31779                        if i > 0 {
31780                            self.write(",");
31781                            self.write_newline();
31782                        }
31783                        self.write_indent();
31784                        self.generate_expression(agg)?;
31785                    }
31786                } else {
31787                    self.write_indent();
31788                    self.generate_expression(aggregates)?;
31789                }
31790                self.indent_level -= 1;
31791            } else {
31792                self.write_space();
31793                self.write_keyword("SET");
31794                self.write_space();
31795                if let Expression::Tuple(t) = aggregates.as_ref() {
31796                    for (i, agg) in t.expressions.iter().enumerate() {
31797                        if i > 0 {
31798                            self.write(", ");
31799                        }
31800                        self.generate_expression(agg)?;
31801                    }
31802                } else {
31803                    self.generate_expression(aggregates)?;
31804                }
31805            }
31806        }
31807        Ok(())
31808    }
31809
31810    fn generate_merge_tree_ttl_action(&mut self, e: &MergeTreeTTLAction) -> Result<()> {
31811        // Python: this [DELETE] [RECOMPRESS codec] [TO DISK disk] [TO VOLUME volume]
31812        self.generate_expression(&e.this)?;
31813        if e.delete.is_some() {
31814            self.write_keyword(" DELETE");
31815        }
31816        if let Some(recompress) = &e.recompress {
31817            self.write_keyword(" RECOMPRESS ");
31818            self.generate_expression(recompress)?;
31819        }
31820        if let Some(to_disk) = &e.to_disk {
31821            self.write_keyword(" TO DISK ");
31822            self.generate_expression(to_disk)?;
31823        }
31824        if let Some(to_volume) = &e.to_volume {
31825            self.write_keyword(" TO VOLUME ");
31826            self.generate_expression(to_volume)?;
31827        }
31828        Ok(())
31829    }
31830
31831    fn generate_minhash(&mut self, e: &Minhash) -> Result<()> {
31832        // MINHASH(this, expressions...)
31833        self.write_keyword("MINHASH");
31834        self.write("(");
31835        self.generate_expression(&e.this)?;
31836        for expr in &e.expressions {
31837            self.write(", ");
31838            self.generate_expression(expr)?;
31839        }
31840        self.write(")");
31841        Ok(())
31842    }
31843
31844    fn generate_model_attribute(&mut self, e: &ModelAttribute) -> Result<()> {
31845        // model!attribute - Snowflake syntax
31846        self.generate_expression(&e.this)?;
31847        self.write("!");
31848        self.generate_expression(&e.expression)?;
31849        Ok(())
31850    }
31851
31852    fn generate_monthname(&mut self, e: &Monthname) -> Result<()> {
31853        // MONTHNAME(this)
31854        self.write_keyword("MONTHNAME");
31855        self.write("(");
31856        self.generate_expression(&e.this)?;
31857        self.write(")");
31858        Ok(())
31859    }
31860
31861    fn generate_multitable_inserts(&mut self, e: &MultitableInserts) -> Result<()> {
31862        // Output leading comments
31863        for comment in &e.leading_comments {
31864            self.write_formatted_comment(comment);
31865            if self.config.pretty {
31866                self.write_newline();
31867                self.write_indent();
31868            } else {
31869                self.write_space();
31870            }
31871        }
31872        // Python: INSERT [OVERWRITE] kind expressions source
31873        self.write_keyword("INSERT");
31874        if e.overwrite {
31875            self.write_space();
31876            self.write_keyword("OVERWRITE");
31877        }
31878        self.write_space();
31879        self.write(&e.kind);
31880        if self.config.pretty {
31881            self.indent_level += 1;
31882            for expr in &e.expressions {
31883                self.write_newline();
31884                self.write_indent();
31885                self.generate_expression(expr)?;
31886            }
31887            self.indent_level -= 1;
31888        } else {
31889            for expr in &e.expressions {
31890                self.write_space();
31891                self.generate_expression(expr)?;
31892            }
31893        }
31894        if let Some(source) = &e.source {
31895            if self.config.pretty {
31896                self.write_newline();
31897                self.write_indent();
31898            } else {
31899                self.write_space();
31900            }
31901            self.generate_expression(source)?;
31902        }
31903        Ok(())
31904    }
31905
31906    fn generate_next_value_for(&mut self, e: &NextValueFor) -> Result<()> {
31907        // Python: NEXT VALUE FOR this [OVER (order)]
31908        self.write_keyword("NEXT VALUE FOR");
31909        self.write_space();
31910        self.generate_expression(&e.this)?;
31911        if let Some(order) = &e.order {
31912            self.write_space();
31913            self.write_keyword("OVER");
31914            self.write(" (");
31915            self.generate_expression(order)?;
31916            self.write(")");
31917        }
31918        Ok(())
31919    }
31920
31921    fn generate_normal(&mut self, e: &Normal) -> Result<()> {
31922        // NORMAL(mean, stddev, gen)
31923        self.write_keyword("NORMAL");
31924        self.write("(");
31925        self.generate_expression(&e.this)?;
31926        if let Some(stddev) = &e.stddev {
31927            self.write(", ");
31928            self.generate_expression(stddev)?;
31929        }
31930        if let Some(gen) = &e.gen {
31931            self.write(", ");
31932            self.generate_expression(gen)?;
31933        }
31934        self.write(")");
31935        Ok(())
31936    }
31937
31938    fn generate_normalize(&mut self, e: &Normalize) -> Result<()> {
31939        // NORMALIZE(this, form) or CASEFOLD version
31940        if e.is_casefold.is_some() {
31941            self.write_keyword("NORMALIZE_AND_CASEFOLD");
31942        } else {
31943            self.write_keyword("NORMALIZE");
31944        }
31945        self.write("(");
31946        self.generate_expression(&e.this)?;
31947        if let Some(form) = &e.form {
31948            self.write(", ");
31949            self.generate_expression(form)?;
31950        }
31951        self.write(")");
31952        Ok(())
31953    }
31954
31955    fn generate_not_null_column_constraint(&mut self, e: &NotNullColumnConstraint) -> Result<()> {
31956        // Python: [NOT ]NULL
31957        if e.allow_null.is_none() {
31958            self.write_keyword("NOT ");
31959        }
31960        self.write_keyword("NULL");
31961        Ok(())
31962    }
31963
31964    fn generate_nullif(&mut self, e: &Nullif) -> Result<()> {
31965        // NULLIF(this, expression)
31966        self.write_keyword("NULLIF");
31967        self.write("(");
31968        self.generate_expression(&e.this)?;
31969        self.write(", ");
31970        self.generate_expression(&e.expression)?;
31971        self.write(")");
31972        Ok(())
31973    }
31974
31975    fn generate_number_to_str(&mut self, e: &NumberToStr) -> Result<()> {
31976        // FORMAT(this, format, culture)
31977        self.write_keyword("FORMAT");
31978        self.write("(");
31979        self.generate_expression(&e.this)?;
31980        self.write(", '");
31981        self.write(&e.format);
31982        self.write("'");
31983        if let Some(culture) = &e.culture {
31984            self.write(", ");
31985            self.generate_expression(culture)?;
31986        }
31987        self.write(")");
31988        Ok(())
31989    }
31990
31991    fn generate_object_agg(&mut self, e: &ObjectAgg) -> Result<()> {
31992        // OBJECT_AGG(key, value)
31993        self.write_keyword("OBJECT_AGG");
31994        self.write("(");
31995        self.generate_expression(&e.this)?;
31996        self.write(", ");
31997        self.generate_expression(&e.expression)?;
31998        self.write(")");
31999        Ok(())
32000    }
32001
32002    fn generate_object_identifier(&mut self, e: &ObjectIdentifier) -> Result<()> {
32003        // Python: Just returns the name
32004        self.generate_expression(&e.this)?;
32005        Ok(())
32006    }
32007
32008    fn generate_object_insert(&mut self, e: &ObjectInsert) -> Result<()> {
32009        // OBJECT_INSERT(obj, key, value, [update_flag])
32010        self.write_keyword("OBJECT_INSERT");
32011        self.write("(");
32012        self.generate_expression(&e.this)?;
32013        if let Some(key) = &e.key {
32014            self.write(", ");
32015            self.generate_expression(key)?;
32016        }
32017        if let Some(value) = &e.value {
32018            self.write(", ");
32019            self.generate_expression(value)?;
32020        }
32021        if let Some(update_flag) = &e.update_flag {
32022            self.write(", ");
32023            self.generate_expression(update_flag)?;
32024        }
32025        self.write(")");
32026        Ok(())
32027    }
32028
32029    fn generate_offset(&mut self, e: &Offset) -> Result<()> {
32030        // OFFSET value [ROW|ROWS]
32031        self.write_keyword("OFFSET");
32032        self.write_space();
32033        self.generate_expression(&e.this)?;
32034        // Output ROWS keyword only for TSQL/Oracle targets
32035        if e.rows == Some(true)
32036            && matches!(
32037                self.config.dialect,
32038                Some(crate::dialects::DialectType::TSQL)
32039                    | Some(crate::dialects::DialectType::Oracle)
32040            )
32041        {
32042            self.write_space();
32043            self.write_keyword("ROWS");
32044        }
32045        Ok(())
32046    }
32047
32048    fn generate_qualify(&mut self, e: &Qualify) -> Result<()> {
32049        // QUALIFY condition (Snowflake/BigQuery)
32050        self.write_keyword("QUALIFY");
32051        self.write_space();
32052        self.generate_expression(&e.this)?;
32053        Ok(())
32054    }
32055
32056    fn generate_on_cluster(&mut self, e: &OnCluster) -> Result<()> {
32057        // ON CLUSTER cluster_name
32058        self.write_keyword("ON CLUSTER");
32059        self.write_space();
32060        self.generate_expression(&e.this)?;
32061        Ok(())
32062    }
32063
32064    fn generate_on_commit_property(&mut self, e: &OnCommitProperty) -> Result<()> {
32065        // ON COMMIT [DELETE ROWS | PRESERVE ROWS]
32066        self.write_keyword("ON COMMIT");
32067        if e.delete.is_some() {
32068            self.write_keyword(" DELETE ROWS");
32069        } else {
32070            self.write_keyword(" PRESERVE ROWS");
32071        }
32072        Ok(())
32073    }
32074
32075    fn generate_on_condition(&mut self, e: &OnCondition) -> Result<()> {
32076        // Python: error/empty/null handling
32077        if let Some(empty) = &e.empty {
32078            self.generate_expression(empty)?;
32079            self.write_keyword(" ON EMPTY");
32080        }
32081        if let Some(error) = &e.error {
32082            if e.empty.is_some() {
32083                self.write_space();
32084            }
32085            self.generate_expression(error)?;
32086            self.write_keyword(" ON ERROR");
32087        }
32088        if let Some(null) = &e.null {
32089            if e.empty.is_some() || e.error.is_some() {
32090                self.write_space();
32091            }
32092            self.generate_expression(null)?;
32093            self.write_keyword(" ON NULL");
32094        }
32095        Ok(())
32096    }
32097
32098    fn generate_on_conflict(&mut self, e: &OnConflict) -> Result<()> {
32099        // Materialize doesn't support ON CONFLICT - skip entirely
32100        if matches!(self.config.dialect, Some(DialectType::Materialize)) {
32101            return Ok(());
32102        }
32103        // Python: ON CONFLICT|ON DUPLICATE KEY [ON CONSTRAINT constraint] [conflict_keys] action
32104        if e.duplicate.is_some() {
32105            // MySQL: ON DUPLICATE KEY UPDATE col = val, ...
32106            self.write_keyword("ON DUPLICATE KEY UPDATE");
32107            for (i, expr) in e.expressions.iter().enumerate() {
32108                if i > 0 {
32109                    self.write(",");
32110                }
32111                self.write_space();
32112                self.generate_expression(expr)?;
32113            }
32114            return Ok(());
32115        } else {
32116            self.write_keyword("ON CONFLICT");
32117        }
32118        if let Some(constraint) = &e.constraint {
32119            self.write_keyword(" ON CONSTRAINT ");
32120            self.generate_expression(constraint)?;
32121        }
32122        if let Some(conflict_keys) = &e.conflict_keys {
32123            // conflict_keys can be a Tuple containing expressions
32124            if let Expression::Tuple(t) = conflict_keys.as_ref() {
32125                self.write("(");
32126                for (i, expr) in t.expressions.iter().enumerate() {
32127                    if i > 0 {
32128                        self.write(", ");
32129                    }
32130                    self.generate_expression(expr)?;
32131                }
32132                self.write(")");
32133            } else {
32134                self.write("(");
32135                self.generate_expression(conflict_keys)?;
32136                self.write(")");
32137            }
32138        }
32139        if let Some(index_predicate) = &e.index_predicate {
32140            self.write_keyword(" WHERE ");
32141            self.generate_expression(index_predicate)?;
32142        }
32143        if let Some(action) = &e.action {
32144            // Check if action is "NOTHING" or an UPDATE set
32145            if let Expression::Identifier(id) = action.as_ref() {
32146                if id.name.eq_ignore_ascii_case("NOTHING") {
32147                    self.write_keyword(" DO NOTHING");
32148                } else {
32149                    self.write_keyword(" DO ");
32150                    self.generate_expression(action)?;
32151                }
32152            } else if let Expression::Tuple(t) = action.as_ref() {
32153                // DO UPDATE SET col1 = val1, col2 = val2
32154                self.write_keyword(" DO UPDATE SET ");
32155                for (i, expr) in t.expressions.iter().enumerate() {
32156                    if i > 0 {
32157                        self.write(", ");
32158                    }
32159                    self.generate_expression(expr)?;
32160                }
32161            } else {
32162                self.write_keyword(" DO ");
32163                self.generate_expression(action)?;
32164            }
32165        }
32166        // WHERE clause for the UPDATE action
32167        if let Some(where_) = &e.where_ {
32168            self.write_keyword(" WHERE ");
32169            self.generate_expression(where_)?;
32170        }
32171        Ok(())
32172    }
32173
32174    fn generate_on_property(&mut self, e: &OnProperty) -> Result<()> {
32175        // ON property_value
32176        self.write_keyword("ON");
32177        self.write_space();
32178        self.generate_expression(&e.this)?;
32179        Ok(())
32180    }
32181
32182    fn generate_opclass(&mut self, e: &Opclass) -> Result<()> {
32183        // Python: this expression (e.g., column opclass)
32184        self.generate_expression(&e.this)?;
32185        self.write_space();
32186        self.generate_expression(&e.expression)?;
32187        Ok(())
32188    }
32189
32190    fn generate_open_json(&mut self, e: &OpenJSON) -> Result<()> {
32191        // Python: OPENJSON(this[, path]) [WITH (columns)]
32192        self.write_keyword("OPENJSON");
32193        self.write("(");
32194        self.generate_expression(&e.this)?;
32195        if let Some(path) = &e.path {
32196            self.write(", ");
32197            self.generate_expression(path)?;
32198        }
32199        self.write(")");
32200        if !e.expressions.is_empty() {
32201            self.write_keyword(" WITH");
32202            if self.config.pretty {
32203                self.write(" (\n");
32204                self.indent_level += 2;
32205                for (i, expr) in e.expressions.iter().enumerate() {
32206                    if i > 0 {
32207                        self.write(",\n");
32208                    }
32209                    self.write_indent();
32210                    self.generate_expression(expr)?;
32211                }
32212                self.write("\n");
32213                self.indent_level -= 2;
32214                self.write(")");
32215            } else {
32216                self.write(" (");
32217                for (i, expr) in e.expressions.iter().enumerate() {
32218                    if i > 0 {
32219                        self.write(", ");
32220                    }
32221                    self.generate_expression(expr)?;
32222                }
32223                self.write(")");
32224            }
32225        }
32226        Ok(())
32227    }
32228
32229    fn generate_open_json_column_def(&mut self, e: &OpenJSONColumnDef) -> Result<()> {
32230        // Python: this kind [path] [AS JSON]
32231        self.generate_expression(&e.this)?;
32232        self.write_space();
32233        // Use parsed data_type if available, otherwise fall back to kind string
32234        if let Some(ref dt) = e.data_type {
32235            self.generate_data_type(dt)?;
32236        } else if !e.kind.is_empty() {
32237            self.write(&e.kind);
32238        }
32239        if let Some(path) = &e.path {
32240            self.write_space();
32241            self.generate_expression(path)?;
32242        }
32243        if e.as_json.is_some() {
32244            self.write_keyword(" AS JSON");
32245        }
32246        Ok(())
32247    }
32248
32249    fn generate_operator(&mut self, e: &Operator) -> Result<()> {
32250        // this OPERATOR(op) expression
32251        self.generate_expression(&e.this)?;
32252        self.write_space();
32253        if let Some(op) = &e.operator {
32254            self.write_keyword("OPERATOR");
32255            self.write("(");
32256            self.generate_expression(op)?;
32257            self.write(")");
32258        }
32259        // Emit inline comments between OPERATOR() and the RHS
32260        for comment in &e.comments {
32261            self.write_space();
32262            self.write_formatted_comment(comment);
32263        }
32264        self.write_space();
32265        self.generate_expression(&e.expression)?;
32266        Ok(())
32267    }
32268
32269    fn generate_order_by(&mut self, e: &OrderBy) -> Result<()> {
32270        // ORDER BY expr1 [ASC|DESC] [NULLS FIRST|LAST], expr2 ...
32271        self.write_keyword("ORDER BY");
32272        let pretty_clickhouse_single_paren = self.config.pretty
32273            && matches!(self.config.dialect, Some(DialectType::ClickHouse))
32274            && e.expressions.len() == 1
32275            && matches!(e.expressions[0].this, Expression::Paren(ref p) if !matches!(p.this, Expression::Tuple(_)));
32276        let clickhouse_single_tuple = matches!(self.config.dialect, Some(DialectType::ClickHouse))
32277            && e.expressions.len() == 1
32278            && matches!(e.expressions[0].this, Expression::Tuple(_))
32279            && !e.expressions[0].desc
32280            && e.expressions[0].nulls_first.is_none();
32281
32282        if pretty_clickhouse_single_paren {
32283            self.write_space();
32284            if let Expression::Paren(p) = &e.expressions[0].this {
32285                self.write("(");
32286                self.write_newline();
32287                self.indent_level += 1;
32288                self.write_indent();
32289                self.generate_expression(&p.this)?;
32290                self.indent_level -= 1;
32291                self.write_newline();
32292                self.write(")");
32293            }
32294            return Ok(());
32295        }
32296
32297        if clickhouse_single_tuple {
32298            self.write_space();
32299            if let Expression::Tuple(t) = &e.expressions[0].this {
32300                self.write("(");
32301                for (i, expr) in t.expressions.iter().enumerate() {
32302                    if i > 0 {
32303                        self.write(", ");
32304                    }
32305                    self.generate_expression(expr)?;
32306                }
32307                self.write(")");
32308            }
32309            return Ok(());
32310        }
32311
32312        self.write_space();
32313        for (i, ordered) in e.expressions.iter().enumerate() {
32314            if i > 0 {
32315                self.write(", ");
32316            }
32317            self.generate_expression(&ordered.this)?;
32318            if ordered.desc {
32319                self.write_space();
32320                self.write_keyword("DESC");
32321            } else if ordered.explicit_asc {
32322                self.write_space();
32323                self.write_keyword("ASC");
32324            }
32325            if let Some(nulls_first) = ordered.nulls_first {
32326                // In Dremio, NULLS LAST is the default, so skip generating it
32327                let skip_nulls_last =
32328                    !nulls_first && matches!(self.config.dialect, Some(DialectType::Dremio));
32329                if !skip_nulls_last {
32330                    self.write_space();
32331                    self.write_keyword("NULLS");
32332                    self.write_space();
32333                    if nulls_first {
32334                        self.write_keyword("FIRST");
32335                    } else {
32336                        self.write_keyword("LAST");
32337                    }
32338                }
32339            }
32340        }
32341        Ok(())
32342    }
32343
32344    fn generate_output_model_property(&mut self, e: &OutputModelProperty) -> Result<()> {
32345        // OUTPUT(model)
32346        self.write_keyword("OUTPUT");
32347        self.write("(");
32348        if self.config.pretty {
32349            self.indent_level += 1;
32350            self.write_newline();
32351            self.write_indent();
32352            self.generate_expression(&e.this)?;
32353            self.indent_level -= 1;
32354            self.write_newline();
32355        } else {
32356            self.generate_expression(&e.this)?;
32357        }
32358        self.write(")");
32359        Ok(())
32360    }
32361
32362    fn generate_overflow_truncate_behavior(&mut self, e: &OverflowTruncateBehavior) -> Result<()> {
32363        // Python: TRUNCATE [filler] WITH|WITHOUT COUNT
32364        self.write_keyword("TRUNCATE");
32365        if let Some(this) = &e.this {
32366            self.write_space();
32367            self.generate_expression(this)?;
32368        }
32369        if e.with_count.is_some() {
32370            self.write_keyword(" WITH COUNT");
32371        } else {
32372            self.write_keyword(" WITHOUT COUNT");
32373        }
32374        Ok(())
32375    }
32376
32377    fn generate_parameterized_agg(&mut self, e: &ParameterizedAgg) -> Result<()> {
32378        // Python: name(expressions)(params)
32379        self.generate_expression(&e.this)?;
32380        self.write("(");
32381        for (i, expr) in e.expressions.iter().enumerate() {
32382            if i > 0 {
32383                self.write(", ");
32384            }
32385            self.generate_expression(expr)?;
32386        }
32387        self.write(")(");
32388        for (i, param) in e.params.iter().enumerate() {
32389            if i > 0 {
32390                self.write(", ");
32391            }
32392            self.generate_expression(param)?;
32393        }
32394        self.write(")");
32395        Ok(())
32396    }
32397
32398    fn generate_parse_datetime(&mut self, e: &ParseDatetime) -> Result<()> {
32399        // PARSE_DATETIME(format, this) or similar
32400        self.write_keyword("PARSE_DATETIME");
32401        self.write("(");
32402        if let Some(format) = &e.format {
32403            self.write("'");
32404            self.write(format);
32405            self.write("', ");
32406        }
32407        self.generate_expression(&e.this)?;
32408        if let Some(zone) = &e.zone {
32409            self.write(", ");
32410            self.generate_expression(zone)?;
32411        }
32412        self.write(")");
32413        Ok(())
32414    }
32415
32416    fn generate_parse_ip(&mut self, e: &ParseIp) -> Result<()> {
32417        // PARSE_IP(this, type, permissive)
32418        self.write_keyword("PARSE_IP");
32419        self.write("(");
32420        self.generate_expression(&e.this)?;
32421        if let Some(type_) = &e.type_ {
32422            self.write(", ");
32423            self.generate_expression(type_)?;
32424        }
32425        if let Some(permissive) = &e.permissive {
32426            self.write(", ");
32427            self.generate_expression(permissive)?;
32428        }
32429        self.write(")");
32430        Ok(())
32431    }
32432
32433    fn generate_parse_json(&mut self, e: &ParseJSON) -> Result<()> {
32434        // PARSE_JSON(this, [expression])
32435        self.write_keyword("PARSE_JSON");
32436        self.write("(");
32437        self.generate_expression(&e.this)?;
32438        if let Some(expression) = &e.expression {
32439            self.write(", ");
32440            self.generate_expression(expression)?;
32441        }
32442        self.write(")");
32443        Ok(())
32444    }
32445
32446    fn generate_parse_time(&mut self, e: &ParseTime) -> Result<()> {
32447        // PARSE_TIME(format, this) or STR_TO_TIME(this, format)
32448        self.write_keyword("PARSE_TIME");
32449        self.write("(");
32450        self.write(&format!("'{}'", e.format));
32451        self.write(", ");
32452        self.generate_expression(&e.this)?;
32453        self.write(")");
32454        Ok(())
32455    }
32456
32457    fn generate_parse_url(&mut self, e: &ParseUrl) -> Result<()> {
32458        // PARSE_URL(this, [part_to_extract], [key], [permissive])
32459        self.write_keyword("PARSE_URL");
32460        self.write("(");
32461        self.generate_expression(&e.this)?;
32462        if let Some(part) = &e.part_to_extract {
32463            self.write(", ");
32464            self.generate_expression(part)?;
32465        }
32466        if let Some(key) = &e.key {
32467            self.write(", ");
32468            self.generate_expression(key)?;
32469        }
32470        if let Some(permissive) = &e.permissive {
32471            self.write(", ");
32472            self.generate_expression(permissive)?;
32473        }
32474        self.write(")");
32475        Ok(())
32476    }
32477
32478    fn generate_partition_expr(&mut self, e: &Partition) -> Result<()> {
32479        // PARTITION(expr1, expr2, ...) or SUBPARTITION(expr1, expr2, ...)
32480        if e.subpartition {
32481            self.write_keyword("SUBPARTITION");
32482        } else {
32483            self.write_keyword("PARTITION");
32484        }
32485        self.write("(");
32486        for (i, expr) in e.expressions.iter().enumerate() {
32487            if i > 0 {
32488                self.write(", ");
32489            }
32490            self.generate_expression(expr)?;
32491        }
32492        self.write(")");
32493        Ok(())
32494    }
32495
32496    fn generate_partition_bound_spec(&mut self, e: &PartitionBoundSpec) -> Result<()> {
32497        // IN (values) or WITH (MODULUS this, REMAINDER expression) or FROM (from) TO (to)
32498        if let Some(this) = &e.this {
32499            if let Some(expression) = &e.expression {
32500                // WITH (MODULUS this, REMAINDER expression)
32501                self.write_keyword("WITH");
32502                self.write(" (");
32503                self.write_keyword("MODULUS");
32504                self.write_space();
32505                self.generate_expression(this)?;
32506                self.write(", ");
32507                self.write_keyword("REMAINDER");
32508                self.write_space();
32509                self.generate_expression(expression)?;
32510                self.write(")");
32511            } else {
32512                // IN (this) - this could be a list
32513                self.write_keyword("IN");
32514                self.write(" (");
32515                self.generate_partition_bound_values(this)?;
32516                self.write(")");
32517            }
32518        } else if let (Some(from), Some(to)) = (&e.from_expressions, &e.to_expressions) {
32519            // FROM (from_expressions) TO (to_expressions)
32520            self.write_keyword("FROM");
32521            self.write(" (");
32522            self.generate_partition_bound_values(from)?;
32523            self.write(") ");
32524            self.write_keyword("TO");
32525            self.write(" (");
32526            self.generate_partition_bound_values(to)?;
32527            self.write(")");
32528        }
32529        Ok(())
32530    }
32531
32532    /// Generate partition bound values - handles Tuple expressions by outputting
32533    /// contents without wrapping parens (since caller provides the parens)
32534    fn generate_partition_bound_values(&mut self, expr: &Expression) -> Result<()> {
32535        if let Expression::Tuple(t) = expr {
32536            for (i, e) in t.expressions.iter().enumerate() {
32537                if i > 0 {
32538                    self.write(", ");
32539                }
32540                self.generate_expression(e)?;
32541            }
32542            Ok(())
32543        } else {
32544            self.generate_expression(expr)
32545        }
32546    }
32547
32548    fn generate_partition_by_list_property(&mut self, e: &PartitionByListProperty) -> Result<()> {
32549        // PARTITION BY LIST (partition_expressions) (create_expressions)
32550        self.write_keyword("PARTITION BY LIST");
32551        if let Some(partition_exprs) = &e.partition_expressions {
32552            self.write(" (");
32553            // Unwrap Tuple for partition columns (don't generate outer parens from Tuple)
32554            self.generate_doris_partition_expressions(partition_exprs)?;
32555            self.write(")");
32556        }
32557        if let Some(create_exprs) = &e.create_expressions {
32558            self.write(" (");
32559            // Unwrap Tuple for partition definitions
32560            self.generate_doris_partition_definitions(create_exprs)?;
32561            self.write(")");
32562        }
32563        Ok(())
32564    }
32565
32566    fn generate_partition_by_range_property(&mut self, e: &PartitionByRangeProperty) -> Result<()> {
32567        // PARTITION BY RANGE (partition_expressions) (create_expressions)
32568        self.write_keyword("PARTITION BY RANGE");
32569        if let Some(partition_exprs) = &e.partition_expressions {
32570            self.write(" (");
32571            // Unwrap Tuple for partition columns (don't generate outer parens from Tuple)
32572            self.generate_doris_partition_expressions(partition_exprs)?;
32573            self.write(")");
32574        }
32575        if let Some(create_exprs) = &e.create_expressions {
32576            self.write(" (");
32577            // Check for dynamic partition (PartitionByRangePropertyDynamic) or static (Tuple of Partition)
32578            self.generate_doris_partition_definitions(create_exprs)?;
32579            self.write(")");
32580        }
32581        Ok(())
32582    }
32583
32584    /// Generate Doris partition column expressions (unwrap Tuple)
32585    fn generate_doris_partition_expressions(&mut self, expr: &Expression) -> Result<()> {
32586        if let Expression::Tuple(t) = expr {
32587            for (i, e) in t.expressions.iter().enumerate() {
32588                if i > 0 {
32589                    self.write(", ");
32590                }
32591                self.generate_expression(e)?;
32592            }
32593        } else {
32594            self.generate_expression(expr)?;
32595        }
32596        Ok(())
32597    }
32598
32599    /// Generate Doris partition definitions (comma-separated Partition expressions)
32600    fn generate_doris_partition_definitions(&mut self, expr: &Expression) -> Result<()> {
32601        match expr {
32602            Expression::Tuple(t) => {
32603                // Multiple partitions, comma-separated
32604                for (i, part) in t.expressions.iter().enumerate() {
32605                    if i > 0 {
32606                        self.write(", ");
32607                    }
32608                    // For Partition expressions, generate the inner PartitionRange/PartitionList directly
32609                    if let Expression::Partition(p) = part {
32610                        for (j, inner) in p.expressions.iter().enumerate() {
32611                            if j > 0 {
32612                                self.write(", ");
32613                            }
32614                            self.generate_expression(inner)?;
32615                        }
32616                    } else {
32617                        self.generate_expression(part)?;
32618                    }
32619                }
32620            }
32621            Expression::PartitionByRangePropertyDynamic(_) => {
32622                // Dynamic partition - FROM/TO/INTERVAL
32623                self.generate_expression(expr)?;
32624            }
32625            _ => {
32626                self.generate_expression(expr)?;
32627            }
32628        }
32629        Ok(())
32630    }
32631
32632    fn generate_partition_by_range_property_dynamic(
32633        &mut self,
32634        e: &PartitionByRangePropertyDynamic,
32635    ) -> Result<()> {
32636        if e.use_start_end {
32637            // StarRocks: START ('val') END ('val') EVERY (expr)
32638            if let Some(start) = &e.start {
32639                self.write_keyword("START");
32640                self.write(" (");
32641                self.generate_expression(start)?;
32642                self.write(")");
32643            }
32644            if let Some(end) = &e.end {
32645                self.write_space();
32646                self.write_keyword("END");
32647                self.write(" (");
32648                self.generate_expression(end)?;
32649                self.write(")");
32650            }
32651            if let Some(every) = &e.every {
32652                self.write_space();
32653                self.write_keyword("EVERY");
32654                self.write(" (");
32655                // Use unquoted interval format for StarRocks
32656                self.generate_doris_interval(every)?;
32657                self.write(")");
32658            }
32659        } else {
32660            // Doris: FROM (start) TO (end) INTERVAL n UNIT
32661            if let Some(start) = &e.start {
32662                self.write_keyword("FROM");
32663                self.write(" (");
32664                self.generate_expression(start)?;
32665                self.write(")");
32666            }
32667            if let Some(end) = &e.end {
32668                self.write_space();
32669                self.write_keyword("TO");
32670                self.write(" (");
32671                self.generate_expression(end)?;
32672                self.write(")");
32673            }
32674            if let Some(every) = &e.every {
32675                self.write_space();
32676                // Generate INTERVAL n UNIT (not quoted, for Doris dynamic partition)
32677                self.generate_doris_interval(every)?;
32678            }
32679        }
32680        Ok(())
32681    }
32682
32683    /// Generate Doris-style interval without quoting numbers: INTERVAL n UNIT
32684    fn generate_doris_interval(&mut self, expr: &Expression) -> Result<()> {
32685        if let Expression::Interval(interval) = expr {
32686            self.write_keyword("INTERVAL");
32687            if let Some(ref value) = interval.this {
32688                self.write_space();
32689                // If the value is a string literal that looks like a number,
32690                // output it without quotes (matching Python sqlglot's
32691                // partitionbyrangepropertydynamic_sql which converts back to number)
32692                match value {
32693                    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()) => {
32694                        if let Literal::String(s) = lit.as_ref() {
32695                            self.write(s);
32696                        }
32697                    }
32698                    _ => {
32699                        self.generate_expression(value)?;
32700                    }
32701                }
32702            }
32703            if let Some(ref unit_spec) = interval.unit {
32704                self.write_space();
32705                self.write_interval_unit_spec(unit_spec)?;
32706            }
32707            Ok(())
32708        } else {
32709            self.generate_expression(expr)
32710        }
32711    }
32712
32713    fn generate_partition_by_truncate(&mut self, e: &PartitionByTruncate) -> Result<()> {
32714        // TRUNCATE(expression, this)
32715        self.write_keyword("TRUNCATE");
32716        self.write("(");
32717        self.generate_expression(&e.expression)?;
32718        self.write(", ");
32719        self.generate_expression(&e.this)?;
32720        self.write(")");
32721        Ok(())
32722    }
32723
32724    fn generate_partition_list(&mut self, e: &PartitionList) -> Result<()> {
32725        // Doris: PARTITION name VALUES IN (val1, val2)
32726        self.write_keyword("PARTITION");
32727        self.write_space();
32728        self.generate_expression(&e.this)?;
32729        self.write_space();
32730        self.write_keyword("VALUES IN");
32731        self.write(" (");
32732        for (i, expr) in e.expressions.iter().enumerate() {
32733            if i > 0 {
32734                self.write(", ");
32735            }
32736            self.generate_expression(expr)?;
32737        }
32738        self.write(")");
32739        Ok(())
32740    }
32741
32742    fn generate_partition_range(&mut self, e: &PartitionRange) -> Result<()> {
32743        // Check if this is a TSQL-style simple range (e.g., "2 TO 5")
32744        // TSQL ranges have no expressions and just use `this TO expression`
32745        if e.expressions.is_empty() && e.expression.is_some() {
32746            // TSQL: simple range like "2 TO 5" - no PARTITION keyword
32747            self.generate_expression(&e.this)?;
32748            self.write_space();
32749            self.write_keyword("TO");
32750            self.write_space();
32751            self.generate_expression(e.expression.as_ref().unwrap())?;
32752            return Ok(());
32753        }
32754
32755        // Doris: PARTITION name VALUES LESS THAN (val) or PARTITION name VALUES [(val1), (val2))
32756        self.write_keyword("PARTITION");
32757        self.write_space();
32758        self.generate_expression(&e.this)?;
32759        self.write_space();
32760
32761        // Check if expressions contain Tuple (bracket notation) or single values (LESS THAN)
32762        if e.expressions.len() == 1 {
32763            // Single value: VALUES LESS THAN (val)
32764            self.write_keyword("VALUES LESS THAN");
32765            self.write(" (");
32766            self.generate_expression(&e.expressions[0])?;
32767            self.write(")");
32768        } else if !e.expressions.is_empty() {
32769            // Multiple values with Tuple: VALUES [(val1), (val2))
32770            self.write_keyword("VALUES");
32771            self.write(" [");
32772            for (i, expr) in e.expressions.iter().enumerate() {
32773                if i > 0 {
32774                    self.write(", ");
32775                }
32776                // If the expr is a Tuple, generate its contents wrapped in parens
32777                if let Expression::Tuple(t) = expr {
32778                    self.write("(");
32779                    for (j, inner) in t.expressions.iter().enumerate() {
32780                        if j > 0 {
32781                            self.write(", ");
32782                        }
32783                        self.generate_expression(inner)?;
32784                    }
32785                    self.write(")");
32786                } else {
32787                    self.write("(");
32788                    self.generate_expression(expr)?;
32789                    self.write(")");
32790                }
32791            }
32792            self.write(")");
32793        }
32794        Ok(())
32795    }
32796
32797    fn generate_partitioned_by_bucket(&mut self, e: &PartitionedByBucket) -> Result<()> {
32798        // BUCKET(this, expression)
32799        self.write_keyword("BUCKET");
32800        self.write("(");
32801        self.generate_expression(&e.this)?;
32802        self.write(", ");
32803        self.generate_expression(&e.expression)?;
32804        self.write(")");
32805        Ok(())
32806    }
32807
32808    fn generate_partition_by_property(&mut self, e: &PartitionByProperty) -> Result<()> {
32809        // BigQuery table property: PARTITION BY expression [, expression ...]
32810        self.write_keyword("PARTITION BY");
32811        self.write_space();
32812        for (i, expr) in e.expressions.iter().enumerate() {
32813            if i > 0 {
32814                self.write(", ");
32815            }
32816            self.generate_expression(expr)?;
32817        }
32818        Ok(())
32819    }
32820
32821    fn generate_partitioned_by_property(&mut self, e: &PartitionedByProperty) -> Result<()> {
32822        // PARTITIONED BY this (Teradata/ClickHouse use PARTITION BY)
32823        if matches!(
32824            self.config.dialect,
32825            Some(crate::dialects::DialectType::Teradata)
32826                | Some(crate::dialects::DialectType::ClickHouse)
32827        ) {
32828            self.write_keyword("PARTITION BY");
32829        } else {
32830            self.write_keyword("PARTITIONED BY");
32831        }
32832        self.write_space();
32833        // In pretty mode, always use multiline tuple format for PARTITIONED BY
32834        if self.config.pretty {
32835            if let Expression::Tuple(ref tuple) = *e.this {
32836                self.write("(");
32837                self.write_newline();
32838                self.indent_level += 1;
32839                for (i, expr) in tuple.expressions.iter().enumerate() {
32840                    if i > 0 {
32841                        self.write(",");
32842                        self.write_newline();
32843                    }
32844                    self.write_indent();
32845                    self.generate_expression(expr)?;
32846                }
32847                self.indent_level -= 1;
32848                self.write_newline();
32849                self.write(")");
32850            } else {
32851                self.generate_expression(&e.this)?;
32852            }
32853        } else {
32854            self.generate_expression(&e.this)?;
32855        }
32856        Ok(())
32857    }
32858
32859    fn generate_partitioned_of_property(&mut self, e: &PartitionedOfProperty) -> Result<()> {
32860        // PARTITION OF this FOR VALUES expression or PARTITION OF this DEFAULT
32861        self.write_keyword("PARTITION OF");
32862        self.write_space();
32863        self.generate_expression(&e.this)?;
32864        // Check if expression is a PartitionBoundSpec
32865        if let Expression::PartitionBoundSpec(_) = e.expression.as_ref() {
32866            self.write_space();
32867            self.write_keyword("FOR VALUES");
32868            self.write_space();
32869            self.generate_expression(&e.expression)?;
32870        } else {
32871            self.write_space();
32872            self.write_keyword("DEFAULT");
32873        }
32874        Ok(())
32875    }
32876
32877    fn generate_period_for_system_time_constraint(
32878        &mut self,
32879        e: &PeriodForSystemTimeConstraint,
32880    ) -> Result<()> {
32881        // PERIOD FOR SYSTEM_TIME (this, expression)
32882        self.write_keyword("PERIOD FOR SYSTEM_TIME");
32883        self.write(" (");
32884        self.generate_expression(&e.this)?;
32885        self.write(", ");
32886        self.generate_expression(&e.expression)?;
32887        self.write(")");
32888        Ok(())
32889    }
32890
32891    fn generate_pivot_alias(&mut self, e: &PivotAlias) -> Result<()> {
32892        // value AS alias
32893        // The alias can be an identifier or an expression (e.g., string concatenation)
32894        self.generate_expression(&e.this)?;
32895        self.write_space();
32896        self.write_keyword("AS");
32897        self.write_space();
32898        // When target dialect uses identifiers for UNPIVOT aliases, convert literals to identifiers
32899        if self.config.unpivot_aliases_are_identifiers {
32900            match &e.alias {
32901                Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
32902                    let Literal::String(s) = lit.as_ref() else {
32903                        unreachable!()
32904                    };
32905                    // Convert string literal to identifier
32906                    self.generate_identifier(&Identifier::new(s.clone()))?;
32907                }
32908                Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(_)) => {
32909                    let Literal::Number(n) = lit.as_ref() else {
32910                        unreachable!()
32911                    };
32912                    // Convert number literal to quoted identifier
32913                    let mut id = Identifier::new(n.clone());
32914                    id.quoted = true;
32915                    self.generate_identifier(&id)?;
32916                }
32917                other => {
32918                    self.generate_expression(other)?;
32919                }
32920            }
32921        } else {
32922            self.generate_expression(&e.alias)?;
32923        }
32924        Ok(())
32925    }
32926
32927    fn generate_pivot_any(&mut self, e: &PivotAny) -> Result<()> {
32928        // ANY or ANY [expression]
32929        self.write_keyword("ANY");
32930        if let Some(this) = &e.this {
32931            self.write_space();
32932            self.generate_expression(this)?;
32933        }
32934        Ok(())
32935    }
32936
32937    fn generate_predict(&mut self, e: &Predict) -> Result<()> {
32938        // ML.PREDICT(MODEL this, expression, [params_struct])
32939        self.write_keyword("ML.PREDICT");
32940        self.write("(");
32941        self.write_keyword("MODEL");
32942        self.write_space();
32943        self.generate_expression(&e.this)?;
32944        self.write(", ");
32945        self.generate_expression(&e.expression)?;
32946        if let Some(params) = &e.params_struct {
32947            self.write(", ");
32948            self.generate_expression(params)?;
32949        }
32950        self.write(")");
32951        Ok(())
32952    }
32953
32954    fn generate_previous_day(&mut self, e: &PreviousDay) -> Result<()> {
32955        // PREVIOUS_DAY(this, expression)
32956        self.write_keyword("PREVIOUS_DAY");
32957        self.write("(");
32958        self.generate_expression(&e.this)?;
32959        self.write(", ");
32960        self.generate_expression(&e.expression)?;
32961        self.write(")");
32962        Ok(())
32963    }
32964
32965    fn generate_primary_key(&mut self, e: &PrimaryKey) -> Result<()> {
32966        // PRIMARY KEY [name] (columns) [INCLUDE (...)] [options]
32967        self.write_keyword("PRIMARY KEY");
32968        if let Some(name) = &e.this {
32969            self.write_space();
32970            self.generate_expression(name)?;
32971        }
32972        if !e.expressions.is_empty() {
32973            self.write(" (");
32974            for (i, expr) in e.expressions.iter().enumerate() {
32975                if i > 0 {
32976                    self.write(", ");
32977                }
32978                self.generate_expression(expr)?;
32979            }
32980            self.write(")");
32981        }
32982        if let Some(include) = &e.include {
32983            self.write_space();
32984            self.generate_expression(include)?;
32985        }
32986        if !e.options.is_empty() {
32987            self.write_space();
32988            for (i, opt) in e.options.iter().enumerate() {
32989                if i > 0 {
32990                    self.write_space();
32991                }
32992                self.generate_expression(opt)?;
32993            }
32994        }
32995        Ok(())
32996    }
32997
32998    fn generate_primary_key_column_constraint(
32999        &mut self,
33000        _e: &PrimaryKeyColumnConstraint,
33001    ) -> Result<()> {
33002        // PRIMARY KEY constraint at column level
33003        self.write_keyword("PRIMARY KEY");
33004        Ok(())
33005    }
33006
33007    fn generate_path_column_constraint(&mut self, e: &PathColumnConstraint) -> Result<()> {
33008        // PATH 'xpath' constraint for XMLTABLE/JSON_TABLE columns
33009        self.write_keyword("PATH");
33010        self.write_space();
33011        self.generate_expression(&e.this)?;
33012        Ok(())
33013    }
33014
33015    fn generate_projection_def(&mut self, e: &ProjectionDef) -> Result<()> {
33016        // PROJECTION this (expression)
33017        self.write_keyword("PROJECTION");
33018        self.write_space();
33019        self.generate_expression(&e.this)?;
33020        self.write(" (");
33021        self.generate_expression(&e.expression)?;
33022        self.write(")");
33023        Ok(())
33024    }
33025
33026    fn generate_properties(&mut self, e: &Properties) -> Result<()> {
33027        // Properties list
33028        for (i, prop) in e.expressions.iter().enumerate() {
33029            if i > 0 {
33030                self.write(", ");
33031            }
33032            self.generate_expression(prop)?;
33033        }
33034        Ok(())
33035    }
33036
33037    fn generate_property(&mut self, e: &Property) -> Result<()> {
33038        // name=value
33039        self.generate_expression(&e.this)?;
33040        if let Some(value) = &e.value {
33041            self.write("=");
33042            self.generate_expression(value)?;
33043        }
33044        Ok(())
33045    }
33046
33047    fn generate_options_property(&mut self, e: &OptionsProperty) -> Result<()> {
33048        self.write_keyword("OPTIONS");
33049        if e.entries.is_empty() {
33050            self.write(" ()");
33051            return Ok(());
33052        }
33053
33054        if self.config.pretty {
33055            self.write(" (");
33056            self.write_newline();
33057            self.indent_level += 1;
33058            for (i, entry) in e.entries.iter().enumerate() {
33059                if i > 0 {
33060                    self.write(",");
33061                    self.write_newline();
33062                }
33063                self.write_indent();
33064                self.generate_identifier(&entry.key)?;
33065                self.write("=");
33066                self.generate_expression(&entry.value)?;
33067            }
33068            self.indent_level -= 1;
33069            self.write_newline();
33070            self.write(")");
33071        } else {
33072            self.write(" (");
33073            for (i, entry) in e.entries.iter().enumerate() {
33074                if i > 0 {
33075                    self.write(", ");
33076                }
33077                self.generate_identifier(&entry.key)?;
33078                self.write("=");
33079                self.generate_expression(&entry.value)?;
33080            }
33081            self.write(")");
33082        }
33083        Ok(())
33084    }
33085
33086    /// Generate BigQuery-style OPTIONS clause: OPTIONS (key=value, key=value, ...)
33087    fn generate_options_clause(&mut self, options: &[Expression]) -> Result<()> {
33088        self.write_keyword("OPTIONS");
33089        self.write(" (");
33090        for (i, opt) in options.iter().enumerate() {
33091            if i > 0 {
33092                self.write(", ");
33093            }
33094            self.generate_option_expression(opt)?;
33095        }
33096        self.write(")");
33097        Ok(())
33098    }
33099
33100    /// Generate Doris/StarRocks-style PROPERTIES clause: PROPERTIES ('key'='value', 'key'='value', ...)
33101    fn generate_properties_clause(&mut self, properties: &[Expression]) -> Result<()> {
33102        self.write_keyword("PROPERTIES");
33103        self.write(" (");
33104        for (i, prop) in properties.iter().enumerate() {
33105            if i > 0 {
33106                self.write(", ");
33107            }
33108            self.generate_option_expression(prop)?;
33109        }
33110        self.write(")");
33111        Ok(())
33112    }
33113
33114    /// Generate Databricks-style ENVIRONMENT clause: ENVIRONMENT (key = 'value', key = 'value', ...)
33115    fn generate_environment_clause(&mut self, environment: &[Expression]) -> Result<()> {
33116        self.write_keyword("ENVIRONMENT");
33117        self.write(" (");
33118        for (i, env_item) in environment.iter().enumerate() {
33119            if i > 0 {
33120                self.write(", ");
33121            }
33122            self.generate_environment_expression(env_item)?;
33123        }
33124        self.write(")");
33125        Ok(())
33126    }
33127
33128    /// Generate an environment expression with spaces around =
33129    fn generate_environment_expression(&mut self, expr: &Expression) -> Result<()> {
33130        match expr {
33131            Expression::Eq(eq) => {
33132                // Generate key = value with spaces (Databricks ENVIRONMENT style)
33133                self.generate_expression(&eq.left)?;
33134                self.write(" = ");
33135                self.generate_expression(&eq.right)?;
33136                Ok(())
33137            }
33138            _ => self.generate_expression(expr),
33139        }
33140    }
33141
33142    /// Generate Hive-style TBLPROPERTIES clause: TBLPROPERTIES ('key'='value', ...)
33143    fn generate_tblproperties_clause(&mut self, options: &[Expression]) -> Result<()> {
33144        self.write_keyword("TBLPROPERTIES");
33145        if self.config.pretty {
33146            self.write(" (");
33147            self.write_newline();
33148            self.indent_level += 1;
33149            for (i, opt) in options.iter().enumerate() {
33150                if i > 0 {
33151                    self.write(",");
33152                    self.write_newline();
33153                }
33154                self.write_indent();
33155                self.generate_option_expression(opt)?;
33156            }
33157            self.indent_level -= 1;
33158            self.write_newline();
33159            self.write(")");
33160        } else {
33161            self.write(" (");
33162            for (i, opt) in options.iter().enumerate() {
33163                if i > 0 {
33164                    self.write(", ");
33165                }
33166                self.generate_option_expression(opt)?;
33167            }
33168            self.write(")");
33169        }
33170        Ok(())
33171    }
33172
33173    /// Generate an option expression without spaces around =
33174    fn generate_option_expression(&mut self, expr: &Expression) -> Result<()> {
33175        match expr {
33176            Expression::Eq(eq) => {
33177                // Generate key=value without spaces
33178                self.generate_expression(&eq.left)?;
33179                self.write("=");
33180                self.generate_expression(&eq.right)?;
33181                Ok(())
33182            }
33183            _ => self.generate_expression(expr),
33184        }
33185    }
33186
33187    fn generate_pseudo_type(&mut self, e: &PseudoType) -> Result<()> {
33188        // Just output the name
33189        self.generate_expression(&e.this)?;
33190        Ok(())
33191    }
33192
33193    fn generate_put(&mut self, e: &PutStmt) -> Result<()> {
33194        // PUT source_file @stage [options]
33195        self.write_keyword("PUT");
33196        self.write_space();
33197
33198        // Source file path - preserve original quoting
33199        if e.source_quoted {
33200            self.write("'");
33201            self.write(&e.source);
33202            self.write("'");
33203        } else {
33204            self.write(&e.source);
33205        }
33206
33207        self.write_space();
33208
33209        // Target stage reference - output the string directly (includes @)
33210        if let Expression::Literal(lit) = &e.target {
33211            if let Literal::String(s) = lit.as_ref() {
33212                self.write(s);
33213            }
33214        } else {
33215            self.generate_expression(&e.target)?;
33216        }
33217
33218        // Optional parameters: KEY=VALUE
33219        for param in &e.params {
33220            self.write_space();
33221            self.write(&param.name);
33222            if let Some(ref value) = param.value {
33223                self.write("=");
33224                self.generate_expression(value)?;
33225            }
33226        }
33227
33228        Ok(())
33229    }
33230
33231    fn generate_quantile(&mut self, e: &Quantile) -> Result<()> {
33232        // QUANTILE(this, quantile)
33233        self.write_keyword("QUANTILE");
33234        self.write("(");
33235        self.generate_expression(&e.this)?;
33236        if let Some(quantile) = &e.quantile {
33237            self.write(", ");
33238            self.generate_expression(quantile)?;
33239        }
33240        self.write(")");
33241        Ok(())
33242    }
33243
33244    fn generate_query_band(&mut self, e: &QueryBand) -> Result<()> {
33245        // QUERY_BAND = this [UPDATE] [FOR scope]
33246        if matches!(
33247            self.config.dialect,
33248            Some(crate::dialects::DialectType::Teradata)
33249        ) {
33250            self.write_keyword("SET");
33251            self.write_space();
33252        }
33253        self.write_keyword("QUERY_BAND");
33254        self.write(" = ");
33255        self.generate_expression(&e.this)?;
33256        if e.update.is_some() {
33257            self.write_space();
33258            self.write_keyword("UPDATE");
33259        }
33260        if let Some(scope) = &e.scope {
33261            self.write_space();
33262            self.write_keyword("FOR");
33263            self.write_space();
33264            self.generate_expression(scope)?;
33265        }
33266        Ok(())
33267    }
33268
33269    fn generate_query_option(&mut self, e: &QueryOption) -> Result<()> {
33270        // this = expression
33271        self.generate_expression(&e.this)?;
33272        if let Some(expression) = &e.expression {
33273            self.write(" = ");
33274            self.generate_expression(expression)?;
33275        }
33276        Ok(())
33277    }
33278
33279    fn generate_query_transform(&mut self, e: &QueryTransform) -> Result<()> {
33280        // TRANSFORM (expressions) [row_format_before] [RECORDWRITER record_writer] USING command_script [AS schema] [row_format_after] [RECORDREADER record_reader]
33281        self.write_keyword("TRANSFORM");
33282        self.write("(");
33283        for (i, expr) in e.expressions.iter().enumerate() {
33284            if i > 0 {
33285                self.write(", ");
33286            }
33287            self.generate_expression(expr)?;
33288        }
33289        self.write(")");
33290        if let Some(row_format_before) = &e.row_format_before {
33291            self.write_space();
33292            self.generate_expression(row_format_before)?;
33293        }
33294        if let Some(record_writer) = &e.record_writer {
33295            self.write_space();
33296            self.write_keyword("RECORDWRITER");
33297            self.write_space();
33298            self.generate_expression(record_writer)?;
33299        }
33300        if let Some(command_script) = &e.command_script {
33301            self.write_space();
33302            self.write_keyword("USING");
33303            self.write_space();
33304            self.generate_expression(command_script)?;
33305        }
33306        if let Some(schema) = &e.schema {
33307            self.write_space();
33308            self.write_keyword("AS");
33309            self.write_space();
33310            self.generate_expression(schema)?;
33311        }
33312        if let Some(row_format_after) = &e.row_format_after {
33313            self.write_space();
33314            self.generate_expression(row_format_after)?;
33315        }
33316        if let Some(record_reader) = &e.record_reader {
33317            self.write_space();
33318            self.write_keyword("RECORDREADER");
33319            self.write_space();
33320            self.generate_expression(record_reader)?;
33321        }
33322        Ok(())
33323    }
33324
33325    fn generate_randn(&mut self, e: &Randn) -> Result<()> {
33326        // RANDN([seed])
33327        self.write_keyword("RANDN");
33328        self.write("(");
33329        if let Some(this) = &e.this {
33330            self.generate_expression(this)?;
33331        }
33332        self.write(")");
33333        Ok(())
33334    }
33335
33336    fn generate_randstr(&mut self, e: &Randstr) -> Result<()> {
33337        // RANDSTR(this, [generator])
33338        self.write_keyword("RANDSTR");
33339        self.write("(");
33340        self.generate_expression(&e.this)?;
33341        if let Some(generator) = &e.generator {
33342            self.write(", ");
33343            self.generate_expression(generator)?;
33344        }
33345        self.write(")");
33346        Ok(())
33347    }
33348
33349    fn generate_range_bucket(&mut self, e: &RangeBucket) -> Result<()> {
33350        // RANGE_BUCKET(this, expression)
33351        self.write_keyword("RANGE_BUCKET");
33352        self.write("(");
33353        self.generate_expression(&e.this)?;
33354        self.write(", ");
33355        self.generate_expression(&e.expression)?;
33356        self.write(")");
33357        Ok(())
33358    }
33359
33360    fn generate_range_n(&mut self, e: &RangeN) -> Result<()> {
33361        // RANGE_N(this BETWEEN expressions [EACH each])
33362        self.write_keyword("RANGE_N");
33363        self.write("(");
33364        self.generate_expression(&e.this)?;
33365        self.write_space();
33366        self.write_keyword("BETWEEN");
33367        self.write_space();
33368        for (i, expr) in e.expressions.iter().enumerate() {
33369            if i > 0 {
33370                self.write(", ");
33371            }
33372            self.generate_expression(expr)?;
33373        }
33374        if let Some(each) = &e.each {
33375            self.write_space();
33376            self.write_keyword("EACH");
33377            self.write_space();
33378            self.generate_expression(each)?;
33379        }
33380        self.write(")");
33381        Ok(())
33382    }
33383
33384    fn generate_read_csv(&mut self, e: &ReadCSV) -> Result<()> {
33385        // READ_CSV(this, expressions...)
33386        self.write_keyword("READ_CSV");
33387        self.write("(");
33388        self.generate_expression(&e.this)?;
33389        for expr in &e.expressions {
33390            self.write(", ");
33391            self.generate_expression(expr)?;
33392        }
33393        self.write(")");
33394        Ok(())
33395    }
33396
33397    fn generate_read_parquet(&mut self, e: &ReadParquet) -> Result<()> {
33398        // READ_PARQUET(expressions...)
33399        self.write_keyword("READ_PARQUET");
33400        self.write("(");
33401        for (i, expr) in e.expressions.iter().enumerate() {
33402            if i > 0 {
33403                self.write(", ");
33404            }
33405            self.generate_expression(expr)?;
33406        }
33407        self.write(")");
33408        Ok(())
33409    }
33410
33411    fn generate_recursive_with_search(&mut self, e: &RecursiveWithSearch) -> Result<()> {
33412        // SEARCH kind FIRST BY this SET expression [USING using]
33413        // or CYCLE this SET expression [USING using]
33414        if e.kind == "CYCLE" {
33415            self.write_keyword("CYCLE");
33416        } else {
33417            self.write_keyword("SEARCH");
33418            self.write_space();
33419            self.write(&e.kind);
33420            self.write_space();
33421            self.write_keyword("FIRST BY");
33422        }
33423        self.write_space();
33424        self.generate_expression(&e.this)?;
33425        self.write_space();
33426        self.write_keyword("SET");
33427        self.write_space();
33428        self.generate_expression(&e.expression)?;
33429        if let Some(using) = &e.using {
33430            self.write_space();
33431            self.write_keyword("USING");
33432            self.write_space();
33433            self.generate_expression(using)?;
33434        }
33435        Ok(())
33436    }
33437
33438    fn generate_reduce(&mut self, e: &Reduce) -> Result<()> {
33439        // REDUCE(this, initial, merge, [finish])
33440        self.write_keyword("REDUCE");
33441        self.write("(");
33442        self.generate_expression(&e.this)?;
33443        if let Some(initial) = &e.initial {
33444            self.write(", ");
33445            self.generate_expression(initial)?;
33446        }
33447        if let Some(merge) = &e.merge {
33448            self.write(", ");
33449            self.generate_expression(merge)?;
33450        }
33451        if let Some(finish) = &e.finish {
33452            self.write(", ");
33453            self.generate_expression(finish)?;
33454        }
33455        self.write(")");
33456        Ok(())
33457    }
33458
33459    fn generate_reference(&mut self, e: &Reference) -> Result<()> {
33460        // REFERENCES this (expressions) [options]
33461        self.write_keyword("REFERENCES");
33462        self.write_space();
33463        self.generate_expression(&e.this)?;
33464        if !e.expressions.is_empty() {
33465            self.write(" (");
33466            for (i, expr) in e.expressions.iter().enumerate() {
33467                if i > 0 {
33468                    self.write(", ");
33469                }
33470                self.generate_expression(expr)?;
33471            }
33472            self.write(")");
33473        }
33474        for opt in &e.options {
33475            self.write_space();
33476            self.generate_expression(opt)?;
33477        }
33478        Ok(())
33479    }
33480
33481    fn generate_refresh(&mut self, e: &Refresh) -> Result<()> {
33482        // REFRESH [kind] this
33483        self.write_keyword("REFRESH");
33484        if !e.kind.is_empty() {
33485            self.write_space();
33486            self.write_keyword(&e.kind);
33487        }
33488        self.write_space();
33489        self.generate_expression(&e.this)?;
33490        Ok(())
33491    }
33492
33493    fn generate_refresh_trigger_property(&mut self, e: &RefreshTriggerProperty) -> Result<()> {
33494        // Doris REFRESH clause: REFRESH method ON kind [EVERY n UNIT] [STARTS 'datetime']
33495        self.write_keyword("REFRESH");
33496        self.write_space();
33497        self.write_keyword(&e.method);
33498
33499        if let Some(ref kind) = e.kind {
33500            self.write_space();
33501            self.write_keyword("ON");
33502            self.write_space();
33503            self.write_keyword(kind);
33504
33505            // EVERY n UNIT
33506            if let Some(ref every) = e.every {
33507                self.write_space();
33508                self.write_keyword("EVERY");
33509                self.write_space();
33510                self.generate_expression(every)?;
33511                if let Some(ref unit) = e.unit {
33512                    self.write_space();
33513                    self.write_keyword(unit);
33514                }
33515            }
33516
33517            // STARTS 'datetime'
33518            if let Some(ref starts) = e.starts {
33519                self.write_space();
33520                self.write_keyword("STARTS");
33521                self.write_space();
33522                self.generate_expression(starts)?;
33523            }
33524        }
33525        Ok(())
33526    }
33527
33528    fn generate_regexp_count(&mut self, e: &RegexpCount) -> Result<()> {
33529        // REGEXP_COUNT(this, expression, position, parameters)
33530        self.write_keyword("REGEXP_COUNT");
33531        self.write("(");
33532        self.generate_expression(&e.this)?;
33533        self.write(", ");
33534        self.generate_expression(&e.expression)?;
33535        if let Some(position) = &e.position {
33536            self.write(", ");
33537            self.generate_expression(position)?;
33538        }
33539        if let Some(parameters) = &e.parameters {
33540            self.write(", ");
33541            self.generate_expression(parameters)?;
33542        }
33543        self.write(")");
33544        Ok(())
33545    }
33546
33547    fn generate_regexp_extract_all(&mut self, e: &RegexpExtractAll) -> Result<()> {
33548        // REGEXP_EXTRACT_ALL(this, expression, group, parameters, position, occurrence)
33549        self.write_keyword("REGEXP_EXTRACT_ALL");
33550        self.write("(");
33551        self.generate_expression(&e.this)?;
33552        self.write(", ");
33553        self.generate_expression(&e.expression)?;
33554        if let Some(group) = &e.group {
33555            self.write(", ");
33556            self.generate_expression(group)?;
33557        }
33558        self.write(")");
33559        Ok(())
33560    }
33561
33562    fn generate_regexp_full_match(&mut self, e: &RegexpFullMatch) -> Result<()> {
33563        // REGEXP_FULL_MATCH(this, expression)
33564        self.write_keyword("REGEXP_FULL_MATCH");
33565        self.write("(");
33566        self.generate_expression(&e.this)?;
33567        self.write(", ");
33568        self.generate_expression(&e.expression)?;
33569        self.write(")");
33570        Ok(())
33571    }
33572
33573    fn generate_regexp_i_like(&mut self, e: &RegexpILike) -> Result<()> {
33574        use crate::dialects::DialectType;
33575        // PostgreSQL/Redshift uses ~* operator for case-insensitive regex matching
33576        if matches!(
33577            self.config.dialect,
33578            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
33579        ) && e.flag.is_none()
33580        {
33581            self.generate_expression(&e.this)?;
33582            self.write(" ~* ");
33583            self.generate_expression(&e.expression)?;
33584        } else if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
33585            // Snowflake uses REGEXP_LIKE(x, pattern, 'i')
33586            self.write_keyword("REGEXP_LIKE");
33587            self.write("(");
33588            self.generate_expression(&e.this)?;
33589            self.write(", ");
33590            self.generate_expression(&e.expression)?;
33591            self.write(", ");
33592            if let Some(flag) = &e.flag {
33593                self.generate_expression(flag)?;
33594            } else {
33595                self.write("'i'");
33596            }
33597            self.write(")");
33598        } else {
33599            // this REGEXP_ILIKE expression or REGEXP_ILIKE(this, expression, flag)
33600            self.generate_expression(&e.this)?;
33601            self.write_space();
33602            self.write_keyword("REGEXP_ILIKE");
33603            self.write_space();
33604            self.generate_expression(&e.expression)?;
33605            if let Some(flag) = &e.flag {
33606                self.write(", ");
33607                self.generate_expression(flag)?;
33608            }
33609        }
33610        Ok(())
33611    }
33612
33613    fn generate_regexp_instr(&mut self, e: &RegexpInstr) -> Result<()> {
33614        // REGEXP_INSTR(this, expression, position, occurrence, option, parameters, group)
33615        self.write_keyword("REGEXP_INSTR");
33616        self.write("(");
33617        self.generate_expression(&e.this)?;
33618        self.write(", ");
33619        self.generate_expression(&e.expression)?;
33620        if let Some(position) = &e.position {
33621            self.write(", ");
33622            self.generate_expression(position)?;
33623        }
33624        if let Some(occurrence) = &e.occurrence {
33625            self.write(", ");
33626            self.generate_expression(occurrence)?;
33627        }
33628        if let Some(option) = &e.option {
33629            self.write(", ");
33630            self.generate_expression(option)?;
33631        }
33632        if let Some(parameters) = &e.parameters {
33633            self.write(", ");
33634            self.generate_expression(parameters)?;
33635        }
33636        if let Some(group) = &e.group {
33637            self.write(", ");
33638            self.generate_expression(group)?;
33639        }
33640        self.write(")");
33641        Ok(())
33642    }
33643
33644    fn generate_regexp_split(&mut self, e: &RegexpSplit) -> Result<()> {
33645        // REGEXP_SPLIT(this, expression, limit)
33646        self.write_keyword("REGEXP_SPLIT");
33647        self.write("(");
33648        self.generate_expression(&e.this)?;
33649        self.write(", ");
33650        self.generate_expression(&e.expression)?;
33651        if let Some(limit) = &e.limit {
33652            self.write(", ");
33653            self.generate_expression(limit)?;
33654        }
33655        self.write(")");
33656        Ok(())
33657    }
33658
33659    fn generate_regr_avgx(&mut self, e: &RegrAvgx) -> Result<()> {
33660        // REGR_AVGX(this, expression)
33661        self.write_keyword("REGR_AVGX");
33662        self.write("(");
33663        self.generate_expression(&e.this)?;
33664        self.write(", ");
33665        self.generate_expression(&e.expression)?;
33666        self.write(")");
33667        Ok(())
33668    }
33669
33670    fn generate_regr_avgy(&mut self, e: &RegrAvgy) -> Result<()> {
33671        // REGR_AVGY(this, expression)
33672        self.write_keyword("REGR_AVGY");
33673        self.write("(");
33674        self.generate_expression(&e.this)?;
33675        self.write(", ");
33676        self.generate_expression(&e.expression)?;
33677        self.write(")");
33678        Ok(())
33679    }
33680
33681    fn generate_regr_count(&mut self, e: &RegrCount) -> Result<()> {
33682        // REGR_COUNT(this, expression)
33683        self.write_keyword("REGR_COUNT");
33684        self.write("(");
33685        self.generate_expression(&e.this)?;
33686        self.write(", ");
33687        self.generate_expression(&e.expression)?;
33688        self.write(")");
33689        Ok(())
33690    }
33691
33692    fn generate_regr_intercept(&mut self, e: &RegrIntercept) -> Result<()> {
33693        // REGR_INTERCEPT(this, expression)
33694        self.write_keyword("REGR_INTERCEPT");
33695        self.write("(");
33696        self.generate_expression(&e.this)?;
33697        self.write(", ");
33698        self.generate_expression(&e.expression)?;
33699        self.write(")");
33700        Ok(())
33701    }
33702
33703    fn generate_regr_r2(&mut self, e: &RegrR2) -> Result<()> {
33704        // REGR_R2(this, expression)
33705        self.write_keyword("REGR_R2");
33706        self.write("(");
33707        self.generate_expression(&e.this)?;
33708        self.write(", ");
33709        self.generate_expression(&e.expression)?;
33710        self.write(")");
33711        Ok(())
33712    }
33713
33714    fn generate_regr_slope(&mut self, e: &RegrSlope) -> Result<()> {
33715        // REGR_SLOPE(this, expression)
33716        self.write_keyword("REGR_SLOPE");
33717        self.write("(");
33718        self.generate_expression(&e.this)?;
33719        self.write(", ");
33720        self.generate_expression(&e.expression)?;
33721        self.write(")");
33722        Ok(())
33723    }
33724
33725    fn generate_regr_sxx(&mut self, e: &RegrSxx) -> Result<()> {
33726        // REGR_SXX(this, expression)
33727        self.write_keyword("REGR_SXX");
33728        self.write("(");
33729        self.generate_expression(&e.this)?;
33730        self.write(", ");
33731        self.generate_expression(&e.expression)?;
33732        self.write(")");
33733        Ok(())
33734    }
33735
33736    fn generate_regr_sxy(&mut self, e: &RegrSxy) -> Result<()> {
33737        // REGR_SXY(this, expression)
33738        self.write_keyword("REGR_SXY");
33739        self.write("(");
33740        self.generate_expression(&e.this)?;
33741        self.write(", ");
33742        self.generate_expression(&e.expression)?;
33743        self.write(")");
33744        Ok(())
33745    }
33746
33747    fn generate_regr_syy(&mut self, e: &RegrSyy) -> Result<()> {
33748        // REGR_SYY(this, expression)
33749        self.write_keyword("REGR_SYY");
33750        self.write("(");
33751        self.generate_expression(&e.this)?;
33752        self.write(", ");
33753        self.generate_expression(&e.expression)?;
33754        self.write(")");
33755        Ok(())
33756    }
33757
33758    fn generate_regr_valx(&mut self, e: &RegrValx) -> Result<()> {
33759        // REGR_VALX(this, expression)
33760        self.write_keyword("REGR_VALX");
33761        self.write("(");
33762        self.generate_expression(&e.this)?;
33763        self.write(", ");
33764        self.generate_expression(&e.expression)?;
33765        self.write(")");
33766        Ok(())
33767    }
33768
33769    fn generate_regr_valy(&mut self, e: &RegrValy) -> Result<()> {
33770        // REGR_VALY(this, expression)
33771        self.write_keyword("REGR_VALY");
33772        self.write("(");
33773        self.generate_expression(&e.this)?;
33774        self.write(", ");
33775        self.generate_expression(&e.expression)?;
33776        self.write(")");
33777        Ok(())
33778    }
33779
33780    fn generate_remote_with_connection_model_property(
33781        &mut self,
33782        e: &RemoteWithConnectionModelProperty,
33783    ) -> Result<()> {
33784        // REMOTE WITH CONNECTION this
33785        self.write_keyword("REMOTE WITH CONNECTION");
33786        self.write_space();
33787        self.generate_expression(&e.this)?;
33788        Ok(())
33789    }
33790
33791    fn generate_rename_column(&mut self, e: &RenameColumn) -> Result<()> {
33792        // RENAME COLUMN [IF EXISTS] this TO new_name
33793        self.write_keyword("RENAME COLUMN");
33794        if e.exists {
33795            self.write_space();
33796            self.write_keyword("IF EXISTS");
33797        }
33798        self.write_space();
33799        self.generate_expression(&e.this)?;
33800        if let Some(to) = &e.to {
33801            self.write_space();
33802            self.write_keyword("TO");
33803            self.write_space();
33804            self.generate_expression(to)?;
33805        }
33806        Ok(())
33807    }
33808
33809    fn generate_replace_partition(&mut self, e: &ReplacePartition) -> Result<()> {
33810        // REPLACE PARTITION expression [FROM source]
33811        self.write_keyword("REPLACE PARTITION");
33812        self.write_space();
33813        self.generate_expression(&e.expression)?;
33814        if let Some(source) = &e.source {
33815            self.write_space();
33816            self.write_keyword("FROM");
33817            self.write_space();
33818            self.generate_expression(source)?;
33819        }
33820        Ok(())
33821    }
33822
33823    fn generate_returning(&mut self, e: &Returning) -> Result<()> {
33824        // RETURNING expressions [INTO into]
33825        // TSQL and Fabric use OUTPUT instead of RETURNING
33826        let keyword = match self.config.dialect {
33827            Some(DialectType::TSQL) | Some(DialectType::Fabric) => "OUTPUT",
33828            _ => "RETURNING",
33829        };
33830        self.write_keyword(keyword);
33831        self.write_space();
33832        for (i, expr) in e.expressions.iter().enumerate() {
33833            if i > 0 {
33834                self.write(", ");
33835            }
33836            self.generate_expression(expr)?;
33837        }
33838        if let Some(into) = &e.into {
33839            self.write_space();
33840            self.write_keyword("INTO");
33841            self.write_space();
33842            self.generate_expression(into)?;
33843        }
33844        Ok(())
33845    }
33846
33847    fn generate_output_clause(&mut self, output: &OutputClause) -> Result<()> {
33848        // OUTPUT expressions [INTO into_table]
33849        self.write_space();
33850        self.write_keyword("OUTPUT");
33851        self.write_space();
33852        for (i, expr) in output.columns.iter().enumerate() {
33853            if i > 0 {
33854                self.write(", ");
33855            }
33856            self.generate_expression(expr)?;
33857        }
33858        if let Some(into_table) = &output.into_table {
33859            self.write_space();
33860            self.write_keyword("INTO");
33861            self.write_space();
33862            self.generate_expression(into_table)?;
33863        }
33864        Ok(())
33865    }
33866
33867    fn generate_returns_property(&mut self, e: &ReturnsProperty) -> Result<()> {
33868        // RETURNS [TABLE] this [NULL ON NULL INPUT | CALLED ON NULL INPUT]
33869        self.write_keyword("RETURNS");
33870        if e.is_table.is_some() {
33871            self.write_space();
33872            self.write_keyword("TABLE");
33873        }
33874        if let Some(table) = &e.table {
33875            self.write_space();
33876            self.generate_expression(table)?;
33877        } else if let Some(this) = &e.this {
33878            self.write_space();
33879            self.generate_expression(this)?;
33880        }
33881        if e.null.is_some() {
33882            self.write_space();
33883            self.write_keyword("NULL ON NULL INPUT");
33884        }
33885        Ok(())
33886    }
33887
33888    fn generate_rollback(&mut self, e: &Rollback) -> Result<()> {
33889        // ROLLBACK [TRANSACTION [transaction_name]] [TO savepoint]
33890        self.write_keyword("ROLLBACK");
33891
33892        // TSQL always uses ROLLBACK TRANSACTION
33893        if e.this.is_none()
33894            && matches!(
33895                self.config.dialect,
33896                Some(DialectType::TSQL) | Some(DialectType::Fabric)
33897            )
33898        {
33899            self.write_space();
33900            self.write_keyword("TRANSACTION");
33901        }
33902
33903        // Check if this has TRANSACTION keyword or transaction name
33904        if let Some(this) = &e.this {
33905            // Check if it's just the "TRANSACTION" marker or an actual transaction name
33906            let is_transaction_marker = matches!(
33907                this.as_ref(),
33908                Expression::Identifier(id) if id.name == "TRANSACTION"
33909            );
33910
33911            self.write_space();
33912            self.write_keyword("TRANSACTION");
33913
33914            // If it's a real transaction name, output it
33915            if !is_transaction_marker {
33916                self.write_space();
33917                self.generate_expression(this)?;
33918            }
33919        }
33920
33921        // Output TO savepoint
33922        if let Some(savepoint) = &e.savepoint {
33923            self.write_space();
33924            self.write_keyword("TO");
33925            self.write_space();
33926            self.generate_expression(savepoint)?;
33927        }
33928        Ok(())
33929    }
33930
33931    fn generate_rollup(&mut self, e: &Rollup) -> Result<()> {
33932        // Python: return f"ROLLUP {self.wrap(expressions)}" if expressions else "WITH ROLLUP"
33933        if e.expressions.is_empty() {
33934            self.write_keyword("WITH ROLLUP");
33935        } else {
33936            self.write_keyword("ROLLUP");
33937            self.write("(");
33938            for (i, expr) in e.expressions.iter().enumerate() {
33939                if i > 0 {
33940                    self.write(", ");
33941                }
33942                self.generate_expression(expr)?;
33943            }
33944            self.write(")");
33945        }
33946        Ok(())
33947    }
33948
33949    fn generate_row_format_delimited_property(
33950        &mut self,
33951        e: &RowFormatDelimitedProperty,
33952    ) -> Result<()> {
33953        // ROW FORMAT DELIMITED [FIELDS TERMINATED BY ...] [ESCAPED BY ...] [COLLECTION ITEMS TERMINATED BY ...] [MAP KEYS TERMINATED BY ...] [LINES TERMINATED BY ...] [NULL DEFINED AS ...]
33954        self.write_keyword("ROW FORMAT DELIMITED");
33955        if let Some(fields) = &e.fields {
33956            self.write_space();
33957            self.write_keyword("FIELDS TERMINATED BY");
33958            self.write_space();
33959            self.generate_expression(fields)?;
33960        }
33961        if let Some(escaped) = &e.escaped {
33962            self.write_space();
33963            self.write_keyword("ESCAPED BY");
33964            self.write_space();
33965            self.generate_expression(escaped)?;
33966        }
33967        if let Some(items) = &e.collection_items {
33968            self.write_space();
33969            self.write_keyword("COLLECTION ITEMS TERMINATED BY");
33970            self.write_space();
33971            self.generate_expression(items)?;
33972        }
33973        if let Some(keys) = &e.map_keys {
33974            self.write_space();
33975            self.write_keyword("MAP KEYS TERMINATED BY");
33976            self.write_space();
33977            self.generate_expression(keys)?;
33978        }
33979        if let Some(lines) = &e.lines {
33980            self.write_space();
33981            self.write_keyword("LINES TERMINATED BY");
33982            self.write_space();
33983            self.generate_expression(lines)?;
33984        }
33985        if let Some(null) = &e.null {
33986            self.write_space();
33987            self.write_keyword("NULL DEFINED AS");
33988            self.write_space();
33989            self.generate_expression(null)?;
33990        }
33991        if let Some(serde) = &e.serde {
33992            self.write_space();
33993            self.generate_expression(serde)?;
33994        }
33995        Ok(())
33996    }
33997
33998    fn generate_row_format_property(&mut self, e: &RowFormatProperty) -> Result<()> {
33999        // ROW FORMAT this
34000        self.write_keyword("ROW FORMAT");
34001        self.write_space();
34002        self.generate_expression(&e.this)?;
34003        Ok(())
34004    }
34005
34006    fn generate_row_format_serde_property(&mut self, e: &RowFormatSerdeProperty) -> Result<()> {
34007        // ROW FORMAT SERDE this [WITH SERDEPROPERTIES (...)]
34008        self.write_keyword("ROW FORMAT SERDE");
34009        self.write_space();
34010        self.generate_expression(&e.this)?;
34011        if let Some(props) = &e.serde_properties {
34012            self.write_space();
34013            // SerdeProperties generates its own "[WITH] SERDEPROPERTIES (...)"
34014            self.generate_expression(props)?;
34015        }
34016        Ok(())
34017    }
34018
34019    fn generate_sha2(&mut self, e: &SHA2) -> Result<()> {
34020        // SHA2(this, length)
34021        self.write_keyword("SHA2");
34022        self.write("(");
34023        self.generate_expression(&e.this)?;
34024        if let Some(length) = e.length {
34025            self.write(", ");
34026            self.write(&length.to_string());
34027        }
34028        self.write(")");
34029        Ok(())
34030    }
34031
34032    fn generate_sha2_digest(&mut self, e: &SHA2Digest) -> Result<()> {
34033        // SHA2_DIGEST(this, length)
34034        self.write_keyword("SHA2_DIGEST");
34035        self.write("(");
34036        self.generate_expression(&e.this)?;
34037        if let Some(length) = e.length {
34038            self.write(", ");
34039            self.write(&length.to_string());
34040        }
34041        self.write(")");
34042        Ok(())
34043    }
34044
34045    fn generate_safe_add(&mut self, e: &SafeAdd) -> Result<()> {
34046        let name = if matches!(
34047            self.config.dialect,
34048            Some(crate::dialects::DialectType::Spark)
34049                | Some(crate::dialects::DialectType::Databricks)
34050        ) {
34051            "TRY_ADD"
34052        } else {
34053            "SAFE_ADD"
34054        };
34055        self.write_keyword(name);
34056        self.write("(");
34057        self.generate_expression(&e.this)?;
34058        self.write(", ");
34059        self.generate_expression(&e.expression)?;
34060        self.write(")");
34061        Ok(())
34062    }
34063
34064    fn generate_safe_divide(&mut self, e: &SafeDivide) -> Result<()> {
34065        // SAFE_DIVIDE(this, expression)
34066        self.write_keyword("SAFE_DIVIDE");
34067        self.write("(");
34068        self.generate_expression(&e.this)?;
34069        self.write(", ");
34070        self.generate_expression(&e.expression)?;
34071        self.write(")");
34072        Ok(())
34073    }
34074
34075    fn generate_safe_multiply(&mut self, e: &SafeMultiply) -> Result<()> {
34076        let name = if matches!(
34077            self.config.dialect,
34078            Some(crate::dialects::DialectType::Spark)
34079                | Some(crate::dialects::DialectType::Databricks)
34080        ) {
34081            "TRY_MULTIPLY"
34082        } else {
34083            "SAFE_MULTIPLY"
34084        };
34085        self.write_keyword(name);
34086        self.write("(");
34087        self.generate_expression(&e.this)?;
34088        self.write(", ");
34089        self.generate_expression(&e.expression)?;
34090        self.write(")");
34091        Ok(())
34092    }
34093
34094    fn generate_safe_subtract(&mut self, e: &SafeSubtract) -> Result<()> {
34095        let name = if matches!(
34096            self.config.dialect,
34097            Some(crate::dialects::DialectType::Spark)
34098                | Some(crate::dialects::DialectType::Databricks)
34099        ) {
34100            "TRY_SUBTRACT"
34101        } else {
34102            "SAFE_SUBTRACT"
34103        };
34104        self.write_keyword(name);
34105        self.write("(");
34106        self.generate_expression(&e.this)?;
34107        self.write(", ");
34108        self.generate_expression(&e.expression)?;
34109        self.write(")");
34110        Ok(())
34111    }
34112
34113    /// Generate the body of a USING SAMPLE or TABLESAMPLE clause:
34114    /// METHOD (size UNIT) [REPEATABLE (seed)]
34115    fn generate_sample_body(&mut self, sample: &Sample) -> Result<()> {
34116        // Handle BUCKET sampling: TABLESAMPLE (BUCKET n OUT OF m [ON col])
34117        if matches!(sample.method, SampleMethod::Bucket) {
34118            self.write(" (");
34119            self.write_keyword("BUCKET");
34120            self.write_space();
34121            if let Some(ref num) = sample.bucket_numerator {
34122                self.generate_expression(num)?;
34123            }
34124            self.write_space();
34125            self.write_keyword("OUT OF");
34126            self.write_space();
34127            if let Some(ref denom) = sample.bucket_denominator {
34128                self.generate_expression(denom)?;
34129            }
34130            if let Some(ref field) = sample.bucket_field {
34131                self.write_space();
34132                self.write_keyword("ON");
34133                self.write_space();
34134                self.generate_expression(field)?;
34135            }
34136            self.write(")");
34137            return Ok(());
34138        }
34139
34140        // Output method name if explicitly specified, or for dialects that always require it
34141        let is_snowflake = matches!(
34142            self.config.dialect,
34143            Some(crate::dialects::DialectType::Snowflake)
34144        );
34145        let is_postgres = matches!(
34146            self.config.dialect,
34147            Some(crate::dialects::DialectType::PostgreSQL)
34148                | Some(crate::dialects::DialectType::Redshift)
34149        );
34150        // Databricks and Spark don't output method names
34151        let is_databricks = matches!(
34152            self.config.dialect,
34153            Some(crate::dialects::DialectType::Databricks)
34154        );
34155        let is_spark = matches!(
34156            self.config.dialect,
34157            Some(crate::dialects::DialectType::Spark)
34158        );
34159        let suppress_method = is_databricks || is_spark || sample.suppress_method_output;
34160        // PostgreSQL always outputs BERNOULLI for BERNOULLI samples
34161        let force_method = is_postgres && matches!(sample.method, SampleMethod::Bernoulli);
34162        if !suppress_method && (sample.explicit_method || is_snowflake || force_method) {
34163            self.write_space();
34164            if !sample.explicit_method && (is_snowflake || force_method) {
34165                // Snowflake/PostgreSQL defaults to BERNOULLI when no method is specified
34166                self.write_keyword("BERNOULLI");
34167            } else {
34168                match sample.method {
34169                    SampleMethod::Bernoulli => self.write_keyword("BERNOULLI"),
34170                    SampleMethod::System => self.write_keyword("SYSTEM"),
34171                    SampleMethod::Block => self.write_keyword("BLOCK"),
34172                    SampleMethod::Row => self.write_keyword("ROW"),
34173                    SampleMethod::Reservoir => self.write_keyword("RESERVOIR"),
34174                    SampleMethod::Percent => self.write_keyword("SYSTEM"),
34175                    SampleMethod::Bucket => {} // handled above
34176                }
34177            }
34178        }
34179
34180        // Output size, with or without parentheses depending on dialect
34181        let emit_size_no_parens = !self.config.tablesample_requires_parens;
34182        if emit_size_no_parens {
34183            self.write_space();
34184            match &sample.size {
34185                Expression::Tuple(tuple) => {
34186                    for (i, expr) in tuple.expressions.iter().enumerate() {
34187                        if i > 0 {
34188                            self.write(", ");
34189                        }
34190                        self.generate_expression(expr)?;
34191                    }
34192                }
34193                expr => self.generate_expression(expr)?,
34194            }
34195        } else {
34196            self.write(" (");
34197            self.generate_expression(&sample.size)?;
34198        }
34199
34200        // Determine unit
34201        let is_rows_method = matches!(
34202            sample.method,
34203            SampleMethod::Reservoir | SampleMethod::Row | SampleMethod::Bucket
34204        );
34205        let is_percent = matches!(
34206            sample.method,
34207            SampleMethod::Percent
34208                | SampleMethod::System
34209                | SampleMethod::Bernoulli
34210                | SampleMethod::Block
34211        );
34212
34213        // For Snowflake, PostgreSQL, and Presto/Trino, only output ROWS/PERCENT when the user explicitly wrote it (unit_after_size).
34214        // These dialects use bare numbers for percentage by default in TABLESAMPLE METHOD(size) syntax.
34215        // For Databricks and Spark, always output PERCENT for percentage samples.
34216        let is_presto = matches!(
34217            self.config.dialect,
34218            Some(crate::dialects::DialectType::Presto)
34219                | Some(crate::dialects::DialectType::Trino)
34220                | Some(crate::dialects::DialectType::Athena)
34221        );
34222        let should_output_unit = if is_databricks || is_spark {
34223            // Always output PERCENT for percentage-based methods, or ROWS for row-based methods
34224            is_percent || is_rows_method || sample.unit_after_size
34225        } else if is_snowflake || is_postgres || is_presto {
34226            sample.unit_after_size
34227        } else {
34228            sample.unit_after_size || (sample.explicit_method && (is_rows_method || is_percent))
34229        };
34230
34231        if should_output_unit {
34232            self.write_space();
34233            if sample.is_percent {
34234                self.write_keyword("PERCENT");
34235            } else if is_rows_method && !sample.unit_after_size {
34236                self.write_keyword("ROWS");
34237            } else if sample.unit_after_size {
34238                match sample.method {
34239                    SampleMethod::Percent
34240                    | SampleMethod::System
34241                    | SampleMethod::Bernoulli
34242                    | SampleMethod::Block => {
34243                        self.write_keyword("PERCENT");
34244                    }
34245                    SampleMethod::Row | SampleMethod::Reservoir => {
34246                        self.write_keyword("ROWS");
34247                    }
34248                    _ => self.write_keyword("ROWS"),
34249                }
34250            } else {
34251                self.write_keyword("PERCENT");
34252            }
34253        }
34254
34255        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
34256            if let Some(ref offset) = sample.offset {
34257                self.write_space();
34258                self.write_keyword("OFFSET");
34259                self.write_space();
34260                self.generate_expression(offset)?;
34261            }
34262        }
34263        if !emit_size_no_parens {
34264            self.write(")");
34265        }
34266
34267        Ok(())
34268    }
34269
34270    fn generate_sample_property(&mut self, e: &SampleProperty) -> Result<()> {
34271        // SAMPLE this (ClickHouse uses SAMPLE BY)
34272        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
34273            self.write_keyword("SAMPLE BY");
34274        } else {
34275            self.write_keyword("SAMPLE");
34276        }
34277        self.write_space();
34278        self.generate_expression(&e.this)?;
34279        Ok(())
34280    }
34281
34282    fn generate_schema(&mut self, e: &Schema) -> Result<()> {
34283        // this (expressions...)
34284        if let Some(this) = &e.this {
34285            self.generate_expression(this)?;
34286        }
34287        if !e.expressions.is_empty() {
34288            // Add space before column list if there's a preceding expression
34289            if e.this.is_some() {
34290                self.write_space();
34291            }
34292            self.write("(");
34293            for (i, expr) in e.expressions.iter().enumerate() {
34294                if i > 0 {
34295                    self.write(", ");
34296                }
34297                self.generate_expression(expr)?;
34298            }
34299            self.write(")");
34300        }
34301        Ok(())
34302    }
34303
34304    fn generate_schema_comment_property(&mut self, e: &SchemaCommentProperty) -> Result<()> {
34305        // COMMENT this
34306        self.write_keyword("COMMENT");
34307        self.write_space();
34308        self.generate_expression(&e.this)?;
34309        Ok(())
34310    }
34311
34312    fn generate_scope_resolution(&mut self, e: &ScopeResolution) -> Result<()> {
34313        // [this::]expression
34314        if let Some(this) = &e.this {
34315            self.generate_expression(this)?;
34316            self.write("::");
34317        }
34318        self.generate_expression(&e.expression)?;
34319        Ok(())
34320    }
34321
34322    fn generate_search(&mut self, e: &Search) -> Result<()> {
34323        // SEARCH(this, expression, [json_scope], [analyzer], [analyzer_options], [search_mode])
34324        self.write_keyword("SEARCH");
34325        self.write("(");
34326        self.generate_expression(&e.this)?;
34327        self.write(", ");
34328        self.generate_expression(&e.expression)?;
34329        if let Some(json_scope) = &e.json_scope {
34330            self.write(", ");
34331            self.generate_expression(json_scope)?;
34332        }
34333        if let Some(analyzer) = &e.analyzer {
34334            self.write(", ");
34335            self.generate_expression(analyzer)?;
34336        }
34337        if let Some(analyzer_options) = &e.analyzer_options {
34338            self.write(", ");
34339            self.generate_expression(analyzer_options)?;
34340        }
34341        if let Some(search_mode) = &e.search_mode {
34342            self.write(", ");
34343            self.generate_expression(search_mode)?;
34344        }
34345        self.write(")");
34346        Ok(())
34347    }
34348
34349    fn generate_search_ip(&mut self, e: &SearchIp) -> Result<()> {
34350        // SEARCH_IP(this, expression)
34351        self.write_keyword("SEARCH_IP");
34352        self.write("(");
34353        self.generate_expression(&e.this)?;
34354        self.write(", ");
34355        self.generate_expression(&e.expression)?;
34356        self.write(")");
34357        Ok(())
34358    }
34359
34360    fn generate_security_property(&mut self, e: &SecurityProperty) -> Result<()> {
34361        // SECURITY this
34362        self.write_keyword("SECURITY");
34363        self.write_space();
34364        self.generate_expression(&e.this)?;
34365        Ok(())
34366    }
34367
34368    fn generate_semantic_view(&mut self, e: &SemanticView) -> Result<()> {
34369        // SEMANTIC_VIEW(this [METRICS ...] [DIMENSIONS ...] [FACTS ...] [WHERE ...])
34370        self.write("SEMANTIC_VIEW(");
34371
34372        if self.config.pretty {
34373            // Pretty print: each clause on its own line
34374            self.write_newline();
34375            self.indent_level += 1;
34376            self.write_indent();
34377            self.generate_expression(&e.this)?;
34378
34379            if let Some(metrics) = &e.metrics {
34380                self.write_newline();
34381                self.write_indent();
34382                self.write_keyword("METRICS");
34383                self.write_space();
34384                self.generate_semantic_view_tuple(metrics)?;
34385            }
34386            if let Some(dimensions) = &e.dimensions {
34387                self.write_newline();
34388                self.write_indent();
34389                self.write_keyword("DIMENSIONS");
34390                self.write_space();
34391                self.generate_semantic_view_tuple(dimensions)?;
34392            }
34393            if let Some(facts) = &e.facts {
34394                self.write_newline();
34395                self.write_indent();
34396                self.write_keyword("FACTS");
34397                self.write_space();
34398                self.generate_semantic_view_tuple(facts)?;
34399            }
34400            if let Some(where_) = &e.where_ {
34401                self.write_newline();
34402                self.write_indent();
34403                self.write_keyword("WHERE");
34404                self.write_space();
34405                self.generate_expression(where_)?;
34406            }
34407            self.write_newline();
34408            self.indent_level -= 1;
34409            self.write_indent();
34410        } else {
34411            // Compact: all on one line
34412            self.generate_expression(&e.this)?;
34413            if let Some(metrics) = &e.metrics {
34414                self.write_space();
34415                self.write_keyword("METRICS");
34416                self.write_space();
34417                self.generate_semantic_view_tuple(metrics)?;
34418            }
34419            if let Some(dimensions) = &e.dimensions {
34420                self.write_space();
34421                self.write_keyword("DIMENSIONS");
34422                self.write_space();
34423                self.generate_semantic_view_tuple(dimensions)?;
34424            }
34425            if let Some(facts) = &e.facts {
34426                self.write_space();
34427                self.write_keyword("FACTS");
34428                self.write_space();
34429                self.generate_semantic_view_tuple(facts)?;
34430            }
34431            if let Some(where_) = &e.where_ {
34432                self.write_space();
34433                self.write_keyword("WHERE");
34434                self.write_space();
34435                self.generate_expression(where_)?;
34436            }
34437        }
34438        self.write(")");
34439        Ok(())
34440    }
34441
34442    /// Helper for SEMANTIC_VIEW tuple contents (without parentheses)
34443    fn generate_semantic_view_tuple(&mut self, expr: &Expression) -> Result<()> {
34444        if let Expression::Tuple(t) = expr {
34445            for (i, e) in t.expressions.iter().enumerate() {
34446                if i > 0 {
34447                    self.write(", ");
34448                }
34449                self.generate_expression(e)?;
34450            }
34451        } else {
34452            self.generate_expression(expr)?;
34453        }
34454        Ok(())
34455    }
34456
34457    fn generate_sequence_properties(&mut self, e: &SequenceProperties) -> Result<()> {
34458        // [START WITH start] [INCREMENT BY increment] [MINVALUE minvalue] [MAXVALUE maxvalue] [CACHE cache] [OWNED BY owned]
34459        if let Some(start) = &e.start {
34460            self.write_keyword("START WITH");
34461            self.write_space();
34462            self.generate_expression(start)?;
34463        }
34464        if let Some(increment) = &e.increment {
34465            self.write_space();
34466            self.write_keyword("INCREMENT BY");
34467            self.write_space();
34468            self.generate_expression(increment)?;
34469        }
34470        if let Some(minvalue) = &e.minvalue {
34471            self.write_space();
34472            self.write_keyword("MINVALUE");
34473            self.write_space();
34474            self.generate_expression(minvalue)?;
34475        }
34476        if let Some(maxvalue) = &e.maxvalue {
34477            self.write_space();
34478            self.write_keyword("MAXVALUE");
34479            self.write_space();
34480            self.generate_expression(maxvalue)?;
34481        }
34482        if let Some(cache) = &e.cache {
34483            self.write_space();
34484            self.write_keyword("CACHE");
34485            self.write_space();
34486            self.generate_expression(cache)?;
34487        }
34488        if let Some(owned) = &e.owned {
34489            self.write_space();
34490            self.write_keyword("OWNED BY");
34491            self.write_space();
34492            self.generate_expression(owned)?;
34493        }
34494        for opt in &e.options {
34495            self.write_space();
34496            self.generate_expression(opt)?;
34497        }
34498        Ok(())
34499    }
34500
34501    fn generate_serde_properties(&mut self, e: &SerdeProperties) -> Result<()> {
34502        // [WITH] SERDEPROPERTIES (expressions)
34503        if e.with_.is_some() {
34504            self.write_keyword("WITH");
34505            self.write_space();
34506        }
34507        self.write_keyword("SERDEPROPERTIES");
34508        self.write(" (");
34509        for (i, expr) in e.expressions.iter().enumerate() {
34510            if i > 0 {
34511                self.write(", ");
34512            }
34513            // Generate key=value without spaces around =
34514            match expr {
34515                Expression::Eq(eq) => {
34516                    self.generate_expression(&eq.left)?;
34517                    self.write("=");
34518                    self.generate_expression(&eq.right)?;
34519                }
34520                _ => self.generate_expression(expr)?,
34521            }
34522        }
34523        self.write(")");
34524        Ok(())
34525    }
34526
34527    fn generate_session_parameter(&mut self, e: &SessionParameter) -> Result<()> {
34528        // @@[kind.]this
34529        self.write("@@");
34530        if let Some(kind) = &e.kind {
34531            self.write(kind);
34532            self.write(".");
34533        }
34534        self.generate_expression(&e.this)?;
34535        Ok(())
34536    }
34537
34538    fn generate_set(&mut self, e: &Set) -> Result<()> {
34539        // SET/UNSET [TAG] expressions
34540        if e.unset.is_some() {
34541            self.write_keyword("UNSET");
34542        } else {
34543            self.write_keyword("SET");
34544        }
34545        if e.tag.is_some() {
34546            self.write_space();
34547            self.write_keyword("TAG");
34548        }
34549        if !e.expressions.is_empty() {
34550            self.write_space();
34551            for (i, expr) in e.expressions.iter().enumerate() {
34552                if i > 0 {
34553                    self.write(", ");
34554                }
34555                self.generate_expression(expr)?;
34556            }
34557        }
34558        Ok(())
34559    }
34560
34561    fn generate_set_config_property(&mut self, e: &SetConfigProperty) -> Result<()> {
34562        // SET this or SETCONFIG this
34563        self.write_keyword("SET");
34564        self.write_space();
34565        self.generate_expression(&e.this)?;
34566        Ok(())
34567    }
34568
34569    fn generate_set_item(&mut self, e: &SetItem) -> Result<()> {
34570        // [kind] name = value
34571        if let Some(kind) = &e.kind {
34572            self.write_keyword(kind);
34573            self.write_space();
34574        }
34575        self.generate_expression(&e.name)?;
34576        self.write(" = ");
34577        self.generate_expression(&e.value)?;
34578        Ok(())
34579    }
34580
34581    fn generate_set_operation(&mut self, e: &SetOperation) -> Result<()> {
34582        // [WITH ...] this UNION|INTERSECT|EXCEPT [ALL|DISTINCT] [BY NAME] expression
34583        if let Some(with_) = &e.with_ {
34584            self.generate_expression(with_)?;
34585            self.write_space();
34586        }
34587        self.generate_expression(&e.this)?;
34588        self.write_space();
34589        // kind should be UNION, INTERSECT, EXCEPT, etc.
34590        if let Some(kind) = &e.kind {
34591            self.write_keyword(kind);
34592        }
34593        if e.distinct {
34594            self.write_space();
34595            self.write_keyword("DISTINCT");
34596        } else {
34597            self.write_space();
34598            self.write_keyword("ALL");
34599        }
34600        if e.by_name.is_some() {
34601            self.write_space();
34602            self.write_keyword("BY NAME");
34603        }
34604        self.write_space();
34605        self.generate_expression(&e.expression)?;
34606        Ok(())
34607    }
34608
34609    fn generate_set_property(&mut self, e: &SetProperty) -> Result<()> {
34610        // SET or MULTISET
34611        if e.multi.is_some() {
34612            self.write_keyword("MULTISET");
34613        } else {
34614            self.write_keyword("SET");
34615        }
34616        Ok(())
34617    }
34618
34619    fn generate_settings_property(&mut self, e: &SettingsProperty) -> Result<()> {
34620        // SETTINGS expressions
34621        self.write_keyword("SETTINGS");
34622        if self.config.pretty && e.expressions.len() > 1 {
34623            // Pretty print: each setting on its own line, indented
34624            self.indent_level += 1;
34625            for (i, expr) in e.expressions.iter().enumerate() {
34626                if i > 0 {
34627                    self.write(",");
34628                }
34629                self.write_newline();
34630                self.write_indent();
34631                self.generate_expression(expr)?;
34632            }
34633            self.indent_level -= 1;
34634        } else {
34635            self.write_space();
34636            for (i, expr) in e.expressions.iter().enumerate() {
34637                if i > 0 {
34638                    self.write(", ");
34639                }
34640                self.generate_expression(expr)?;
34641            }
34642        }
34643        Ok(())
34644    }
34645
34646    fn generate_sharing_property(&mut self, e: &SharingProperty) -> Result<()> {
34647        // SHARING = this
34648        self.write_keyword("SHARING");
34649        if let Some(this) = &e.this {
34650            self.write(" = ");
34651            self.generate_expression(this)?;
34652        }
34653        Ok(())
34654    }
34655
34656    fn generate_slice(&mut self, e: &Slice) -> Result<()> {
34657        // Python array slicing: begin:end:step
34658        if let Some(begin) = &e.this {
34659            self.generate_expression(begin)?;
34660        }
34661        self.write(":");
34662        if let Some(end) = &e.expression {
34663            self.generate_expression(end)?;
34664        }
34665        if let Some(step) = &e.step {
34666            self.write(":");
34667            self.generate_expression(step)?;
34668        }
34669        Ok(())
34670    }
34671
34672    fn generate_sort_array(&mut self, e: &SortArray) -> Result<()> {
34673        // SORT_ARRAY(this, asc)
34674        self.write_keyword("SORT_ARRAY");
34675        self.write("(");
34676        self.generate_expression(&e.this)?;
34677        if let Some(asc) = &e.asc {
34678            self.write(", ");
34679            self.generate_expression(asc)?;
34680        }
34681        self.write(")");
34682        Ok(())
34683    }
34684
34685    fn generate_sort_by(&mut self, e: &SortBy) -> Result<()> {
34686        // SORT BY expressions
34687        self.write_keyword("SORT BY");
34688        self.write_space();
34689        for (i, expr) in e.expressions.iter().enumerate() {
34690            if i > 0 {
34691                self.write(", ");
34692            }
34693            self.generate_ordered(expr)?;
34694        }
34695        Ok(())
34696    }
34697
34698    fn generate_sort_key_property(&mut self, e: &SortKeyProperty) -> Result<()> {
34699        // [COMPOUND] SORTKEY(col1, col2, ...) - no space before paren
34700        if e.compound.is_some() {
34701            self.write_keyword("COMPOUND");
34702            self.write_space();
34703        }
34704        self.write_keyword("SORTKEY");
34705        self.write("(");
34706        // If this is a Tuple, unwrap its contents to avoid double parentheses
34707        if let Expression::Tuple(t) = e.this.as_ref() {
34708            for (i, expr) in t.expressions.iter().enumerate() {
34709                if i > 0 {
34710                    self.write(", ");
34711                }
34712                self.generate_expression(expr)?;
34713            }
34714        } else {
34715            self.generate_expression(&e.this)?;
34716        }
34717        self.write(")");
34718        Ok(())
34719    }
34720
34721    fn generate_split_part(&mut self, e: &SplitPart) -> Result<()> {
34722        // SPLIT_PART(this, delimiter, part_index)
34723        self.write_keyword("SPLIT_PART");
34724        self.write("(");
34725        self.generate_expression(&e.this)?;
34726        if let Some(delimiter) = &e.delimiter {
34727            self.write(", ");
34728            self.generate_expression(delimiter)?;
34729        }
34730        if let Some(part_index) = &e.part_index {
34731            self.write(", ");
34732            self.generate_expression(part_index)?;
34733        }
34734        self.write(")");
34735        Ok(())
34736    }
34737
34738    fn generate_sql_read_write_property(&mut self, e: &SqlReadWriteProperty) -> Result<()> {
34739        // READS SQL DATA or MODIFIES SQL DATA, etc.
34740        self.generate_expression(&e.this)?;
34741        Ok(())
34742    }
34743
34744    fn generate_sql_security_property(&mut self, e: &SqlSecurityProperty) -> Result<()> {
34745        // SQL SECURITY DEFINER or SQL SECURITY INVOKER
34746        self.write_keyword("SQL SECURITY");
34747        self.write_space();
34748        self.generate_expression(&e.this)?;
34749        Ok(())
34750    }
34751
34752    fn generate_st_distance(&mut self, e: &StDistance) -> Result<()> {
34753        // ST_DISTANCE(this, expression, [use_spheroid])
34754        self.write_keyword("ST_DISTANCE");
34755        self.write("(");
34756        self.generate_expression(&e.this)?;
34757        self.write(", ");
34758        self.generate_expression(&e.expression)?;
34759        if let Some(use_spheroid) = &e.use_spheroid {
34760            self.write(", ");
34761            self.generate_expression(use_spheroid)?;
34762        }
34763        self.write(")");
34764        Ok(())
34765    }
34766
34767    fn generate_st_point(&mut self, e: &StPoint) -> Result<()> {
34768        // ST_POINT(this, expression)
34769        self.write_keyword("ST_POINT");
34770        self.write("(");
34771        self.generate_expression(&e.this)?;
34772        self.write(", ");
34773        self.generate_expression(&e.expression)?;
34774        self.write(")");
34775        Ok(())
34776    }
34777
34778    fn generate_stability_property(&mut self, e: &StabilityProperty) -> Result<()> {
34779        // IMMUTABLE, STABLE, VOLATILE
34780        self.generate_expression(&e.this)?;
34781        Ok(())
34782    }
34783
34784    fn generate_standard_hash(&mut self, e: &StandardHash) -> Result<()> {
34785        // STANDARD_HASH(this, [expression])
34786        self.write_keyword("STANDARD_HASH");
34787        self.write("(");
34788        self.generate_expression(&e.this)?;
34789        if let Some(expression) = &e.expression {
34790            self.write(", ");
34791            self.generate_expression(expression)?;
34792        }
34793        self.write(")");
34794        Ok(())
34795    }
34796
34797    fn generate_storage_handler_property(&mut self, e: &StorageHandlerProperty) -> Result<()> {
34798        // STORED BY this
34799        self.write_keyword("STORED BY");
34800        self.write_space();
34801        self.generate_expression(&e.this)?;
34802        Ok(())
34803    }
34804
34805    fn generate_str_position(&mut self, e: &StrPosition) -> Result<()> {
34806        // STRPOS(this, substr) or STRPOS(this, substr, position)
34807        // Different dialects have different function names
34808        use crate::dialects::DialectType;
34809        if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
34810            // Snowflake: CHARINDEX(substr, str[, position])
34811            self.write_keyword("CHARINDEX");
34812            self.write("(");
34813            if let Some(substr) = &e.substr {
34814                self.generate_expression(substr)?;
34815                self.write(", ");
34816            }
34817            self.generate_expression(&e.this)?;
34818            if let Some(position) = &e.position {
34819                self.write(", ");
34820                self.generate_expression(position)?;
34821            }
34822            self.write(")");
34823        } else if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
34824            self.write_keyword("POSITION");
34825            self.write("(");
34826            self.generate_expression(&e.this)?;
34827            if let Some(substr) = &e.substr {
34828                self.write(", ");
34829                self.generate_expression(substr)?;
34830            }
34831            if let Some(position) = &e.position {
34832                self.write(", ");
34833                self.generate_expression(position)?;
34834            }
34835            if let Some(occurrence) = &e.occurrence {
34836                self.write(", ");
34837                self.generate_expression(occurrence)?;
34838            }
34839            self.write(")");
34840        } else if matches!(
34841            self.config.dialect,
34842            Some(DialectType::SQLite)
34843                | Some(DialectType::Oracle)
34844                | Some(DialectType::BigQuery)
34845                | Some(DialectType::Teradata)
34846        ) {
34847            self.write_keyword("INSTR");
34848            self.write("(");
34849            self.generate_expression(&e.this)?;
34850            if let Some(substr) = &e.substr {
34851                self.write(", ");
34852                self.generate_expression(substr)?;
34853            }
34854            if let Some(position) = &e.position {
34855                self.write(", ");
34856                self.generate_expression(position)?;
34857            } else if e.occurrence.is_some() {
34858                // INSTR requires a position arg before occurrence: INSTR(str, substr, start, nth)
34859                // Default start position is 1
34860                self.write(", 1");
34861            }
34862            if let Some(occurrence) = &e.occurrence {
34863                self.write(", ");
34864                self.generate_expression(occurrence)?;
34865            }
34866            self.write(")");
34867        } else if matches!(
34868            self.config.dialect,
34869            Some(DialectType::MySQL)
34870                | Some(DialectType::SingleStore)
34871                | Some(DialectType::Doris)
34872                | Some(DialectType::StarRocks)
34873                | Some(DialectType::Hive)
34874                | Some(DialectType::Spark)
34875                | Some(DialectType::Databricks)
34876        ) {
34877            // LOCATE(substr, str[, position]) - substr first
34878            self.write_keyword("LOCATE");
34879            self.write("(");
34880            if let Some(substr) = &e.substr {
34881                self.generate_expression(substr)?;
34882                self.write(", ");
34883            }
34884            self.generate_expression(&e.this)?;
34885            if let Some(position) = &e.position {
34886                self.write(", ");
34887                self.generate_expression(position)?;
34888            }
34889            self.write(")");
34890        } else if matches!(self.config.dialect, Some(DialectType::TSQL)) {
34891            // CHARINDEX(substr, str[, position])
34892            self.write_keyword("CHARINDEX");
34893            self.write("(");
34894            if let Some(substr) = &e.substr {
34895                self.generate_expression(substr)?;
34896                self.write(", ");
34897            }
34898            self.generate_expression(&e.this)?;
34899            if let Some(position) = &e.position {
34900                self.write(", ");
34901                self.generate_expression(position)?;
34902            }
34903            self.write(")");
34904        } else if matches!(
34905            self.config.dialect,
34906            Some(DialectType::PostgreSQL)
34907                | Some(DialectType::Materialize)
34908                | Some(DialectType::RisingWave)
34909                | Some(DialectType::Redshift)
34910        ) {
34911            // POSITION(substr IN str) syntax
34912            self.write_keyword("POSITION");
34913            self.write("(");
34914            if let Some(substr) = &e.substr {
34915                self.generate_expression(substr)?;
34916                self.write(" IN ");
34917            }
34918            self.generate_expression(&e.this)?;
34919            self.write(")");
34920        } else {
34921            self.write_keyword("STRPOS");
34922            self.write("(");
34923            self.generate_expression(&e.this)?;
34924            if let Some(substr) = &e.substr {
34925                self.write(", ");
34926                self.generate_expression(substr)?;
34927            }
34928            if let Some(position) = &e.position {
34929                self.write(", ");
34930                self.generate_expression(position)?;
34931            }
34932            if let Some(occurrence) = &e.occurrence {
34933                self.write(", ");
34934                self.generate_expression(occurrence)?;
34935            }
34936            self.write(")");
34937        }
34938        Ok(())
34939    }
34940
34941    fn generate_str_to_date(&mut self, e: &StrToDate) -> Result<()> {
34942        match self.config.dialect {
34943            Some(DialectType::Spark) | Some(DialectType::Databricks) | Some(DialectType::Hive) => {
34944                // TO_DATE(this, java_format)
34945                self.write_keyword("TO_DATE");
34946                self.write("(");
34947                self.generate_expression(&e.this)?;
34948                if let Some(format) = &e.format {
34949                    self.write(", '");
34950                    self.write(&Self::strftime_to_java_format(format));
34951                    self.write("'");
34952                }
34953                self.write(")");
34954            }
34955            Some(DialectType::DuckDB) => {
34956                // CAST(STRPTIME(this, format) AS DATE)
34957                self.write_keyword("CAST");
34958                self.write("(");
34959                self.write_keyword("STRPTIME");
34960                self.write("(");
34961                self.generate_expression(&e.this)?;
34962                if let Some(format) = &e.format {
34963                    self.write(", '");
34964                    self.write(format);
34965                    self.write("'");
34966                }
34967                self.write(")");
34968                self.write_keyword(" AS ");
34969                self.write_keyword("DATE");
34970                self.write(")");
34971            }
34972            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift) => {
34973                // TO_DATE(this, pg_format)
34974                self.write_keyword("TO_DATE");
34975                self.write("(");
34976                self.generate_expression(&e.this)?;
34977                if let Some(format) = &e.format {
34978                    self.write(", '");
34979                    self.write(&Self::strftime_to_postgres_format(format));
34980                    self.write("'");
34981                }
34982                self.write(")");
34983            }
34984            Some(DialectType::BigQuery) => {
34985                // PARSE_DATE(format, this) - note: format comes first for BigQuery
34986                self.write_keyword("PARSE_DATE");
34987                self.write("(");
34988                if let Some(format) = &e.format {
34989                    self.write("'");
34990                    self.write(format);
34991                    self.write("'");
34992                    self.write(", ");
34993                }
34994                self.generate_expression(&e.this)?;
34995                self.write(")");
34996            }
34997            Some(DialectType::Teradata) => {
34998                // CAST(this AS DATE FORMAT 'teradata_fmt')
34999                self.write_keyword("CAST");
35000                self.write("(");
35001                self.generate_expression(&e.this)?;
35002                self.write_keyword(" AS ");
35003                self.write_keyword("DATE");
35004                if let Some(format) = &e.format {
35005                    self.write_keyword(" FORMAT ");
35006                    self.write("'");
35007                    self.write(&Self::strftime_to_teradata_format(format));
35008                    self.write("'");
35009                }
35010                self.write(")");
35011            }
35012            _ => {
35013                // STR_TO_DATE(this, format) - MySQL default
35014                self.write_keyword("STR_TO_DATE");
35015                self.write("(");
35016                self.generate_expression(&e.this)?;
35017                if let Some(format) = &e.format {
35018                    self.write(", '");
35019                    self.write(format);
35020                    self.write("'");
35021                }
35022                self.write(")");
35023            }
35024        }
35025        Ok(())
35026    }
35027
35028    /// Convert strftime format to Teradata date format (YYYY, DD, MM, etc.)
35029    fn strftime_to_teradata_format(fmt: &str) -> String {
35030        let mut result = String::with_capacity(fmt.len() * 2);
35031        let bytes = fmt.as_bytes();
35032        let len = bytes.len();
35033        let mut i = 0;
35034        while i < len {
35035            if bytes[i] == b'%' && i + 1 < len {
35036                let replacement = match bytes[i + 1] {
35037                    b'Y' => "YYYY",
35038                    b'y' => "YY",
35039                    b'm' => "MM",
35040                    b'B' => "MMMM",
35041                    b'b' => "MMM",
35042                    b'd' => "DD",
35043                    b'j' => "DDD",
35044                    b'H' => "HH",
35045                    b'M' => "MI",
35046                    b'S' => "SS",
35047                    b'f' => "SSSSSS",
35048                    b'A' => "EEEE",
35049                    b'a' => "EEE",
35050                    _ => {
35051                        result.push('%');
35052                        i += 1;
35053                        continue;
35054                    }
35055                };
35056                result.push_str(replacement);
35057                i += 2;
35058            } else {
35059                result.push(bytes[i] as char);
35060                i += 1;
35061            }
35062        }
35063        result
35064    }
35065
35066    /// Convert strftime format (%Y, %m, %d, etc.) to Java date format (yyyy, MM, dd, etc.)
35067    /// Public static version for use by other modules
35068    pub fn strftime_to_java_format_static(fmt: &str) -> String {
35069        Self::strftime_to_java_format(fmt)
35070    }
35071
35072    /// Convert strftime format (%Y, %m, %d, etc.) to Java date format (yyyy, MM, dd, etc.)
35073    fn strftime_to_java_format(fmt: &str) -> String {
35074        let mut result = String::with_capacity(fmt.len() * 2);
35075        let bytes = fmt.as_bytes();
35076        let len = bytes.len();
35077        let mut i = 0;
35078        while i < len {
35079            if bytes[i] == b'%' && i + 1 < len {
35080                // Check for non-padded variants (%-X)
35081                if bytes[i + 1] == b'-' && i + 2 < len {
35082                    let replacement = match bytes[i + 2] {
35083                        b'd' => "d",
35084                        b'm' => "M",
35085                        b'H' => "H",
35086                        b'M' => "m",
35087                        b'S' => "s",
35088                        _ => {
35089                            result.push('%');
35090                            i += 1;
35091                            continue;
35092                        }
35093                    };
35094                    result.push_str(replacement);
35095                    i += 3;
35096                } else {
35097                    let replacement = match bytes[i + 1] {
35098                        b'Y' => "yyyy",
35099                        b'y' => "yy",
35100                        b'm' => "MM",
35101                        b'B' => "MMMM",
35102                        b'b' => "MMM",
35103                        b'd' => "dd",
35104                        b'j' => "DDD",
35105                        b'H' => "HH",
35106                        b'M' => "mm",
35107                        b'S' => "ss",
35108                        b'f' => "SSSSSS",
35109                        b'A' => "EEEE",
35110                        b'a' => "EEE",
35111                        _ => {
35112                            result.push('%');
35113                            i += 1;
35114                            continue;
35115                        }
35116                    };
35117                    result.push_str(replacement);
35118                    i += 2;
35119                }
35120            } else {
35121                result.push(bytes[i] as char);
35122                i += 1;
35123            }
35124        }
35125        result
35126    }
35127
35128    /// Convert strftime format (%Y, %m, %d, etc.) to .NET date format for TSQL FORMAT()
35129    /// Similar to Java but uses ffffff for microseconds instead of SSSSSS
35130    fn strftime_to_tsql_format(fmt: &str) -> String {
35131        let mut result = String::with_capacity(fmt.len() * 2);
35132        let bytes = fmt.as_bytes();
35133        let len = bytes.len();
35134        let mut i = 0;
35135        while i < len {
35136            if bytes[i] == b'%' && i + 1 < len {
35137                // Check for non-padded variants (%-X)
35138                if bytes[i + 1] == b'-' && i + 2 < len {
35139                    let replacement = match bytes[i + 2] {
35140                        b'd' => "d",
35141                        b'm' => "M",
35142                        b'H' => "H",
35143                        b'M' => "m",
35144                        b'S' => "s",
35145                        _ => {
35146                            result.push('%');
35147                            i += 1;
35148                            continue;
35149                        }
35150                    };
35151                    result.push_str(replacement);
35152                    i += 3;
35153                } else {
35154                    let replacement = match bytes[i + 1] {
35155                        b'Y' => "yyyy",
35156                        b'y' => "yy",
35157                        b'm' => "MM",
35158                        b'B' => "MMMM",
35159                        b'b' => "MMM",
35160                        b'd' => "dd",
35161                        b'j' => "DDD",
35162                        b'H' => "HH",
35163                        b'M' => "mm",
35164                        b'S' => "ss",
35165                        b'f' => "ffffff",
35166                        b'A' => "dddd",
35167                        b'a' => "ddd",
35168                        _ => {
35169                            result.push('%');
35170                            i += 1;
35171                            continue;
35172                        }
35173                    };
35174                    result.push_str(replacement);
35175                    i += 2;
35176                }
35177            } else {
35178                result.push(bytes[i] as char);
35179                i += 1;
35180            }
35181        }
35182        result
35183    }
35184
35185    /// Decompose a JSON path string like "$.y[0].z" into individual parts: ["y", "0", "z"]
35186    /// This is used for PostgreSQL/Redshift JSON_EXTRACT_PATH / JSON_EXTRACT_PATH_TEXT
35187    fn decompose_json_path(path: &str) -> Vec<String> {
35188        let mut parts = Vec::new();
35189        // Strip leading $ and optional .
35190        let path = if path.starts_with("$.") {
35191            &path[2..]
35192        } else if path.starts_with('$') {
35193            &path[1..]
35194        } else {
35195            path
35196        };
35197        if path.is_empty() {
35198            return parts;
35199        }
35200        let mut current = String::new();
35201        let chars: Vec<char> = path.chars().collect();
35202        let mut i = 0;
35203        while i < chars.len() {
35204            match chars[i] {
35205                '.' => {
35206                    if !current.is_empty() {
35207                        parts.push(current.clone());
35208                        current.clear();
35209                    }
35210                    i += 1;
35211                }
35212                '[' => {
35213                    if !current.is_empty() {
35214                        parts.push(current.clone());
35215                        current.clear();
35216                    }
35217                    i += 1;
35218                    // Read the content inside brackets
35219                    let mut bracket_content = String::new();
35220                    while i < chars.len() && chars[i] != ']' {
35221                        // Skip quotes inside brackets
35222                        if chars[i] == '"' || chars[i] == '\'' {
35223                            let quote = chars[i];
35224                            i += 1;
35225                            while i < chars.len() && chars[i] != quote {
35226                                bracket_content.push(chars[i]);
35227                                i += 1;
35228                            }
35229                            if i < chars.len() {
35230                                i += 1;
35231                            } // skip closing quote
35232                        } else {
35233                            bracket_content.push(chars[i]);
35234                            i += 1;
35235                        }
35236                    }
35237                    if i < chars.len() {
35238                        i += 1;
35239                    } // skip ]
35240                      // Skip wildcard [*] - don't add as a part
35241                    if bracket_content != "*" {
35242                        parts.push(bracket_content);
35243                    }
35244                }
35245                _ => {
35246                    current.push(chars[i]);
35247                    i += 1;
35248                }
35249            }
35250        }
35251        if !current.is_empty() {
35252            parts.push(current);
35253        }
35254        parts
35255    }
35256
35257    /// Convert strftime format to PostgreSQL date format (YYYY, MM, DD, etc.)
35258    fn strftime_to_postgres_format(fmt: &str) -> String {
35259        let mut result = String::with_capacity(fmt.len() * 2);
35260        let bytes = fmt.as_bytes();
35261        let len = bytes.len();
35262        let mut i = 0;
35263        while i < len {
35264            if bytes[i] == b'%' && i + 1 < len {
35265                // Check for non-padded variants (%-X)
35266                if bytes[i + 1] == b'-' && i + 2 < len {
35267                    let replacement = match bytes[i + 2] {
35268                        b'd' => "FMDD",
35269                        b'm' => "FMMM",
35270                        b'H' => "FMHH24",
35271                        b'M' => "FMMI",
35272                        b'S' => "FMSS",
35273                        _ => {
35274                            result.push('%');
35275                            i += 1;
35276                            continue;
35277                        }
35278                    };
35279                    result.push_str(replacement);
35280                    i += 3;
35281                } else {
35282                    let replacement = match bytes[i + 1] {
35283                        b'Y' => "YYYY",
35284                        b'y' => "YY",
35285                        b'm' => "MM",
35286                        b'B' => "Month",
35287                        b'b' => "Mon",
35288                        b'd' => "DD",
35289                        b'j' => "DDD",
35290                        b'H' => "HH24",
35291                        b'M' => "MI",
35292                        b'S' => "SS",
35293                        b'f' => "US",
35294                        b'A' => "Day",
35295                        b'a' => "Dy",
35296                        _ => {
35297                            result.push('%');
35298                            i += 1;
35299                            continue;
35300                        }
35301                    };
35302                    result.push_str(replacement);
35303                    i += 2;
35304                }
35305            } else {
35306                result.push(bytes[i] as char);
35307                i += 1;
35308            }
35309        }
35310        result
35311    }
35312
35313    /// Convert strftime format to Snowflake date format (yyyy, mm, DD, etc.)
35314    fn strftime_to_snowflake_format(fmt: &str) -> String {
35315        let mut result = String::with_capacity(fmt.len() * 2);
35316        let bytes = fmt.as_bytes();
35317        let len = bytes.len();
35318        let mut i = 0;
35319        while i < len {
35320            if bytes[i] == b'%' && i + 1 < len {
35321                // Check for non-padded variants (%-X)
35322                if bytes[i + 1] == b'-' && i + 2 < len {
35323                    let replacement = match bytes[i + 2] {
35324                        b'd' => "dd",
35325                        b'm' => "mm",
35326                        _ => {
35327                            result.push('%');
35328                            i += 1;
35329                            continue;
35330                        }
35331                    };
35332                    result.push_str(replacement);
35333                    i += 3;
35334                } else {
35335                    let replacement = match bytes[i + 1] {
35336                        b'Y' => "yyyy",
35337                        b'y' => "yy",
35338                        b'm' => "mm",
35339                        b'd' => "DD",
35340                        b'H' => "hh24",
35341                        b'M' => "mi",
35342                        b'S' => "ss",
35343                        b'f' => "ff",
35344                        _ => {
35345                            result.push('%');
35346                            i += 1;
35347                            continue;
35348                        }
35349                    };
35350                    result.push_str(replacement);
35351                    i += 2;
35352                }
35353            } else {
35354                result.push(bytes[i] as char);
35355                i += 1;
35356            }
35357        }
35358        result
35359    }
35360
35361    fn generate_str_to_map(&mut self, e: &StrToMap) -> Result<()> {
35362        // STR_TO_MAP(this, pair_delim, key_value_delim)
35363        self.write_keyword("STR_TO_MAP");
35364        self.write("(");
35365        self.generate_expression(&e.this)?;
35366        // Spark/Hive: STR_TO_MAP needs explicit default delimiters
35367        let needs_defaults = matches!(
35368            self.config.dialect,
35369            Some(DialectType::Spark) | Some(DialectType::Hive) | Some(DialectType::Databricks)
35370        );
35371        if let Some(pair_delim) = &e.pair_delim {
35372            self.write(", ");
35373            self.generate_expression(pair_delim)?;
35374        } else if needs_defaults {
35375            self.write(", ','");
35376        }
35377        if let Some(key_value_delim) = &e.key_value_delim {
35378            self.write(", ");
35379            self.generate_expression(key_value_delim)?;
35380        } else if needs_defaults {
35381            self.write(", ':'");
35382        }
35383        self.write(")");
35384        Ok(())
35385    }
35386
35387    fn generate_str_to_time(&mut self, e: &StrToTime) -> Result<()> {
35388        // Detect format style: strftime (starts with %) vs Snowflake/Java
35389        let is_strftime = e.format.contains('%');
35390        // Helper: get strftime format from whatever style is stored
35391        let to_strftime = |f: &str| -> String {
35392            if is_strftime {
35393                f.to_string()
35394            } else {
35395                Self::snowflake_format_to_strftime(f)
35396            }
35397        };
35398        // Helper: get Java format
35399        let to_java = |f: &str| -> String {
35400            if is_strftime {
35401                Self::strftime_to_java_format(f)
35402            } else {
35403                Self::snowflake_format_to_spark(f)
35404            }
35405        };
35406        // Helper: get PG format
35407        let to_pg = |f: &str| -> String {
35408            if is_strftime {
35409                Self::strftime_to_postgres_format(f)
35410            } else {
35411                Self::convert_strptime_to_postgres_format(f)
35412            }
35413        };
35414
35415        match self.config.dialect {
35416            Some(DialectType::Exasol) => {
35417                self.write_keyword("TO_DATE");
35418                self.write("(");
35419                self.generate_expression(&e.this)?;
35420                self.write(", '");
35421                self.write(&Self::convert_strptime_to_exasol_format(&e.format));
35422                self.write("'");
35423                self.write(")");
35424            }
35425            Some(DialectType::BigQuery) => {
35426                // BigQuery: PARSE_TIMESTAMP(format, value) - note swapped args
35427                let fmt = to_strftime(&e.format);
35428                // BigQuery normalizes: %Y-%m-%d -> %F, %H:%M:%S -> %T
35429                let fmt = fmt.replace("%Y-%m-%d", "%F").replace("%H:%M:%S", "%T");
35430                self.write_keyword("PARSE_TIMESTAMP");
35431                self.write("('");
35432                self.write(&fmt);
35433                self.write("', ");
35434                self.generate_expression(&e.this)?;
35435                self.write(")");
35436            }
35437            Some(DialectType::Hive) => {
35438                // Hive: CAST(x AS TIMESTAMP) for simple date formats
35439                // Check both the raw format and the converted format (in case it's already Java)
35440                let java_fmt = to_java(&e.format);
35441                if java_fmt == "yyyy-MM-dd HH:mm:ss"
35442                    || java_fmt == "yyyy-MM-dd"
35443                    || e.format == "yyyy-MM-dd HH:mm:ss"
35444                    || e.format == "yyyy-MM-dd"
35445                {
35446                    self.write_keyword("CAST");
35447                    self.write("(");
35448                    self.generate_expression(&e.this)?;
35449                    self.write(" ");
35450                    self.write_keyword("AS TIMESTAMP");
35451                    self.write(")");
35452                } else {
35453                    // CAST(FROM_UNIXTIME(UNIX_TIMESTAMP(x, java_fmt)) AS TIMESTAMP)
35454                    self.write_keyword("CAST");
35455                    self.write("(");
35456                    self.write_keyword("FROM_UNIXTIME");
35457                    self.write("(");
35458                    self.write_keyword("UNIX_TIMESTAMP");
35459                    self.write("(");
35460                    self.generate_expression(&e.this)?;
35461                    self.write(", '");
35462                    self.write(&java_fmt);
35463                    self.write("')");
35464                    self.write(") ");
35465                    self.write_keyword("AS TIMESTAMP");
35466                    self.write(")");
35467                }
35468            }
35469            Some(DialectType::Spark) | Some(DialectType::Databricks) => {
35470                // Spark: TO_TIMESTAMP(value, java_format)
35471                let java_fmt = to_java(&e.format);
35472                self.write_keyword("TO_TIMESTAMP");
35473                self.write("(");
35474                self.generate_expression(&e.this)?;
35475                self.write(", '");
35476                self.write(&java_fmt);
35477                self.write("')");
35478            }
35479            Some(DialectType::MySQL) => {
35480                // MySQL: STR_TO_DATE(value, format)
35481                let mut fmt = to_strftime(&e.format);
35482                // MySQL uses %e for non-padded day, %T for %H:%M:%S
35483                fmt = fmt.replace("%-d", "%e");
35484                fmt = fmt.replace("%-m", "%c");
35485                fmt = fmt.replace("%H:%M:%S", "%T");
35486                self.write_keyword("STR_TO_DATE");
35487                self.write("(");
35488                self.generate_expression(&e.this)?;
35489                self.write(", '");
35490                self.write(&fmt);
35491                self.write("')");
35492            }
35493            Some(DialectType::Drill) => {
35494                // Drill: TO_TIMESTAMP(value, java_format) with T quoted in single quotes
35495                let java_fmt = to_java(&e.format);
35496                // Drill quotes literal T character: T -> ''T'' (double-quoted within SQL string literal)
35497                let java_fmt = java_fmt.replace('T', "''T''");
35498                self.write_keyword("TO_TIMESTAMP");
35499                self.write("(");
35500                self.generate_expression(&e.this)?;
35501                self.write(", '");
35502                self.write(&java_fmt);
35503                self.write("')");
35504            }
35505            Some(DialectType::Presto) | Some(DialectType::Trino) | Some(DialectType::Athena) => {
35506                // Presto: DATE_PARSE(value, strftime_format)
35507                let mut fmt = to_strftime(&e.format);
35508                // Presto uses %e for non-padded day, %T for %H:%M:%S
35509                fmt = fmt.replace("%-d", "%e");
35510                fmt = fmt.replace("%-m", "%c");
35511                fmt = fmt.replace("%H:%M:%S", "%T");
35512                self.write_keyword("DATE_PARSE");
35513                self.write("(");
35514                self.generate_expression(&e.this)?;
35515                self.write(", '");
35516                self.write(&fmt);
35517                self.write("')");
35518            }
35519            Some(DialectType::DuckDB) => {
35520                // DuckDB: STRPTIME(value, strftime_format)
35521                let fmt = to_strftime(&e.format);
35522                self.write_keyword("STRPTIME");
35523                self.write("(");
35524                self.generate_expression(&e.this)?;
35525                self.write(", '");
35526                self.write(&fmt);
35527                self.write("')");
35528            }
35529            Some(DialectType::PostgreSQL)
35530            | Some(DialectType::Redshift)
35531            | Some(DialectType::Materialize) => {
35532                // PostgreSQL/Redshift/Materialize: TO_TIMESTAMP(value, pg_format)
35533                let pg_fmt = to_pg(&e.format);
35534                self.write_keyword("TO_TIMESTAMP");
35535                self.write("(");
35536                self.generate_expression(&e.this)?;
35537                self.write(", '");
35538                self.write(&pg_fmt);
35539                self.write("')");
35540            }
35541            Some(DialectType::Oracle) => {
35542                // Oracle: TO_TIMESTAMP(value, pg_format)
35543                let pg_fmt = to_pg(&e.format);
35544                self.write_keyword("TO_TIMESTAMP");
35545                self.write("(");
35546                self.generate_expression(&e.this)?;
35547                self.write(", '");
35548                self.write(&pg_fmt);
35549                self.write("')");
35550            }
35551            Some(DialectType::Snowflake) => {
35552                // Snowflake: TO_TIMESTAMP(value, format) - native format
35553                self.write_keyword("TO_TIMESTAMP");
35554                self.write("(");
35555                self.generate_expression(&e.this)?;
35556                self.write(", '");
35557                self.write(&e.format);
35558                self.write("')");
35559            }
35560            _ => {
35561                // Default: STR_TO_TIME(this, format)
35562                self.write_keyword("STR_TO_TIME");
35563                self.write("(");
35564                self.generate_expression(&e.this)?;
35565                self.write(", '");
35566                self.write(&e.format);
35567                self.write("'");
35568                self.write(")");
35569            }
35570        }
35571        Ok(())
35572    }
35573
35574    /// Convert Snowflake normalized format to strftime-style (%Y, %m, etc.)
35575    fn snowflake_format_to_strftime(format: &str) -> String {
35576        let mut result = String::new();
35577        let chars: Vec<char> = format.chars().collect();
35578        let mut i = 0;
35579        while i < chars.len() {
35580            let remaining = &format[i..];
35581            if remaining.starts_with("yyyy") {
35582                result.push_str("%Y");
35583                i += 4;
35584            } else if remaining.starts_with("yy") {
35585                result.push_str("%y");
35586                i += 2;
35587            } else if remaining.starts_with("mmmm") {
35588                result.push_str("%B"); // full month name
35589                i += 4;
35590            } else if remaining.starts_with("mon") {
35591                result.push_str("%b"); // abbreviated month
35592                i += 3;
35593            } else if remaining.starts_with("mm") {
35594                result.push_str("%m");
35595                i += 2;
35596            } else if remaining.starts_with("DD") {
35597                result.push_str("%d");
35598                i += 2;
35599            } else if remaining.starts_with("dy") {
35600                result.push_str("%a"); // abbreviated day name
35601                i += 2;
35602            } else if remaining.starts_with("hh24") {
35603                result.push_str("%H");
35604                i += 4;
35605            } else if remaining.starts_with("hh12") {
35606                result.push_str("%I");
35607                i += 4;
35608            } else if remaining.starts_with("hh") {
35609                result.push_str("%H");
35610                i += 2;
35611            } else if remaining.starts_with("mi") {
35612                result.push_str("%M");
35613                i += 2;
35614            } else if remaining.starts_with("ss") {
35615                result.push_str("%S");
35616                i += 2;
35617            } else if remaining.starts_with("ff") {
35618                // Fractional seconds
35619                result.push_str("%f");
35620                i += 2;
35621                // Skip digits after ff (ff3, ff6, ff9)
35622                while i < chars.len() && chars[i].is_ascii_digit() {
35623                    i += 1;
35624                }
35625            } else if remaining.starts_with("am") || remaining.starts_with("pm") {
35626                result.push_str("%p");
35627                i += 2;
35628            } else if remaining.starts_with("tz") {
35629                result.push_str("%Z");
35630                i += 2;
35631            } else {
35632                result.push(chars[i]);
35633                i += 1;
35634            }
35635        }
35636        result
35637    }
35638
35639    /// Convert Snowflake normalized format to Spark format (Java-style)
35640    fn snowflake_format_to_spark(format: &str) -> String {
35641        let mut result = String::new();
35642        let chars: Vec<char> = format.chars().collect();
35643        let mut i = 0;
35644        while i < chars.len() {
35645            let remaining = &format[i..];
35646            if remaining.starts_with("yyyy") {
35647                result.push_str("yyyy");
35648                i += 4;
35649            } else if remaining.starts_with("yy") {
35650                result.push_str("yy");
35651                i += 2;
35652            } else if remaining.starts_with("mmmm") {
35653                result.push_str("MMMM"); // full month name
35654                i += 4;
35655            } else if remaining.starts_with("mon") {
35656                result.push_str("MMM"); // abbreviated month
35657                i += 3;
35658            } else if remaining.starts_with("mm") {
35659                result.push_str("MM");
35660                i += 2;
35661            } else if remaining.starts_with("DD") {
35662                result.push_str("dd");
35663                i += 2;
35664            } else if remaining.starts_with("dy") {
35665                result.push_str("EEE"); // abbreviated day name
35666                i += 2;
35667            } else if remaining.starts_with("hh24") {
35668                result.push_str("HH");
35669                i += 4;
35670            } else if remaining.starts_with("hh12") {
35671                result.push_str("hh");
35672                i += 4;
35673            } else if remaining.starts_with("hh") {
35674                result.push_str("HH");
35675                i += 2;
35676            } else if remaining.starts_with("mi") {
35677                result.push_str("mm");
35678                i += 2;
35679            } else if remaining.starts_with("ss") {
35680                result.push_str("ss");
35681                i += 2;
35682            } else if remaining.starts_with("ff") {
35683                result.push_str("SSS"); // milliseconds
35684                i += 2;
35685                // Skip digits after ff
35686                while i < chars.len() && chars[i].is_ascii_digit() {
35687                    i += 1;
35688                }
35689            } else if remaining.starts_with("am") || remaining.starts_with("pm") {
35690                result.push_str("a");
35691                i += 2;
35692            } else if remaining.starts_with("tz") {
35693                result.push_str("z");
35694                i += 2;
35695            } else {
35696                result.push(chars[i]);
35697                i += 1;
35698            }
35699        }
35700        result
35701    }
35702
35703    fn generate_str_to_unix(&mut self, e: &StrToUnix) -> Result<()> {
35704        match self.config.dialect {
35705            Some(DialectType::DuckDB) => {
35706                // DuckDB: EPOCH(STRPTIME(value, format))
35707                self.write_keyword("EPOCH");
35708                self.write("(");
35709                self.write_keyword("STRPTIME");
35710                self.write("(");
35711                if let Some(this) = &e.this {
35712                    self.generate_expression(this)?;
35713                }
35714                if let Some(format) = &e.format {
35715                    self.write(", '");
35716                    self.write(format);
35717                    self.write("'");
35718                }
35719                self.write("))");
35720            }
35721            Some(DialectType::Hive) => {
35722                // Hive: UNIX_TIMESTAMP(value, java_format) - convert C fmt to Java
35723                self.write_keyword("UNIX_TIMESTAMP");
35724                self.write("(");
35725                if let Some(this) = &e.this {
35726                    self.generate_expression(this)?;
35727                }
35728                if let Some(format) = &e.format {
35729                    let java_fmt = Self::strftime_to_java_format(format);
35730                    if java_fmt != "yyyy-MM-dd HH:mm:ss" {
35731                        self.write(", '");
35732                        self.write(&java_fmt);
35733                        self.write("'");
35734                    }
35735                }
35736                self.write(")");
35737            }
35738            Some(DialectType::Doris) | Some(DialectType::StarRocks) => {
35739                // Doris/StarRocks: UNIX_TIMESTAMP(value, format) - C format
35740                self.write_keyword("UNIX_TIMESTAMP");
35741                self.write("(");
35742                if let Some(this) = &e.this {
35743                    self.generate_expression(this)?;
35744                }
35745                if let Some(format) = &e.format {
35746                    self.write(", '");
35747                    self.write(format);
35748                    self.write("'");
35749                }
35750                self.write(")");
35751            }
35752            Some(DialectType::Presto) | Some(DialectType::Trino) => {
35753                // Presto: TO_UNIXTIME(COALESCE(TRY(DATE_PARSE(CAST(value AS VARCHAR), c_format)),
35754                //   PARSE_DATETIME(DATE_FORMAT(CAST(value AS TIMESTAMP), c_format), java_format)))
35755                let c_fmt = e.format.as_deref().unwrap_or("%Y-%m-%d %T");
35756                let java_fmt = Self::strftime_to_java_format(c_fmt);
35757                self.write_keyword("TO_UNIXTIME");
35758                self.write("(");
35759                self.write_keyword("COALESCE");
35760                self.write("(");
35761                self.write_keyword("TRY");
35762                self.write("(");
35763                self.write_keyword("DATE_PARSE");
35764                self.write("(");
35765                self.write_keyword("CAST");
35766                self.write("(");
35767                if let Some(this) = &e.this {
35768                    self.generate_expression(this)?;
35769                }
35770                self.write(" ");
35771                self.write_keyword("AS VARCHAR");
35772                self.write("), '");
35773                self.write(c_fmt);
35774                self.write("')), ");
35775                self.write_keyword("PARSE_DATETIME");
35776                self.write("(");
35777                self.write_keyword("DATE_FORMAT");
35778                self.write("(");
35779                self.write_keyword("CAST");
35780                self.write("(");
35781                if let Some(this) = &e.this {
35782                    self.generate_expression(this)?;
35783                }
35784                self.write(" ");
35785                self.write_keyword("AS TIMESTAMP");
35786                self.write("), '");
35787                self.write(c_fmt);
35788                self.write("'), '");
35789                self.write(&java_fmt);
35790                self.write("')))");
35791            }
35792            Some(DialectType::Spark) | Some(DialectType::Databricks) => {
35793                // Spark: UNIX_TIMESTAMP(value, java_format)
35794                self.write_keyword("UNIX_TIMESTAMP");
35795                self.write("(");
35796                if let Some(this) = &e.this {
35797                    self.generate_expression(this)?;
35798                }
35799                if let Some(format) = &e.format {
35800                    let java_fmt = Self::strftime_to_java_format(format);
35801                    self.write(", '");
35802                    self.write(&java_fmt);
35803                    self.write("'");
35804                }
35805                self.write(")");
35806            }
35807            _ => {
35808                // Default: STR_TO_UNIX(this, format)
35809                self.write_keyword("STR_TO_UNIX");
35810                self.write("(");
35811                if let Some(this) = &e.this {
35812                    self.generate_expression(this)?;
35813                }
35814                if let Some(format) = &e.format {
35815                    self.write(", '");
35816                    self.write(format);
35817                    self.write("'");
35818                }
35819                self.write(")");
35820            }
35821        }
35822        Ok(())
35823    }
35824
35825    fn generate_string_to_array(&mut self, e: &StringToArray) -> Result<()> {
35826        // STRING_TO_ARRAY(this, delimiter, null_string)
35827        self.write_keyword("STRING_TO_ARRAY");
35828        self.write("(");
35829        self.generate_expression(&e.this)?;
35830        if let Some(expression) = &e.expression {
35831            self.write(", ");
35832            self.generate_expression(expression)?;
35833        }
35834        if let Some(null_val) = &e.null {
35835            self.write(", ");
35836            self.generate_expression(null_val)?;
35837        }
35838        self.write(")");
35839        Ok(())
35840    }
35841
35842    fn generate_struct(&mut self, e: &Struct) -> Result<()> {
35843        if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
35844            // Snowflake: OBJECT_CONSTRUCT('key', value, 'key', value, ...)
35845            self.write_keyword("OBJECT_CONSTRUCT");
35846            self.write("(");
35847            for (i, (name, expr)) in e.fields.iter().enumerate() {
35848                if i > 0 {
35849                    self.write(", ");
35850                }
35851                if let Some(name) = name {
35852                    self.write("'");
35853                    self.write(name);
35854                    self.write("'");
35855                    self.write(", ");
35856                } else {
35857                    self.write("'_");
35858                    self.write(&i.to_string());
35859                    self.write("'");
35860                    self.write(", ");
35861                }
35862                self.generate_expression(expr)?;
35863            }
35864            self.write(")");
35865        } else if self.config.struct_curly_brace_notation {
35866            // DuckDB-style: {'key': value, ...}
35867            self.write("{");
35868            for (i, (name, expr)) in e.fields.iter().enumerate() {
35869                if i > 0 {
35870                    self.write(", ");
35871                }
35872                if let Some(name) = name {
35873                    // Quote the key as a string literal
35874                    self.write("'");
35875                    self.write(name);
35876                    self.write("'");
35877                    self.write(": ");
35878                } else {
35879                    // Unnamed field: use positional key
35880                    self.write("'_");
35881                    self.write(&i.to_string());
35882                    self.write("'");
35883                    self.write(": ");
35884                }
35885                self.generate_expression(expr)?;
35886            }
35887            self.write("}");
35888        } else {
35889            // Standard SQL struct notation
35890            // BigQuery/Spark/Databricks use: STRUCT(value AS name, ...)
35891            // Others (Presto etc.) use: STRUCT(name AS value, ...) or ROW(value, ...)
35892            let value_as_name = matches!(
35893                self.config.dialect,
35894                Some(DialectType::BigQuery)
35895                    | Some(DialectType::Spark)
35896                    | Some(DialectType::Databricks)
35897                    | Some(DialectType::Hive)
35898            );
35899            self.write_keyword("STRUCT");
35900            self.write("(");
35901            for (i, (name, expr)) in e.fields.iter().enumerate() {
35902                if i > 0 {
35903                    self.write(", ");
35904                }
35905                if let Some(name) = name {
35906                    if value_as_name {
35907                        // STRUCT(value AS name)
35908                        self.generate_expression(expr)?;
35909                        self.write_space();
35910                        self.write_keyword("AS");
35911                        self.write_space();
35912                        // Quote name if it contains spaces or special chars
35913                        let needs_quoting = name.contains(' ') || name.contains('-');
35914                        if needs_quoting {
35915                            if matches!(
35916                                self.config.dialect,
35917                                Some(DialectType::Spark)
35918                                    | Some(DialectType::Databricks)
35919                                    | Some(DialectType::Hive)
35920                            ) {
35921                                self.write("`");
35922                                self.write(name);
35923                                self.write("`");
35924                            } else {
35925                                self.write(name);
35926                            }
35927                        } else {
35928                            self.write(name);
35929                        }
35930                    } else {
35931                        // STRUCT(name AS value)
35932                        self.write(name);
35933                        self.write_space();
35934                        self.write_keyword("AS");
35935                        self.write_space();
35936                        self.generate_expression(expr)?;
35937                    }
35938                } else {
35939                    self.generate_expression(expr)?;
35940                }
35941            }
35942            self.write(")");
35943        }
35944        Ok(())
35945    }
35946
35947    fn generate_stuff(&mut self, e: &Stuff) -> Result<()> {
35948        // STUFF(this, start, length, expression)
35949        self.write_keyword("STUFF");
35950        self.write("(");
35951        self.generate_expression(&e.this)?;
35952        if let Some(start) = &e.start {
35953            self.write(", ");
35954            self.generate_expression(start)?;
35955        }
35956        if let Some(length) = e.length {
35957            self.write(", ");
35958            self.write(&length.to_string());
35959        }
35960        self.write(", ");
35961        self.generate_expression(&e.expression)?;
35962        self.write(")");
35963        Ok(())
35964    }
35965
35966    fn generate_substring_index(&mut self, e: &SubstringIndex) -> Result<()> {
35967        // SUBSTRING_INDEX(this, delimiter, count)
35968        self.write_keyword("SUBSTRING_INDEX");
35969        self.write("(");
35970        self.generate_expression(&e.this)?;
35971        if let Some(delimiter) = &e.delimiter {
35972            self.write(", ");
35973            self.generate_expression(delimiter)?;
35974        }
35975        if let Some(count) = &e.count {
35976            self.write(", ");
35977            self.generate_expression(count)?;
35978        }
35979        self.write(")");
35980        Ok(())
35981    }
35982
35983    fn generate_summarize(&mut self, e: &Summarize) -> Result<()> {
35984        // SUMMARIZE [TABLE] this
35985        self.write_keyword("SUMMARIZE");
35986        if e.table.is_some() {
35987            self.write_space();
35988            self.write_keyword("TABLE");
35989        }
35990        self.write_space();
35991        self.generate_expression(&e.this)?;
35992        Ok(())
35993    }
35994
35995    fn generate_systimestamp(&mut self, _e: &Systimestamp) -> Result<()> {
35996        // SYSTIMESTAMP
35997        self.write_keyword("SYSTIMESTAMP");
35998        Ok(())
35999    }
36000
36001    fn generate_table_alias(&mut self, e: &TableAlias) -> Result<()> {
36002        // alias (columns...)
36003        if let Some(this) = &e.this {
36004            self.generate_expression(this)?;
36005        }
36006        if !e.columns.is_empty() {
36007            self.write("(");
36008            for (i, col) in e.columns.iter().enumerate() {
36009                if i > 0 {
36010                    self.write(", ");
36011                }
36012                self.generate_expression(col)?;
36013            }
36014            self.write(")");
36015        }
36016        Ok(())
36017    }
36018
36019    fn generate_table_from_rows(&mut self, e: &TableFromRows) -> Result<()> {
36020        // TABLE(this) [AS alias]
36021        self.write_keyword("TABLE");
36022        self.write("(");
36023        self.generate_expression(&e.this)?;
36024        self.write(")");
36025        if let Some(alias) = &e.alias {
36026            self.write_space();
36027            self.write_keyword("AS");
36028            self.write_space();
36029            self.write(alias);
36030        }
36031        Ok(())
36032    }
36033
36034    fn generate_rows_from(&mut self, e: &RowsFrom) -> Result<()> {
36035        // ROWS FROM (func1(...) AS alias1(...), func2(...) AS alias2(...)) [WITH ORDINALITY] [AS alias(...)]
36036        self.write_keyword("ROWS FROM");
36037        self.write(" (");
36038        for (i, expr) in e.expressions.iter().enumerate() {
36039            if i > 0 {
36040                self.write(", ");
36041            }
36042            // Each expression is either:
36043            // - A plain function (no alias)
36044            // - A Tuple(function, TableAlias) for: FUNC() AS alias(col type, ...)
36045            match expr {
36046                Expression::Tuple(tuple) if tuple.expressions.len() == 2 => {
36047                    // First element is the function, second is the TableAlias
36048                    self.generate_expression(&tuple.expressions[0])?;
36049                    self.write_space();
36050                    self.write_keyword("AS");
36051                    self.write_space();
36052                    self.generate_expression(&tuple.expressions[1])?;
36053                }
36054                _ => {
36055                    self.generate_expression(expr)?;
36056                }
36057            }
36058        }
36059        self.write(")");
36060        if e.ordinality {
36061            self.write_space();
36062            self.write_keyword("WITH ORDINALITY");
36063        }
36064        if let Some(alias) = &e.alias {
36065            self.write_space();
36066            self.write_keyword("AS");
36067            self.write_space();
36068            self.generate_expression(alias)?;
36069        }
36070        Ok(())
36071    }
36072
36073    fn generate_table_sample(&mut self, e: &TableSample) -> Result<()> {
36074        use crate::dialects::DialectType;
36075
36076        // New wrapper pattern: expression + Sample struct
36077        if let (Some(this), Some(sample)) = (&e.this, &e.sample) {
36078            // For alias_post_tablesample dialects (Spark, Hive, Oracle): output base expr, TABLESAMPLE, then alias
36079            if self.config.alias_post_tablesample {
36080                // Handle Subquery with alias and Alias wrapper
36081                if let Expression::Subquery(ref s) = **this {
36082                    if let Some(ref alias) = s.alias {
36083                        // Create a clone without alias for output
36084                        let mut subquery_no_alias = (**s).clone();
36085                        subquery_no_alias.alias = None;
36086                        subquery_no_alias.column_aliases = Vec::new();
36087                        self.generate_expression(&Expression::Subquery(Box::new(
36088                            subquery_no_alias,
36089                        )))?;
36090                        self.write_space();
36091                        self.write_keyword(self.config.tablesample_keywords);
36092                        self.generate_sample_body(sample)?;
36093                        if let Some(ref seed) = sample.seed {
36094                            self.write_space();
36095                            let use_seed = sample.use_seed_keyword
36096                                && !matches!(
36097                                    self.config.dialect,
36098                                    Some(crate::dialects::DialectType::Databricks)
36099                                        | Some(crate::dialects::DialectType::Spark)
36100                                );
36101                            if use_seed {
36102                                self.write_keyword("SEED");
36103                            } else {
36104                                self.write_keyword("REPEATABLE");
36105                            }
36106                            self.write(" (");
36107                            self.generate_expression(seed)?;
36108                            self.write(")");
36109                        }
36110                        self.write_space();
36111                        self.write_keyword("AS");
36112                        self.write_space();
36113                        self.generate_identifier(alias)?;
36114                        return Ok(());
36115                    }
36116                } else if let Expression::Alias(ref a) = **this {
36117                    // Output the base expression without alias
36118                    self.generate_expression(&a.this)?;
36119                    self.write_space();
36120                    self.write_keyword(self.config.tablesample_keywords);
36121                    self.generate_sample_body(sample)?;
36122                    if let Some(ref seed) = sample.seed {
36123                        self.write_space();
36124                        let use_seed = sample.use_seed_keyword
36125                            && !matches!(
36126                                self.config.dialect,
36127                                Some(crate::dialects::DialectType::Databricks)
36128                                    | Some(crate::dialects::DialectType::Spark)
36129                            );
36130                        if use_seed {
36131                            self.write_keyword("SEED");
36132                        } else {
36133                            self.write_keyword("REPEATABLE");
36134                        }
36135                        self.write(" (");
36136                        self.generate_expression(seed)?;
36137                        self.write(")");
36138                    }
36139                    // Output alias after TABLESAMPLE
36140                    self.write_space();
36141                    self.write_keyword("AS");
36142                    self.write_space();
36143                    self.generate_identifier(&a.alias)?;
36144                    return Ok(());
36145                }
36146            }
36147            // Default: generate wrapped expression first, then TABLESAMPLE
36148            self.generate_expression(this)?;
36149            self.write_space();
36150            self.write_keyword(self.config.tablesample_keywords);
36151            self.generate_sample_body(sample)?;
36152            // Seed for table-level sample
36153            if let Some(ref seed) = sample.seed {
36154                self.write_space();
36155                // Databricks uses REPEATABLE, not SEED
36156                let use_seed = sample.use_seed_keyword
36157                    && !matches!(
36158                        self.config.dialect,
36159                        Some(crate::dialects::DialectType::Databricks)
36160                            | Some(crate::dialects::DialectType::Spark)
36161                    );
36162                if use_seed {
36163                    self.write_keyword("SEED");
36164                } else {
36165                    self.write_keyword("REPEATABLE");
36166                }
36167                self.write(" (");
36168                self.generate_expression(seed)?;
36169                self.write(")");
36170            }
36171            return Ok(());
36172        }
36173
36174        // Legacy pattern: TABLESAMPLE [method] (expressions) or TABLESAMPLE method BUCKET numerator OUT OF denominator
36175        self.write_keyword(self.config.tablesample_keywords);
36176        if let Some(method) = &e.method {
36177            self.write_space();
36178            self.write_keyword(method);
36179        } else if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
36180            // Snowflake defaults to BERNOULLI when no method is specified
36181            self.write_space();
36182            self.write_keyword("BERNOULLI");
36183        }
36184        if let (Some(numerator), Some(denominator)) = (&e.bucket_numerator, &e.bucket_denominator) {
36185            self.write_space();
36186            self.write_keyword("BUCKET");
36187            self.write_space();
36188            self.generate_expression(numerator)?;
36189            self.write_space();
36190            self.write_keyword("OUT OF");
36191            self.write_space();
36192            self.generate_expression(denominator)?;
36193            if let Some(field) = &e.bucket_field {
36194                self.write_space();
36195                self.write_keyword("ON");
36196                self.write_space();
36197                self.generate_expression(field)?;
36198            }
36199        } else if !e.expressions.is_empty() {
36200            self.write(" (");
36201            for (i, expr) in e.expressions.iter().enumerate() {
36202                if i > 0 {
36203                    self.write(", ");
36204                }
36205                self.generate_expression(expr)?;
36206            }
36207            self.write(")");
36208        } else if let Some(percent) = &e.percent {
36209            self.write(" (");
36210            self.generate_expression(percent)?;
36211            self.write_space();
36212            self.write_keyword("PERCENT");
36213            self.write(")");
36214        }
36215        Ok(())
36216    }
36217
36218    fn generate_tag(&mut self, e: &Tag) -> Result<()> {
36219        // [prefix]this[postfix]
36220        if let Some(prefix) = &e.prefix {
36221            self.generate_expression(prefix)?;
36222        }
36223        if let Some(this) = &e.this {
36224            self.generate_expression(this)?;
36225        }
36226        if let Some(postfix) = &e.postfix {
36227            self.generate_expression(postfix)?;
36228        }
36229        Ok(())
36230    }
36231
36232    fn generate_tags(&mut self, e: &Tags) -> Result<()> {
36233        // TAG (expressions)
36234        self.write_keyword("TAG");
36235        self.write(" (");
36236        for (i, expr) in e.expressions.iter().enumerate() {
36237            if i > 0 {
36238                self.write(", ");
36239            }
36240            self.generate_expression(expr)?;
36241        }
36242        self.write(")");
36243        Ok(())
36244    }
36245
36246    fn generate_temporary_property(&mut self, e: &TemporaryProperty) -> Result<()> {
36247        // TEMPORARY or TEMP or [this] TEMPORARY
36248        if let Some(this) = &e.this {
36249            self.generate_expression(this)?;
36250            self.write_space();
36251        }
36252        self.write_keyword("TEMPORARY");
36253        Ok(())
36254    }
36255
36256    /// Generate a Time function expression
36257    /// For most dialects: TIME('value')
36258    fn generate_time_func(&mut self, e: &UnaryFunc) -> Result<()> {
36259        // Standard: TIME(value)
36260        self.write_keyword("TIME");
36261        self.write("(");
36262        self.generate_expression(&e.this)?;
36263        self.write(")");
36264        Ok(())
36265    }
36266
36267    fn generate_time_add(&mut self, e: &TimeAdd) -> Result<()> {
36268        // TIME_ADD(this, expression, unit)
36269        self.write_keyword("TIME_ADD");
36270        self.write("(");
36271        self.generate_expression(&e.this)?;
36272        self.write(", ");
36273        self.generate_expression(&e.expression)?;
36274        if let Some(unit) = &e.unit {
36275            self.write(", ");
36276            self.write_keyword(unit);
36277        }
36278        self.write(")");
36279        Ok(())
36280    }
36281
36282    fn generate_time_diff(&mut self, e: &TimeDiff) -> Result<()> {
36283        // TIME_DIFF(this, expression, unit)
36284        self.write_keyword("TIME_DIFF");
36285        self.write("(");
36286        self.generate_expression(&e.this)?;
36287        self.write(", ");
36288        self.generate_expression(&e.expression)?;
36289        if let Some(unit) = &e.unit {
36290            self.write(", ");
36291            self.write_keyword(unit);
36292        }
36293        self.write(")");
36294        Ok(())
36295    }
36296
36297    fn generate_time_from_parts(&mut self, e: &TimeFromParts) -> Result<()> {
36298        // TIME_FROM_PARTS(hour, minute, second, nanosecond)
36299        self.write_keyword("TIME_FROM_PARTS");
36300        self.write("(");
36301        let mut first = true;
36302        if let Some(hour) = &e.hour {
36303            self.generate_expression(hour)?;
36304            first = false;
36305        }
36306        if let Some(minute) = &e.min {
36307            if !first {
36308                self.write(", ");
36309            }
36310            self.generate_expression(minute)?;
36311            first = false;
36312        }
36313        if let Some(second) = &e.sec {
36314            if !first {
36315                self.write(", ");
36316            }
36317            self.generate_expression(second)?;
36318            first = false;
36319        }
36320        if let Some(ns) = &e.nano {
36321            if !first {
36322                self.write(", ");
36323            }
36324            self.generate_expression(ns)?;
36325        }
36326        self.write(")");
36327        Ok(())
36328    }
36329
36330    fn generate_time_slice(&mut self, e: &TimeSlice) -> Result<()> {
36331        // TIME_SLICE(this, expression, unit)
36332        self.write_keyword("TIME_SLICE");
36333        self.write("(");
36334        self.generate_expression(&e.this)?;
36335        self.write(", ");
36336        self.generate_expression(&e.expression)?;
36337        self.write(", ");
36338        self.write_keyword(&e.unit);
36339        self.write(")");
36340        Ok(())
36341    }
36342
36343    fn generate_time_str_to_time(&mut self, e: &TimeStrToTime) -> Result<()> {
36344        // TIME_STR_TO_TIME(this)
36345        self.write_keyword("TIME_STR_TO_TIME");
36346        self.write("(");
36347        self.generate_expression(&e.this)?;
36348        self.write(")");
36349        Ok(())
36350    }
36351
36352    fn generate_time_sub(&mut self, e: &TimeSub) -> Result<()> {
36353        // TIME_SUB(this, expression, unit)
36354        self.write_keyword("TIME_SUB");
36355        self.write("(");
36356        self.generate_expression(&e.this)?;
36357        self.write(", ");
36358        self.generate_expression(&e.expression)?;
36359        if let Some(unit) = &e.unit {
36360            self.write(", ");
36361            self.write_keyword(unit);
36362        }
36363        self.write(")");
36364        Ok(())
36365    }
36366
36367    fn generate_time_to_str(&mut self, e: &TimeToStr) -> Result<()> {
36368        match self.config.dialect {
36369            Some(DialectType::Exasol) => {
36370                // Exasol uses TO_CHAR with Exasol-specific format
36371                self.write_keyword("TO_CHAR");
36372                self.write("(");
36373                self.generate_expression(&e.this)?;
36374                self.write(", '");
36375                self.write(&Self::convert_strptime_to_exasol_format(&e.format));
36376                self.write("'");
36377                self.write(")");
36378            }
36379            Some(DialectType::PostgreSQL)
36380            | Some(DialectType::Redshift)
36381            | Some(DialectType::Materialize) => {
36382                // PostgreSQL/Redshift/Materialize uses TO_CHAR with PG-specific format
36383                self.write_keyword("TO_CHAR");
36384                self.write("(");
36385                self.generate_expression(&e.this)?;
36386                self.write(", '");
36387                self.write(&Self::convert_strptime_to_postgres_format(&e.format));
36388                self.write("'");
36389                self.write(")");
36390            }
36391            Some(DialectType::Oracle) => {
36392                // Oracle uses TO_CHAR with PG-like format
36393                self.write_keyword("TO_CHAR");
36394                self.write("(");
36395                self.generate_expression(&e.this)?;
36396                self.write(", '");
36397                self.write(&Self::convert_strptime_to_postgres_format(&e.format));
36398                self.write("'");
36399                self.write(")");
36400            }
36401            Some(DialectType::Drill) => {
36402                // Drill: TO_CHAR with Java format
36403                self.write_keyword("TO_CHAR");
36404                self.write("(");
36405                self.generate_expression(&e.this)?;
36406                self.write(", '");
36407                self.write(&Self::strftime_to_java_format(&e.format));
36408                self.write("'");
36409                self.write(")");
36410            }
36411            Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
36412                // TSQL: FORMAT(value, format) with .NET-style format
36413                self.write_keyword("FORMAT");
36414                self.write("(");
36415                self.generate_expression(&e.this)?;
36416                self.write(", '");
36417                self.write(&Self::strftime_to_tsql_format(&e.format));
36418                self.write("'");
36419                self.write(")");
36420            }
36421            Some(DialectType::DuckDB) => {
36422                // DuckDB: STRFTIME(value, format) - keeps C format
36423                self.write_keyword("STRFTIME");
36424                self.write("(");
36425                self.generate_expression(&e.this)?;
36426                self.write(", '");
36427                self.write(&e.format);
36428                self.write("'");
36429                self.write(")");
36430            }
36431            Some(DialectType::BigQuery) => {
36432                // BigQuery: FORMAT_DATE(format, value) - note swapped arg order
36433                // Normalize: %Y-%m-%d -> %F, %H:%M:%S -> %T
36434                let fmt = e.format.replace("%Y-%m-%d", "%F").replace("%H:%M:%S", "%T");
36435                self.write_keyword("FORMAT_DATE");
36436                self.write("('");
36437                self.write(&fmt);
36438                self.write("', ");
36439                self.generate_expression(&e.this)?;
36440                self.write(")");
36441            }
36442            Some(DialectType::Hive) | Some(DialectType::Spark) | Some(DialectType::Databricks) => {
36443                // Hive/Spark: DATE_FORMAT(value, java_format)
36444                self.write_keyword("DATE_FORMAT");
36445                self.write("(");
36446                self.generate_expression(&e.this)?;
36447                self.write(", '");
36448                self.write(&Self::strftime_to_java_format(&e.format));
36449                self.write("'");
36450                self.write(")");
36451            }
36452            Some(DialectType::Presto) | Some(DialectType::Trino) | Some(DialectType::Athena) => {
36453                // Presto/Trino: DATE_FORMAT(value, format) - keeps C format
36454                self.write_keyword("DATE_FORMAT");
36455                self.write("(");
36456                self.generate_expression(&e.this)?;
36457                self.write(", '");
36458                self.write(&e.format);
36459                self.write("'");
36460                self.write(")");
36461            }
36462            Some(DialectType::Doris) | Some(DialectType::StarRocks) => {
36463                // Doris/StarRocks: DATE_FORMAT(value, format) - keeps C format
36464                self.write_keyword("DATE_FORMAT");
36465                self.write("(");
36466                self.generate_expression(&e.this)?;
36467                self.write(", '");
36468                self.write(&e.format);
36469                self.write("'");
36470                self.write(")");
36471            }
36472            _ => {
36473                // Default: TIME_TO_STR(this, format)
36474                self.write_keyword("TIME_TO_STR");
36475                self.write("(");
36476                self.generate_expression(&e.this)?;
36477                self.write(", '");
36478                self.write(&e.format);
36479                self.write("'");
36480                self.write(")");
36481            }
36482        }
36483        Ok(())
36484    }
36485
36486    fn generate_time_to_unix(&mut self, e: &crate::expressions::UnaryFunc) -> Result<()> {
36487        match self.config.dialect {
36488            Some(DialectType::DuckDB) => {
36489                // DuckDB: EPOCH(x)
36490                self.write_keyword("EPOCH");
36491                self.write("(");
36492                self.generate_expression(&e.this)?;
36493                self.write(")");
36494            }
36495            Some(DialectType::Hive)
36496            | Some(DialectType::Spark)
36497            | Some(DialectType::Databricks)
36498            | Some(DialectType::Doris)
36499            | Some(DialectType::StarRocks)
36500            | Some(DialectType::Drill) => {
36501                // Hive/Spark/Doris/StarRocks/Drill: UNIX_TIMESTAMP(x)
36502                self.write_keyword("UNIX_TIMESTAMP");
36503                self.write("(");
36504                self.generate_expression(&e.this)?;
36505                self.write(")");
36506            }
36507            Some(DialectType::Presto) | Some(DialectType::Trino) => {
36508                // Presto: TO_UNIXTIME(x)
36509                self.write_keyword("TO_UNIXTIME");
36510                self.write("(");
36511                self.generate_expression(&e.this)?;
36512                self.write(")");
36513            }
36514            _ => {
36515                // Default: TIME_TO_UNIX(x)
36516                self.write_keyword("TIME_TO_UNIX");
36517                self.write("(");
36518                self.generate_expression(&e.this)?;
36519                self.write(")");
36520            }
36521        }
36522        Ok(())
36523    }
36524
36525    fn generate_time_str_to_date(&mut self, e: &crate::expressions::UnaryFunc) -> Result<()> {
36526        match self.config.dialect {
36527            Some(DialectType::Hive) => {
36528                // Hive: TO_DATE(x)
36529                self.write_keyword("TO_DATE");
36530                self.write("(");
36531                self.generate_expression(&e.this)?;
36532                self.write(")");
36533            }
36534            _ => {
36535                // Default: TIME_STR_TO_DATE(x)
36536                self.write_keyword("TIME_STR_TO_DATE");
36537                self.write("(");
36538                self.generate_expression(&e.this)?;
36539                self.write(")");
36540            }
36541        }
36542        Ok(())
36543    }
36544
36545    fn generate_time_trunc(&mut self, e: &TimeTrunc) -> Result<()> {
36546        // TIME_TRUNC(this, unit)
36547        self.write_keyword("TIME_TRUNC");
36548        self.write("(");
36549        self.generate_expression(&e.this)?;
36550        self.write(", ");
36551        self.write_keyword(&e.unit);
36552        self.write(")");
36553        Ok(())
36554    }
36555
36556    fn generate_time_unit(&mut self, e: &TimeUnit) -> Result<()> {
36557        // Just output the unit name
36558        if let Some(unit) = &e.unit {
36559            self.write_keyword(unit);
36560        }
36561        Ok(())
36562    }
36563
36564    /// Generate a Timestamp function expression
36565    /// For Exasol: {ts'value'} -> TO_TIMESTAMP('value')
36566    /// For other dialects: TIMESTAMP('value')
36567    fn generate_timestamp_func(&mut self, e: &TimestampFunc) -> Result<()> {
36568        use crate::dialects::DialectType;
36569        use crate::expressions::Literal;
36570
36571        match self.config.dialect {
36572            // Exasol uses TO_TIMESTAMP for Timestamp expressions
36573            Some(DialectType::Exasol) => {
36574                self.write_keyword("TO_TIMESTAMP");
36575                self.write("(");
36576                // Extract the string value from the expression if it's a string literal
36577                if let Some(this) = &e.this {
36578                    match this.as_ref() {
36579                        Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
36580                            let Literal::String(s) = lit.as_ref() else {
36581                                unreachable!()
36582                            };
36583                            self.write("'");
36584                            self.write(s);
36585                            self.write("'");
36586                        }
36587                        _ => {
36588                            self.generate_expression(this)?;
36589                        }
36590                    }
36591                }
36592                self.write(")");
36593            }
36594            // Standard: TIMESTAMP(value) or TIMESTAMP(value, zone)
36595            _ => {
36596                self.write_keyword("TIMESTAMP");
36597                self.write("(");
36598                if let Some(this) = &e.this {
36599                    self.generate_expression(this)?;
36600                }
36601                if let Some(zone) = &e.zone {
36602                    self.write(", ");
36603                    self.generate_expression(zone)?;
36604                }
36605                self.write(")");
36606            }
36607        }
36608        Ok(())
36609    }
36610
36611    fn generate_timestamp_add(&mut self, e: &TimestampAdd) -> Result<()> {
36612        // TIMESTAMP_ADD(this, expression, unit)
36613        self.write_keyword("TIMESTAMP_ADD");
36614        self.write("(");
36615        self.generate_expression(&e.this)?;
36616        self.write(", ");
36617        self.generate_expression(&e.expression)?;
36618        if let Some(unit) = &e.unit {
36619            self.write(", ");
36620            self.write_keyword(unit);
36621        }
36622        self.write(")");
36623        Ok(())
36624    }
36625
36626    fn generate_timestamp_diff(&mut self, e: &TimestampDiff) -> Result<()> {
36627        // TIMESTAMP_DIFF(this, expression, unit)
36628        self.write_keyword("TIMESTAMP_DIFF");
36629        self.write("(");
36630        self.generate_expression(&e.this)?;
36631        self.write(", ");
36632        self.generate_expression(&e.expression)?;
36633        if let Some(unit) = &e.unit {
36634            self.write(", ");
36635            self.write_keyword(unit);
36636        }
36637        self.write(")");
36638        Ok(())
36639    }
36640
36641    fn generate_timestamp_from_parts(&mut self, e: &TimestampFromParts) -> Result<()> {
36642        // TIMESTAMP_FROM_PARTS(this, expression)
36643        self.write_keyword("TIMESTAMP_FROM_PARTS");
36644        self.write("(");
36645        if let Some(this) = &e.this {
36646            self.generate_expression(this)?;
36647        }
36648        if let Some(expression) = &e.expression {
36649            self.write(", ");
36650            self.generate_expression(expression)?;
36651        }
36652        if let Some(zone) = &e.zone {
36653            self.write(", ");
36654            self.generate_expression(zone)?;
36655        }
36656        if let Some(milli) = &e.milli {
36657            self.write(", ");
36658            self.generate_expression(milli)?;
36659        }
36660        self.write(")");
36661        Ok(())
36662    }
36663
36664    fn generate_timestamp_sub(&mut self, e: &TimestampSub) -> Result<()> {
36665        // TIMESTAMP_SUB(this, INTERVAL expression unit)
36666        self.write_keyword("TIMESTAMP_SUB");
36667        self.write("(");
36668        self.generate_expression(&e.this)?;
36669        self.write(", ");
36670        self.write_keyword("INTERVAL");
36671        self.write_space();
36672        self.generate_expression(&e.expression)?;
36673        if let Some(unit) = &e.unit {
36674            self.write_space();
36675            self.write_keyword(unit);
36676        }
36677        self.write(")");
36678        Ok(())
36679    }
36680
36681    fn generate_timestamp_tz_from_parts(&mut self, e: &TimestampTzFromParts) -> Result<()> {
36682        // TIMESTAMP_TZ_FROM_PARTS(...)
36683        self.write_keyword("TIMESTAMP_TZ_FROM_PARTS");
36684        self.write("(");
36685        if let Some(zone) = &e.zone {
36686            self.generate_expression(zone)?;
36687        }
36688        self.write(")");
36689        Ok(())
36690    }
36691
36692    fn generate_to_binary(&mut self, e: &ToBinary) -> Result<()> {
36693        // TO_BINARY(this, [format])
36694        self.write_keyword("TO_BINARY");
36695        self.write("(");
36696        self.generate_expression(&e.this)?;
36697        if let Some(format) = &e.format {
36698            self.write(", '");
36699            self.write(format);
36700            self.write("'");
36701        }
36702        self.write(")");
36703        Ok(())
36704    }
36705
36706    fn generate_to_boolean(&mut self, e: &ToBoolean) -> Result<()> {
36707        // TO_BOOLEAN(this)
36708        self.write_keyword("TO_BOOLEAN");
36709        self.write("(");
36710        self.generate_expression(&e.this)?;
36711        self.write(")");
36712        Ok(())
36713    }
36714
36715    fn generate_to_char(&mut self, e: &ToChar) -> Result<()> {
36716        // TO_CHAR(this, [format], [nlsparam])
36717        self.write_keyword("TO_CHAR");
36718        self.write("(");
36719        self.generate_expression(&e.this)?;
36720        if let Some(format) = &e.format {
36721            self.write(", '");
36722            self.write(format);
36723            self.write("'");
36724        }
36725        if let Some(nlsparam) = &e.nlsparam {
36726            self.write(", ");
36727            self.generate_expression(nlsparam)?;
36728        }
36729        self.write(")");
36730        Ok(())
36731    }
36732
36733    fn generate_to_decfloat(&mut self, e: &ToDecfloat) -> Result<()> {
36734        // TO_DECFLOAT(this, [format])
36735        self.write_keyword("TO_DECFLOAT");
36736        self.write("(");
36737        self.generate_expression(&e.this)?;
36738        if let Some(format) = &e.format {
36739            self.write(", '");
36740            self.write(format);
36741            self.write("'");
36742        }
36743        self.write(")");
36744        Ok(())
36745    }
36746
36747    fn generate_to_double(&mut self, e: &ToDouble) -> Result<()> {
36748        // TO_DOUBLE(this, [format])
36749        self.write_keyword("TO_DOUBLE");
36750        self.write("(");
36751        self.generate_expression(&e.this)?;
36752        if let Some(format) = &e.format {
36753            self.write(", '");
36754            self.write(format);
36755            self.write("'");
36756        }
36757        self.write(")");
36758        Ok(())
36759    }
36760
36761    fn generate_to_file(&mut self, e: &ToFile) -> Result<()> {
36762        // TO_FILE(this, path)
36763        self.write_keyword("TO_FILE");
36764        self.write("(");
36765        self.generate_expression(&e.this)?;
36766        if let Some(path) = &e.path {
36767            self.write(", ");
36768            self.generate_expression(path)?;
36769        }
36770        self.write(")");
36771        Ok(())
36772    }
36773
36774    fn generate_to_number(&mut self, e: &ToNumber) -> Result<()> {
36775        // TO_NUMBER or TRY_TO_NUMBER (this, [format], [precision], [scale])
36776        // If safe flag is set, output TRY_TO_NUMBER
36777        let is_safe = e.safe.is_some();
36778        if is_safe {
36779            self.write_keyword("TRY_TO_NUMBER");
36780        } else {
36781            self.write_keyword("TO_NUMBER");
36782        }
36783        self.write("(");
36784        self.generate_expression(&e.this)?;
36785        let precision_is_snowflake_default = e.precision.is_none()
36786            || matches!(
36787                e.precision.as_deref(),
36788                Some(Expression::Literal(lit))
36789                    if matches!(lit.as_ref(), Literal::Number(n) if n == "0")
36790            );
36791        let is_snowflake_default_precision =
36792            matches!(self.config.dialect, Some(DialectType::Snowflake))
36793                && e.nlsparam.is_none()
36794                && e.scale.is_none()
36795                && matches!(
36796                    e.format.as_deref(),
36797                    Some(Expression::Literal(lit))
36798                        if matches!(lit.as_ref(), Literal::Number(n) if n == "38")
36799                )
36800                && precision_is_snowflake_default;
36801
36802        if !is_snowflake_default_precision {
36803            if let Some(format) = &e.format {
36804                self.write(", ");
36805                self.generate_expression(format)?;
36806            }
36807            if let Some(nlsparam) = &e.nlsparam {
36808                self.write(", ");
36809                self.generate_expression(nlsparam)?;
36810            }
36811            if let Some(precision) = &e.precision {
36812                self.write(", ");
36813                self.generate_expression(precision)?;
36814            }
36815            if let Some(scale) = &e.scale {
36816                self.write(", ");
36817                self.generate_expression(scale)?;
36818            }
36819        }
36820        self.write(")");
36821        Ok(())
36822    }
36823
36824    fn generate_to_table_property(&mut self, e: &ToTableProperty) -> Result<()> {
36825        // TO_TABLE this
36826        self.write_keyword("TO_TABLE");
36827        self.write_space();
36828        self.generate_expression(&e.this)?;
36829        Ok(())
36830    }
36831
36832    fn generate_transaction(&mut self, e: &Transaction) -> Result<()> {
36833        // Check mark to determine the format
36834        let mark_text = e.mark.as_ref().map(|m| match m.as_ref() {
36835            Expression::Identifier(id) => id.name.clone(),
36836            Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
36837                let Literal::String(s) = lit.as_ref() else {
36838                    unreachable!()
36839                };
36840                s.clone()
36841            }
36842            _ => String::new(),
36843        });
36844
36845        let is_start = mark_text.as_ref().map_or(false, |s| s == "START");
36846        let has_transaction_keyword = mark_text.as_ref().map_or(false, |s| s == "TRANSACTION");
36847        let has_with_mark = e.mark.as_ref().map_or(false, |m| {
36848            matches!(m.as_ref(), Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)))
36849        });
36850
36851        // For Presto/Trino: always use START TRANSACTION
36852        let use_start_transaction = matches!(
36853            self.config.dialect,
36854            Some(DialectType::Presto) | Some(DialectType::Trino) | Some(DialectType::Athena)
36855        );
36856        // For most dialects: strip TRANSACTION keyword
36857        let strip_transaction = matches!(
36858            self.config.dialect,
36859            Some(DialectType::Snowflake)
36860                | Some(DialectType::PostgreSQL)
36861                | Some(DialectType::Redshift)
36862                | Some(DialectType::MySQL)
36863                | Some(DialectType::Hive)
36864                | Some(DialectType::Spark)
36865                | Some(DialectType::Databricks)
36866                | Some(DialectType::DuckDB)
36867                | Some(DialectType::Oracle)
36868                | Some(DialectType::Doris)
36869                | Some(DialectType::StarRocks)
36870                | Some(DialectType::Materialize)
36871                | Some(DialectType::ClickHouse)
36872        );
36873
36874        if is_start || use_start_transaction {
36875            // START TRANSACTION [modes]
36876            self.write_keyword("START TRANSACTION");
36877            if let Some(modes) = &e.modes {
36878                self.write_space();
36879                self.generate_expression(modes)?;
36880            }
36881        } else {
36882            // BEGIN [DEFERRED|IMMEDIATE|EXCLUSIVE] [TRANSACTION] [transaction_name] [WITH MARK 'desc']
36883            self.write_keyword("BEGIN");
36884
36885            // Check if `this` is a transaction kind (DEFERRED/IMMEDIATE/EXCLUSIVE)
36886            let is_kind = e.this.as_ref().map_or(false, |t| {
36887                if let Expression::Identifier(id) = t.as_ref() {
36888                    id.name.eq_ignore_ascii_case("DEFERRED")
36889                        || id.name.eq_ignore_ascii_case("IMMEDIATE")
36890                        || id.name.eq_ignore_ascii_case("EXCLUSIVE")
36891                } else {
36892                    false
36893                }
36894            });
36895
36896            // Output kind before TRANSACTION keyword
36897            if is_kind {
36898                if let Some(this) = &e.this {
36899                    self.write_space();
36900                    if let Expression::Identifier(id) = this.as_ref() {
36901                        self.write_keyword(&id.name);
36902                    }
36903                }
36904            }
36905
36906            // Output TRANSACTION keyword if it was present and target supports it
36907            if (has_transaction_keyword || has_with_mark) && !strip_transaction {
36908                self.write_space();
36909                self.write_keyword("TRANSACTION");
36910            }
36911
36912            // Output transaction name (not kind)
36913            if !is_kind {
36914                if let Some(this) = &e.this {
36915                    self.write_space();
36916                    self.generate_expression(this)?;
36917                }
36918            }
36919
36920            // Output WITH MARK 'description' for TSQL
36921            if has_with_mark {
36922                self.write_space();
36923                self.write_keyword("WITH MARK");
36924                if let Some(Expression::Literal(lit)) = e.mark.as_deref() {
36925                    if let Literal::String(desc) = lit.as_ref() {
36926                        if !desc.is_empty() {
36927                            self.write_space();
36928                            self.write(&format!("'{}'", desc));
36929                        }
36930                    }
36931                }
36932            }
36933
36934            // Output modes (isolation levels, etc.)
36935            if let Some(modes) = &e.modes {
36936                self.write_space();
36937                self.generate_expression(modes)?;
36938            }
36939        }
36940        Ok(())
36941    }
36942
36943    fn generate_transform(&mut self, e: &Transform) -> Result<()> {
36944        // TRANSFORM(this, expression)
36945        self.write_keyword("TRANSFORM");
36946        self.write("(");
36947        self.generate_expression(&e.this)?;
36948        self.write(", ");
36949        self.generate_expression(&e.expression)?;
36950        self.write(")");
36951        Ok(())
36952    }
36953
36954    fn generate_transform_model_property(&mut self, e: &TransformModelProperty) -> Result<()> {
36955        // TRANSFORM(expressions)
36956        self.write_keyword("TRANSFORM");
36957        self.write("(");
36958        if self.config.pretty && !e.expressions.is_empty() {
36959            self.indent_level += 1;
36960            for (i, expr) in e.expressions.iter().enumerate() {
36961                if i > 0 {
36962                    self.write(",");
36963                }
36964                self.write_newline();
36965                self.write_indent();
36966                self.generate_expression(expr)?;
36967            }
36968            self.indent_level -= 1;
36969            self.write_newline();
36970            self.write(")");
36971        } else {
36972            for (i, expr) in e.expressions.iter().enumerate() {
36973                if i > 0 {
36974                    self.write(", ");
36975                }
36976                self.generate_expression(expr)?;
36977            }
36978            self.write(")");
36979        }
36980        Ok(())
36981    }
36982
36983    fn generate_transient_property(&mut self, e: &TransientProperty) -> Result<()> {
36984        use crate::dialects::DialectType;
36985        // TRANSIENT is Snowflake-specific; skip for other dialects
36986        if let Some(this) = &e.this {
36987            self.generate_expression(this)?;
36988            if matches!(self.config.dialect, Some(DialectType::Snowflake) | None) {
36989                self.write_space();
36990            }
36991        }
36992        if matches!(self.config.dialect, Some(DialectType::Snowflake) | None) {
36993            self.write_keyword("TRANSIENT");
36994        }
36995        Ok(())
36996    }
36997
36998    fn generate_translate(&mut self, e: &Translate) -> Result<()> {
36999        // TRANSLATE(this, from_, to)
37000        self.write_keyword("TRANSLATE");
37001        self.write("(");
37002        self.generate_expression(&e.this)?;
37003        if let Some(from) = &e.from_ {
37004            self.write(", ");
37005            self.generate_expression(from)?;
37006        }
37007        if let Some(to) = &e.to {
37008            self.write(", ");
37009            self.generate_expression(to)?;
37010        }
37011        self.write(")");
37012        Ok(())
37013    }
37014
37015    fn generate_translate_characters(&mut self, e: &TranslateCharacters) -> Result<()> {
37016        // TRANSLATE(this USING expression)
37017        self.write_keyword("TRANSLATE");
37018        self.write("(");
37019        self.generate_expression(&e.this)?;
37020        self.write_space();
37021        self.write_keyword("USING");
37022        self.write_space();
37023        self.generate_expression(&e.expression)?;
37024        if e.with_error.is_some() {
37025            self.write_space();
37026            self.write_keyword("WITH ERROR");
37027        }
37028        self.write(")");
37029        Ok(())
37030    }
37031
37032    fn generate_truncate_table(&mut self, e: &TruncateTable) -> Result<()> {
37033        // TRUNCATE TABLE table1, table2, ...
37034        self.write_keyword("TRUNCATE TABLE");
37035        self.write_space();
37036        for (i, expr) in e.expressions.iter().enumerate() {
37037            if i > 0 {
37038                self.write(", ");
37039            }
37040            self.generate_expression(expr)?;
37041        }
37042        Ok(())
37043    }
37044
37045    fn generate_try_base64_decode_binary(&mut self, e: &TryBase64DecodeBinary) -> Result<()> {
37046        // TRY_BASE64_DECODE_BINARY(this, [alphabet])
37047        self.write_keyword("TRY_BASE64_DECODE_BINARY");
37048        self.write("(");
37049        self.generate_expression(&e.this)?;
37050        if let Some(alphabet) = &e.alphabet {
37051            self.write(", ");
37052            self.generate_expression(alphabet)?;
37053        }
37054        self.write(")");
37055        Ok(())
37056    }
37057
37058    fn generate_try_base64_decode_string(&mut self, e: &TryBase64DecodeString) -> Result<()> {
37059        // TRY_BASE64_DECODE_STRING(this, [alphabet])
37060        self.write_keyword("TRY_BASE64_DECODE_STRING");
37061        self.write("(");
37062        self.generate_expression(&e.this)?;
37063        if let Some(alphabet) = &e.alphabet {
37064            self.write(", ");
37065            self.generate_expression(alphabet)?;
37066        }
37067        self.write(")");
37068        Ok(())
37069    }
37070
37071    fn generate_try_to_decfloat(&mut self, e: &TryToDecfloat) -> Result<()> {
37072        // TRY_TO_DECFLOAT(this, [format])
37073        self.write_keyword("TRY_TO_DECFLOAT");
37074        self.write("(");
37075        self.generate_expression(&e.this)?;
37076        if let Some(format) = &e.format {
37077            self.write(", '");
37078            self.write(format);
37079            self.write("'");
37080        }
37081        self.write(")");
37082        Ok(())
37083    }
37084
37085    fn generate_ts_or_ds_add(&mut self, e: &TsOrDsAdd) -> Result<()> {
37086        // TS_OR_DS_ADD(this, expression, [unit], [return_type])
37087        self.write_keyword("TS_OR_DS_ADD");
37088        self.write("(");
37089        self.generate_expression(&e.this)?;
37090        self.write(", ");
37091        self.generate_expression(&e.expression)?;
37092        if let Some(unit) = &e.unit {
37093            self.write(", ");
37094            self.write_keyword(unit);
37095        }
37096        if let Some(return_type) = &e.return_type {
37097            self.write(", ");
37098            self.generate_expression(return_type)?;
37099        }
37100        self.write(")");
37101        Ok(())
37102    }
37103
37104    fn generate_ts_or_ds_diff(&mut self, e: &TsOrDsDiff) -> Result<()> {
37105        // TS_OR_DS_DIFF(this, expression, [unit])
37106        self.write_keyword("TS_OR_DS_DIFF");
37107        self.write("(");
37108        self.generate_expression(&e.this)?;
37109        self.write(", ");
37110        self.generate_expression(&e.expression)?;
37111        if let Some(unit) = &e.unit {
37112            self.write(", ");
37113            self.write_keyword(unit);
37114        }
37115        self.write(")");
37116        Ok(())
37117    }
37118
37119    fn generate_ts_or_ds_to_date(&mut self, e: &TsOrDsToDate) -> Result<()> {
37120        let default_time_format = "%Y-%m-%d %H:%M:%S";
37121        let default_date_format = "%Y-%m-%d";
37122        let has_non_default_format = e.format.as_ref().map_or(false, |f| {
37123            f != default_time_format && f != default_date_format
37124        });
37125
37126        if has_non_default_format {
37127            // With non-default format: dialect-specific handling
37128            let fmt = e.format.as_ref().unwrap();
37129            match self.config.dialect {
37130                Some(DialectType::MySQL) | Some(DialectType::StarRocks) => {
37131                    // MySQL/StarRocks: STR_TO_DATE(x, fmt) - no CAST wrapper
37132                    // STR_TO_DATE is the MySQL-native form of StrToTime
37133                    let str_to_time = crate::expressions::StrToTime {
37134                        this: Box::new((*e.this).clone()),
37135                        format: fmt.clone(),
37136                        zone: None,
37137                        safe: None,
37138                        target_type: None,
37139                    };
37140                    self.generate_str_to_time(&str_to_time)?;
37141                }
37142                Some(DialectType::Hive)
37143                | Some(DialectType::Spark)
37144                | Some(DialectType::Databricks) => {
37145                    // Hive/Spark: TO_DATE(x, java_fmt)
37146                    self.write_keyword("TO_DATE");
37147                    self.write("(");
37148                    self.generate_expression(&e.this)?;
37149                    self.write(", '");
37150                    self.write(&Self::strftime_to_java_format(fmt));
37151                    self.write("')");
37152                }
37153                Some(DialectType::Snowflake) => {
37154                    // Snowflake: TO_DATE(x, snowflake_fmt)
37155                    self.write_keyword("TO_DATE");
37156                    self.write("(");
37157                    self.generate_expression(&e.this)?;
37158                    self.write(", '");
37159                    self.write(&Self::strftime_to_snowflake_format(fmt));
37160                    self.write("')");
37161                }
37162                Some(DialectType::Doris) => {
37163                    // Doris: TO_DATE(x) - ignores format
37164                    self.write_keyword("TO_DATE");
37165                    self.write("(");
37166                    self.generate_expression(&e.this)?;
37167                    self.write(")");
37168                }
37169                _ => {
37170                    // Default: CAST(STR_TO_TIME(x, fmt) AS DATE)
37171                    self.write_keyword("CAST");
37172                    self.write("(");
37173                    let str_to_time = crate::expressions::StrToTime {
37174                        this: Box::new((*e.this).clone()),
37175                        format: fmt.clone(),
37176                        zone: None,
37177                        safe: None,
37178                        target_type: None,
37179                    };
37180                    self.generate_str_to_time(&str_to_time)?;
37181                    self.write_keyword(" AS ");
37182                    self.write_keyword("DATE");
37183                    self.write(")");
37184                }
37185            }
37186        } else {
37187            // Without format (or default format): simple date conversion
37188            match self.config.dialect {
37189                Some(DialectType::MySQL)
37190                | Some(DialectType::SQLite)
37191                | Some(DialectType::StarRocks) => {
37192                    // MySQL/SQLite/StarRocks: DATE(x)
37193                    self.write_keyword("DATE");
37194                    self.write("(");
37195                    self.generate_expression(&e.this)?;
37196                    self.write(")");
37197                }
37198                Some(DialectType::Hive)
37199                | Some(DialectType::Spark)
37200                | Some(DialectType::Databricks)
37201                | Some(DialectType::Snowflake)
37202                | Some(DialectType::Doris) => {
37203                    // Hive/Spark/Databricks/Snowflake/Doris: TO_DATE(x)
37204                    self.write_keyword("TO_DATE");
37205                    self.write("(");
37206                    self.generate_expression(&e.this)?;
37207                    self.write(")");
37208                }
37209                Some(DialectType::Presto)
37210                | Some(DialectType::Trino)
37211                | Some(DialectType::Athena) => {
37212                    // Presto/Trino: CAST(CAST(x AS TIMESTAMP) AS DATE)
37213                    self.write_keyword("CAST");
37214                    self.write("(");
37215                    self.write_keyword("CAST");
37216                    self.write("(");
37217                    self.generate_expression(&e.this)?;
37218                    self.write_keyword(" AS ");
37219                    self.write_keyword("TIMESTAMP");
37220                    self.write(")");
37221                    self.write_keyword(" AS ");
37222                    self.write_keyword("DATE");
37223                    self.write(")");
37224                }
37225                Some(DialectType::ClickHouse) => {
37226                    // ClickHouse: CAST(x AS Nullable(DATE))
37227                    self.write_keyword("CAST");
37228                    self.write("(");
37229                    self.generate_expression(&e.this)?;
37230                    self.write_keyword(" AS ");
37231                    self.write("Nullable(DATE)");
37232                    self.write(")");
37233                }
37234                _ => {
37235                    // Default: CAST(x AS DATE)
37236                    self.write_keyword("CAST");
37237                    self.write("(");
37238                    self.generate_expression(&e.this)?;
37239                    self.write_keyword(" AS ");
37240                    self.write_keyword("DATE");
37241                    self.write(")");
37242                }
37243            }
37244        }
37245        Ok(())
37246    }
37247
37248    fn generate_ts_or_ds_to_time(&mut self, e: &TsOrDsToTime) -> Result<()> {
37249        // TS_OR_DS_TO_TIME(this, [format])
37250        self.write_keyword("TS_OR_DS_TO_TIME");
37251        self.write("(");
37252        self.generate_expression(&e.this)?;
37253        if let Some(format) = &e.format {
37254            self.write(", '");
37255            self.write(format);
37256            self.write("'");
37257        }
37258        self.write(")");
37259        Ok(())
37260    }
37261
37262    fn generate_unhex(&mut self, e: &Unhex) -> Result<()> {
37263        // UNHEX(this, [expression])
37264        self.write_keyword("UNHEX");
37265        self.write("(");
37266        self.generate_expression(&e.this)?;
37267        if let Some(expression) = &e.expression {
37268            self.write(", ");
37269            self.generate_expression(expression)?;
37270        }
37271        self.write(")");
37272        Ok(())
37273    }
37274
37275    fn generate_unicode_string(&mut self, e: &UnicodeString) -> Result<()> {
37276        // U&this [UESCAPE escape]
37277        self.write("U&");
37278        self.generate_expression(&e.this)?;
37279        if let Some(escape) = &e.escape {
37280            self.write_space();
37281            self.write_keyword("UESCAPE");
37282            self.write_space();
37283            self.generate_expression(escape)?;
37284        }
37285        Ok(())
37286    }
37287
37288    fn generate_uniform(&mut self, e: &Uniform) -> Result<()> {
37289        // UNIFORM(this, expression, [gen], [seed])
37290        self.write_keyword("UNIFORM");
37291        self.write("(");
37292        self.generate_expression(&e.this)?;
37293        self.write(", ");
37294        self.generate_expression(&e.expression)?;
37295        if let Some(gen) = &e.gen {
37296            self.write(", ");
37297            self.generate_expression(gen)?;
37298        }
37299        if let Some(seed) = &e.seed {
37300            self.write(", ");
37301            self.generate_expression(seed)?;
37302        }
37303        self.write(")");
37304        Ok(())
37305    }
37306
37307    fn generate_unique_column_constraint(&mut self, e: &UniqueColumnConstraint) -> Result<()> {
37308        // UNIQUE [NULLS NOT DISTINCT] [this] [index_type] [on_conflict] [options]
37309        self.write_keyword("UNIQUE");
37310        // Output NULLS NOT DISTINCT if nulls is set (PostgreSQL 15+ feature)
37311        if e.nulls.is_some() {
37312            self.write(" NULLS NOT DISTINCT");
37313        }
37314        if let Some(this) = &e.this {
37315            self.write_space();
37316            self.generate_expression(this)?;
37317        }
37318        if let Some(index_type) = &e.index_type {
37319            self.write(" USING ");
37320            self.generate_expression(index_type)?;
37321        }
37322        if let Some(on_conflict) = &e.on_conflict {
37323            self.write_space();
37324            self.generate_expression(on_conflict)?;
37325        }
37326        for opt in &e.options {
37327            self.write_space();
37328            self.generate_expression(opt)?;
37329        }
37330        Ok(())
37331    }
37332
37333    fn generate_unique_key_property(&mut self, e: &UniqueKeyProperty) -> Result<()> {
37334        // UNIQUE KEY (expressions)
37335        self.write_keyword("UNIQUE KEY");
37336        self.write(" (");
37337        for (i, expr) in e.expressions.iter().enumerate() {
37338            if i > 0 {
37339                self.write(", ");
37340            }
37341            self.generate_expression(expr)?;
37342        }
37343        self.write(")");
37344        Ok(())
37345    }
37346
37347    fn generate_rollup_property(&mut self, e: &RollupProperty) -> Result<()> {
37348        // ROLLUP (r1(col1, col2), r2(col1))
37349        self.write_keyword("ROLLUP");
37350        self.write(" (");
37351        for (i, index) in e.expressions.iter().enumerate() {
37352            if i > 0 {
37353                self.write(", ");
37354            }
37355            self.generate_identifier(&index.name)?;
37356            self.write("(");
37357            for (j, col) in index.expressions.iter().enumerate() {
37358                if j > 0 {
37359                    self.write(", ");
37360                }
37361                self.generate_identifier(col)?;
37362            }
37363            self.write(")");
37364        }
37365        self.write(")");
37366        Ok(())
37367    }
37368
37369    fn generate_unix_to_str(&mut self, e: &UnixToStr) -> Result<()> {
37370        match self.config.dialect {
37371            Some(DialectType::DuckDB) => {
37372                // DuckDB: STRFTIME(TO_TIMESTAMP(value), format)
37373                self.write_keyword("STRFTIME");
37374                self.write("(");
37375                self.write_keyword("TO_TIMESTAMP");
37376                self.write("(");
37377                self.generate_expression(&e.this)?;
37378                self.write("), '");
37379                if let Some(format) = &e.format {
37380                    self.write(format);
37381                }
37382                self.write("')");
37383            }
37384            Some(DialectType::Hive) => {
37385                // Hive: FROM_UNIXTIME(value, format) - elide format when it's the default
37386                self.write_keyword("FROM_UNIXTIME");
37387                self.write("(");
37388                self.generate_expression(&e.this)?;
37389                if let Some(format) = &e.format {
37390                    if format != "yyyy-MM-dd HH:mm:ss" {
37391                        self.write(", '");
37392                        self.write(format);
37393                        self.write("'");
37394                    }
37395                }
37396                self.write(")");
37397            }
37398            Some(DialectType::Presto) | Some(DialectType::Trino) => {
37399                // Presto: DATE_FORMAT(FROM_UNIXTIME(value), format)
37400                self.write_keyword("DATE_FORMAT");
37401                self.write("(");
37402                self.write_keyword("FROM_UNIXTIME");
37403                self.write("(");
37404                self.generate_expression(&e.this)?;
37405                self.write("), '");
37406                if let Some(format) = &e.format {
37407                    self.write(format);
37408                }
37409                self.write("')");
37410            }
37411            Some(DialectType::Spark) | Some(DialectType::Databricks) => {
37412                // Spark: FROM_UNIXTIME(value, format)
37413                self.write_keyword("FROM_UNIXTIME");
37414                self.write("(");
37415                self.generate_expression(&e.this)?;
37416                if let Some(format) = &e.format {
37417                    self.write(", '");
37418                    self.write(format);
37419                    self.write("'");
37420                }
37421                self.write(")");
37422            }
37423            _ => {
37424                // Default: UNIX_TO_STR(this, [format])
37425                self.write_keyword("UNIX_TO_STR");
37426                self.write("(");
37427                self.generate_expression(&e.this)?;
37428                if let Some(format) = &e.format {
37429                    self.write(", '");
37430                    self.write(format);
37431                    self.write("'");
37432                }
37433                self.write(")");
37434            }
37435        }
37436        Ok(())
37437    }
37438
37439    fn generate_unix_to_time(&mut self, e: &UnixToTime) -> Result<()> {
37440        use crate::dialects::DialectType;
37441        let scale = e.scale.unwrap_or(0); // 0 = seconds
37442
37443        match self.config.dialect {
37444            Some(DialectType::Snowflake) => {
37445                // Snowflake: TO_TIMESTAMP(value[, scale]) - skip scale for seconds (0)
37446                self.write_keyword("TO_TIMESTAMP");
37447                self.write("(");
37448                self.generate_expression(&e.this)?;
37449                if let Some(s) = e.scale {
37450                    if s > 0 {
37451                        self.write(", ");
37452                        self.write(&s.to_string());
37453                    }
37454                }
37455                self.write(")");
37456            }
37457            Some(DialectType::BigQuery) => {
37458                // BigQuery: TIMESTAMP_SECONDS(value) / TIMESTAMP_MILLIS(value)
37459                // or TIMESTAMP_SECONDS(CAST(value / POWER(10, scale) AS INT64)) for other scales
37460                match scale {
37461                    0 => {
37462                        self.write_keyword("TIMESTAMP_SECONDS");
37463                        self.write("(");
37464                        self.generate_expression(&e.this)?;
37465                        self.write(")");
37466                    }
37467                    3 => {
37468                        self.write_keyword("TIMESTAMP_MILLIS");
37469                        self.write("(");
37470                        self.generate_expression(&e.this)?;
37471                        self.write(")");
37472                    }
37473                    6 => {
37474                        self.write_keyword("TIMESTAMP_MICROS");
37475                        self.write("(");
37476                        self.generate_expression(&e.this)?;
37477                        self.write(")");
37478                    }
37479                    _ => {
37480                        // TIMESTAMP_SECONDS(CAST(value / POWER(10, scale) AS INT64))
37481                        self.write_keyword("TIMESTAMP_SECONDS");
37482                        self.write("(CAST(");
37483                        self.generate_expression(&e.this)?;
37484                        self.write(&format!(" / POWER(10, {}) AS INT64))", scale));
37485                    }
37486                }
37487            }
37488            Some(DialectType::Spark) => {
37489                // Spark: CAST(FROM_UNIXTIME(value) AS TIMESTAMP) for scale=0
37490                // TIMESTAMP_MILLIS(value) for scale=3
37491                // TIMESTAMP_MICROS(value) for scale=6
37492                // TIMESTAMP_SECONDS(value / POWER(10, scale)) for other scales
37493                match scale {
37494                    0 => {
37495                        self.write_keyword("CAST");
37496                        self.write("(");
37497                        self.write_keyword("FROM_UNIXTIME");
37498                        self.write("(");
37499                        self.generate_expression(&e.this)?;
37500                        self.write(") ");
37501                        self.write_keyword("AS TIMESTAMP");
37502                        self.write(")");
37503                    }
37504                    3 => {
37505                        self.write_keyword("TIMESTAMP_MILLIS");
37506                        self.write("(");
37507                        self.generate_expression(&e.this)?;
37508                        self.write(")");
37509                    }
37510                    6 => {
37511                        self.write_keyword("TIMESTAMP_MICROS");
37512                        self.write("(");
37513                        self.generate_expression(&e.this)?;
37514                        self.write(")");
37515                    }
37516                    _ => {
37517                        self.write_keyword("TIMESTAMP_SECONDS");
37518                        self.write("(");
37519                        self.generate_expression(&e.this)?;
37520                        self.write(&format!(" / POWER(10, {}))", scale));
37521                    }
37522                }
37523            }
37524            Some(DialectType::Databricks) => {
37525                // Databricks: CAST(FROM_UNIXTIME(value) AS TIMESTAMP) for scale=0
37526                // TIMESTAMP_MILLIS(value) for scale=3
37527                // TIMESTAMP_MICROS(value) for scale=6
37528                match scale {
37529                    0 => {
37530                        self.write_keyword("CAST");
37531                        self.write("(");
37532                        self.write_keyword("FROM_UNIXTIME");
37533                        self.write("(");
37534                        self.generate_expression(&e.this)?;
37535                        self.write(") ");
37536                        self.write_keyword("AS TIMESTAMP");
37537                        self.write(")");
37538                    }
37539                    3 => {
37540                        self.write_keyword("TIMESTAMP_MILLIS");
37541                        self.write("(");
37542                        self.generate_expression(&e.this)?;
37543                        self.write(")");
37544                    }
37545                    6 => {
37546                        self.write_keyword("TIMESTAMP_MICROS");
37547                        self.write("(");
37548                        self.generate_expression(&e.this)?;
37549                        self.write(")");
37550                    }
37551                    _ => {
37552                        self.write_keyword("TIMESTAMP_SECONDS");
37553                        self.write("(");
37554                        self.generate_expression(&e.this)?;
37555                        self.write(&format!(" / POWER(10, {}))", scale));
37556                    }
37557                }
37558            }
37559            Some(DialectType::Hive) => {
37560                // Hive: FROM_UNIXTIME(value)
37561                if scale == 0 {
37562                    self.write_keyword("FROM_UNIXTIME");
37563                    self.write("(");
37564                    self.generate_expression(&e.this)?;
37565                    self.write(")");
37566                } else {
37567                    self.write_keyword("FROM_UNIXTIME");
37568                    self.write("(");
37569                    self.generate_expression(&e.this)?;
37570                    self.write(&format!(" / POWER(10, {})", scale));
37571                    self.write(")");
37572                }
37573            }
37574            Some(DialectType::Presto) | Some(DialectType::Trino) => {
37575                // Presto: FROM_UNIXTIME(CAST(value AS DOUBLE) / POW(10, scale)) for scale > 0
37576                // FROM_UNIXTIME(value) for scale=0
37577                if scale == 0 {
37578                    self.write_keyword("FROM_UNIXTIME");
37579                    self.write("(");
37580                    self.generate_expression(&e.this)?;
37581                    self.write(")");
37582                } else {
37583                    self.write_keyword("FROM_UNIXTIME");
37584                    self.write("(CAST(");
37585                    self.generate_expression(&e.this)?;
37586                    self.write(&format!(" AS DOUBLE) / POW(10, {}))", scale));
37587                }
37588            }
37589            Some(DialectType::DuckDB) => {
37590                // DuckDB: TO_TIMESTAMP(value) for scale=0
37591                // EPOCH_MS(value) for scale=3
37592                // MAKE_TIMESTAMP(value) for scale=6
37593                match scale {
37594                    0 => {
37595                        self.write_keyword("TO_TIMESTAMP");
37596                        self.write("(");
37597                        self.generate_expression(&e.this)?;
37598                        self.write(")");
37599                    }
37600                    3 => {
37601                        self.write_keyword("EPOCH_MS");
37602                        self.write("(");
37603                        self.generate_expression(&e.this)?;
37604                        self.write(")");
37605                    }
37606                    6 => {
37607                        self.write_keyword("MAKE_TIMESTAMP");
37608                        self.write("(");
37609                        self.generate_expression(&e.this)?;
37610                        self.write(")");
37611                    }
37612                    _ => {
37613                        self.write_keyword("TO_TIMESTAMP");
37614                        self.write("(");
37615                        self.generate_expression(&e.this)?;
37616                        self.write(&format!(" / POWER(10, {}))", scale));
37617                        self.write_keyword(" AT TIME ZONE");
37618                        self.write(" 'UTC'");
37619                    }
37620                }
37621            }
37622            Some(DialectType::Doris) | Some(DialectType::StarRocks) => {
37623                // Doris/StarRocks: FROM_UNIXTIME(value)
37624                self.write_keyword("FROM_UNIXTIME");
37625                self.write("(");
37626                self.generate_expression(&e.this)?;
37627                self.write(")");
37628            }
37629            Some(DialectType::Oracle) => {
37630                // Oracle: TO_DATE('1970-01-01', 'YYYY-MM-DD') + (x / 86400)
37631                self.write("TO_DATE('1970-01-01', 'YYYY-MM-DD') + (");
37632                self.generate_expression(&e.this)?;
37633                self.write(" / 86400)");
37634            }
37635            Some(DialectType::Redshift) => {
37636                // Redshift: (TIMESTAMP 'epoch' + value * INTERVAL '1 SECOND') for scale=0
37637                // (TIMESTAMP 'epoch' + (value / POWER(10, scale)) * INTERVAL '1 SECOND') for scale > 0
37638                self.write("(TIMESTAMP 'epoch' + ");
37639                if scale == 0 {
37640                    self.generate_expression(&e.this)?;
37641                } else {
37642                    self.write("(");
37643                    self.generate_expression(&e.this)?;
37644                    self.write(&format!(" / POWER(10, {}))", scale));
37645                }
37646                self.write(" * INTERVAL '1 SECOND')");
37647            }
37648            Some(DialectType::Exasol) => {
37649                // Exasol: FROM_POSIX_TIME(value)
37650                self.write_keyword("FROM_POSIX_TIME");
37651                self.write("(");
37652                self.generate_expression(&e.this)?;
37653                self.write(")");
37654            }
37655            _ => {
37656                // Default: TO_TIMESTAMP(value[, scale])
37657                self.write_keyword("TO_TIMESTAMP");
37658                self.write("(");
37659                self.generate_expression(&e.this)?;
37660                if let Some(s) = e.scale {
37661                    self.write(", ");
37662                    self.write(&s.to_string());
37663                }
37664                self.write(")");
37665            }
37666        }
37667        Ok(())
37668    }
37669
37670    fn generate_unpivot_columns(&mut self, e: &UnpivotColumns) -> Result<()> {
37671        // NAME col VALUE col1, col2, ...
37672        if !matches!(&*e.this, Expression::Null(_)) {
37673            self.write_keyword("NAME");
37674            self.write_space();
37675            self.generate_expression(&e.this)?;
37676        }
37677        if !e.expressions.is_empty() {
37678            self.write_space();
37679            self.write_keyword("VALUE");
37680            self.write_space();
37681            for (i, expr) in e.expressions.iter().enumerate() {
37682                if i > 0 {
37683                    self.write(", ");
37684                }
37685                self.generate_expression(expr)?;
37686            }
37687        }
37688        Ok(())
37689    }
37690
37691    fn generate_user_defined_function(&mut self, e: &UserDefinedFunction) -> Result<()> {
37692        // this(expressions) or (this)(expressions)
37693        if e.wrapped.is_some() {
37694            self.write("(");
37695        }
37696        self.generate_expression(&e.this)?;
37697        if e.wrapped.is_some() {
37698            self.write(")");
37699        }
37700        self.write("(");
37701        for (i, expr) in e.expressions.iter().enumerate() {
37702            if i > 0 {
37703                self.write(", ");
37704            }
37705            self.generate_expression(expr)?;
37706        }
37707        self.write(")");
37708        Ok(())
37709    }
37710
37711    fn generate_using_template_property(&mut self, e: &UsingTemplateProperty) -> Result<()> {
37712        // USING TEMPLATE this
37713        self.write_keyword("USING TEMPLATE");
37714        self.write_space();
37715        self.generate_expression(&e.this)?;
37716        Ok(())
37717    }
37718
37719    fn generate_utc_time(&mut self, _e: &UtcTime) -> Result<()> {
37720        // UTC_TIME
37721        self.write_keyword("UTC_TIME");
37722        Ok(())
37723    }
37724
37725    fn generate_utc_timestamp(&mut self, _e: &UtcTimestamp) -> Result<()> {
37726        if matches!(
37727            self.config.dialect,
37728            Some(crate::dialects::DialectType::ClickHouse)
37729        ) {
37730            self.write_keyword("CURRENT_TIMESTAMP");
37731            self.write("('UTC')");
37732        } else {
37733            self.write_keyword("UTC_TIMESTAMP");
37734        }
37735        Ok(())
37736    }
37737
37738    fn generate_uuid(&mut self, e: &Uuid) -> Result<()> {
37739        use crate::dialects::DialectType;
37740        // Choose UUID function name based on target dialect
37741        let func_name = match self.config.dialect {
37742            Some(DialectType::Snowflake) => "UUID_STRING",
37743            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift) => "GEN_RANDOM_UUID",
37744            Some(DialectType::BigQuery) => "GENERATE_UUID",
37745            _ => {
37746                if let Some(name) = &e.name {
37747                    name.as_str()
37748                } else {
37749                    "UUID"
37750                }
37751            }
37752        };
37753        self.write_keyword(func_name);
37754        self.write("(");
37755        if let Some(this) = &e.this {
37756            self.generate_expression(this)?;
37757        }
37758        self.write(")");
37759        Ok(())
37760    }
37761
37762    fn generate_var_map(&mut self, e: &VarMap) -> Result<()> {
37763        // MAP(key1, value1, key2, value2, ...)
37764        self.write_keyword("MAP");
37765        self.write("(");
37766        let mut first = true;
37767        for (k, v) in e.keys.iter().zip(e.values.iter()) {
37768            if !first {
37769                self.write(", ");
37770            }
37771            self.generate_expression(k)?;
37772            self.write(", ");
37773            self.generate_expression(v)?;
37774            first = false;
37775        }
37776        self.write(")");
37777        Ok(())
37778    }
37779
37780    fn generate_vector_search(&mut self, e: &VectorSearch) -> Result<()> {
37781        // VECTOR_SEARCH(this, column_to_search, query_table, query_column_to_search, top_k, distance_type, ...)
37782        self.write_keyword("VECTOR_SEARCH");
37783        self.write("(");
37784        self.generate_expression(&e.this)?;
37785        if let Some(col) = &e.column_to_search {
37786            self.write(", ");
37787            self.generate_expression(col)?;
37788        }
37789        if let Some(query_table) = &e.query_table {
37790            self.write(", ");
37791            self.generate_expression(query_table)?;
37792        }
37793        if let Some(query_col) = &e.query_column_to_search {
37794            self.write(", ");
37795            self.generate_expression(query_col)?;
37796        }
37797        if let Some(top_k) = &e.top_k {
37798            self.write(", ");
37799            self.generate_expression(top_k)?;
37800        }
37801        if let Some(dist_type) = &e.distance_type {
37802            self.write(", ");
37803            self.generate_expression(dist_type)?;
37804        }
37805        self.write(")");
37806        Ok(())
37807    }
37808
37809    fn generate_version(&mut self, e: &Version) -> Result<()> {
37810        // Python: f"FOR {expression.name} {kind} {expr}"
37811        // e.this = Identifier("TIMESTAMP" or "VERSION")
37812        // e.kind = "AS OF" (or "BETWEEN", etc.)
37813        // e.expression = the value expression
37814        // Hive does NOT use the FOR prefix for time travel
37815        use crate::dialects::DialectType;
37816        let skip_for = matches!(
37817            self.config.dialect,
37818            Some(DialectType::Hive) | Some(DialectType::Spark) | Some(DialectType::Databricks)
37819        );
37820        if !skip_for {
37821            self.write_keyword("FOR");
37822            self.write_space();
37823        }
37824        // Extract the name from this (which is an Identifier expression)
37825        match e.this.as_ref() {
37826            Expression::Identifier(ident) => {
37827                self.write_keyword(&ident.name);
37828            }
37829            _ => {
37830                self.generate_expression(&e.this)?;
37831            }
37832        }
37833        self.write_space();
37834        self.write_keyword(&e.kind);
37835        if let Some(expression) = &e.expression {
37836            self.write_space();
37837            self.generate_expression(expression)?;
37838        }
37839        Ok(())
37840    }
37841
37842    fn generate_view_attribute_property(&mut self, e: &ViewAttributeProperty) -> Result<()> {
37843        // Python: return self.sql(expression, "this")
37844        self.generate_expression(&e.this)?;
37845        Ok(())
37846    }
37847
37848    fn generate_volatile_property(&mut self, e: &VolatileProperty) -> Result<()> {
37849        // Python: return "VOLATILE" if expression.args.get("this") is None else "NOT VOLATILE"
37850        if e.this.is_some() {
37851            self.write_keyword("NOT VOLATILE");
37852        } else {
37853            self.write_keyword("VOLATILE");
37854        }
37855        Ok(())
37856    }
37857
37858    fn generate_watermark_column_constraint(
37859        &mut self,
37860        e: &WatermarkColumnConstraint,
37861    ) -> Result<()> {
37862        // Python: f"WATERMARK FOR {self.sql(expression, 'this')} AS {self.sql(expression, 'expression')}"
37863        self.write_keyword("WATERMARK FOR");
37864        self.write_space();
37865        self.generate_expression(&e.this)?;
37866        self.write_space();
37867        self.write_keyword("AS");
37868        self.write_space();
37869        self.generate_expression(&e.expression)?;
37870        Ok(())
37871    }
37872
37873    fn generate_week(&mut self, e: &Week) -> Result<()> {
37874        // Python: return self.func("WEEK", expression.this, expression.args.get("mode"))
37875        self.write_keyword("WEEK");
37876        self.write("(");
37877        self.generate_expression(&e.this)?;
37878        if let Some(mode) = &e.mode {
37879            self.write(", ");
37880            self.generate_expression(mode)?;
37881        }
37882        self.write(")");
37883        Ok(())
37884    }
37885
37886    fn generate_when(&mut self, e: &When) -> Result<()> {
37887        // Python: WHEN {matched}{source}{condition} THEN {then}
37888        // matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED"
37889        // source = " BY SOURCE" if MATCHED_BY_SOURCE and expression.args.get("source") else ""
37890        self.write_keyword("WHEN");
37891        self.write_space();
37892
37893        // Check if matched
37894        if let Some(matched) = &e.matched {
37895            // Check the expression - if it's a boolean true, use MATCHED, otherwise NOT MATCHED
37896            match matched.as_ref() {
37897                Expression::Boolean(b) if b.value => {
37898                    self.write_keyword("MATCHED");
37899                }
37900                _ => {
37901                    self.write_keyword("NOT MATCHED");
37902                }
37903            }
37904        } else {
37905            self.write_keyword("NOT MATCHED");
37906        }
37907
37908        // BY SOURCE / BY TARGET
37909        // source = Boolean(true) means BY SOURCE, Boolean(false) means BY TARGET
37910        // BY TARGET is the default and typically omitted in output
37911        // Only emit if the dialect supports BY SOURCE syntax
37912        if self.config.matched_by_source {
37913            if let Some(source) = &e.source {
37914                if let Expression::Boolean(b) = source.as_ref() {
37915                    if b.value {
37916                        // BY SOURCE
37917                        self.write_space();
37918                        self.write_keyword("BY SOURCE");
37919                    }
37920                    // BY TARGET (b.value == false) is omitted as it's the default
37921                } else {
37922                    // For non-boolean source, output as BY SOURCE (legacy behavior)
37923                    self.write_space();
37924                    self.write_keyword("BY SOURCE");
37925                }
37926            }
37927        }
37928
37929        // Condition
37930        if let Some(condition) = &e.condition {
37931            self.write_space();
37932            self.write_keyword("AND");
37933            self.write_space();
37934            self.generate_expression(condition)?;
37935        }
37936
37937        self.write_space();
37938        self.write_keyword("THEN");
37939        self.write_space();
37940
37941        // Generate the then expression (could be INSERT, UPDATE, DELETE)
37942        // MERGE actions are stored as Tuples with the action keyword as first element
37943        self.generate_merge_action(&e.then)?;
37944
37945        Ok(())
37946    }
37947
37948    fn generate_merge_action(&mut self, action: &Expression) -> Result<()> {
37949        match action {
37950            Expression::Tuple(tuple) => {
37951                let elements = &tuple.expressions;
37952                if elements.is_empty() {
37953                    return self.generate_expression(action);
37954                }
37955                // Check if first element is a Var (INSERT, UPDATE, DELETE, etc.)
37956                match &elements[0] {
37957                    Expression::Var(v) if v.this == "INSERT" => {
37958                        self.write_keyword("INSERT");
37959                        // Spark: INSERT * (insert all columns)
37960                        if elements.len() > 1 && matches!(&elements[1], Expression::Star(_)) {
37961                            self.write(" *");
37962                            if let Some(Expression::Where(w)) = elements.get(2) {
37963                                self.write_space();
37964                                self.generate_where(w)?;
37965                            }
37966                        } else {
37967                            let mut values_idx = 1;
37968                            // Check if second element is column list (Tuple)
37969                            if elements.len() > 1 {
37970                                if let Expression::Tuple(cols) = &elements[1] {
37971                                    // Could be columns or values - if there's a third element, second is columns
37972                                    if elements.len() > 2 {
37973                                        // Second is columns, third is values
37974                                        self.write(" (");
37975                                        for (i, col) in cols.expressions.iter().enumerate() {
37976                                            if i > 0 {
37977                                                self.write(", ");
37978                                            }
37979                                            // Strip MERGE target qualifiers from INSERT column list
37980                                            if !self.merge_strip_qualifiers.is_empty() {
37981                                                let stripped = self.strip_merge_qualifier(col);
37982                                                self.generate_expression(&stripped)?;
37983                                            } else {
37984                                                self.generate_expression(col)?;
37985                                            }
37986                                        }
37987                                        self.write(")");
37988                                        values_idx = 2;
37989                                    } else {
37990                                        // Only two elements: INSERT + values (no explicit columns)
37991                                        values_idx = 1;
37992                                    }
37993                                }
37994                            }
37995                            let mut next_idx = values_idx;
37996                            // Generate VALUES clause
37997                            if values_idx < elements.len()
37998                                && !matches!(&elements[values_idx], Expression::Where(_))
37999                            {
38000                                // Check if it's INSERT ROW (BigQuery) — no VALUES keyword needed
38001                                let is_row = matches!(&elements[values_idx], Expression::Var(v) if v.this == "ROW");
38002                                if !is_row {
38003                                    self.write_space();
38004                                    self.write_keyword("VALUES");
38005                                }
38006                                self.write(" ");
38007                                if let Expression::Tuple(vals) = &elements[values_idx] {
38008                                    self.write("(");
38009                                    for (i, val) in vals.expressions.iter().enumerate() {
38010                                        if i > 0 {
38011                                            self.write(", ");
38012                                        }
38013                                        self.generate_expression(val)?;
38014                                    }
38015                                    self.write(")");
38016                                } else {
38017                                    self.generate_expression(&elements[values_idx])?;
38018                                }
38019                                next_idx += 1;
38020                            }
38021                            if let Some(Expression::Where(w)) = elements.get(next_idx) {
38022                                self.write_space();
38023                                self.generate_where(w)?;
38024                            }
38025                        } // close else for INSERT * check
38026                    }
38027                    Expression::Var(v) if v.this == "UPDATE" => {
38028                        self.write_keyword("UPDATE");
38029                        // Spark: UPDATE * (update all columns)
38030                        if elements.len() > 1 && matches!(&elements[1], Expression::Star(_)) {
38031                            self.write(" *");
38032                            if let Some(Expression::Where(w)) = elements.get(2) {
38033                                self.write_space();
38034                                self.generate_where(w)?;
38035                            }
38036                        } else if elements.len() > 1 {
38037                            self.write_space();
38038                            self.write_keyword("SET");
38039                            // In pretty mode, put assignments on next line with extra indent
38040                            if self.config.pretty {
38041                                self.write_newline();
38042                                self.indent_level += 1;
38043                                self.write_indent();
38044                            } else {
38045                                self.write_space();
38046                            }
38047                            if let Expression::Tuple(assignments) = &elements[1] {
38048                                for (i, assignment) in assignments.expressions.iter().enumerate() {
38049                                    if i > 0 {
38050                                        if self.config.pretty {
38051                                            self.write(",");
38052                                            self.write_newline();
38053                                            self.write_indent();
38054                                        } else {
38055                                            self.write(", ");
38056                                        }
38057                                    }
38058                                    // Strip MERGE target qualifiers from left side of UPDATE SET
38059                                    if !self.merge_strip_qualifiers.is_empty() {
38060                                        self.generate_merge_set_assignment(assignment)?;
38061                                    } else {
38062                                        self.generate_expression(assignment)?;
38063                                    }
38064                                }
38065                            } else {
38066                                self.generate_expression(&elements[1])?;
38067                            }
38068                            if self.config.pretty {
38069                                self.indent_level -= 1;
38070                            }
38071                            if let Some(Expression::Where(w)) = elements.get(2) {
38072                                self.write_space();
38073                                self.generate_where(w)?;
38074                            }
38075                        }
38076                    }
38077                    Expression::Var(v) if v.this == "DELETE" => {
38078                        self.write_keyword("DELETE");
38079                        if let Some(Expression::Where(w)) = elements.get(1) {
38080                            self.write_space();
38081                            self.generate_where(w)?;
38082                        }
38083                    }
38084                    _ => {
38085                        // Fallback: generic tuple generation
38086                        self.generate_expression(action)?;
38087                    }
38088                }
38089            }
38090            Expression::Var(v)
38091                if v.this == "INSERT"
38092                    || v.this == "UPDATE"
38093                    || v.this == "DELETE"
38094                    || v.this == "DO NOTHING" =>
38095            {
38096                self.write_keyword(&v.this);
38097            }
38098            _ => {
38099                self.generate_expression(action)?;
38100            }
38101        }
38102        Ok(())
38103    }
38104
38105    /// Generate a MERGE UPDATE SET assignment, stripping target table qualifier from left side
38106    fn generate_merge_set_assignment(&mut self, assignment: &Expression) -> Result<()> {
38107        match assignment {
38108            Expression::Eq(eq) => {
38109                // Strip qualifier from the left side if it matches a MERGE target name
38110                let stripped_left = self.strip_merge_qualifier(&eq.left);
38111                self.generate_expression(&stripped_left)?;
38112                self.write(" = ");
38113                self.generate_expression(&eq.right)?;
38114                Ok(())
38115            }
38116            other => self.generate_expression(other),
38117        }
38118    }
38119
38120    /// Strip table qualifier from a column reference if it matches a MERGE target name
38121    fn strip_merge_qualifier(&self, expr: &Expression) -> Expression {
38122        match expr {
38123            Expression::Column(col) => {
38124                if let Some(ref table_ident) = col.table {
38125                    if self
38126                        .merge_strip_qualifiers
38127                        .iter()
38128                        .any(|n| n.eq_ignore_ascii_case(&table_ident.name))
38129                    {
38130                        // Strip the table qualifier
38131                        let mut col = col.clone();
38132                        col.table = None;
38133                        return Expression::Column(col);
38134                    }
38135                }
38136                expr.clone()
38137            }
38138            Expression::Dot(dot) => {
38139                // table.column -> column (strip qualifier)
38140                if let Expression::Identifier(id) = &dot.this {
38141                    if self
38142                        .merge_strip_qualifiers
38143                        .iter()
38144                        .any(|n| n.eq_ignore_ascii_case(&id.name))
38145                    {
38146                        return Expression::Identifier(dot.field.clone());
38147                    }
38148                }
38149                expr.clone()
38150            }
38151            _ => expr.clone(),
38152        }
38153    }
38154
38155    fn generate_whens(&mut self, e: &Whens) -> Result<()> {
38156        // Python: return self.expressions(expression, sep=" ", indent=False)
38157        for (i, expr) in e.expressions.iter().enumerate() {
38158            if i > 0 {
38159                // In pretty mode, each WHEN clause on its own line
38160                if self.config.pretty {
38161                    self.write_newline();
38162                    self.write_indent();
38163                } else {
38164                    self.write_space();
38165                }
38166            }
38167            self.generate_expression(expr)?;
38168        }
38169        Ok(())
38170    }
38171
38172    fn generate_where(&mut self, e: &Where) -> Result<()> {
38173        // Python: return f"{self.seg('WHERE')}{self.sep()}{this}"
38174        self.write_keyword("WHERE");
38175        self.write_space();
38176        self.generate_expression(&e.this)?;
38177        Ok(())
38178    }
38179
38180    fn generate_width_bucket(&mut self, e: &WidthBucket) -> Result<()> {
38181        // Python: return self.func("WIDTH_BUCKET", expression.this, ...)
38182        self.write_keyword("WIDTH_BUCKET");
38183        self.write("(");
38184        self.generate_expression(&e.this)?;
38185        if let Some(min_value) = &e.min_value {
38186            self.write(", ");
38187            self.generate_expression(min_value)?;
38188        }
38189        if let Some(max_value) = &e.max_value {
38190            self.write(", ");
38191            self.generate_expression(max_value)?;
38192        }
38193        if let Some(num_buckets) = &e.num_buckets {
38194            self.write(", ");
38195            self.generate_expression(num_buckets)?;
38196        }
38197        self.write(")");
38198        Ok(())
38199    }
38200
38201    fn generate_window(&mut self, e: &WindowSpec) -> Result<()> {
38202        // Window specification: PARTITION BY ... ORDER BY ... frame
38203        self.generate_window_spec(e)
38204    }
38205
38206    fn generate_window_spec(&mut self, e: &WindowSpec) -> Result<()> {
38207        // Window specification: PARTITION BY ... ORDER BY ... frame
38208        let mut has_content = false;
38209
38210        // PARTITION BY
38211        if !e.partition_by.is_empty() {
38212            self.write_keyword("PARTITION BY");
38213            self.write_space();
38214            for (i, expr) in e.partition_by.iter().enumerate() {
38215                if i > 0 {
38216                    self.write(", ");
38217                }
38218                self.generate_expression(expr)?;
38219            }
38220            has_content = true;
38221        }
38222
38223        // ORDER BY
38224        if !e.order_by.is_empty() {
38225            if has_content {
38226                self.write_space();
38227            }
38228            self.write_keyword("ORDER BY");
38229            self.write_space();
38230            for (i, ordered) in e.order_by.iter().enumerate() {
38231                if i > 0 {
38232                    self.write(", ");
38233                }
38234                self.generate_expression(&ordered.this)?;
38235                if ordered.desc {
38236                    self.write_space();
38237                    self.write_keyword("DESC");
38238                } else if ordered.explicit_asc {
38239                    self.write_space();
38240                    self.write_keyword("ASC");
38241                }
38242                if let Some(nulls_first) = ordered.nulls_first {
38243                    self.write_space();
38244                    self.write_keyword("NULLS");
38245                    self.write_space();
38246                    if nulls_first {
38247                        self.write_keyword("FIRST");
38248                    } else {
38249                        self.write_keyword("LAST");
38250                    }
38251                }
38252            }
38253            has_content = true;
38254        }
38255
38256        // Frame specification
38257        if let Some(frame) = &e.frame {
38258            if has_content {
38259                self.write_space();
38260            }
38261            self.generate_window_frame(frame)?;
38262        }
38263
38264        Ok(())
38265    }
38266
38267    fn generate_with_data_property(&mut self, e: &WithDataProperty) -> Result<()> {
38268        // Python: f"WITH {'NO ' if expression.args.get('no') else ''}DATA"
38269        self.write_keyword("WITH");
38270        self.write_space();
38271        if e.no.is_some() {
38272            self.write_keyword("NO");
38273            self.write_space();
38274        }
38275        self.write_keyword("DATA");
38276
38277        // statistics
38278        if let Some(statistics) = &e.statistics {
38279            self.write_space();
38280            self.write_keyword("AND");
38281            self.write_space();
38282            // Check if statistics is true or false
38283            match statistics.as_ref() {
38284                Expression::Boolean(b) if !b.value => {
38285                    self.write_keyword("NO");
38286                    self.write_space();
38287                }
38288                _ => {}
38289            }
38290            self.write_keyword("STATISTICS");
38291        }
38292        Ok(())
38293    }
38294
38295    fn generate_with_fill(&mut self, e: &WithFill) -> Result<()> {
38296        // Python: f"WITH FILL{from_sql}{to_sql}{step_sql}{interpolate}"
38297        self.write_keyword("WITH FILL");
38298
38299        if let Some(from_) = &e.from_ {
38300            self.write_space();
38301            self.write_keyword("FROM");
38302            self.write_space();
38303            self.generate_expression(from_)?;
38304        }
38305
38306        if let Some(to) = &e.to {
38307            self.write_space();
38308            self.write_keyword("TO");
38309            self.write_space();
38310            self.generate_expression(to)?;
38311        }
38312
38313        if let Some(step) = &e.step {
38314            self.write_space();
38315            self.write_keyword("STEP");
38316            self.write_space();
38317            self.generate_expression(step)?;
38318        }
38319
38320        if let Some(staleness) = &e.staleness {
38321            self.write_space();
38322            self.write_keyword("STALENESS");
38323            self.write_space();
38324            self.generate_expression(staleness)?;
38325        }
38326
38327        if let Some(interpolate) = &e.interpolate {
38328            self.write_space();
38329            self.write_keyword("INTERPOLATE");
38330            self.write(" (");
38331            // INTERPOLATE items use reversed alias format: name AS expression
38332            self.generate_interpolate_item(interpolate)?;
38333            self.write(")");
38334        }
38335
38336        Ok(())
38337    }
38338
38339    /// Generate INTERPOLATE items with reversed alias format (name AS expression)
38340    fn generate_interpolate_item(&mut self, expr: &Expression) -> Result<()> {
38341        match expr {
38342            Expression::Alias(alias) => {
38343                // Output as: alias_name AS expression
38344                self.generate_identifier(&alias.alias)?;
38345                self.write_space();
38346                self.write_keyword("AS");
38347                self.write_space();
38348                self.generate_expression(&alias.this)?;
38349            }
38350            Expression::Tuple(tuple) => {
38351                for (i, item) in tuple.expressions.iter().enumerate() {
38352                    if i > 0 {
38353                        self.write(", ");
38354                    }
38355                    self.generate_interpolate_item(item)?;
38356                }
38357            }
38358            other => {
38359                self.generate_expression(other)?;
38360            }
38361        }
38362        Ok(())
38363    }
38364
38365    fn generate_with_journal_table_property(&mut self, e: &WithJournalTableProperty) -> Result<()> {
38366        // Python: return f"WITH JOURNAL TABLE={self.sql(expression, 'this')}"
38367        self.write_keyword("WITH JOURNAL TABLE");
38368        self.write("=");
38369        self.generate_expression(&e.this)?;
38370        Ok(())
38371    }
38372
38373    fn generate_with_operator(&mut self, e: &WithOperator) -> Result<()> {
38374        // Python: return f"{self.sql(expression, 'this')} WITH {self.sql(expression, 'op')}"
38375        self.generate_expression(&e.this)?;
38376        self.write_space();
38377        self.write_keyword("WITH");
38378        self.write_space();
38379        self.write_keyword(&e.op);
38380        Ok(())
38381    }
38382
38383    fn generate_with_procedure_options(&mut self, e: &WithProcedureOptions) -> Result<()> {
38384        // Python: return f"WITH {self.expressions(expression, flat=True)}"
38385        self.write_keyword("WITH");
38386        self.write_space();
38387        for (i, expr) in e.expressions.iter().enumerate() {
38388            if i > 0 {
38389                self.write(", ");
38390            }
38391            self.generate_expression(expr)?;
38392        }
38393        Ok(())
38394    }
38395
38396    fn generate_with_schema_binding_property(
38397        &mut self,
38398        e: &WithSchemaBindingProperty,
38399    ) -> Result<()> {
38400        // Python: return f"WITH {self.sql(expression, 'this')}"
38401        self.write_keyword("WITH");
38402        self.write_space();
38403        self.generate_expression(&e.this)?;
38404        Ok(())
38405    }
38406
38407    fn generate_with_system_versioning_property(
38408        &mut self,
38409        e: &WithSystemVersioningProperty,
38410    ) -> Result<()> {
38411        // Python: complex logic for SYSTEM_VERSIONING with options
38412        // SYSTEM_VERSIONING=ON(HISTORY_TABLE=..., DATA_CONSISTENCY_CHECK=..., HISTORY_RETENTION_PERIOD=...)
38413        // or SYSTEM_VERSIONING=ON/OFF
38414        // with WITH(...) wrapper if with_ is set
38415
38416        let mut parts = Vec::new();
38417
38418        if let Some(this) = &e.this {
38419            // HISTORY_TABLE=...
38420            let mut s = String::from("HISTORY_TABLE=");
38421            let mut gen = Generator::new();
38422            gen.generate_expression(this)?;
38423            s.push_str(&gen.output);
38424            parts.push(s);
38425        }
38426
38427        if let Some(data_consistency) = &e.data_consistency {
38428            let mut s = String::from("DATA_CONSISTENCY_CHECK=");
38429            let mut gen = Generator::new();
38430            gen.generate_expression(data_consistency)?;
38431            s.push_str(&gen.output);
38432            parts.push(s);
38433        }
38434
38435        if let Some(retention_period) = &e.retention_period {
38436            let mut s = String::from("HISTORY_RETENTION_PERIOD=");
38437            let mut gen = Generator::new();
38438            gen.generate_expression(retention_period)?;
38439            s.push_str(&gen.output);
38440            parts.push(s);
38441        }
38442
38443        self.write_keyword("SYSTEM_VERSIONING");
38444        self.write("=");
38445
38446        if !parts.is_empty() {
38447            self.write_keyword("ON");
38448            self.write("(");
38449            self.write(&parts.join(", "));
38450            self.write(")");
38451        } else if e.on.is_some() {
38452            self.write_keyword("ON");
38453        } else {
38454            self.write_keyword("OFF");
38455        }
38456
38457        // Wrap in WITH(...) if with_ is set
38458        if e.with_.is_some() {
38459            let inner = self.output.clone();
38460            self.output.clear();
38461            self.write("WITH(");
38462            self.write(&inner);
38463            self.write(")");
38464        }
38465
38466        Ok(())
38467    }
38468
38469    fn generate_with_table_hint(&mut self, e: &WithTableHint) -> Result<()> {
38470        // Python: f"WITH ({self.expressions(expression, flat=True)})"
38471        self.write_keyword("WITH");
38472        self.write(" (");
38473        for (i, expr) in e.expressions.iter().enumerate() {
38474            if i > 0 {
38475                self.write(", ");
38476            }
38477            self.generate_expression(expr)?;
38478        }
38479        self.write(")");
38480        Ok(())
38481    }
38482
38483    fn generate_xml_element(&mut self, e: &XMLElement) -> Result<()> {
38484        // Python: prefix = "EVALNAME" if expression.args.get("evalname") else "NAME"
38485        // return self.func("XMLELEMENT", name, *expression.expressions)
38486        self.write_keyword("XMLELEMENT");
38487        self.write("(");
38488
38489        if e.evalname.is_some() {
38490            self.write_keyword("EVALNAME");
38491        } else {
38492            self.write_keyword("NAME");
38493        }
38494        self.write_space();
38495        self.generate_expression(&e.this)?;
38496
38497        for expr in &e.expressions {
38498            self.write(", ");
38499            self.generate_expression(expr)?;
38500        }
38501        self.write(")");
38502        Ok(())
38503    }
38504
38505    fn generate_xml_get(&mut self, e: &XMLGet) -> Result<()> {
38506        // XMLGET(this, expression [, instance])
38507        self.write_keyword("XMLGET");
38508        self.write("(");
38509        self.generate_expression(&e.this)?;
38510        self.write(", ");
38511        self.generate_expression(&e.expression)?;
38512        if let Some(instance) = &e.instance {
38513            self.write(", ");
38514            self.generate_expression(instance)?;
38515        }
38516        self.write(")");
38517        Ok(())
38518    }
38519
38520    fn generate_xml_key_value_option(&mut self, e: &XMLKeyValueOption) -> Result<()> {
38521        // Python: this + optional (expr)
38522        self.generate_expression(&e.this)?;
38523        if let Some(expression) = &e.expression {
38524            self.write("(");
38525            self.generate_expression(expression)?;
38526            self.write(")");
38527        }
38528        Ok(())
38529    }
38530
38531    fn generate_xml_table(&mut self, e: &XMLTable) -> Result<()> {
38532        // Python: XMLTABLE(namespaces + this + passing + by_ref + columns)
38533        self.write_keyword("XMLTABLE");
38534        self.write("(");
38535
38536        if self.config.pretty {
38537            self.indent_level += 1;
38538            self.write_newline();
38539            self.write_indent();
38540            self.generate_expression(&e.this)?;
38541
38542            if let Some(passing) = &e.passing {
38543                self.write_newline();
38544                self.write_indent();
38545                self.write_keyword("PASSING");
38546                if let Expression::Tuple(tuple) = passing.as_ref() {
38547                    for expr in &tuple.expressions {
38548                        self.write_newline();
38549                        self.indent_level += 1;
38550                        self.write_indent();
38551                        self.generate_expression(expr)?;
38552                        self.indent_level -= 1;
38553                    }
38554                } else {
38555                    self.write_newline();
38556                    self.indent_level += 1;
38557                    self.write_indent();
38558                    self.generate_expression(passing)?;
38559                    self.indent_level -= 1;
38560                }
38561            }
38562
38563            if e.by_ref.is_some() {
38564                self.write_newline();
38565                self.write_indent();
38566                self.write_keyword("RETURNING SEQUENCE BY REF");
38567            }
38568
38569            if !e.columns.is_empty() {
38570                self.write_newline();
38571                self.write_indent();
38572                self.write_keyword("COLUMNS");
38573                for (i, col) in e.columns.iter().enumerate() {
38574                    self.write_newline();
38575                    self.indent_level += 1;
38576                    self.write_indent();
38577                    self.generate_expression(col)?;
38578                    self.indent_level -= 1;
38579                    if i < e.columns.len() - 1 {
38580                        self.write(",");
38581                    }
38582                }
38583            }
38584
38585            self.indent_level -= 1;
38586            self.write_newline();
38587            self.write_indent();
38588            self.write(")");
38589            return Ok(());
38590        }
38591
38592        // Namespaces - unwrap Tuple to generate comma-separated list without parentheses
38593        if let Some(namespaces) = &e.namespaces {
38594            self.write_keyword("XMLNAMESPACES");
38595            self.write("(");
38596            // Unwrap Tuple if present to avoid extra parentheses
38597            if let Expression::Tuple(tuple) = namespaces.as_ref() {
38598                for (i, expr) in tuple.expressions.iter().enumerate() {
38599                    if i > 0 {
38600                        self.write(", ");
38601                    }
38602                    // Python pattern: if it's an Alias, output as-is; otherwise prepend DEFAULT
38603                    // See xmlnamespace_sql in generator.py
38604                    if !matches!(expr, Expression::Alias(_)) {
38605                        self.write_keyword("DEFAULT");
38606                        self.write_space();
38607                    }
38608                    self.generate_expression(expr)?;
38609                }
38610            } else {
38611                // Single namespace - check if DEFAULT
38612                if !matches!(namespaces.as_ref(), Expression::Alias(_)) {
38613                    self.write_keyword("DEFAULT");
38614                    self.write_space();
38615                }
38616                self.generate_expression(namespaces)?;
38617            }
38618            self.write("), ");
38619        }
38620
38621        // XPath expression
38622        self.generate_expression(&e.this)?;
38623
38624        // PASSING clause - unwrap Tuple to generate comma-separated list without parentheses
38625        if let Some(passing) = &e.passing {
38626            self.write_space();
38627            self.write_keyword("PASSING");
38628            self.write_space();
38629            // Unwrap Tuple if present to avoid extra parentheses
38630            if let Expression::Tuple(tuple) = passing.as_ref() {
38631                for (i, expr) in tuple.expressions.iter().enumerate() {
38632                    if i > 0 {
38633                        self.write(", ");
38634                    }
38635                    self.generate_expression(expr)?;
38636                }
38637            } else {
38638                self.generate_expression(passing)?;
38639            }
38640        }
38641
38642        // RETURNING SEQUENCE BY REF
38643        if e.by_ref.is_some() {
38644            self.write_space();
38645            self.write_keyword("RETURNING SEQUENCE BY REF");
38646        }
38647
38648        // COLUMNS clause
38649        if !e.columns.is_empty() {
38650            self.write_space();
38651            self.write_keyword("COLUMNS");
38652            self.write_space();
38653            for (i, col) in e.columns.iter().enumerate() {
38654                if i > 0 {
38655                    self.write(", ");
38656                }
38657                self.generate_expression(col)?;
38658            }
38659        }
38660
38661        self.write(")");
38662        Ok(())
38663    }
38664
38665    fn generate_xor(&mut self, e: &Xor) -> Result<()> {
38666        // Python: return self.connector_sql(expression, "XOR", stack)
38667        // Handles: this XOR expression or expressions joined by XOR
38668        if let Some(this) = &e.this {
38669            self.generate_expression(this)?;
38670            if let Some(expression) = &e.expression {
38671                self.write_space();
38672                self.write_keyword("XOR");
38673                self.write_space();
38674                self.generate_expression(expression)?;
38675            }
38676        }
38677
38678        // Handle multiple expressions
38679        for (i, expr) in e.expressions.iter().enumerate() {
38680            if i > 0 || e.this.is_some() {
38681                self.write_space();
38682                self.write_keyword("XOR");
38683                self.write_space();
38684            }
38685            self.generate_expression(expr)?;
38686        }
38687        Ok(())
38688    }
38689
38690    fn generate_zipf(&mut self, e: &Zipf) -> Result<()> {
38691        // ZIPF(this, elementcount [, gen])
38692        self.write_keyword("ZIPF");
38693        self.write("(");
38694        self.generate_expression(&e.this)?;
38695        if let Some(elementcount) = &e.elementcount {
38696            self.write(", ");
38697            self.generate_expression(elementcount)?;
38698        }
38699        if let Some(gen) = &e.gen {
38700            self.write(", ");
38701            self.generate_expression(gen)?;
38702        }
38703        self.write(")");
38704        Ok(())
38705    }
38706}
38707
38708impl Default for Generator {
38709    fn default() -> Self {
38710        Self::new()
38711    }
38712}
38713
38714#[cfg(test)]
38715mod tests {
38716    use super::*;
38717    use crate::parser::Parser;
38718
38719    fn roundtrip(sql: &str) -> String {
38720        let ast = Parser::parse_sql(sql).unwrap();
38721        Generator::sql(&ast[0]).unwrap()
38722    }
38723
38724    #[test]
38725    fn test_simple_select() {
38726        let result = roundtrip("SELECT 1");
38727        assert_eq!(result, "SELECT 1");
38728    }
38729
38730    #[test]
38731    fn test_select_from() {
38732        let result = roundtrip("SELECT a, b FROM t");
38733        assert_eq!(result, "SELECT a, b FROM t");
38734    }
38735
38736    #[test]
38737    fn test_select_where() {
38738        let result = roundtrip("SELECT * FROM t WHERE x = 1");
38739        assert_eq!(result, "SELECT * FROM t WHERE x = 1");
38740    }
38741
38742    #[test]
38743    fn test_select_join() {
38744        let result = roundtrip("SELECT * FROM a JOIN b ON a.id = b.id");
38745        assert_eq!(result, "SELECT * FROM a JOIN b ON a.id = b.id");
38746    }
38747
38748    #[test]
38749    fn test_insert() {
38750        let result = roundtrip("INSERT INTO t (a, b) VALUES (1, 2)");
38751        assert_eq!(result, "INSERT INTO t (a, b) VALUES (1, 2)");
38752    }
38753
38754    #[test]
38755    fn test_pretty_print() {
38756        let ast = Parser::parse_sql("SELECT a, b FROM t WHERE x = 1").unwrap();
38757        let result = Generator::pretty_sql(&ast[0]).unwrap();
38758        assert!(result.contains('\n'));
38759    }
38760
38761    #[test]
38762    fn test_window_function() {
38763        let result = roundtrip("SELECT ROW_NUMBER() OVER (PARTITION BY category ORDER BY id)");
38764        assert_eq!(
38765            result,
38766            "SELECT ROW_NUMBER() OVER (PARTITION BY category ORDER BY id)"
38767        );
38768    }
38769
38770    #[test]
38771    fn test_window_function_with_frame() {
38772        let result = roundtrip("SELECT SUM(amount) OVER (ORDER BY order_date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)");
38773        assert_eq!(result, "SELECT SUM(amount) OVER (ORDER BY order_date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)");
38774    }
38775
38776    #[test]
38777    fn test_aggregate_with_filter() {
38778        let result = roundtrip("SELECT COUNT(*) FILTER (WHERE status = 1) FROM orders");
38779        assert_eq!(
38780            result,
38781            "SELECT COUNT(*) FILTER(WHERE status = 1) FROM orders"
38782        );
38783    }
38784
38785    #[test]
38786    fn test_subscript() {
38787        let result = roundtrip("SELECT arr[0]");
38788        assert_eq!(result, "SELECT arr[0]");
38789    }
38790
38791    // DDL tests
38792    #[test]
38793    fn test_create_table() {
38794        let result = roundtrip("CREATE TABLE users (id INT, name VARCHAR(100))");
38795        assert_eq!(result, "CREATE TABLE users (id INT, name VARCHAR(100))");
38796    }
38797
38798    #[test]
38799    fn test_create_table_with_constraints() {
38800        let result = roundtrip(
38801            "CREATE TABLE users (id INT PRIMARY KEY, email VARCHAR(255) UNIQUE NOT NULL)",
38802        );
38803        assert_eq!(
38804            result,
38805            "CREATE TABLE users (id INT PRIMARY KEY, email VARCHAR(255) UNIQUE NOT NULL)"
38806        );
38807    }
38808
38809    #[test]
38810    fn test_create_table_if_not_exists() {
38811        let result = roundtrip("CREATE TABLE IF NOT EXISTS t (id INT)");
38812        assert_eq!(result, "CREATE TABLE IF NOT EXISTS t (id INT)");
38813    }
38814
38815    #[test]
38816    fn test_drop_table() {
38817        let result = roundtrip("DROP TABLE users");
38818        assert_eq!(result, "DROP TABLE users");
38819    }
38820
38821    #[test]
38822    fn test_drop_table_if_exists_cascade() {
38823        let result = roundtrip("DROP TABLE IF EXISTS users CASCADE");
38824        assert_eq!(result, "DROP TABLE IF EXISTS users CASCADE");
38825    }
38826
38827    #[test]
38828    fn test_alter_table_add_column() {
38829        let result = roundtrip("ALTER TABLE users ADD COLUMN email VARCHAR(255)");
38830        assert_eq!(result, "ALTER TABLE users ADD COLUMN email VARCHAR(255)");
38831    }
38832
38833    #[test]
38834    fn test_alter_table_drop_column() {
38835        let result = roundtrip("ALTER TABLE users DROP COLUMN email");
38836        assert_eq!(result, "ALTER TABLE users DROP COLUMN email");
38837    }
38838
38839    #[test]
38840    fn test_create_index() {
38841        let result = roundtrip("CREATE INDEX idx_name ON users(name)");
38842        assert_eq!(result, "CREATE INDEX idx_name ON users(name)");
38843    }
38844
38845    #[test]
38846    fn test_create_unique_index() {
38847        let result = roundtrip("CREATE UNIQUE INDEX idx_email ON users(email)");
38848        assert_eq!(result, "CREATE UNIQUE INDEX idx_email ON users(email)");
38849    }
38850
38851    #[test]
38852    fn test_drop_index() {
38853        let result = roundtrip("DROP INDEX idx_name");
38854        assert_eq!(result, "DROP INDEX idx_name");
38855    }
38856
38857    #[test]
38858    fn test_create_view() {
38859        let result = roundtrip("CREATE VIEW active_users AS SELECT * FROM users WHERE active = 1");
38860        assert_eq!(
38861            result,
38862            "CREATE VIEW active_users AS SELECT * FROM users WHERE active = 1"
38863        );
38864    }
38865
38866    #[test]
38867    fn test_drop_view() {
38868        let result = roundtrip("DROP VIEW active_users");
38869        assert_eq!(result, "DROP VIEW active_users");
38870    }
38871
38872    #[test]
38873    fn test_truncate() {
38874        let result = roundtrip("TRUNCATE TABLE users");
38875        assert_eq!(result, "TRUNCATE TABLE users");
38876    }
38877
38878    #[test]
38879    fn test_string_literal_escaping_default() {
38880        // Default: double single quotes
38881        let result = roundtrip("SELECT 'hello'");
38882        assert_eq!(result, "SELECT 'hello'");
38883
38884        // Single quotes are doubled
38885        let result = roundtrip("SELECT 'it''s a test'");
38886        assert_eq!(result, "SELECT 'it''s a test'");
38887    }
38888
38889    #[test]
38890    fn test_not_in_style_prefix_default_generic() {
38891        let result = roundtrip("SELECT id FROM users WHERE status NOT IN ('deleted', 'banned')");
38892        assert_eq!(
38893            result,
38894            "SELECT id FROM users WHERE NOT status IN ('deleted', 'banned')"
38895        );
38896    }
38897
38898    #[test]
38899    fn test_not_in_style_infix_generic_override() {
38900        let ast =
38901            Parser::parse_sql("SELECT id FROM users WHERE status NOT IN ('deleted', 'banned')")
38902                .unwrap();
38903        let config = GeneratorConfig {
38904            not_in_style: NotInStyle::Infix,
38905            ..Default::default()
38906        };
38907        let mut gen = Generator::with_config(config);
38908        let result = gen.generate(&ast[0]).unwrap();
38909        assert_eq!(
38910            result,
38911            "SELECT id FROM users WHERE status NOT IN ('deleted', 'banned')"
38912        );
38913    }
38914
38915    #[test]
38916    fn test_string_literal_escaping_mysql() {
38917        use crate::dialects::DialectType;
38918
38919        let config = GeneratorConfig {
38920            dialect: Some(DialectType::MySQL),
38921            ..Default::default()
38922        };
38923
38924        let ast = Parser::parse_sql("SELECT 'hello'").unwrap();
38925        let mut gen = Generator::with_config(config.clone());
38926        let result = gen.generate(&ast[0]).unwrap();
38927        assert_eq!(result, "SELECT 'hello'");
38928
38929        // MySQL uses SQL standard quote doubling for escaping (matches Python sqlglot)
38930        let ast = Parser::parse_sql("SELECT 'it''s'").unwrap();
38931        let mut gen = Generator::with_config(config.clone());
38932        let result = gen.generate(&ast[0]).unwrap();
38933        assert_eq!(result, "SELECT 'it''s'");
38934    }
38935
38936    #[test]
38937    fn test_string_literal_escaping_postgres() {
38938        use crate::dialects::DialectType;
38939
38940        let config = GeneratorConfig {
38941            dialect: Some(DialectType::PostgreSQL),
38942            ..Default::default()
38943        };
38944
38945        let ast = Parser::parse_sql("SELECT 'hello'").unwrap();
38946        let mut gen = Generator::with_config(config.clone());
38947        let result = gen.generate(&ast[0]).unwrap();
38948        assert_eq!(result, "SELECT 'hello'");
38949
38950        // PostgreSQL uses doubled quotes for regular strings
38951        let ast = Parser::parse_sql("SELECT 'it''s'").unwrap();
38952        let mut gen = Generator::with_config(config.clone());
38953        let result = gen.generate(&ast[0]).unwrap();
38954        assert_eq!(result, "SELECT 'it''s'");
38955    }
38956
38957    #[test]
38958    fn test_string_literal_escaping_bigquery() {
38959        use crate::dialects::DialectType;
38960
38961        let config = GeneratorConfig {
38962            dialect: Some(DialectType::BigQuery),
38963            ..Default::default()
38964        };
38965
38966        let ast = Parser::parse_sql("SELECT 'hello'").unwrap();
38967        let mut gen = Generator::with_config(config.clone());
38968        let result = gen.generate(&ast[0]).unwrap();
38969        assert_eq!(result, "SELECT 'hello'");
38970
38971        // BigQuery escapes single quotes with backslash
38972        let ast = Parser::parse_sql("SELECT 'it''s'").unwrap();
38973        let mut gen = Generator::with_config(config.clone());
38974        let result = gen.generate(&ast[0]).unwrap();
38975        assert_eq!(result, "SELECT 'it\\'s'");
38976    }
38977
38978    #[test]
38979    fn test_generate_deep_and_chain_without_stack_growth() {
38980        let mut expr = Expression::Eq(Box::new(BinaryOp::new(
38981            Expression::column("c0"),
38982            Expression::number(0),
38983        )));
38984
38985        for i in 1..2500 {
38986            let predicate = Expression::Eq(Box::new(BinaryOp::new(
38987                Expression::column(format!("c{i}")),
38988                Expression::number(i as i64),
38989            )));
38990            expr = Expression::And(Box::new(BinaryOp::new(expr, predicate)));
38991        }
38992
38993        let sql = Generator::sql(&expr).expect("deep AND chain should generate");
38994        assert!(sql.contains("c2499 = 2499"), "{}", sql);
38995    }
38996
38997    #[test]
38998    fn test_generate_deep_or_chain_without_stack_growth() {
38999        let mut expr = Expression::Eq(Box::new(BinaryOp::new(
39000            Expression::column("c0"),
39001            Expression::number(0),
39002        )));
39003
39004        for i in 1..2500 {
39005            let predicate = Expression::Eq(Box::new(BinaryOp::new(
39006                Expression::column(format!("c{i}")),
39007                Expression::number(i as i64),
39008            )));
39009            expr = Expression::Or(Box::new(BinaryOp::new(expr, predicate)));
39010        }
39011
39012        let sql = Generator::sql(&expr).expect("deep OR chain should generate");
39013        assert!(sql.contains("c2499 = 2499"), "{}", sql);
39014    }
39015}