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                pre_alias_comments: Vec::new(),
2207                trailing_comments: Vec::new(),
2208                inferred_type: None,
2209            })),
2210
2211            // Identifier: add alias from identifier name
2212            Expression::Identifier(ident) => Expression::Alias(Box::new(Alias {
2213                this: expr.clone(),
2214                alias: ident.clone(),
2215                column_aliases: Vec::new(),
2216                pre_alias_comments: Vec::new(),
2217                trailing_comments: Vec::new(),
2218                inferred_type: None,
2219            })),
2220
2221            // Subquery: recursively process and add alias if inner returns a named column
2222            Expression::Subquery(sq) => {
2223                let processed = Self::add_column_aliases_to_query(Expression::Subquery(sq.clone()));
2224                // Subqueries that are already aliased keep their alias
2225                if sq.alias.is_some() {
2226                    processed
2227                } else {
2228                    // If there's no alias, keep it as-is (let TSQL handle it)
2229                    processed
2230                }
2231            }
2232
2233            // Star expressions (*) - don't alias
2234            Expression::Star(_) => expr,
2235
2236            // For other expressions, don't add an alias
2237            // (function calls, literals, etc. would need explicit aliases anyway)
2238            _ => expr,
2239        }
2240    }
2241
2242    /// Try to evaluate a constant arithmetic expression to a number literal.
2243    /// Returns the evaluated result if the expression is a constant arithmetic expression,
2244    /// otherwise returns the original expression.
2245    fn try_evaluate_constant(expr: &Expression) -> Option<i64> {
2246        match expr {
2247            Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(_)) => {
2248                let Literal::Number(n) = lit.as_ref() else {
2249                    unreachable!()
2250                };
2251                n.parse::<i64>().ok()
2252            }
2253            Expression::Add(op) => {
2254                let left = Self::try_evaluate_constant(&op.left)?;
2255                let right = Self::try_evaluate_constant(&op.right)?;
2256                Some(left + right)
2257            }
2258            Expression::Sub(op) => {
2259                let left = Self::try_evaluate_constant(&op.left)?;
2260                let right = Self::try_evaluate_constant(&op.right)?;
2261                Some(left - right)
2262            }
2263            Expression::Mul(op) => {
2264                let left = Self::try_evaluate_constant(&op.left)?;
2265                let right = Self::try_evaluate_constant(&op.right)?;
2266                Some(left * right)
2267            }
2268            Expression::Div(op) => {
2269                let left = Self::try_evaluate_constant(&op.left)?;
2270                let right = Self::try_evaluate_constant(&op.right)?;
2271                if right != 0 {
2272                    Some(left / right)
2273                } else {
2274                    None
2275                }
2276            }
2277            Expression::Paren(p) => Self::try_evaluate_constant(&p.this),
2278            _ => None,
2279        }
2280    }
2281
2282    /// Check if an identifier is a reserved keyword for the current dialect
2283    fn is_reserved_keyword(&self, name: &str) -> bool {
2284        use crate::dialects::DialectType;
2285        let mut buf = [0u8; 128];
2286        let lower_ref: &str = if name.len() <= 128 {
2287            for (i, b) in name.bytes().enumerate() {
2288                buf[i] = b.to_ascii_lowercase();
2289            }
2290            // SAFETY: input is valid UTF-8 and ASCII lowercase preserves that
2291            std::str::from_utf8(&buf[..name.len()]).unwrap_or(name)
2292        } else {
2293            return false;
2294        };
2295
2296        match self.config.dialect {
2297            Some(DialectType::BigQuery) => reserved_keywords::BIGQUERY_RESERVED.contains(lower_ref),
2298            Some(DialectType::MySQL) | Some(DialectType::TiDB) => {
2299                reserved_keywords::MYSQL_RESERVED.contains(lower_ref)
2300            }
2301            Some(DialectType::Doris) => reserved_keywords::DORIS_RESERVED.contains(lower_ref),
2302            Some(DialectType::SingleStore) => {
2303                reserved_keywords::SINGLESTORE_RESERVED.contains(lower_ref)
2304            }
2305            Some(DialectType::StarRocks) => {
2306                reserved_keywords::STARROCKS_RESERVED.contains(lower_ref)
2307            }
2308            Some(DialectType::PostgreSQL)
2309            | Some(DialectType::CockroachDB)
2310            | Some(DialectType::Materialize)
2311            | Some(DialectType::RisingWave) => {
2312                reserved_keywords::POSTGRES_RESERVED.contains(lower_ref)
2313            }
2314            Some(DialectType::Redshift) => reserved_keywords::REDSHIFT_RESERVED.contains(lower_ref),
2315            // Snowflake: Python sqlglot has RESERVED_KEYWORDS = set() for Snowflake,
2316            // meaning it never quotes identifiers based on reserved word status.
2317            Some(DialectType::Snowflake) => false,
2318            // ClickHouse: don't quote reserved keywords to preserve identity output
2319            Some(DialectType::ClickHouse) => false,
2320            Some(DialectType::DuckDB) => reserved_keywords::DUCKDB_RESERVED.contains(lower_ref),
2321            // Teradata: Python sqlglot has RESERVED_KEYWORDS = set() for Teradata
2322            Some(DialectType::Teradata) => false,
2323            // TSQL, Fabric, Oracle, Spark, Hive, Solr: Python sqlglot has no RESERVED_KEYWORDS for these dialects, so don't quote identifiers
2324            Some(DialectType::TSQL)
2325            | Some(DialectType::Fabric)
2326            | Some(DialectType::Oracle)
2327            | Some(DialectType::Spark)
2328            | Some(DialectType::Databricks)
2329            | Some(DialectType::Hive)
2330            | Some(DialectType::Solr) => false,
2331            Some(DialectType::Presto) | Some(DialectType::Trino) | Some(DialectType::Athena) => {
2332                reserved_keywords::PRESTO_TRINO_RESERVED.contains(lower_ref)
2333            }
2334            Some(DialectType::SQLite) => reserved_keywords::SQLITE_RESERVED.contains(lower_ref),
2335            // For Generic dialect or None, don't add extra quoting to preserve identity
2336            Some(DialectType::Generic) | None => false,
2337            // For other dialects, use standard SQL reserved keywords
2338            _ => reserved_keywords::SQL_RESERVED.contains(lower_ref),
2339        }
2340    }
2341
2342    /// Normalize function name based on dialect settings
2343    fn normalize_func_name<'a>(&self, name: &'a str) -> Cow<'a, str> {
2344        match self.config.normalize_functions {
2345            NormalizeFunctions::Upper => Cow::Owned(name.to_ascii_uppercase()),
2346            NormalizeFunctions::Lower => Cow::Owned(name.to_ascii_lowercase()),
2347            NormalizeFunctions::None => Cow::Borrowed(name),
2348        }
2349    }
2350
2351    /// Generate a SQL string from an AST expression.
2352    ///
2353    /// This is the primary generation method. It clears any previous internal state,
2354    /// walks the expression tree, and returns the resulting SQL text. The output
2355    /// respects the [`GeneratorConfig`] that was supplied at construction time.
2356    ///
2357    /// The generator can be reused across multiple calls; each call to `generate`
2358    /// resets the internal buffer.
2359    pub fn generate(&mut self, expr: &Expression) -> Result<String> {
2360        self.output.clear();
2361        self.unsupported_messages.clear();
2362        self.generate_expression(expr)?;
2363        if self.config.unsupported_level == UnsupportedLevel::Raise
2364            && !self.unsupported_messages.is_empty()
2365        {
2366            return Err(crate::error::Error::generate(
2367                self.format_unsupported_messages(),
2368            ));
2369        }
2370        Ok(std::mem::take(&mut self.output))
2371    }
2372
2373    /// Returns the unsupported diagnostics collected during the most recent generate call.
2374    pub fn unsupported_messages(&self) -> &[String] {
2375        &self.unsupported_messages
2376    }
2377
2378    fn unsupported(&mut self, message: impl Into<String>) -> Result<()> {
2379        let message = message.into();
2380        if self.config.unsupported_level == UnsupportedLevel::Immediate {
2381            return Err(crate::error::Error::generate(message));
2382        }
2383        self.unsupported_messages.push(message);
2384        Ok(())
2385    }
2386
2387    fn write_unsupported_comment(&mut self, message: &str) -> Result<()> {
2388        self.unsupported(message.to_string())?;
2389        self.write("/* ");
2390        self.write(message);
2391        self.write(" */");
2392        Ok(())
2393    }
2394
2395    fn format_unsupported_messages(&self) -> String {
2396        let limit = self.config.max_unsupported.max(1);
2397        if self.unsupported_messages.len() <= limit {
2398            return self.unsupported_messages.join("; ");
2399        }
2400
2401        let mut messages = self
2402            .unsupported_messages
2403            .iter()
2404            .take(limit)
2405            .cloned()
2406            .collect::<Vec<_>>();
2407        messages.push(format!(
2408            "... and {} more",
2409            self.unsupported_messages.len() - limit
2410        ));
2411        messages.join("; ")
2412    }
2413
2414    /// Convenience: generate SQL with the default configuration (no dialect, compact output).
2415    ///
2416    /// This is a static helper that creates a throwaway `Generator` internally.
2417    /// For repeated generation, prefer constructing a `Generator` once and calling
2418    /// [`generate`](Self::generate) on it.
2419    pub fn sql(expr: &Expression) -> Result<String> {
2420        let mut gen = Generator::new();
2421        gen.generate(expr)
2422    }
2423
2424    /// Convenience: generate SQL with pretty-printing enabled (indented, multi-line).
2425    ///
2426    /// Produces human-readable output with newlines and indentation. A trailing
2427    /// semicolon is appended automatically if not already present.
2428    pub fn pretty_sql(expr: &Expression) -> Result<String> {
2429        let config = GeneratorConfig {
2430            pretty: true,
2431            ..Default::default()
2432        };
2433        let mut gen = Generator::with_config(config);
2434        let mut sql = gen.generate(expr)?;
2435        // Add semicolon for pretty output
2436        if !sql.ends_with(';') {
2437            sql.push(';');
2438        }
2439        Ok(sql)
2440    }
2441
2442    fn generate_expression(&mut self, expr: &Expression) -> Result<()> {
2443        #[cfg(feature = "stacker")]
2444        {
2445            let red_zone = if cfg!(debug_assertions) {
2446                4 * 1024 * 1024
2447            } else {
2448                1024 * 1024
2449            };
2450            stacker::maybe_grow(red_zone, 8 * 1024 * 1024, || {
2451                self.generate_expression_inner(expr)
2452            })
2453        }
2454        #[cfg(not(feature = "stacker"))]
2455        {
2456            self.generate_expression_inner(expr)
2457        }
2458    }
2459
2460    fn generate_expression_inner(&mut self, expr: &Expression) -> Result<()> {
2461        match expr {
2462            Expression::Select(select) => self.generate_select(select),
2463            Expression::Union(union) => self.generate_union(union),
2464            Expression::Intersect(intersect) => self.generate_intersect(intersect),
2465            Expression::Except(except) => self.generate_except(except),
2466            Expression::Insert(insert) => self.generate_insert(insert),
2467            Expression::Update(update) => self.generate_update(update),
2468            Expression::Delete(delete) => self.generate_delete(delete),
2469            Expression::Literal(lit) => self.generate_literal(lit),
2470            Expression::Boolean(b) => self.generate_boolean(b),
2471            Expression::Null(_) => {
2472                self.write_keyword("NULL");
2473                Ok(())
2474            }
2475            Expression::Identifier(id) => self.generate_identifier(id),
2476            Expression::Column(col) => self.generate_column(col),
2477            Expression::Pseudocolumn(pc) => self.generate_pseudocolumn(pc),
2478            Expression::Connect(c) => self.generate_connect_expr(c),
2479            Expression::Prior(p) => self.generate_prior(p),
2480            Expression::ConnectByRoot(cbr) => self.generate_connect_by_root(cbr),
2481            Expression::MatchRecognize(mr) => self.generate_match_recognize(mr),
2482            Expression::Table(table) => self.generate_table(table),
2483            Expression::StageReference(sr) => self.generate_stage_reference(sr),
2484            Expression::HistoricalData(hd) => self.generate_historical_data(hd),
2485            Expression::JoinedTable(jt) => self.generate_joined_table(jt),
2486            Expression::Star(star) => self.generate_star(star),
2487            Expression::BracedWildcard(expr) => self.generate_braced_wildcard(expr),
2488            Expression::Alias(alias) => self.generate_alias(alias),
2489            Expression::Cast(cast) => self.generate_cast(cast),
2490            Expression::Collation(coll) => self.generate_collation(coll),
2491            Expression::Case(case) => self.generate_case(case),
2492            Expression::Function(func) => self.generate_function(func),
2493            Expression::FunctionEmits(fe) => self.generate_function_emits(fe),
2494            Expression::AggregateFunction(func) => self.generate_aggregate_function(func),
2495            Expression::WindowFunction(wf) => self.generate_window_function(wf),
2496            Expression::WithinGroup(wg) => self.generate_within_group(wg),
2497            Expression::Interval(interval) => self.generate_interval(interval),
2498
2499            // String functions
2500            Expression::ConcatWs(f) => self.generate_concat_ws(f),
2501            Expression::Substring(f) => self.generate_substring(f),
2502            Expression::Upper(f) => self.generate_unary_func("UPPER", f),
2503            Expression::Lower(f) => self.generate_unary_func("LOWER", f),
2504            Expression::Length(f) => self.generate_unary_func("LENGTH", f),
2505            Expression::Trim(f) => self.generate_trim(f),
2506            Expression::LTrim(f) => self.generate_simple_func("LTRIM", &f.this),
2507            Expression::RTrim(f) => self.generate_simple_func("RTRIM", &f.this),
2508            Expression::Replace(f) => self.generate_replace(f),
2509            Expression::Reverse(f) => self.generate_simple_func("REVERSE", &f.this),
2510            Expression::Left(f) => self.generate_left_right("LEFT", f),
2511            Expression::Right(f) => self.generate_left_right("RIGHT", f),
2512            Expression::Repeat(f) => self.generate_repeat(f),
2513            Expression::Lpad(f) => self.generate_pad("LPAD", f),
2514            Expression::Rpad(f) => self.generate_pad("RPAD", f),
2515            Expression::Split(f) => self.generate_split(f),
2516            Expression::RegexpLike(f) => self.generate_regexp_like(f),
2517            Expression::RegexpReplace(f) => self.generate_regexp_replace(f),
2518            Expression::RegexpExtract(f) => self.generate_regexp_extract(f),
2519            Expression::Overlay(f) => self.generate_overlay(f),
2520
2521            // Math functions
2522            Expression::Abs(f) => self.generate_simple_func("ABS", &f.this),
2523            Expression::Round(f) => self.generate_round(f),
2524            Expression::Floor(f) => self.generate_floor(f),
2525            Expression::Ceil(f) => self.generate_ceil(f),
2526            Expression::Power(f) => self.generate_power(f),
2527            Expression::Sqrt(f) => self.generate_sqrt_cbrt(f, "SQRT", "|/"),
2528            Expression::Cbrt(f) => self.generate_sqrt_cbrt(f, "CBRT", "||/"),
2529            Expression::Ln(f) => self.generate_simple_func("LN", &f.this),
2530            Expression::Log(f) => self.generate_log(f),
2531            Expression::Exp(f) => self.generate_simple_func("EXP", &f.this),
2532            Expression::Sign(f) => self.generate_simple_func("SIGN", &f.this),
2533            Expression::Greatest(f) => self.generate_vararg_func("GREATEST", &f.expressions),
2534            Expression::Least(f) => self.generate_vararg_func("LEAST", &f.expressions),
2535
2536            // Date/time functions
2537            Expression::CurrentDate(_) => {
2538                self.write_keyword("CURRENT_DATE");
2539                Ok(())
2540            }
2541            Expression::CurrentTime(f) => self.generate_current_time(f),
2542            Expression::CurrentTimestamp(f) => self.generate_current_timestamp(f),
2543            Expression::AtTimeZone(f) => self.generate_at_time_zone(f),
2544            Expression::DateAdd(f) => self.generate_date_add(f, "DATE_ADD"),
2545            Expression::DateSub(f) => self.generate_date_add(f, "DATE_SUB"),
2546            Expression::DateDiff(f) => self.generate_datediff(f),
2547            Expression::DateTrunc(f) => self.generate_date_trunc(f),
2548            Expression::Extract(f) => self.generate_extract(f),
2549            Expression::ToDate(f) => self.generate_to_date(f),
2550            Expression::ToTimestamp(f) => self.generate_to_timestamp(f),
2551
2552            // Control flow functions
2553            Expression::Coalesce(f) => {
2554                // Use original function name if preserved (COALESCE, IFNULL)
2555                let func_name = f.original_name.as_deref().unwrap_or("COALESCE");
2556                self.generate_vararg_func(func_name, &f.expressions)
2557            }
2558            Expression::NullIf(f) => self.generate_binary_func("NULLIF", &f.this, &f.expression),
2559            Expression::IfFunc(f) => self.generate_if_func(f),
2560            Expression::IfNull(f) => self.generate_ifnull(f),
2561            Expression::Nvl(f) => self.generate_nvl(f),
2562            Expression::Nvl2(f) => self.generate_nvl2(f),
2563
2564            // Type conversion
2565            Expression::TryCast(cast) => self.generate_try_cast(cast),
2566            Expression::SafeCast(cast) => self.generate_safe_cast(cast),
2567
2568            // Typed aggregate functions
2569            Expression::Count(f) => self.generate_count(f),
2570            Expression::Sum(f) => self.generate_agg_func("SUM", f),
2571            Expression::Avg(f) => self.generate_agg_func("AVG", f),
2572            Expression::Min(f) => self.generate_agg_func("MIN", f),
2573            Expression::Max(f) => self.generate_agg_func("MAX", f),
2574            Expression::GroupConcat(f) => self.generate_group_concat(f),
2575            Expression::StringAgg(f) => self.generate_string_agg(f),
2576            Expression::ListAgg(f) => self.generate_listagg(f),
2577            Expression::ArrayAgg(f) => {
2578                // Allow cross-dialect transforms to override the function name
2579                // (e.g., COLLECT_LIST for Spark)
2580                let override_name = f
2581                    .name
2582                    .as_ref()
2583                    .filter(|n| !n.eq_ignore_ascii_case("ARRAY_AGG"))
2584                    .map(|n| n.to_ascii_uppercase());
2585                match override_name {
2586                    Some(name) => self.generate_agg_func(&name, f),
2587                    None => self.generate_agg_func("ARRAY_AGG", f),
2588                }
2589            }
2590            Expression::ArrayConcatAgg(f) => self.generate_agg_func("ARRAY_CONCAT_AGG", f),
2591            Expression::CountIf(f) => self.generate_agg_func("COUNT_IF", f),
2592            Expression::SumIf(f) => self.generate_sum_if(f),
2593            Expression::Stddev(f) => self.generate_agg_func("STDDEV", f),
2594            Expression::StddevPop(f) => self.generate_agg_func("STDDEV_POP", f),
2595            Expression::StddevSamp(f) => self.generate_stddev_samp(f),
2596            Expression::Variance(f) => self.generate_agg_func("VARIANCE", f),
2597            Expression::VarPop(f) => {
2598                let name = if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
2599                    "VARIANCE_POP"
2600                } else {
2601                    "VAR_POP"
2602                };
2603                self.generate_agg_func(name, f)
2604            }
2605            Expression::VarSamp(f) => self.generate_agg_func("VAR_SAMP", f),
2606            Expression::Skewness(f) => {
2607                let name = match self.config.dialect {
2608                    Some(DialectType::Snowflake) => "SKEW",
2609                    _ => "SKEWNESS",
2610                };
2611                self.generate_agg_func(name, f)
2612            }
2613            Expression::Median(f) => self.generate_agg_func("MEDIAN", f),
2614            Expression::Mode(f) => self.generate_agg_func("MODE", f),
2615            Expression::First(f) => self.generate_agg_func_with_ignore_nulls_bool("FIRST", f),
2616            Expression::Last(f) => self.generate_agg_func_with_ignore_nulls_bool("LAST", f),
2617            Expression::AnyValue(f) => self.generate_agg_func("ANY_VALUE", f),
2618            Expression::ApproxDistinct(f) => {
2619                match self.config.dialect {
2620                    Some(DialectType::Hive)
2621                    | Some(DialectType::Spark)
2622                    | Some(DialectType::Databricks)
2623                    | Some(DialectType::BigQuery) => {
2624                        // These dialects use APPROX_COUNT_DISTINCT (single arg only)
2625                        self.generate_agg_func("APPROX_COUNT_DISTINCT", f)
2626                    }
2627                    Some(DialectType::Redshift) => {
2628                        // Redshift uses APPROXIMATE COUNT(DISTINCT expr)
2629                        self.write_keyword("APPROXIMATE COUNT");
2630                        self.write("(");
2631                        self.write_keyword("DISTINCT");
2632                        self.write(" ");
2633                        self.generate_expression(&f.this)?;
2634                        self.write(")");
2635                        Ok(())
2636                    }
2637                    _ => self.generate_agg_func("APPROX_DISTINCT", f),
2638                }
2639            }
2640            Expression::ApproxCountDistinct(f) => {
2641                self.generate_agg_func("APPROX_COUNT_DISTINCT", f)
2642            }
2643            Expression::ApproxPercentile(f) => self.generate_approx_percentile(f),
2644            Expression::Percentile(f) => self.generate_percentile("PERCENTILE", f),
2645            Expression::LogicalAnd(f) => {
2646                let name = match self.config.dialect {
2647                    Some(DialectType::Snowflake) => "BOOLAND_AGG",
2648                    Some(DialectType::Spark)
2649                    | Some(DialectType::Databricks)
2650                    | Some(DialectType::PostgreSQL)
2651                    | Some(DialectType::DuckDB)
2652                    | Some(DialectType::Redshift) => "BOOL_AND",
2653                    Some(DialectType::Oracle)
2654                    | Some(DialectType::SQLite)
2655                    | Some(DialectType::MySQL) => "MIN",
2656                    _ => "BOOL_AND",
2657                };
2658                self.generate_agg_func(name, f)
2659            }
2660            Expression::LogicalOr(f) => {
2661                let name = match self.config.dialect {
2662                    Some(DialectType::Snowflake) => "BOOLOR_AGG",
2663                    Some(DialectType::Spark)
2664                    | Some(DialectType::Databricks)
2665                    | Some(DialectType::PostgreSQL)
2666                    | Some(DialectType::DuckDB)
2667                    | Some(DialectType::Redshift) => "BOOL_OR",
2668                    Some(DialectType::Oracle)
2669                    | Some(DialectType::SQLite)
2670                    | Some(DialectType::MySQL) => "MAX",
2671                    _ => "BOOL_OR",
2672                };
2673                self.generate_agg_func(name, f)
2674            }
2675
2676            // Typed window functions
2677            Expression::RowNumber(_) => {
2678                if self.config.dialect == Some(DialectType::ClickHouse) {
2679                    self.write("row_number");
2680                } else {
2681                    self.write_keyword("ROW_NUMBER");
2682                }
2683                self.write("()");
2684                Ok(())
2685            }
2686            Expression::Rank(r) => {
2687                self.write_keyword("RANK");
2688                self.write("(");
2689                // Oracle hypothetical rank args: RANK(val1, val2, ...) WITHIN GROUP (ORDER BY ...)
2690                if !r.args.is_empty() {
2691                    for (i, arg) in r.args.iter().enumerate() {
2692                        if i > 0 {
2693                            self.write(", ");
2694                        }
2695                        self.generate_expression(arg)?;
2696                    }
2697                } else if let Some(order_by) = &r.order_by {
2698                    // DuckDB: RANK(ORDER BY col)
2699                    self.write_keyword(" ORDER BY ");
2700                    for (i, ob) in order_by.iter().enumerate() {
2701                        if i > 0 {
2702                            self.write(", ");
2703                        }
2704                        self.generate_ordered(ob)?;
2705                    }
2706                }
2707                self.write(")");
2708                Ok(())
2709            }
2710            Expression::DenseRank(dr) => {
2711                self.write_keyword("DENSE_RANK");
2712                self.write("(");
2713                // Oracle hypothetical rank args: DENSE_RANK(val1, val2, ...) WITHIN GROUP (ORDER BY ...)
2714                for (i, arg) in dr.args.iter().enumerate() {
2715                    if i > 0 {
2716                        self.write(", ");
2717                    }
2718                    self.generate_expression(arg)?;
2719                }
2720                self.write(")");
2721                Ok(())
2722            }
2723            Expression::NTile(f) => self.generate_ntile(f),
2724            Expression::Lead(f) => self.generate_lead_lag("LEAD", f),
2725            Expression::Lag(f) => self.generate_lead_lag("LAG", f),
2726            Expression::FirstValue(f) => {
2727                self.generate_value_func_with_ignore_nulls_bool("FIRST_VALUE", f)
2728            }
2729            Expression::LastValue(f) => {
2730                self.generate_value_func_with_ignore_nulls_bool("LAST_VALUE", f)
2731            }
2732            Expression::NthValue(f) => self.generate_nth_value(f),
2733            Expression::PercentRank(pr) => {
2734                self.write_keyword("PERCENT_RANK");
2735                self.write("(");
2736                // Oracle hypothetical rank args: PERCENT_RANK(val1, val2, ...) WITHIN GROUP (ORDER BY ...)
2737                if !pr.args.is_empty() {
2738                    for (i, arg) in pr.args.iter().enumerate() {
2739                        if i > 0 {
2740                            self.write(", ");
2741                        }
2742                        self.generate_expression(arg)?;
2743                    }
2744                } else if let Some(order_by) = &pr.order_by {
2745                    // DuckDB: PERCENT_RANK(ORDER BY col)
2746                    self.write_keyword(" ORDER BY ");
2747                    for (i, ob) in order_by.iter().enumerate() {
2748                        if i > 0 {
2749                            self.write(", ");
2750                        }
2751                        self.generate_ordered(ob)?;
2752                    }
2753                }
2754                self.write(")");
2755                Ok(())
2756            }
2757            Expression::CumeDist(cd) => {
2758                self.write_keyword("CUME_DIST");
2759                self.write("(");
2760                // Oracle hypothetical rank args: CUME_DIST(val1, val2, ...) WITHIN GROUP (ORDER BY ...)
2761                if !cd.args.is_empty() {
2762                    for (i, arg) in cd.args.iter().enumerate() {
2763                        if i > 0 {
2764                            self.write(", ");
2765                        }
2766                        self.generate_expression(arg)?;
2767                    }
2768                } else if let Some(order_by) = &cd.order_by {
2769                    // DuckDB: CUME_DIST(ORDER BY col)
2770                    self.write_keyword(" ORDER BY ");
2771                    for (i, ob) in order_by.iter().enumerate() {
2772                        if i > 0 {
2773                            self.write(", ");
2774                        }
2775                        self.generate_ordered(ob)?;
2776                    }
2777                }
2778                self.write(")");
2779                Ok(())
2780            }
2781            Expression::PercentileCont(f) => self.generate_percentile("PERCENTILE_CONT", f),
2782            Expression::PercentileDisc(f) => self.generate_percentile("PERCENTILE_DISC", f),
2783
2784            // Additional string functions
2785            Expression::Contains(f) => {
2786                self.generate_binary_func("CONTAINS", &f.this, &f.expression)
2787            }
2788            Expression::StartsWith(f) => {
2789                let name = match self.config.dialect {
2790                    Some(DialectType::Spark) | Some(DialectType::Databricks) => "STARTSWITH",
2791                    _ => "STARTS_WITH",
2792                };
2793                self.generate_binary_func(name, &f.this, &f.expression)
2794            }
2795            Expression::EndsWith(f) => {
2796                let name = match self.config.dialect {
2797                    Some(DialectType::Snowflake) => "ENDSWITH",
2798                    Some(DialectType::Spark) | Some(DialectType::Databricks) => "ENDSWITH",
2799                    Some(DialectType::ClickHouse) => "endsWith",
2800                    _ => "ENDS_WITH",
2801                };
2802                self.generate_binary_func(name, &f.this, &f.expression)
2803            }
2804            Expression::Position(f) => self.generate_position(f),
2805            Expression::Initcap(f) => match self.config.dialect {
2806                Some(DialectType::Presto)
2807                | Some(DialectType::Trino)
2808                | Some(DialectType::Athena) => {
2809                    self.write_keyword("REGEXP_REPLACE");
2810                    self.write("(");
2811                    self.generate_expression(&f.this)?;
2812                    self.write(", '(\\w)(\\w*)', x -> UPPER(x[1]) || LOWER(x[2]))");
2813                    Ok(())
2814                }
2815                _ => self.generate_simple_func("INITCAP", &f.this),
2816            },
2817            Expression::Ascii(f) => self.generate_simple_func("ASCII", &f.this),
2818            Expression::Chr(f) => self.generate_simple_func("CHR", &f.this),
2819            Expression::CharFunc(f) => self.generate_char_func(f),
2820            Expression::Soundex(f) => self.generate_simple_func("SOUNDEX", &f.this),
2821            Expression::Levenshtein(f) => {
2822                self.generate_binary_func("LEVENSHTEIN", &f.this, &f.expression)
2823            }
2824
2825            // Additional math functions
2826            Expression::ModFunc(f) => self.generate_mod_func(f),
2827            Expression::Random(_) => {
2828                self.write_keyword("RANDOM");
2829                self.write("()");
2830                Ok(())
2831            }
2832            Expression::Rand(f) => self.generate_rand(f),
2833            Expression::TruncFunc(f) => self.generate_truncate_func(f),
2834            Expression::Pi(_) => {
2835                self.write_keyword("PI");
2836                self.write("()");
2837                Ok(())
2838            }
2839            Expression::Radians(f) => self.generate_simple_func("RADIANS", &f.this),
2840            Expression::Degrees(f) => self.generate_simple_func("DEGREES", &f.this),
2841            Expression::Sin(f) => self.generate_simple_func("SIN", &f.this),
2842            Expression::Cos(f) => self.generate_simple_func("COS", &f.this),
2843            Expression::Tan(f) => self.generate_simple_func("TAN", &f.this),
2844            Expression::Asin(f) => self.generate_simple_func("ASIN", &f.this),
2845            Expression::Acos(f) => self.generate_simple_func("ACOS", &f.this),
2846            Expression::Atan(f) => self.generate_simple_func("ATAN", &f.this),
2847            Expression::Atan2(f) => {
2848                let name = f.original_name.as_deref().unwrap_or("ATAN2");
2849                self.generate_binary_func(name, &f.this, &f.expression)
2850            }
2851
2852            // Control flow
2853            Expression::Decode(f) => self.generate_decode(f),
2854
2855            // Additional date/time functions
2856            Expression::DateFormat(f) => self.generate_date_format("DATE_FORMAT", f),
2857            Expression::FormatDate(f) => self.generate_date_format("FORMAT_DATE", f),
2858            Expression::Year(f) => self.generate_simple_func("YEAR", &f.this),
2859            Expression::Month(f) => self.generate_simple_func("MONTH", &f.this),
2860            Expression::Day(f) => self.generate_simple_func("DAY", &f.this),
2861            Expression::Hour(f) => self.generate_simple_func("HOUR", &f.this),
2862            Expression::Minute(f) => self.generate_simple_func("MINUTE", &f.this),
2863            Expression::Second(f) => self.generate_simple_func("SECOND", &f.this),
2864            Expression::DayOfWeek(f) => {
2865                let name = match self.config.dialect {
2866                    Some(DialectType::Presto)
2867                    | Some(DialectType::Trino)
2868                    | Some(DialectType::Athena) => "DAY_OF_WEEK",
2869                    Some(DialectType::DuckDB) => "ISODOW",
2870                    _ => "DAYOFWEEK",
2871                };
2872                self.generate_simple_func(name, &f.this)
2873            }
2874            Expression::DayOfMonth(f) => {
2875                let name = match self.config.dialect {
2876                    Some(DialectType::Presto)
2877                    | Some(DialectType::Trino)
2878                    | Some(DialectType::Athena) => "DAY_OF_MONTH",
2879                    _ => "DAYOFMONTH",
2880                };
2881                self.generate_simple_func(name, &f.this)
2882            }
2883            Expression::DayOfYear(f) => {
2884                let name = match self.config.dialect {
2885                    Some(DialectType::Presto)
2886                    | Some(DialectType::Trino)
2887                    | Some(DialectType::Athena) => "DAY_OF_YEAR",
2888                    _ => "DAYOFYEAR",
2889                };
2890                self.generate_simple_func(name, &f.this)
2891            }
2892            Expression::WeekOfYear(f) => {
2893                // Python sqlglot default is WEEK_OF_YEAR; Hive/DuckDB/Spark/MySQL override to WEEKOFYEAR
2894                let name = match self.config.dialect {
2895                    Some(DialectType::Hive)
2896                    | Some(DialectType::DuckDB)
2897                    | Some(DialectType::Spark)
2898                    | Some(DialectType::Databricks)
2899                    | Some(DialectType::MySQL) => "WEEKOFYEAR",
2900                    _ => "WEEK_OF_YEAR",
2901                };
2902                self.generate_simple_func(name, &f.this)
2903            }
2904            Expression::Quarter(f) => self.generate_simple_func("QUARTER", &f.this),
2905            Expression::AddMonths(f) => {
2906                self.generate_binary_func("ADD_MONTHS", &f.this, &f.expression)
2907            }
2908            Expression::MonthsBetween(f) => {
2909                self.generate_binary_func("MONTHS_BETWEEN", &f.this, &f.expression)
2910            }
2911            Expression::LastDay(f) => self.generate_last_day(f),
2912            Expression::NextDay(f) => self.generate_binary_func("NEXT_DAY", &f.this, &f.expression),
2913            Expression::Epoch(f) => self.generate_simple_func("EPOCH", &f.this),
2914            Expression::EpochMs(f) => self.generate_simple_func("EPOCH_MS", &f.this),
2915            Expression::FromUnixtime(f) => self.generate_from_unixtime(f),
2916            Expression::UnixTimestamp(f) => self.generate_unix_timestamp(f),
2917            Expression::MakeDate(f) => self.generate_make_date(f),
2918            Expression::MakeTimestamp(f) => self.generate_make_timestamp(f),
2919            Expression::TimestampTrunc(f) => self.generate_date_trunc(f),
2920
2921            // Array functions
2922            Expression::ArrayFunc(f) => self.generate_array_constructor(f),
2923            Expression::ArrayLength(f) => self.generate_simple_func("ARRAY_LENGTH", &f.this),
2924            Expression::ArraySize(f) => self.generate_simple_func("ARRAY_SIZE", &f.this),
2925            Expression::Cardinality(f) => self.generate_simple_func("CARDINALITY", &f.this),
2926            Expression::ArrayContains(f) => {
2927                self.generate_binary_func("ARRAY_CONTAINS", &f.this, &f.expression)
2928            }
2929            Expression::ArrayPosition(f) => {
2930                self.generate_binary_func("ARRAY_POSITION", &f.this, &f.expression)
2931            }
2932            Expression::ArrayAppend(f) => {
2933                self.generate_binary_func("ARRAY_APPEND", &f.this, &f.expression)
2934            }
2935            Expression::ArrayPrepend(f) => {
2936                self.generate_binary_func("ARRAY_PREPEND", &f.this, &f.expression)
2937            }
2938            Expression::ArrayConcat(f) => self.generate_vararg_func("ARRAY_CONCAT", &f.expressions),
2939            Expression::ArraySort(f) => self.generate_array_sort(f),
2940            Expression::ArrayReverse(f) => self.generate_simple_func("ARRAY_REVERSE", &f.this),
2941            Expression::ArrayDistinct(f) => self.generate_simple_func("ARRAY_DISTINCT", &f.this),
2942            Expression::ArrayJoin(f) => self.generate_array_join("ARRAY_JOIN", f),
2943            Expression::ArrayToString(f) => self.generate_array_join("ARRAY_TO_STRING", f),
2944            Expression::Unnest(f) => self.generate_unnest(f),
2945            Expression::Explode(f) => self.generate_simple_func("EXPLODE", &f.this),
2946            Expression::ExplodeOuter(f) => self.generate_simple_func("EXPLODE_OUTER", &f.this),
2947            Expression::ArrayFilter(f) => self.generate_array_filter(f),
2948            Expression::ArrayTransform(f) => self.generate_array_transform(f),
2949            Expression::ArrayFlatten(f) => self.generate_simple_func("FLATTEN", &f.this),
2950            Expression::ArrayCompact(f) => {
2951                if matches!(self.config.dialect, Some(DialectType::DuckDB)) {
2952                    // DuckDB: ARRAY_COMPACT(arr) -> LIST_FILTER(arr, _u -> NOT _u IS NULL)
2953                    self.write("LIST_FILTER(");
2954                    self.generate_expression(&f.this)?;
2955                    self.write(", _u -> NOT _u IS NULL)");
2956                    Ok(())
2957                } else {
2958                    self.generate_simple_func("ARRAY_COMPACT", &f.this)
2959                }
2960            }
2961            Expression::ArrayIntersect(f) => {
2962                let func_name = f.original_name.as_deref().unwrap_or("ARRAY_INTERSECT");
2963                self.generate_vararg_func(func_name, &f.expressions)
2964            }
2965            Expression::ArrayUnion(f) => {
2966                self.generate_binary_func("ARRAY_UNION", &f.this, &f.expression)
2967            }
2968            Expression::ArrayExcept(f) => {
2969                self.generate_binary_func("ARRAY_EXCEPT", &f.this, &f.expression)
2970            }
2971            Expression::ArrayRemove(f) => {
2972                self.generate_binary_func("ARRAY_REMOVE", &f.this, &f.expression)
2973            }
2974            Expression::ArrayZip(f) => self.generate_vararg_func("ARRAYS_ZIP", &f.expressions),
2975            Expression::Sequence(f) => self.generate_sequence("SEQUENCE", f),
2976            Expression::Generate(f) => self.generate_sequence("GENERATE_SERIES", f),
2977
2978            // Struct functions
2979            Expression::StructFunc(f) => self.generate_struct_constructor(f),
2980            Expression::StructExtract(f) => self.generate_struct_extract(f),
2981            Expression::NamedStruct(f) => self.generate_named_struct(f),
2982
2983            // Map functions
2984            Expression::MapFunc(f) => self.generate_map_constructor(f),
2985            Expression::MapFromEntries(f) => self.generate_simple_func("MAP_FROM_ENTRIES", &f.this),
2986            Expression::MapFromArrays(f) => {
2987                self.generate_binary_func("MAP_FROM_ARRAYS", &f.this, &f.expression)
2988            }
2989            Expression::MapKeys(f) => self.generate_simple_func("MAP_KEYS", &f.this),
2990            Expression::MapValues(f) => self.generate_simple_func("MAP_VALUES", &f.this),
2991            Expression::MapContainsKey(f) => {
2992                self.generate_binary_func("MAP_CONTAINS_KEY", &f.this, &f.expression)
2993            }
2994            Expression::MapConcat(f) => self.generate_vararg_func("MAP_CONCAT", &f.expressions),
2995            Expression::ElementAt(f) => {
2996                self.generate_binary_func("ELEMENT_AT", &f.this, &f.expression)
2997            }
2998            Expression::TransformKeys(f) => self.generate_transform_func("TRANSFORM_KEYS", f),
2999            Expression::TransformValues(f) => self.generate_transform_func("TRANSFORM_VALUES", f),
3000
3001            // JSON functions
3002            Expression::JsonExtract(f) => self.generate_json_extract("JSON_EXTRACT", f),
3003            Expression::JsonExtractScalar(f) => {
3004                self.generate_json_extract("JSON_EXTRACT_SCALAR", f)
3005            }
3006            Expression::JsonExtractPath(f) => self.generate_json_path("JSON_EXTRACT_PATH", f),
3007            Expression::JsonArray(f) => self.generate_vararg_func("JSON_ARRAY", &f.expressions),
3008            Expression::JsonObject(f) => self.generate_json_object(f),
3009            Expression::JsonQuery(f) => self.generate_json_extract("JSON_QUERY", f),
3010            Expression::JsonValue(f) => self.generate_json_extract("JSON_VALUE", f),
3011            Expression::JsonArrayLength(f) => {
3012                self.generate_simple_func("JSON_ARRAY_LENGTH", &f.this)
3013            }
3014            Expression::JsonKeys(f) => self.generate_simple_func("JSON_KEYS", &f.this),
3015            Expression::JsonType(f) => self.generate_simple_func("JSON_TYPE", &f.this),
3016            Expression::ParseJson(f) => {
3017                let name = match self.config.dialect {
3018                    Some(DialectType::Presto)
3019                    | Some(DialectType::Trino)
3020                    | Some(DialectType::Athena) => "JSON_PARSE",
3021                    Some(DialectType::PostgreSQL) | Some(DialectType::Redshift) => {
3022                        // PostgreSQL: CAST(x AS JSON)
3023                        self.write_keyword("CAST");
3024                        self.write("(");
3025                        self.generate_expression(&f.this)?;
3026                        self.write_keyword(" AS ");
3027                        self.write_keyword("JSON");
3028                        self.write(")");
3029                        return Ok(());
3030                    }
3031                    Some(DialectType::Hive)
3032                    | Some(DialectType::Spark)
3033                    | Some(DialectType::MySQL)
3034                    | Some(DialectType::SingleStore)
3035                    | Some(DialectType::TiDB)
3036                    | Some(DialectType::TSQL) => {
3037                        // Hive/Spark/MySQL/TSQL: just emit the string literal
3038                        self.generate_expression(&f.this)?;
3039                        return Ok(());
3040                    }
3041                    Some(DialectType::DuckDB) => "JSON",
3042                    _ => "PARSE_JSON",
3043                };
3044                self.generate_simple_func(name, &f.this)
3045            }
3046            Expression::ToJson(f) => self.generate_simple_func("TO_JSON", &f.this),
3047            Expression::JsonSet(f) => self.generate_json_modify("JSON_SET", f),
3048            Expression::JsonInsert(f) => self.generate_json_modify("JSON_INSERT", f),
3049            Expression::JsonRemove(f) => self.generate_json_path("JSON_REMOVE", f),
3050            Expression::JsonMergePatch(f) => {
3051                self.generate_binary_func("JSON_MERGE_PATCH", &f.this, &f.expression)
3052            }
3053            Expression::JsonArrayAgg(f) => self.generate_json_array_agg(f),
3054            Expression::JsonObjectAgg(f) => self.generate_json_object_agg(f),
3055
3056            // Type casting/conversion
3057            Expression::Convert(f) => self.generate_convert(f),
3058            Expression::Typeof(f) => self.generate_simple_func("TYPEOF", &f.this),
3059
3060            // Additional expressions
3061            Expression::Lambda(f) => self.generate_lambda(f),
3062            Expression::Parameter(f) => self.generate_parameter(f),
3063            Expression::Placeholder(f) => self.generate_placeholder(f),
3064            Expression::NamedArgument(f) => self.generate_named_argument(f),
3065            Expression::TableArgument(f) => self.generate_table_argument(f),
3066            Expression::SqlComment(f) => self.generate_sql_comment(f),
3067
3068            // Additional predicates
3069            Expression::NullSafeEq(op) => self.generate_null_safe_eq(op),
3070            Expression::NullSafeNeq(op) => self.generate_null_safe_neq(op),
3071            Expression::Glob(op) => self.generate_binary_op(op, "GLOB"),
3072            Expression::SimilarTo(f) => self.generate_similar_to(f),
3073            Expression::Any(f) => self.generate_quantified("ANY", f),
3074            Expression::All(f) => self.generate_quantified("ALL", f),
3075            Expression::Overlaps(f) => self.generate_overlaps(f),
3076
3077            // Bitwise operations
3078            Expression::BitwiseLeftShift(op) => {
3079                if matches!(
3080                    self.config.dialect,
3081                    Some(DialectType::Presto) | Some(DialectType::Trino)
3082                ) {
3083                    self.write_keyword("BITWISE_ARITHMETIC_SHIFT_LEFT");
3084                    self.write("(");
3085                    self.generate_expression(&op.left)?;
3086                    self.write(", ");
3087                    self.generate_expression(&op.right)?;
3088                    self.write(")");
3089                    Ok(())
3090                } else if matches!(
3091                    self.config.dialect,
3092                    Some(DialectType::Spark) | Some(DialectType::Databricks)
3093                ) {
3094                    self.write_keyword("SHIFTLEFT");
3095                    self.write("(");
3096                    self.generate_expression(&op.left)?;
3097                    self.write(", ");
3098                    self.generate_expression(&op.right)?;
3099                    self.write(")");
3100                    Ok(())
3101                } else {
3102                    self.generate_binary_op(op, "<<")
3103                }
3104            }
3105            Expression::BitwiseRightShift(op) => {
3106                if matches!(
3107                    self.config.dialect,
3108                    Some(DialectType::Presto) | Some(DialectType::Trino)
3109                ) {
3110                    self.write_keyword("BITWISE_ARITHMETIC_SHIFT_RIGHT");
3111                    self.write("(");
3112                    self.generate_expression(&op.left)?;
3113                    self.write(", ");
3114                    self.generate_expression(&op.right)?;
3115                    self.write(")");
3116                    Ok(())
3117                } else if matches!(
3118                    self.config.dialect,
3119                    Some(DialectType::Spark) | Some(DialectType::Databricks)
3120                ) {
3121                    self.write_keyword("SHIFTRIGHT");
3122                    self.write("(");
3123                    self.generate_expression(&op.left)?;
3124                    self.write(", ");
3125                    self.generate_expression(&op.right)?;
3126                    self.write(")");
3127                    Ok(())
3128                } else {
3129                    self.generate_binary_op(op, ">>")
3130                }
3131            }
3132            Expression::BitwiseAndAgg(f) => self.generate_agg_func("BIT_AND", f),
3133            Expression::BitwiseOrAgg(f) => self.generate_agg_func("BIT_OR", f),
3134            Expression::BitwiseXorAgg(f) => self.generate_agg_func("BIT_XOR", f),
3135
3136            // Array/struct/map access
3137            Expression::Subscript(s) => self.generate_subscript(s),
3138            Expression::Dot(d) => self.generate_dot_access(d),
3139            Expression::MethodCall(m) => self.generate_method_call(m),
3140            Expression::ArraySlice(s) => self.generate_array_slice(s),
3141
3142            Expression::And(op) => self.generate_connector_op(op, ConnectorOperator::And),
3143            Expression::Or(op) => self.generate_connector_op(op, ConnectorOperator::Or),
3144            Expression::Add(op) => self.generate_binary_op(op, "+"),
3145            Expression::Sub(op) => self.generate_binary_op(op, "-"),
3146            Expression::Mul(op) => self.generate_binary_op(op, "*"),
3147            Expression::Div(op) => self.generate_binary_op(op, "/"),
3148            Expression::IntDiv(f) => {
3149                use crate::dialects::DialectType;
3150                if matches!(self.config.dialect, Some(DialectType::DuckDB)) {
3151                    // DuckDB uses // operator for integer division
3152                    self.generate_expression(&f.this)?;
3153                    self.write(" // ");
3154                    self.generate_expression(&f.expression)?;
3155                    Ok(())
3156                } else if matches!(
3157                    self.config.dialect,
3158                    Some(DialectType::Hive | DialectType::Spark | DialectType::Databricks)
3159                ) {
3160                    // Hive/Spark use DIV as an infix operator
3161                    self.generate_expression(&f.this)?;
3162                    self.write(" ");
3163                    self.write_keyword("DIV");
3164                    self.write(" ");
3165                    self.generate_expression(&f.expression)?;
3166                    Ok(())
3167                } else {
3168                    // Other dialects use DIV function
3169                    self.write_keyword("DIV");
3170                    self.write("(");
3171                    self.generate_expression(&f.this)?;
3172                    self.write(", ");
3173                    self.generate_expression(&f.expression)?;
3174                    self.write(")");
3175                    Ok(())
3176                }
3177            }
3178            Expression::Mod(op) => {
3179                if matches!(self.config.dialect, Some(DialectType::Teradata)) {
3180                    self.generate_binary_op(op, "MOD")
3181                } else {
3182                    self.generate_binary_op(op, "%")
3183                }
3184            }
3185            Expression::Eq(op) => self.generate_binary_op(op, "="),
3186            Expression::Neq(op) => self.generate_binary_op(op, "<>"),
3187            Expression::Lt(op) => self.generate_binary_op(op, "<"),
3188            Expression::Lte(op) => self.generate_binary_op(op, "<="),
3189            Expression::Gt(op) => self.generate_binary_op(op, ">"),
3190            Expression::Gte(op) => self.generate_binary_op(op, ">="),
3191            Expression::Like(op) => self.generate_like_op(op, "LIKE"),
3192            Expression::ILike(op) => self.generate_like_op(op, "ILIKE"),
3193            Expression::Match(op) => self.generate_binary_op(op, "MATCH"),
3194            Expression::Concat(op) => {
3195                // In Solr, || is OR, not string concatenation (DPIPE_IS_STRING_CONCAT = False)
3196                if self.config.dialect == Some(DialectType::Solr) {
3197                    self.generate_binary_op(op, "OR")
3198                } else if self.config.dialect == Some(DialectType::MySQL) {
3199                    self.generate_mysql_concat_from_concat(op)
3200                } else {
3201                    self.generate_binary_op(op, "||")
3202                }
3203            }
3204            Expression::BitwiseAnd(op) => {
3205                // Presto/Trino use BITWISE_AND function
3206                if matches!(
3207                    self.config.dialect,
3208                    Some(DialectType::Presto) | Some(DialectType::Trino)
3209                ) {
3210                    self.write_keyword("BITWISE_AND");
3211                    self.write("(");
3212                    self.generate_expression(&op.left)?;
3213                    self.write(", ");
3214                    self.generate_expression(&op.right)?;
3215                    self.write(")");
3216                    Ok(())
3217                } else {
3218                    self.generate_binary_op(op, "&")
3219                }
3220            }
3221            Expression::BitwiseOr(op) => {
3222                // Presto/Trino use BITWISE_OR function
3223                if matches!(
3224                    self.config.dialect,
3225                    Some(DialectType::Presto) | Some(DialectType::Trino)
3226                ) {
3227                    self.write_keyword("BITWISE_OR");
3228                    self.write("(");
3229                    self.generate_expression(&op.left)?;
3230                    self.write(", ");
3231                    self.generate_expression(&op.right)?;
3232                    self.write(")");
3233                    Ok(())
3234                } else {
3235                    self.generate_binary_op(op, "|")
3236                }
3237            }
3238            Expression::BitwiseXor(op) => {
3239                // Presto/Trino use BITWISE_XOR function, PostgreSQL uses #, others use ^
3240                if matches!(
3241                    self.config.dialect,
3242                    Some(DialectType::Presto) | Some(DialectType::Trino)
3243                ) {
3244                    self.write_keyword("BITWISE_XOR");
3245                    self.write("(");
3246                    self.generate_expression(&op.left)?;
3247                    self.write(", ");
3248                    self.generate_expression(&op.right)?;
3249                    self.write(")");
3250                    Ok(())
3251                } else if matches!(self.config.dialect, Some(DialectType::PostgreSQL)) {
3252                    self.generate_binary_op(op, "#")
3253                } else {
3254                    self.generate_binary_op(op, "^")
3255                }
3256            }
3257            Expression::Adjacent(op) => self.generate_binary_op(op, "-|-"),
3258            Expression::TsMatch(op) => self.generate_binary_op(op, "@@"),
3259            Expression::PropertyEQ(op) => self.generate_binary_op(op, ":="),
3260            Expression::ArrayContainsAll(op) => self.generate_binary_op(op, "@>"),
3261            Expression::ArrayContainedBy(op) => self.generate_binary_op(op, "<@"),
3262            Expression::ArrayOverlaps(op) => self.generate_binary_op(op, "&&"),
3263            Expression::JSONBContainsAllTopKeys(op) => self.generate_binary_op(op, "?&"),
3264            Expression::JSONBContainsAnyTopKeys(op) => self.generate_binary_op(op, "?|"),
3265            Expression::JSONBContains(f) => {
3266                // PostgreSQL JSONB contains key operator: a ? b
3267                self.generate_expression(&f.this)?;
3268                self.write_space();
3269                self.write("?");
3270                self.write_space();
3271                self.generate_expression(&f.expression)
3272            }
3273            Expression::JSONBDeleteAtPath(op) => self.generate_binary_op(op, "#-"),
3274            Expression::ExtendsLeft(op) => self.generate_binary_op(op, "&<"),
3275            Expression::ExtendsRight(op) => self.generate_binary_op(op, "&>"),
3276            Expression::Not(op) => self.generate_unary_op(op, "NOT"),
3277            Expression::Neg(op) => self.generate_unary_op(op, "-"),
3278            Expression::BitwiseNot(op) => {
3279                // Presto/Trino use BITWISE_NOT function
3280                if matches!(
3281                    self.config.dialect,
3282                    Some(DialectType::Presto) | Some(DialectType::Trino)
3283                ) {
3284                    self.write_keyword("BITWISE_NOT");
3285                    self.write("(");
3286                    self.generate_expression(&op.this)?;
3287                    self.write(")");
3288                    Ok(())
3289                } else {
3290                    self.generate_unary_op(op, "~")
3291                }
3292            }
3293            Expression::In(in_expr) => self.generate_in(in_expr),
3294            Expression::Between(between) => self.generate_between(between),
3295            Expression::IsNull(is_null) => self.generate_is_null(is_null),
3296            Expression::IsTrue(is_true) => self.generate_is_true(is_true),
3297            Expression::IsFalse(is_false) => self.generate_is_false(is_false),
3298            Expression::IsJson(is_json) => self.generate_is_json(is_json),
3299            Expression::Is(is_expr) => self.generate_is(is_expr),
3300            Expression::Exists(exists) => self.generate_exists(exists),
3301            Expression::MemberOf(member_of) => self.generate_member_of(member_of),
3302            Expression::Subquery(subquery) => self.generate_subquery(subquery),
3303            Expression::Paren(paren) => {
3304                // JoinedTable already outputs its own parentheses, so don't double-wrap
3305                let skip_parens = matches!(&paren.this, Expression::JoinedTable(_));
3306
3307                if !skip_parens {
3308                    self.write("(");
3309                    if self.config.pretty {
3310                        self.write_newline();
3311                        self.indent_level += 1;
3312                        self.write_indent();
3313                    }
3314                }
3315                self.generate_expression(&paren.this)?;
3316                if !skip_parens {
3317                    if self.config.pretty {
3318                        self.write_newline();
3319                        self.indent_level -= 1;
3320                        self.write_indent();
3321                    }
3322                    self.write(")");
3323                }
3324                // Output trailing comments after closing paren
3325                for comment in &paren.trailing_comments {
3326                    self.write(" ");
3327                    self.write_formatted_comment(comment);
3328                }
3329                Ok(())
3330            }
3331            Expression::Array(arr) => self.generate_array(arr),
3332            Expression::Tuple(tuple) => self.generate_tuple(tuple),
3333            Expression::PipeOperator(pipe) => self.generate_pipe_operator(pipe),
3334            Expression::Ordered(ordered) => self.generate_ordered(ordered),
3335            Expression::DataType(dt) => self.generate_data_type(dt),
3336            Expression::Raw(raw) => {
3337                self.write(&raw.sql);
3338                Ok(())
3339            }
3340            Expression::CreateTask(task) => self.generate_create_task(task),
3341            Expression::Command(cmd) => {
3342                self.write(&cmd.this);
3343                Ok(())
3344            }
3345            Expression::Kill(kill) => {
3346                self.write_keyword("KILL");
3347                if let Some(kind) = &kill.kind {
3348                    self.write_space();
3349                    self.write_keyword(kind);
3350                }
3351                self.write_space();
3352                self.generate_expression(&kill.this)?;
3353                Ok(())
3354            }
3355            Expression::Execute(exec) => {
3356                self.write_keyword("EXECUTE");
3357                self.write_space();
3358                self.generate_expression(&exec.this)?;
3359                for (i, param) in exec.parameters.iter().enumerate() {
3360                    if i == 0 {
3361                        self.write_space();
3362                    } else {
3363                        self.write(", ");
3364                    }
3365                    self.write(&param.name);
3366                    // Only write = value for named parameters (not positional)
3367                    if !param.positional {
3368                        self.write(" = ");
3369                        self.generate_expression(&param.value)?;
3370                    }
3371                    if param.output {
3372                        self.write_space();
3373                        self.write_keyword("OUTPUT");
3374                    }
3375                }
3376                if let Some(ref suffix) = exec.suffix {
3377                    self.write_space();
3378                    self.write(suffix);
3379                }
3380                Ok(())
3381            }
3382            Expression::Annotated(annotated) => {
3383                self.generate_expression(&annotated.this)?;
3384                for comment in &annotated.trailing_comments {
3385                    self.write(" ");
3386                    self.write_formatted_comment(comment);
3387                }
3388                Ok(())
3389            }
3390
3391            // DDL statements
3392            Expression::CreateTable(ct) => self.generate_create_table(ct),
3393            Expression::DropTable(dt) => self.generate_drop_table(dt),
3394            Expression::Undrop(u) => self.generate_undrop(u),
3395            Expression::AlterTable(at) => self.generate_alter_table(at),
3396            Expression::CreateIndex(ci) => self.generate_create_index(ci),
3397            Expression::DropIndex(di) => self.generate_drop_index(di),
3398            Expression::CreateView(cv) => self.generate_create_view(cv),
3399            Expression::DropView(dv) => self.generate_drop_view(dv),
3400            Expression::AlterView(av) => self.generate_alter_view(av),
3401            Expression::AlterIndex(ai) => self.generate_alter_index(ai),
3402            Expression::Truncate(tr) => self.generate_truncate(tr),
3403            Expression::Use(u) => self.generate_use(u),
3404            // Phase 4: Additional DDL statements
3405            Expression::CreateSchema(cs) => self.generate_create_schema(cs),
3406            Expression::DropSchema(ds) => self.generate_drop_schema(ds),
3407            Expression::DropNamespace(dn) => self.generate_drop_namespace(dn),
3408            Expression::CreateDatabase(cd) => self.generate_create_database(cd),
3409            Expression::DropDatabase(dd) => self.generate_drop_database(dd),
3410            Expression::CreateFunction(cf) => self.generate_create_function(cf),
3411            Expression::DropFunction(df) => self.generate_drop_function(df),
3412            Expression::CreateProcedure(cp) => self.generate_create_procedure(cp),
3413            Expression::DropProcedure(dp) => self.generate_drop_procedure(dp),
3414            Expression::CreateSequence(cs) => self.generate_create_sequence(cs),
3415            Expression::CreateSynonym(cs) => {
3416                self.write_keyword("CREATE SYNONYM");
3417                self.write_space();
3418                self.generate_table(&cs.name)?;
3419                self.write_space();
3420                self.write_keyword("FOR");
3421                self.write_space();
3422                self.generate_table(&cs.target)?;
3423                Ok(())
3424            }
3425            Expression::DropSequence(ds) => self.generate_drop_sequence(ds),
3426            Expression::AlterSequence(als) => self.generate_alter_sequence(als),
3427            Expression::CreateTrigger(ct) => self.generate_create_trigger(ct),
3428            Expression::DropTrigger(dt) => self.generate_drop_trigger(dt),
3429            Expression::CreateType(ct) => self.generate_create_type(ct),
3430            Expression::DropType(dt) => self.generate_drop_type(dt),
3431            Expression::Describe(d) => self.generate_describe(d),
3432            Expression::Show(s) => self.generate_show(s),
3433
3434            // CACHE/UNCACHE/LOAD TABLE (Spark/Hive)
3435            Expression::Cache(c) => self.generate_cache(c),
3436            Expression::Uncache(u) => self.generate_uncache(u),
3437            Expression::LoadData(l) => self.generate_load_data(l),
3438            Expression::Pragma(p) => self.generate_pragma(p),
3439            Expression::Grant(g) => self.generate_grant(g),
3440            Expression::Revoke(r) => self.generate_revoke(r),
3441            Expression::Comment(c) => self.generate_comment(c),
3442            Expression::SetStatement(s) => self.generate_set_statement(s),
3443
3444            // PIVOT/UNPIVOT
3445            Expression::Pivot(pivot) => self.generate_pivot(pivot),
3446            Expression::Unpivot(unpivot) => self.generate_unpivot(unpivot),
3447
3448            // VALUES table constructor
3449            Expression::Values(values) => self.generate_values(values),
3450
3451            // === BATCH-GENERATED MATCH ARMS (481 variants) ===
3452            Expression::AIAgg(e) => self.generate_ai_agg(e),
3453            Expression::AIClassify(e) => self.generate_ai_classify(e),
3454            Expression::AddPartition(e) => self.generate_add_partition(e),
3455            Expression::AlgorithmProperty(e) => self.generate_algorithm_property(e),
3456            Expression::Aliases(e) => self.generate_aliases(e),
3457            Expression::AllowedValuesProperty(e) => self.generate_allowed_values_property(e),
3458            Expression::AlterColumn(e) => self.generate_alter_column(e),
3459            Expression::AlterSession(e) => self.generate_alter_session(e),
3460            Expression::AlterSet(e) => self.generate_alter_set(e),
3461            Expression::AlterSortKey(e) => self.generate_alter_sort_key(e),
3462            Expression::Analyze(e) => self.generate_analyze(e),
3463            Expression::AnalyzeDelete(e) => self.generate_analyze_delete(e),
3464            Expression::AnalyzeHistogram(e) => self.generate_analyze_histogram(e),
3465            Expression::AnalyzeListChainedRows(e) => self.generate_analyze_list_chained_rows(e),
3466            Expression::AnalyzeSample(e) => self.generate_analyze_sample(e),
3467            Expression::AnalyzeStatistics(e) => self.generate_analyze_statistics(e),
3468            Expression::AnalyzeValidate(e) => self.generate_analyze_validate(e),
3469            Expression::AnalyzeWith(e) => self.generate_analyze_with(e),
3470            Expression::Anonymous(e) => self.generate_anonymous(e),
3471            Expression::AnonymousAggFunc(e) => self.generate_anonymous_agg_func(e),
3472            Expression::Apply(e) => self.generate_apply(e),
3473            Expression::ApproxPercentileEstimate(e) => self.generate_approx_percentile_estimate(e),
3474            Expression::ApproxQuantile(e) => self.generate_approx_quantile(e),
3475            Expression::ApproxQuantiles(e) => self.generate_approx_quantiles(e),
3476            Expression::ApproxTopK(e) => self.generate_approx_top_k(e),
3477            Expression::ApproxTopKAccumulate(e) => self.generate_approx_top_k_accumulate(e),
3478            Expression::ApproxTopKCombine(e) => self.generate_approx_top_k_combine(e),
3479            Expression::ApproxTopKEstimate(e) => self.generate_approx_top_k_estimate(e),
3480            Expression::ApproxTopSum(e) => self.generate_approx_top_sum(e),
3481            Expression::ArgMax(e) => self.generate_arg_max(e),
3482            Expression::ArgMin(e) => self.generate_arg_min(e),
3483            Expression::ArrayAll(e) => self.generate_array_all(e),
3484            Expression::ArrayAny(e) => self.generate_array_any(e),
3485            Expression::ArrayConstructCompact(e) => self.generate_array_construct_compact(e),
3486            Expression::ArraySum(e) => self.generate_array_sum(e),
3487            Expression::AtIndex(e) => self.generate_at_index(e),
3488            Expression::Attach(e) => self.generate_attach(e),
3489            Expression::AttachOption(e) => self.generate_attach_option(e),
3490            Expression::AutoIncrementProperty(e) => self.generate_auto_increment_property(e),
3491            Expression::AutoRefreshProperty(e) => self.generate_auto_refresh_property(e),
3492            Expression::BackupProperty(e) => self.generate_backup_property(e),
3493            Expression::Base64DecodeBinary(e) => self.generate_base64_decode_binary(e),
3494            Expression::Base64DecodeString(e) => self.generate_base64_decode_string(e),
3495            Expression::Base64Encode(e) => self.generate_base64_encode(e),
3496            Expression::BlockCompressionProperty(e) => self.generate_block_compression_property(e),
3497            Expression::Booland(e) => self.generate_booland(e),
3498            Expression::Boolor(e) => self.generate_boolor(e),
3499            Expression::BuildProperty(e) => self.generate_build_property(e),
3500            Expression::ByteString(e) => self.generate_byte_string(e),
3501            Expression::CaseSpecificColumnConstraint(e) => {
3502                self.generate_case_specific_column_constraint(e)
3503            }
3504            Expression::CastToStrType(e) => self.generate_cast_to_str_type(e),
3505            Expression::Changes(e) => self.generate_changes(e),
3506            Expression::CharacterSetColumnConstraint(e) => {
3507                self.generate_character_set_column_constraint(e)
3508            }
3509            Expression::CharacterSetProperty(e) => self.generate_character_set_property(e),
3510            Expression::CheckColumnConstraint(e) => self.generate_check_column_constraint(e),
3511            Expression::AssumeColumnConstraint(e) => self.generate_assume_column_constraint(e),
3512            Expression::CheckJson(e) => self.generate_check_json(e),
3513            Expression::CheckXml(e) => self.generate_check_xml(e),
3514            Expression::ChecksumProperty(e) => self.generate_checksum_property(e),
3515            Expression::Clone(e) => self.generate_clone(e),
3516            Expression::ClusterBy(e) => self.generate_cluster_by(e),
3517            Expression::ClusterByColumnsProperty(e) => self.generate_cluster_by_columns_property(e),
3518            Expression::ClusteredByProperty(e) => self.generate_clustered_by_property(e),
3519            Expression::CollateProperty(e) => self.generate_collate_property(e),
3520            Expression::ColumnConstraint(e) => self.generate_column_constraint(e),
3521            Expression::ColumnDef(e) => self.generate_column_def_expr(e),
3522            Expression::ColumnPosition(e) => self.generate_column_position(e),
3523            Expression::ColumnPrefix(e) => self.generate_column_prefix(e),
3524            Expression::Columns(e) => self.generate_columns(e),
3525            Expression::CombinedAggFunc(e) => self.generate_combined_agg_func(e),
3526            Expression::CombinedParameterizedAgg(e) => self.generate_combined_parameterized_agg(e),
3527            Expression::Commit(e) => self.generate_commit(e),
3528            Expression::Comprehension(e) => self.generate_comprehension(e),
3529            Expression::Compress(e) => self.generate_compress(e),
3530            Expression::CompressColumnConstraint(e) => self.generate_compress_column_constraint(e),
3531            Expression::ComputedColumnConstraint(e) => self.generate_computed_column_constraint(e),
3532            Expression::ConditionalInsert(e) => self.generate_conditional_insert(e),
3533            Expression::Constraint(e) => self.generate_constraint(e),
3534            Expression::ConvertTimezone(e) => self.generate_convert_timezone(e),
3535            Expression::ConvertToCharset(e) => self.generate_convert_to_charset(e),
3536            Expression::Copy(e) => self.generate_copy(e),
3537            Expression::CopyParameter(e) => self.generate_copy_parameter(e),
3538            Expression::Corr(e) => self.generate_corr(e),
3539            Expression::CosineDistance(e) => self.generate_cosine_distance(e),
3540            Expression::CovarPop(e) => self.generate_covar_pop(e),
3541            Expression::CovarSamp(e) => self.generate_covar_samp(e),
3542            Expression::Credentials(e) => self.generate_credentials(e),
3543            Expression::CredentialsProperty(e) => self.generate_credentials_property(e),
3544            Expression::Cte(e) => self.generate_cte(e),
3545            Expression::Cube(e) => self.generate_cube(e),
3546            Expression::CurrentDatetime(e) => self.generate_current_datetime(e),
3547            Expression::CurrentSchema(e) => self.generate_current_schema(e),
3548            Expression::CurrentSchemas(e) => self.generate_current_schemas(e),
3549            Expression::CurrentUser(e) => self.generate_current_user(e),
3550            Expression::DPipe(e) => self.generate_d_pipe(e),
3551            Expression::DataBlocksizeProperty(e) => self.generate_data_blocksize_property(e),
3552            Expression::DataDeletionProperty(e) => self.generate_data_deletion_property(e),
3553            Expression::Date(e) => self.generate_date_func(e),
3554            Expression::DateBin(e) => self.generate_date_bin(e),
3555            Expression::DateFormatColumnConstraint(e) => {
3556                self.generate_date_format_column_constraint(e)
3557            }
3558            Expression::DateFromParts(e) => self.generate_date_from_parts(e),
3559            Expression::Datetime(e) => self.generate_datetime(e),
3560            Expression::DatetimeAdd(e) => self.generate_datetime_add(e),
3561            Expression::DatetimeDiff(e) => self.generate_datetime_diff(e),
3562            Expression::DatetimeSub(e) => self.generate_datetime_sub(e),
3563            Expression::DatetimeTrunc(e) => self.generate_datetime_trunc(e),
3564            Expression::Dayname(e) => self.generate_dayname(e),
3565            Expression::Declare(e) => self.generate_declare(e),
3566            Expression::DeclareItem(e) => self.generate_declare_item(e),
3567            Expression::DecodeCase(e) => self.generate_decode_case(e),
3568            Expression::DecompressBinary(e) => self.generate_decompress_binary(e),
3569            Expression::DecompressString(e) => self.generate_decompress_string(e),
3570            Expression::Decrypt(e) => self.generate_decrypt(e),
3571            Expression::DecryptRaw(e) => self.generate_decrypt_raw(e),
3572            Expression::DefaultColumnConstraint(e) => {
3573                self.write_keyword("DEFAULT");
3574                self.write_space();
3575                self.generate_expression(&e.this)?;
3576                if let Some(ref col) = e.for_column {
3577                    self.write_space();
3578                    self.write_keyword("FOR");
3579                    self.write_space();
3580                    self.generate_identifier(col)?;
3581                }
3582                Ok(())
3583            }
3584            Expression::DefinerProperty(e) => self.generate_definer_property(e),
3585            Expression::Detach(e) => self.generate_detach(e),
3586            Expression::DictProperty(e) => self.generate_dict_property(e),
3587            Expression::DictRange(e) => self.generate_dict_range(e),
3588            Expression::Directory(e) => self.generate_directory(e),
3589            Expression::DistKeyProperty(e) => self.generate_dist_key_property(e),
3590            Expression::DistStyleProperty(e) => self.generate_dist_style_property(e),
3591            Expression::DistributeBy(e) => self.generate_distribute_by(e),
3592            Expression::DistributedByProperty(e) => self.generate_distributed_by_property(e),
3593            Expression::DotProduct(e) => self.generate_dot_product(e),
3594            Expression::DropPartition(e) => self.generate_drop_partition(e),
3595            Expression::DuplicateKeyProperty(e) => self.generate_duplicate_key_property(e),
3596            Expression::Elt(e) => self.generate_elt(e),
3597            Expression::Encode(e) => self.generate_encode(e),
3598            Expression::EncodeProperty(e) => self.generate_encode_property(e),
3599            Expression::Encrypt(e) => self.generate_encrypt(e),
3600            Expression::EncryptRaw(e) => self.generate_encrypt_raw(e),
3601            Expression::EngineProperty(e) => self.generate_engine_property(e),
3602            Expression::EnviromentProperty(e) => self.generate_enviroment_property(e),
3603            Expression::EphemeralColumnConstraint(e) => {
3604                self.generate_ephemeral_column_constraint(e)
3605            }
3606            Expression::EqualNull(e) => self.generate_equal_null(e),
3607            Expression::EuclideanDistance(e) => self.generate_euclidean_distance(e),
3608            Expression::ExecuteAsProperty(e) => self.generate_execute_as_property(e),
3609            Expression::Export(e) => self.generate_export(e),
3610            Expression::ExternalProperty(e) => self.generate_external_property(e),
3611            Expression::FallbackProperty(e) => self.generate_fallback_property(e),
3612            Expression::FarmFingerprint(e) => self.generate_farm_fingerprint(e),
3613            Expression::FeaturesAtTime(e) => self.generate_features_at_time(e),
3614            Expression::Fetch(e) => self.generate_fetch(e),
3615            Expression::FileFormatProperty(e) => self.generate_file_format_property(e),
3616            Expression::Filter(e) => self.generate_filter(e),
3617            Expression::Float64(e) => self.generate_float64(e),
3618            Expression::ForIn(e) => self.generate_for_in(e),
3619            Expression::ForeignKey(e) => self.generate_foreign_key(e),
3620            Expression::Format(e) => self.generate_format(e),
3621            Expression::FormatPhrase(e) => self.generate_format_phrase(e),
3622            Expression::FreespaceProperty(e) => self.generate_freespace_property(e),
3623            Expression::From(e) => self.generate_from(e),
3624            Expression::FromBase(e) => self.generate_from_base(e),
3625            Expression::FromTimeZone(e) => self.generate_from_time_zone(e),
3626            Expression::GapFill(e) => self.generate_gap_fill(e),
3627            Expression::GenerateDateArray(e) => self.generate_generate_date_array(e),
3628            Expression::GenerateEmbedding(e) => self.generate_generate_embedding(e),
3629            Expression::GenerateSeries(e) => self.generate_generate_series(e),
3630            Expression::GenerateTimestampArray(e) => self.generate_generate_timestamp_array(e),
3631            Expression::GeneratedAsIdentityColumnConstraint(e) => {
3632                self.generate_generated_as_identity_column_constraint(e)
3633            }
3634            Expression::GeneratedAsRowColumnConstraint(e) => {
3635                self.generate_generated_as_row_column_constraint(e)
3636            }
3637            Expression::Get(e) => self.generate_get(e),
3638            Expression::GetExtract(e) => self.generate_get_extract(e),
3639            Expression::Getbit(e) => self.generate_getbit(e),
3640            Expression::GrantPrincipal(e) => self.generate_grant_principal(e),
3641            Expression::GrantPrivilege(e) => self.generate_grant_privilege(e),
3642            Expression::Group(e) => self.generate_group(e),
3643            Expression::GroupBy(e) => self.generate_group_by(e),
3644            Expression::Grouping(e) => self.generate_grouping(e),
3645            Expression::GroupingId(e) => self.generate_grouping_id(e),
3646            Expression::GroupingSets(e) => self.generate_grouping_sets(e),
3647            Expression::HashAgg(e) => self.generate_hash_agg(e),
3648            Expression::Having(e) => self.generate_having(e),
3649            Expression::HavingMax(e) => self.generate_having_max(e),
3650            Expression::Heredoc(e) => self.generate_heredoc(e),
3651            Expression::HexEncode(e) => self.generate_hex_encode(e),
3652            Expression::Hll(e) => self.generate_hll(e),
3653            Expression::InOutColumnConstraint(e) => self.generate_in_out_column_constraint(e),
3654            Expression::IncludeProperty(e) => self.generate_include_property(e),
3655            Expression::Index(e) => self.generate_index(e),
3656            Expression::IndexColumnConstraint(e) => self.generate_index_column_constraint(e),
3657            Expression::IndexConstraintOption(e) => self.generate_index_constraint_option(e),
3658            Expression::IndexParameters(e) => self.generate_index_parameters(e),
3659            Expression::IndexTableHint(e) => self.generate_index_table_hint(e),
3660            Expression::InheritsProperty(e) => self.generate_inherits_property(e),
3661            Expression::InputModelProperty(e) => self.generate_input_model_property(e),
3662            Expression::InputOutputFormat(e) => self.generate_input_output_format(e),
3663            Expression::Install(e) => self.generate_install(e),
3664            Expression::IntervalOp(e) => self.generate_interval_op(e),
3665            Expression::IntervalSpan(e) => self.generate_interval_span(e),
3666            Expression::IntoClause(e) => self.generate_into_clause(e),
3667            Expression::Introducer(e) => self.generate_introducer(e),
3668            Expression::IsolatedLoadingProperty(e) => self.generate_isolated_loading_property(e),
3669            Expression::JSON(e) => self.generate_json(e),
3670            Expression::JSONArray(e) => self.generate_json_array(e),
3671            Expression::JSONArrayAgg(e) => self.generate_json_array_agg_struct(e),
3672            Expression::JSONArrayAppend(e) => self.generate_json_array_append(e),
3673            Expression::JSONArrayContains(e) => self.generate_json_array_contains(e),
3674            Expression::JSONArrayInsert(e) => self.generate_json_array_insert(e),
3675            Expression::JSONBExists(e) => self.generate_jsonb_exists(e),
3676            Expression::JSONBExtractScalar(e) => self.generate_jsonb_extract_scalar(e),
3677            Expression::JSONBObjectAgg(e) => self.generate_jsonb_object_agg(e),
3678            Expression::JSONObjectAgg(e) => self.generate_json_object_agg_struct(e),
3679            Expression::JSONColumnDef(e) => self.generate_json_column_def(e),
3680            Expression::JSONExists(e) => self.generate_json_exists(e),
3681            Expression::JSONCast(e) => self.generate_json_cast(e),
3682            Expression::JSONExtract(e) => self.generate_json_extract_path(e),
3683            Expression::JSONExtractArray(e) => self.generate_json_extract_array(e),
3684            Expression::JSONExtractQuote(e) => self.generate_json_extract_quote(e),
3685            Expression::JSONExtractScalar(e) => self.generate_json_extract_scalar(e),
3686            Expression::JSONFormat(e) => self.generate_json_format(e),
3687            Expression::JSONKeyValue(e) => self.generate_json_key_value(e),
3688            Expression::JSONKeys(e) => self.generate_json_keys(e),
3689            Expression::JSONKeysAtDepth(e) => self.generate_json_keys_at_depth(e),
3690            Expression::JSONPath(e) => self.generate_json_path_expr(e),
3691            Expression::JSONPathFilter(e) => self.generate_json_path_filter(e),
3692            Expression::JSONPathKey(e) => self.generate_json_path_key(e),
3693            Expression::JSONPathRecursive(e) => self.generate_json_path_recursive(e),
3694            Expression::JSONPathRoot(_) => self.generate_json_path_root(),
3695            Expression::JSONPathScript(e) => self.generate_json_path_script(e),
3696            Expression::JSONPathSelector(e) => self.generate_json_path_selector(e),
3697            Expression::JSONPathSlice(e) => self.generate_json_path_slice(e),
3698            Expression::JSONPathSubscript(e) => self.generate_json_path_subscript(e),
3699            Expression::JSONPathUnion(e) => self.generate_json_path_union(e),
3700            Expression::JSONRemove(e) => self.generate_json_remove(e),
3701            Expression::JSONSchema(e) => self.generate_json_schema(e),
3702            Expression::JSONSet(e) => self.generate_json_set(e),
3703            Expression::JSONStripNulls(e) => self.generate_json_strip_nulls(e),
3704            Expression::JSONTable(e) => self.generate_json_table(e),
3705            Expression::JSONType(e) => self.generate_json_type(e),
3706            Expression::JSONValue(e) => self.generate_json_value(e),
3707            Expression::JSONValueArray(e) => self.generate_json_value_array(e),
3708            Expression::JarowinklerSimilarity(e) => self.generate_jarowinkler_similarity(e),
3709            Expression::JoinHint(e) => self.generate_join_hint(e),
3710            Expression::JournalProperty(e) => self.generate_journal_property(e),
3711            Expression::LanguageProperty(e) => self.generate_language_property(e),
3712            Expression::Lateral(e) => self.generate_lateral(e),
3713            Expression::LikeProperty(e) => self.generate_like_property(e),
3714            Expression::Limit(e) => self.generate_limit(e),
3715            Expression::LimitOptions(e) => self.generate_limit_options(e),
3716            Expression::List(e) => self.generate_list(e),
3717            Expression::ToMap(e) => self.generate_tomap(e),
3718            Expression::Localtime(e) => self.generate_localtime(e),
3719            Expression::Localtimestamp(e) => self.generate_localtimestamp(e),
3720            Expression::LocationProperty(e) => self.generate_location_property(e),
3721            Expression::Lock(e) => self.generate_lock(e),
3722            Expression::LockProperty(e) => self.generate_lock_property(e),
3723            Expression::LockingProperty(e) => self.generate_locking_property(e),
3724            Expression::LockingStatement(e) => self.generate_locking_statement(e),
3725            Expression::LogProperty(e) => self.generate_log_property(e),
3726            Expression::MD5Digest(e) => self.generate_md5_digest(e),
3727            Expression::MLForecast(e) => self.generate_ml_forecast(e),
3728            Expression::MLTranslate(e) => self.generate_ml_translate(e),
3729            Expression::MakeInterval(e) => self.generate_make_interval(e),
3730            Expression::ManhattanDistance(e) => self.generate_manhattan_distance(e),
3731            Expression::Map(e) => self.generate_map(e),
3732            Expression::MapCat(e) => self.generate_map_cat(e),
3733            Expression::MapDelete(e) => self.generate_map_delete(e),
3734            Expression::MapInsert(e) => self.generate_map_insert(e),
3735            Expression::MapPick(e) => self.generate_map_pick(e),
3736            Expression::MaskingPolicyColumnConstraint(e) => {
3737                self.generate_masking_policy_column_constraint(e)
3738            }
3739            Expression::MatchAgainst(e) => self.generate_match_against(e),
3740            Expression::MatchRecognizeMeasure(e) => self.generate_match_recognize_measure(e),
3741            Expression::MaterializedProperty(e) => self.generate_materialized_property(e),
3742            Expression::Merge(e) => self.generate_merge(e),
3743            Expression::MergeBlockRatioProperty(e) => self.generate_merge_block_ratio_property(e),
3744            Expression::MergeTreeTTL(e) => self.generate_merge_tree_ttl(e),
3745            Expression::MergeTreeTTLAction(e) => self.generate_merge_tree_ttl_action(e),
3746            Expression::Minhash(e) => self.generate_minhash(e),
3747            Expression::ModelAttribute(e) => self.generate_model_attribute(e),
3748            Expression::Monthname(e) => self.generate_monthname(e),
3749            Expression::MultitableInserts(e) => self.generate_multitable_inserts(e),
3750            Expression::NextValueFor(e) => self.generate_next_value_for(e),
3751            Expression::Normal(e) => self.generate_normal(e),
3752            Expression::Normalize(e) => self.generate_normalize(e),
3753            Expression::NotNullColumnConstraint(e) => self.generate_not_null_column_constraint(e),
3754            Expression::Nullif(e) => self.generate_nullif(e),
3755            Expression::NumberToStr(e) => self.generate_number_to_str(e),
3756            Expression::ObjectAgg(e) => self.generate_object_agg(e),
3757            Expression::ObjectIdentifier(e) => self.generate_object_identifier(e),
3758            Expression::ObjectInsert(e) => self.generate_object_insert(e),
3759            Expression::Offset(e) => self.generate_offset(e),
3760            Expression::Qualify(e) => self.generate_qualify(e),
3761            Expression::OnCluster(e) => self.generate_on_cluster(e),
3762            Expression::OnCommitProperty(e) => self.generate_on_commit_property(e),
3763            Expression::OnCondition(e) => self.generate_on_condition(e),
3764            Expression::OnConflict(e) => self.generate_on_conflict(e),
3765            Expression::OnProperty(e) => self.generate_on_property(e),
3766            Expression::Opclass(e) => self.generate_opclass(e),
3767            Expression::OpenJSON(e) => self.generate_open_json(e),
3768            Expression::OpenJSONColumnDef(e) => self.generate_open_json_column_def(e),
3769            Expression::Operator(e) => self.generate_operator(e),
3770            Expression::OrderBy(e) => self.generate_order_by(e),
3771            Expression::OutputModelProperty(e) => self.generate_output_model_property(e),
3772            Expression::OverflowTruncateBehavior(e) => self.generate_overflow_truncate_behavior(e),
3773            Expression::ParameterizedAgg(e) => self.generate_parameterized_agg(e),
3774            Expression::ParseDatetime(e) => self.generate_parse_datetime(e),
3775            Expression::ParseIp(e) => self.generate_parse_ip(e),
3776            Expression::ParseJSON(e) => self.generate_parse_json(e),
3777            Expression::ParseTime(e) => self.generate_parse_time(e),
3778            Expression::ParseUrl(e) => self.generate_parse_url(e),
3779            Expression::Partition(e) => self.generate_partition_expr(e),
3780            Expression::PartitionBoundSpec(e) => self.generate_partition_bound_spec(e),
3781            Expression::PartitionByListProperty(e) => self.generate_partition_by_list_property(e),
3782            Expression::PartitionByRangeProperty(e) => self.generate_partition_by_range_property(e),
3783            Expression::PartitionByRangePropertyDynamic(e) => {
3784                self.generate_partition_by_range_property_dynamic(e)
3785            }
3786            Expression::PartitionByTruncate(e) => self.generate_partition_by_truncate(e),
3787            Expression::PartitionList(e) => self.generate_partition_list(e),
3788            Expression::PartitionRange(e) => self.generate_partition_range(e),
3789            Expression::PartitionByProperty(e) => self.generate_partition_by_property(e),
3790            Expression::PartitionedByBucket(e) => self.generate_partitioned_by_bucket(e),
3791            Expression::PartitionedByProperty(e) => self.generate_partitioned_by_property(e),
3792            Expression::PartitionedOfProperty(e) => self.generate_partitioned_of_property(e),
3793            Expression::PeriodForSystemTimeConstraint(e) => {
3794                self.generate_period_for_system_time_constraint(e)
3795            }
3796            Expression::PivotAlias(e) => self.generate_pivot_alias(e),
3797            Expression::PivotAny(e) => self.generate_pivot_any(e),
3798            Expression::Predict(e) => self.generate_predict(e),
3799            Expression::PreviousDay(e) => self.generate_previous_day(e),
3800            Expression::PrimaryKey(e) => self.generate_primary_key(e),
3801            Expression::PrimaryKeyColumnConstraint(e) => {
3802                self.generate_primary_key_column_constraint(e)
3803            }
3804            Expression::PathColumnConstraint(e) => self.generate_path_column_constraint(e),
3805            Expression::ProjectionDef(e) => self.generate_projection_def(e),
3806            Expression::OptionsProperty(e) => self.generate_options_property(e),
3807            Expression::Properties(e) => self.generate_properties(e),
3808            Expression::Property(e) => self.generate_property(e),
3809            Expression::PseudoType(e) => self.generate_pseudo_type(e),
3810            Expression::Put(e) => self.generate_put(e),
3811            Expression::Quantile(e) => self.generate_quantile(e),
3812            Expression::QueryBand(e) => self.generate_query_band(e),
3813            Expression::QueryOption(e) => self.generate_query_option(e),
3814            Expression::QueryTransform(e) => self.generate_query_transform(e),
3815            Expression::Randn(e) => self.generate_randn(e),
3816            Expression::Randstr(e) => self.generate_randstr(e),
3817            Expression::RangeBucket(e) => self.generate_range_bucket(e),
3818            Expression::RangeN(e) => self.generate_range_n(e),
3819            Expression::ReadCSV(e) => self.generate_read_csv(e),
3820            Expression::ReadParquet(e) => self.generate_read_parquet(e),
3821            Expression::RecursiveWithSearch(e) => self.generate_recursive_with_search(e),
3822            Expression::Reduce(e) => self.generate_reduce(e),
3823            Expression::Reference(e) => self.generate_reference(e),
3824            Expression::Refresh(e) => self.generate_refresh(e),
3825            Expression::RefreshTriggerProperty(e) => self.generate_refresh_trigger_property(e),
3826            Expression::RegexpCount(e) => self.generate_regexp_count(e),
3827            Expression::RegexpExtractAll(e) => self.generate_regexp_extract_all(e),
3828            Expression::RegexpFullMatch(e) => self.generate_regexp_full_match(e),
3829            Expression::RegexpILike(e) => self.generate_regexp_i_like(e),
3830            Expression::RegexpInstr(e) => self.generate_regexp_instr(e),
3831            Expression::RegexpSplit(e) => self.generate_regexp_split(e),
3832            Expression::RegrAvgx(e) => self.generate_regr_avgx(e),
3833            Expression::RegrAvgy(e) => self.generate_regr_avgy(e),
3834            Expression::RegrCount(e) => self.generate_regr_count(e),
3835            Expression::RegrIntercept(e) => self.generate_regr_intercept(e),
3836            Expression::RegrR2(e) => self.generate_regr_r2(e),
3837            Expression::RegrSlope(e) => self.generate_regr_slope(e),
3838            Expression::RegrSxx(e) => self.generate_regr_sxx(e),
3839            Expression::RegrSxy(e) => self.generate_regr_sxy(e),
3840            Expression::RegrSyy(e) => self.generate_regr_syy(e),
3841            Expression::RegrValx(e) => self.generate_regr_valx(e),
3842            Expression::RegrValy(e) => self.generate_regr_valy(e),
3843            Expression::RemoteWithConnectionModelProperty(e) => {
3844                self.generate_remote_with_connection_model_property(e)
3845            }
3846            Expression::RenameColumn(e) => self.generate_rename_column(e),
3847            Expression::ReplacePartition(e) => self.generate_replace_partition(e),
3848            Expression::Returning(e) => self.generate_returning(e),
3849            Expression::ReturnsProperty(e) => self.generate_returns_property(e),
3850            Expression::Rollback(e) => self.generate_rollback(e),
3851            Expression::Rollup(e) => self.generate_rollup(e),
3852            Expression::RowFormatDelimitedProperty(e) => {
3853                self.generate_row_format_delimited_property(e)
3854            }
3855            Expression::RowFormatProperty(e) => self.generate_row_format_property(e),
3856            Expression::RowFormatSerdeProperty(e) => self.generate_row_format_serde_property(e),
3857            Expression::SHA2(e) => self.generate_sha2(e),
3858            Expression::SHA2Digest(e) => self.generate_sha2_digest(e),
3859            Expression::SafeAdd(e) => self.generate_safe_add(e),
3860            Expression::SafeDivide(e) => self.generate_safe_divide(e),
3861            Expression::SafeMultiply(e) => self.generate_safe_multiply(e),
3862            Expression::SafeSubtract(e) => self.generate_safe_subtract(e),
3863            Expression::SampleProperty(e) => self.generate_sample_property(e),
3864            Expression::Schema(e) => self.generate_schema(e),
3865            Expression::SchemaCommentProperty(e) => self.generate_schema_comment_property(e),
3866            Expression::ScopeResolution(e) => self.generate_scope_resolution(e),
3867            Expression::Search(e) => self.generate_search(e),
3868            Expression::SearchIp(e) => self.generate_search_ip(e),
3869            Expression::SecurityProperty(e) => self.generate_security_property(e),
3870            Expression::SemanticView(e) => self.generate_semantic_view(e),
3871            Expression::SequenceProperties(e) => self.generate_sequence_properties(e),
3872            Expression::SerdeProperties(e) => self.generate_serde_properties(e),
3873            Expression::SessionParameter(e) => self.generate_session_parameter(e),
3874            Expression::Set(e) => self.generate_set(e),
3875            Expression::SetConfigProperty(e) => self.generate_set_config_property(e),
3876            Expression::SetItem(e) => self.generate_set_item(e),
3877            Expression::SetOperation(e) => self.generate_set_operation(e),
3878            Expression::SetProperty(e) => self.generate_set_property(e),
3879            Expression::SettingsProperty(e) => self.generate_settings_property(e),
3880            Expression::SharingProperty(e) => self.generate_sharing_property(e),
3881            Expression::Slice(e) => self.generate_slice(e),
3882            Expression::SortArray(e) => self.generate_sort_array(e),
3883            Expression::SortBy(e) => self.generate_sort_by(e),
3884            Expression::SortKeyProperty(e) => self.generate_sort_key_property(e),
3885            Expression::SplitPart(e) => self.generate_split_part(e),
3886            Expression::SqlReadWriteProperty(e) => self.generate_sql_read_write_property(e),
3887            Expression::SqlSecurityProperty(e) => self.generate_sql_security_property(e),
3888            Expression::StDistance(e) => self.generate_st_distance(e),
3889            Expression::StPoint(e) => self.generate_st_point(e),
3890            Expression::StabilityProperty(e) => self.generate_stability_property(e),
3891            Expression::StandardHash(e) => self.generate_standard_hash(e),
3892            Expression::StorageHandlerProperty(e) => self.generate_storage_handler_property(e),
3893            Expression::StrPosition(e) => self.generate_str_position(e),
3894            Expression::StrToDate(e) => self.generate_str_to_date(e),
3895            Expression::DateStrToDate(f) => self.generate_simple_func("DATE_STR_TO_DATE", &f.this),
3896            Expression::DateToDateStr(f) => self.generate_simple_func("DATE_TO_DATE_STR", &f.this),
3897            Expression::StrToMap(e) => self.generate_str_to_map(e),
3898            Expression::StrToTime(e) => self.generate_str_to_time(e),
3899            Expression::StrToUnix(e) => self.generate_str_to_unix(e),
3900            Expression::StringToArray(e) => self.generate_string_to_array(e),
3901            Expression::Struct(e) => self.generate_struct(e),
3902            Expression::Stuff(e) => self.generate_stuff(e),
3903            Expression::SubstringIndex(e) => self.generate_substring_index(e),
3904            Expression::Summarize(e) => self.generate_summarize(e),
3905            Expression::Systimestamp(e) => self.generate_systimestamp(e),
3906            Expression::TableAlias(e) => self.generate_table_alias(e),
3907            Expression::TableFromRows(e) => self.generate_table_from_rows(e),
3908            Expression::RowsFrom(e) => self.generate_rows_from(e),
3909            Expression::TableSample(e) => self.generate_table_sample(e),
3910            Expression::Tag(e) => self.generate_tag(e),
3911            Expression::Tags(e) => self.generate_tags(e),
3912            Expression::TemporaryProperty(e) => self.generate_temporary_property(e),
3913            Expression::Time(e) => self.generate_time_func(e),
3914            Expression::TimeAdd(e) => self.generate_time_add(e),
3915            Expression::TimeDiff(e) => self.generate_time_diff(e),
3916            Expression::TimeFromParts(e) => self.generate_time_from_parts(e),
3917            Expression::TimeSlice(e) => self.generate_time_slice(e),
3918            Expression::TimeStrToDate(e) => self.generate_time_str_to_date(e),
3919            Expression::TimeStrToTime(e) => self.generate_time_str_to_time(e),
3920            Expression::TimeSub(e) => self.generate_time_sub(e),
3921            Expression::TimeToStr(e) => self.generate_time_to_str(e),
3922            Expression::TimeToUnix(e) => self.generate_time_to_unix(e),
3923            Expression::TimeTrunc(e) => self.generate_time_trunc(e),
3924            Expression::TimeUnit(e) => self.generate_time_unit(e),
3925            Expression::Timestamp(e) => self.generate_timestamp_func(e),
3926            Expression::TimestampAdd(e) => self.generate_timestamp_add(e),
3927            Expression::TimestampDiff(e) => self.generate_timestamp_diff(e),
3928            Expression::TimestampFromParts(e) => self.generate_timestamp_from_parts(e),
3929            Expression::TimestampSub(e) => self.generate_timestamp_sub(e),
3930            Expression::TimestampTzFromParts(e) => self.generate_timestamp_tz_from_parts(e),
3931            Expression::ToBinary(e) => self.generate_to_binary(e),
3932            Expression::ToBoolean(e) => self.generate_to_boolean(e),
3933            Expression::ToChar(e) => self.generate_to_char(e),
3934            Expression::ToDecfloat(e) => self.generate_to_decfloat(e),
3935            Expression::ToDouble(e) => self.generate_to_double(e),
3936            Expression::ToFile(e) => self.generate_to_file(e),
3937            Expression::ToNumber(e) => self.generate_to_number(e),
3938            Expression::ToTableProperty(e) => self.generate_to_table_property(e),
3939            Expression::Transaction(e) => self.generate_transaction(e),
3940            Expression::Transform(e) => self.generate_transform(e),
3941            Expression::TransformModelProperty(e) => self.generate_transform_model_property(e),
3942            Expression::TransientProperty(e) => self.generate_transient_property(e),
3943            Expression::Translate(e) => self.generate_translate(e),
3944            Expression::TranslateCharacters(e) => self.generate_translate_characters(e),
3945            Expression::TruncateTable(e) => self.generate_truncate_table(e),
3946            Expression::TryBase64DecodeBinary(e) => self.generate_try_base64_decode_binary(e),
3947            Expression::TryBase64DecodeString(e) => self.generate_try_base64_decode_string(e),
3948            Expression::TryToDecfloat(e) => self.generate_try_to_decfloat(e),
3949            Expression::TsOrDsAdd(e) => self.generate_ts_or_ds_add(e),
3950            Expression::TsOrDsDiff(e) => self.generate_ts_or_ds_diff(e),
3951            Expression::TsOrDsToDate(e) => self.generate_ts_or_ds_to_date(e),
3952            Expression::TsOrDsToTime(e) => self.generate_ts_or_ds_to_time(e),
3953            Expression::Unhex(e) => self.generate_unhex(e),
3954            Expression::UnicodeString(e) => self.generate_unicode_string(e),
3955            Expression::Uniform(e) => self.generate_uniform(e),
3956            Expression::UniqueColumnConstraint(e) => self.generate_unique_column_constraint(e),
3957            Expression::UniqueKeyProperty(e) => self.generate_unique_key_property(e),
3958            Expression::RollupProperty(e) => self.generate_rollup_property(e),
3959            Expression::UnixToStr(e) => self.generate_unix_to_str(e),
3960            Expression::UnixToTime(e) => self.generate_unix_to_time(e),
3961            Expression::UnpivotColumns(e) => self.generate_unpivot_columns(e),
3962            Expression::UserDefinedFunction(e) => self.generate_user_defined_function(e),
3963            Expression::UsingTemplateProperty(e) => self.generate_using_template_property(e),
3964            Expression::UtcTime(e) => self.generate_utc_time(e),
3965            Expression::UtcTimestamp(e) => self.generate_utc_timestamp(e),
3966            Expression::Uuid(e) => self.generate_uuid(e),
3967            Expression::Var(v) => {
3968                if matches!(self.config.dialect, Some(DialectType::MySQL))
3969                    && v.this.len() > 2
3970                    && (v.this.starts_with("0x") || v.this.starts_with("0X"))
3971                    && !v.this[2..].chars().all(|c| c.is_ascii_hexdigit())
3972                {
3973                    return self.generate_identifier(&Identifier {
3974                        name: v.this.clone(),
3975                        quoted: true,
3976                        trailing_comments: Vec::new(),
3977                        span: None,
3978                    });
3979                }
3980                self.write(&v.this);
3981                Ok(())
3982            }
3983            Expression::Variadic(e) => {
3984                self.write_keyword("VARIADIC");
3985                self.write_space();
3986                self.generate_expression(&e.this)?;
3987                Ok(())
3988            }
3989            Expression::VarMap(e) => self.generate_var_map(e),
3990            Expression::VectorSearch(e) => self.generate_vector_search(e),
3991            Expression::Version(e) => self.generate_version(e),
3992            Expression::ViewAttributeProperty(e) => self.generate_view_attribute_property(e),
3993            Expression::VolatileProperty(e) => self.generate_volatile_property(e),
3994            Expression::WatermarkColumnConstraint(e) => {
3995                self.generate_watermark_column_constraint(e)
3996            }
3997            Expression::Week(e) => self.generate_week(e),
3998            Expression::When(e) => self.generate_when(e),
3999            Expression::Whens(e) => self.generate_whens(e),
4000            Expression::Where(e) => self.generate_where(e),
4001            Expression::WidthBucket(e) => self.generate_width_bucket(e),
4002            Expression::Window(e) => self.generate_window(e),
4003            Expression::WindowSpec(e) => self.generate_window_spec(e),
4004            Expression::WithDataProperty(e) => self.generate_with_data_property(e),
4005            Expression::WithFill(e) => self.generate_with_fill(e),
4006            Expression::WithJournalTableProperty(e) => self.generate_with_journal_table_property(e),
4007            Expression::WithOperator(e) => self.generate_with_operator(e),
4008            Expression::WithProcedureOptions(e) => self.generate_with_procedure_options(e),
4009            Expression::WithSchemaBindingProperty(e) => {
4010                self.generate_with_schema_binding_property(e)
4011            }
4012            Expression::WithSystemVersioningProperty(e) => {
4013                self.generate_with_system_versioning_property(e)
4014            }
4015            Expression::WithTableHint(e) => self.generate_with_table_hint(e),
4016            Expression::XMLElement(e) => self.generate_xml_element(e),
4017            Expression::XMLGet(e) => self.generate_xml_get(e),
4018            Expression::XMLKeyValueOption(e) => self.generate_xml_key_value_option(e),
4019            Expression::XMLTable(e) => self.generate_xml_table(e),
4020            Expression::Xor(e) => self.generate_xor(e),
4021            Expression::Zipf(e) => self.generate_zipf(e),
4022            _ => self.write_unsupported_comment("unsupported expression"),
4023        }
4024    }
4025
4026    fn generate_select(&mut self, select: &Select) -> Result<()> {
4027        use crate::dialects::DialectType;
4028
4029        // Redshift-style EXCLUDE: for dialects other than Redshift, wrap in a derived table
4030        // e.g., SELECT *, col4 EXCLUDE (col2, col3) FROM t
4031        //   → SELECT * EXCLUDE (col2, col3) FROM (SELECT *, col4 FROM t)
4032        if let Some(exclude) = &select.exclude {
4033            if !exclude.is_empty() && !matches!(self.config.dialect, Some(DialectType::Redshift)) {
4034                // Build the inner select (same as original but without exclude)
4035                let mut inner_select = select.clone();
4036                inner_select.exclude = None;
4037                let inner_expr = Expression::Select(Box::new(inner_select));
4038
4039                // Build the subquery
4040                let subquery = crate::expressions::Subquery {
4041                    this: inner_expr,
4042                    alias: None,
4043                    column_aliases: Vec::new(),
4044                    order_by: None,
4045                    limit: None,
4046                    offset: None,
4047                    distribute_by: None,
4048                    sort_by: None,
4049                    cluster_by: None,
4050                    lateral: false,
4051                    modifiers_inside: false,
4052                    trailing_comments: Vec::new(),
4053                    inferred_type: None,
4054                };
4055
4056                // Build the outer select: SELECT * EXCLUDE (cols) FROM (inner)
4057                let star = Expression::Star(crate::expressions::Star {
4058                    table: None,
4059                    except: Some(
4060                        exclude
4061                            .iter()
4062                            .map(|e| match e {
4063                                Expression::Column(col) => col.name.clone(),
4064                                Expression::Identifier(id) => id.clone(),
4065                                _ => crate::expressions::Identifier::new("unknown".to_string()),
4066                            })
4067                            .collect(),
4068                    ),
4069                    replace: None,
4070                    rename: None,
4071                    trailing_comments: Vec::new(),
4072                    span: None,
4073                });
4074
4075                let outer_select = Select {
4076                    expressions: vec![star],
4077                    from: Some(crate::expressions::From {
4078                        expressions: vec![Expression::Subquery(Box::new(subquery))],
4079                    }),
4080                    ..Select::new()
4081                };
4082
4083                return self.generate_select(&outer_select);
4084            }
4085        }
4086
4087        // Output leading comments before SELECT
4088        for comment in &select.leading_comments {
4089            self.write_formatted_comment(comment);
4090            self.write(" ");
4091        }
4092
4093        // WITH clause
4094        if let Some(with) = &select.with {
4095            self.generate_with(with)?;
4096            if self.config.pretty {
4097                self.write_newline();
4098                self.write_indent();
4099            } else {
4100                self.write_space();
4101            }
4102        }
4103
4104        // Output post-SELECT comments (comments that appeared after SELECT keyword)
4105        // These are output BEFORE SELECT, as Python SQLGlot normalizes them this way
4106        for comment in &select.post_select_comments {
4107            self.write_formatted_comment(comment);
4108            self.write(" ");
4109        }
4110
4111        self.write_keyword("SELECT");
4112
4113        // Generate query hint if present /*+ ... */
4114        if let Some(hint) = &select.hint {
4115            self.generate_hint(hint)?;
4116        }
4117
4118        // For SQL Server, convert LIMIT to TOP (structural transformation)
4119        // But only when there's no OFFSET (otherwise use OFFSET/FETCH syntax)
4120        // TOP clause (SQL Server style - before DISTINCT)
4121        let use_top_from_limit = matches!(
4122            self.config.dialect,
4123            Some(DialectType::TSQL) | Some(DialectType::Fabric)
4124        ) && select.top.is_none()
4125            && select.limit.is_some()
4126            && select.offset.is_none(); // Don't use TOP when there's OFFSET
4127
4128        // For TOP-supporting dialects: DISTINCT before TOP
4129        // For non-TOP dialects: TOP is converted to LIMIT later; DISTINCT goes here
4130        let is_top_dialect = matches!(
4131            self.config.dialect,
4132            Some(DialectType::TSQL) | Some(DialectType::Teradata) | Some(DialectType::Fabric)
4133        );
4134        let keep_top_verbatim = !is_top_dialect
4135            && select.limit.is_none()
4136            && select
4137                .top
4138                .as_ref()
4139                .map_or(false, |top| top.percent || top.with_ties);
4140
4141        if select.distinct && (is_top_dialect || select.top.is_some()) {
4142            self.write_space();
4143            self.write_keyword("DISTINCT");
4144        }
4145
4146        if is_top_dialect || keep_top_verbatim {
4147            if let Some(top) = &select.top {
4148                self.write_space();
4149                self.write_keyword("TOP");
4150                if top.parenthesized {
4151                    if matches!(&top.this, Expression::Subquery(_) | Expression::Paren(_)) {
4152                        self.write_space();
4153                        self.generate_expression(&top.this)?;
4154                    } else {
4155                        self.write(" (");
4156                        self.generate_expression(&top.this)?;
4157                        self.write(")");
4158                    }
4159                } else {
4160                    self.write_space();
4161                    self.generate_expression(&top.this)?;
4162                }
4163                if top.percent {
4164                    self.write_space();
4165                    self.write_keyword("PERCENT");
4166                }
4167                if top.with_ties {
4168                    self.write_space();
4169                    self.write_keyword("WITH TIES");
4170                }
4171            } else if use_top_from_limit {
4172                // Convert LIMIT to TOP for SQL Server (only when no OFFSET)
4173                if let Some(limit) = &select.limit {
4174                    self.write_space();
4175                    self.write_keyword("TOP");
4176                    // Use parentheses for complex expressions, but not for simple literals
4177                    let is_simple_literal = matches!(&limit.this, Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(_)));
4178                    if is_simple_literal {
4179                        self.write_space();
4180                        self.generate_expression(&limit.this)?;
4181                    } else {
4182                        self.write(" (");
4183                        self.generate_expression(&limit.this)?;
4184                        self.write(")");
4185                    }
4186                }
4187            }
4188        }
4189
4190        if select.distinct && !is_top_dialect && select.top.is_none() {
4191            self.write_space();
4192            self.write_keyword("DISTINCT");
4193        }
4194
4195        // DISTINCT ON clause (PostgreSQL)
4196        if let Some(distinct_on) = &select.distinct_on {
4197            self.write_space();
4198            self.write_keyword("ON");
4199            self.write(" (");
4200            for (i, expr) in distinct_on.iter().enumerate() {
4201                if i > 0 {
4202                    self.write(", ");
4203                }
4204                self.generate_expression(expr)?;
4205            }
4206            self.write(")");
4207        }
4208
4209        // MySQL operation modifiers (HIGH_PRIORITY, STRAIGHT_JOIN, SQL_CALC_FOUND_ROWS, etc.)
4210        for modifier in &select.operation_modifiers {
4211            self.write_space();
4212            self.write_keyword(modifier);
4213        }
4214
4215        // BigQuery SELECT AS STRUCT / SELECT AS VALUE
4216        if let Some(kind) = &select.kind {
4217            self.write_space();
4218            self.write_keyword("AS");
4219            self.write_space();
4220            self.write_keyword(kind);
4221        }
4222
4223        // Expressions (only if there are any)
4224        if !select.expressions.is_empty() {
4225            if self.config.pretty {
4226                self.write_newline();
4227                self.indent_level += 1;
4228            } else {
4229                self.write_space();
4230            }
4231        }
4232
4233        for (i, expr) in select.expressions.iter().enumerate() {
4234            if i > 0 {
4235                self.write(",");
4236                if self.config.pretty {
4237                    self.write_newline();
4238                } else {
4239                    self.write_space();
4240                }
4241            }
4242            if self.config.pretty {
4243                self.write_indent();
4244            }
4245            self.generate_expression(expr)?;
4246        }
4247
4248        if self.config.pretty && !select.expressions.is_empty() {
4249            self.indent_level -= 1;
4250        }
4251
4252        // Redshift-style EXCLUDE clause at the end of the projection list
4253        // For Redshift dialect: append EXCLUDE (col1, col2) after the expressions
4254        // For other dialects (DuckDB, Snowflake): this is handled by wrapping in a derived table
4255        // (done after the full select is generated below)
4256        if let Some(exclude) = &select.exclude {
4257            if !exclude.is_empty() && matches!(self.config.dialect, Some(DialectType::Redshift)) {
4258                self.write_space();
4259                self.write_keyword("EXCLUDE");
4260                self.write(" (");
4261                for (i, col) in exclude.iter().enumerate() {
4262                    if i > 0 {
4263                        self.write(", ");
4264                    }
4265                    self.generate_expression(col)?;
4266                }
4267                self.write(")");
4268            }
4269        }
4270
4271        // INTO clause (SELECT ... INTO table_name)
4272        // Also handles Oracle PL/SQL: BULK COLLECT INTO v1, v2, ...
4273        if let Some(into) = &select.into {
4274            if self.config.pretty {
4275                self.write_newline();
4276                self.write_indent();
4277            } else {
4278                self.write_space();
4279            }
4280            if into.bulk_collect {
4281                self.write_keyword("BULK COLLECT INTO");
4282            } else {
4283                self.write_keyword("INTO");
4284            }
4285            if into.temporary {
4286                self.write_space();
4287                self.write_keyword("TEMPORARY");
4288            }
4289            if into.unlogged {
4290                self.write_space();
4291                self.write_keyword("UNLOGGED");
4292            }
4293            self.write_space();
4294            // If we have multiple expressions, output them comma-separated
4295            if !into.expressions.is_empty() {
4296                for (i, expr) in into.expressions.iter().enumerate() {
4297                    if i > 0 {
4298                        self.write(", ");
4299                    }
4300                    self.generate_expression(expr)?;
4301                }
4302            } else {
4303                self.generate_expression(&into.this)?;
4304            }
4305        }
4306
4307        // FROM clause
4308        if let Some(from) = &select.from {
4309            if self.config.pretty {
4310                self.write_newline();
4311                self.write_indent();
4312            } else {
4313                self.write_space();
4314            }
4315            self.write_keyword("FROM");
4316            self.write_space();
4317
4318            // BigQuery, Hive, Spark, Databricks, SQLite, and ClickHouse prefer explicit CROSS JOIN over comma syntax for multiple tables
4319            // But keep commas when TABLESAMPLE is present (Spark/Hive handle TABLESAMPLE differently with commas)
4320            // Also keep commas when the source dialect is Generic/None and target is one of these dialects
4321            // (Python sqlglot: the Hive/Spark parser marks comma joins as CROSS, but Generic parser keeps them implicit)
4322            let has_tablesample = from
4323                .expressions
4324                .iter()
4325                .any(|e| matches!(e, Expression::TableSample(_)));
4326            let is_cross_join_dialect = matches!(
4327                self.config.dialect,
4328                Some(DialectType::BigQuery)
4329                    | Some(DialectType::Hive)
4330                    | Some(DialectType::Spark)
4331                    | Some(DialectType::Databricks)
4332                    | Some(DialectType::SQLite)
4333                    | Some(DialectType::ClickHouse)
4334            );
4335            // Skip CROSS JOIN conversion when source is Generic/None and target is a CROSS JOIN dialect
4336            // This matches Python sqlglot where comma-to-CROSS-JOIN is done in the dialect's parser, not generator
4337            let source_is_same_as_target = self.config.source_dialect.is_some()
4338                && self.config.source_dialect == self.config.dialect;
4339            let source_is_cross_join_dialect = matches!(
4340                self.config.source_dialect,
4341                Some(DialectType::BigQuery)
4342                    | Some(DialectType::Hive)
4343                    | Some(DialectType::Spark)
4344                    | Some(DialectType::Databricks)
4345                    | Some(DialectType::SQLite)
4346                    | Some(DialectType::ClickHouse)
4347            );
4348            let use_cross_join = !has_tablesample
4349                && is_cross_join_dialect
4350                && (source_is_same_as_target
4351                    || source_is_cross_join_dialect
4352                    || self.config.source_dialect.is_none());
4353
4354            // Snowflake wraps standalone VALUES in FROM clause with parentheses
4355            let wrap_values_in_parens = matches!(self.config.dialect, Some(DialectType::Snowflake));
4356
4357            for (i, expr) in from.expressions.iter().enumerate() {
4358                if i > 0 {
4359                    if use_cross_join {
4360                        self.write(" CROSS JOIN ");
4361                    } else {
4362                        self.write(", ");
4363                    }
4364                }
4365                if wrap_values_in_parens && matches!(expr, Expression::Values(_)) {
4366                    self.write("(");
4367                    self.generate_expression(expr)?;
4368                    self.write(")");
4369                } else {
4370                    self.generate_expression(expr)?;
4371                }
4372                // Output leading comments that were on the table name before FROM
4373                // (e.g., FROM \n/* comment */\n tbl PIVOT(...) -> ... PIVOT(...) /* comment */)
4374                let leading = Self::extract_table_leading_comments(expr);
4375                for comment in &leading {
4376                    self.write_space();
4377                    self.write_formatted_comment(comment);
4378                }
4379            }
4380        }
4381
4382        // JOINs - handle nested join structure for pretty printing
4383        // Deferred-condition joins "own" the non-deferred joins that follow them
4384        // until the next deferred join or end of list
4385        if self.config.pretty {
4386            self.generate_joins_with_nesting(&select.joins)?;
4387        } else {
4388            for join in &select.joins {
4389                self.generate_join(join)?;
4390            }
4391            // Output deferred ON/USING conditions (right-to-left, which is reverse order)
4392            for join in select.joins.iter().rev() {
4393                if join.deferred_condition {
4394                    self.generate_join_condition(join)?;
4395                }
4396            }
4397        }
4398
4399        // LATERAL VIEW clauses (Hive/Spark)
4400        for (lv_idx, lateral_view) in select.lateral_views.iter().enumerate() {
4401            self.generate_lateral_view(lateral_view, lv_idx)?;
4402        }
4403
4404        // PREWHERE (ClickHouse)
4405        if let Some(prewhere) = &select.prewhere {
4406            self.write_clause_condition("PREWHERE", prewhere)?;
4407        }
4408
4409        // WHERE
4410        if let Some(where_clause) = &select.where_clause {
4411            self.write_clause_condition("WHERE", &where_clause.this)?;
4412        }
4413
4414        // CONNECT BY (Oracle hierarchical queries)
4415        if let Some(connect) = &select.connect {
4416            self.generate_connect(connect)?;
4417        }
4418
4419        // GROUP BY
4420        if let Some(group_by) = &select.group_by {
4421            if self.config.pretty {
4422                // Output leading comments on their own lines before GROUP BY
4423                for comment in &group_by.comments {
4424                    self.write_newline();
4425                    self.write_indent();
4426                    self.write_formatted_comment(comment);
4427                }
4428                self.write_newline();
4429                self.write_indent();
4430            } else {
4431                self.write_space();
4432                // In non-pretty mode, output comments inline
4433                for comment in &group_by.comments {
4434                    self.write_formatted_comment(comment);
4435                    self.write_space();
4436                }
4437            }
4438            self.write_keyword("GROUP BY");
4439            // Handle ALL/DISTINCT modifier: Some(true) = ALL, Some(false) = DISTINCT
4440            match group_by.all {
4441                Some(true) => {
4442                    self.write_space();
4443                    self.write_keyword("ALL");
4444                }
4445                Some(false) => {
4446                    self.write_space();
4447                    self.write_keyword("DISTINCT");
4448                }
4449                None => {}
4450            }
4451            if !group_by.expressions.is_empty() {
4452                // Check for trailing WITH CUBE or WITH ROLLUP (Hive/MySQL syntax)
4453                // These are represented as Cube/Rollup expressions with empty expressions at the end
4454                let mut trailing_cube = false;
4455                let mut trailing_rollup = false;
4456                let mut plain_expressions: Vec<&Expression> = Vec::new();
4457                let mut grouping_sets_expressions: Vec<&Expression> = Vec::new();
4458                let mut cube_expressions: Vec<&Expression> = Vec::new();
4459                let mut rollup_expressions: Vec<&Expression> = Vec::new();
4460
4461                for expr in &group_by.expressions {
4462                    match expr {
4463                        Expression::Cube(c) if c.expressions.is_empty() => {
4464                            trailing_cube = true;
4465                        }
4466                        Expression::Rollup(r) if r.expressions.is_empty() => {
4467                            trailing_rollup = true;
4468                        }
4469                        Expression::Function(f) if f.name == "CUBE" => {
4470                            cube_expressions.push(expr);
4471                        }
4472                        Expression::Function(f) if f.name == "ROLLUP" => {
4473                            rollup_expressions.push(expr);
4474                        }
4475                        Expression::Function(f) if f.name == "GROUPING SETS" => {
4476                            grouping_sets_expressions.push(expr);
4477                        }
4478                        _ => {
4479                            plain_expressions.push(expr);
4480                        }
4481                    }
4482                }
4483
4484                // Reorder: plain expressions first, then GROUPING SETS, CUBE, ROLLUP
4485                let mut regular_expressions: Vec<&Expression> = Vec::new();
4486                regular_expressions.extend(plain_expressions);
4487                regular_expressions.extend(grouping_sets_expressions);
4488                regular_expressions.extend(cube_expressions);
4489                regular_expressions.extend(rollup_expressions);
4490
4491                if self.config.pretty {
4492                    self.write_newline();
4493                    self.indent_level += 1;
4494                    self.write_indent();
4495                } else {
4496                    self.write_space();
4497                }
4498
4499                for (i, expr) in regular_expressions.iter().enumerate() {
4500                    if i > 0 {
4501                        if self.config.pretty {
4502                            self.write(",");
4503                            self.write_newline();
4504                            self.write_indent();
4505                        } else {
4506                            self.write(", ");
4507                        }
4508                    }
4509                    self.generate_expression(expr)?;
4510                }
4511
4512                if self.config.pretty {
4513                    self.indent_level -= 1;
4514                }
4515
4516                // Output trailing WITH CUBE or WITH ROLLUP
4517                if trailing_cube {
4518                    self.write_space();
4519                    self.write_keyword("WITH CUBE");
4520                } else if trailing_rollup {
4521                    self.write_space();
4522                    self.write_keyword("WITH ROLLUP");
4523                }
4524            }
4525
4526            // ClickHouse: WITH TOTALS
4527            if group_by.totals {
4528                self.write_space();
4529                self.write_keyword("WITH TOTALS");
4530            }
4531        }
4532
4533        // HAVING
4534        if let Some(having) = &select.having {
4535            if self.config.pretty {
4536                // Output leading comments on their own lines before HAVING
4537                for comment in &having.comments {
4538                    self.write_newline();
4539                    self.write_indent();
4540                    self.write_formatted_comment(comment);
4541                }
4542            } else {
4543                for comment in &having.comments {
4544                    self.write_space();
4545                    self.write_formatted_comment(comment);
4546                }
4547            }
4548            self.write_clause_condition("HAVING", &having.this)?;
4549        }
4550
4551        // QUALIFY and WINDOW clause ordering depends on input SQL
4552        if select.qualify_after_window {
4553            // WINDOW before QUALIFY (DuckDB style)
4554            if let Some(windows) = &select.windows {
4555                self.write_window_clause(windows)?;
4556            }
4557            if let Some(qualify) = &select.qualify {
4558                self.write_clause_condition("QUALIFY", &qualify.this)?;
4559            }
4560        } else {
4561            // QUALIFY before WINDOW (Snowflake/BigQuery default)
4562            if let Some(qualify) = &select.qualify {
4563                self.write_clause_condition("QUALIFY", &qualify.this)?;
4564            }
4565            if let Some(windows) = &select.windows {
4566                self.write_window_clause(windows)?;
4567            }
4568        }
4569
4570        // DISTRIBUTE BY (Hive/Spark)
4571        if let Some(distribute_by) = &select.distribute_by {
4572            self.write_clause_expressions("DISTRIBUTE BY", &distribute_by.expressions)?;
4573        }
4574
4575        // CLUSTER BY (Hive/Spark)
4576        if let Some(cluster_by) = &select.cluster_by {
4577            self.write_order_clause("CLUSTER BY", &cluster_by.expressions)?;
4578        }
4579
4580        // SORT BY (Hive/Spark - comes before ORDER BY)
4581        if let Some(sort_by) = &select.sort_by {
4582            self.write_order_clause("SORT BY", &sort_by.expressions)?;
4583        }
4584
4585        // ORDER BY (or ORDER SIBLINGS BY for Oracle hierarchical queries)
4586        if let Some(order_by) = &select.order_by {
4587            if self.config.pretty {
4588                // Output leading comments on their own lines before ORDER BY
4589                for comment in &order_by.comments {
4590                    self.write_newline();
4591                    self.write_indent();
4592                    self.write_formatted_comment(comment);
4593                }
4594            } else {
4595                for comment in &order_by.comments {
4596                    self.write_space();
4597                    self.write_formatted_comment(comment);
4598                }
4599            }
4600            let keyword = if order_by.siblings {
4601                "ORDER SIBLINGS BY"
4602            } else {
4603                "ORDER BY"
4604            };
4605            self.write_order_clause(keyword, &order_by.expressions)?;
4606        }
4607
4608        // TSQL: FETCH requires ORDER BY. If there's a FETCH but no ORDER BY, add ORDER BY (SELECT NULL) OFFSET 0 ROWS
4609        if select.order_by.is_none()
4610            && select.fetch.is_some()
4611            && matches!(
4612                self.config.dialect,
4613                Some(DialectType::TSQL) | Some(DialectType::Fabric)
4614            )
4615        {
4616            if self.config.pretty {
4617                self.write_newline();
4618                self.write_indent();
4619            } else {
4620                self.write_space();
4621            }
4622            self.write_keyword("ORDER BY (SELECT NULL) OFFSET 0 ROWS");
4623        }
4624
4625        // LIMIT and OFFSET
4626        // PostgreSQL and others use: LIMIT count OFFSET offset
4627        // SQL Server uses: OFFSET ... FETCH (no LIMIT)
4628        // Presto/Trino uses: OFFSET n LIMIT m (offset before limit)
4629        let is_presto_like = matches!(
4630            self.config.dialect,
4631            Some(DialectType::Presto) | Some(DialectType::Trino)
4632        );
4633
4634        if is_presto_like && select.offset.is_some() {
4635            // Presto/Trino syntax: OFFSET n LIMIT m (offset comes first)
4636            if let Some(offset) = &select.offset {
4637                if self.config.pretty {
4638                    self.write_newline();
4639                    self.write_indent();
4640                } else {
4641                    self.write_space();
4642                }
4643                self.write_keyword("OFFSET");
4644                self.write_space();
4645                self.write_limit_expr(&offset.this)?;
4646                if offset.rows == Some(true) {
4647                    self.write_space();
4648                    self.write_keyword("ROWS");
4649                }
4650            }
4651            if let Some(limit) = &select.limit {
4652                if self.config.pretty {
4653                    self.write_newline();
4654                    self.write_indent();
4655                } else {
4656                    self.write_space();
4657                }
4658                self.write_keyword("LIMIT");
4659                self.write_space();
4660                self.write_limit_expr(&limit.this)?;
4661                if limit.percent {
4662                    self.write_space();
4663                    self.write_keyword("PERCENT");
4664                }
4665                // Emit any comments that were captured from before the LIMIT keyword
4666                for comment in &limit.comments {
4667                    self.write(" ");
4668                    self.write_formatted_comment(comment);
4669                }
4670            }
4671        } else {
4672            // Check if FETCH will be converted to LIMIT (used for ordering)
4673            let fetch_as_limit = select.fetch.as_ref().map_or(false, |fetch| {
4674                !fetch.percent
4675                    && !fetch.with_ties
4676                    && fetch.count.is_some()
4677                    && matches!(
4678                        self.config.dialect,
4679                        Some(DialectType::Spark)
4680                            | Some(DialectType::Hive)
4681                            | Some(DialectType::DuckDB)
4682                            | Some(DialectType::SQLite)
4683                            | Some(DialectType::MySQL)
4684                            | Some(DialectType::BigQuery)
4685                            | Some(DialectType::Databricks)
4686                            | Some(DialectType::StarRocks)
4687                            | Some(DialectType::Doris)
4688                            | Some(DialectType::Athena)
4689                            | Some(DialectType::ClickHouse)
4690                            | Some(DialectType::Redshift)
4691                    )
4692            });
4693
4694            // Standard LIMIT clause (skip for SQL Server - we use TOP or OFFSET/FETCH instead)
4695            if let Some(limit) = &select.limit {
4696                // SQL Server uses TOP (no OFFSET) or OFFSET/FETCH (with OFFSET) instead of LIMIT
4697                if !matches!(
4698                    self.config.dialect,
4699                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
4700                ) {
4701                    if self.config.pretty {
4702                        self.write_newline();
4703                        self.write_indent();
4704                    } else {
4705                        self.write_space();
4706                    }
4707                    self.write_keyword("LIMIT");
4708                    self.write_space();
4709                    self.write_limit_expr(&limit.this)?;
4710                    if limit.percent {
4711                        self.write_space();
4712                        self.write_keyword("PERCENT");
4713                    }
4714                    // Emit any comments that were captured from before the LIMIT keyword
4715                    for comment in &limit.comments {
4716                        self.write(" ");
4717                        self.write_formatted_comment(comment);
4718                    }
4719                }
4720            }
4721
4722            // Convert TOP to LIMIT for non-TOP dialects
4723            if select.top.is_some() && !is_top_dialect && select.limit.is_none() {
4724                if let Some(top) = &select.top {
4725                    if !top.percent && !top.with_ties {
4726                        if self.config.pretty {
4727                            self.write_newline();
4728                            self.write_indent();
4729                        } else {
4730                            self.write_space();
4731                        }
4732                        self.write_keyword("LIMIT");
4733                        self.write_space();
4734                        self.generate_expression(&top.this)?;
4735                    }
4736                }
4737            }
4738
4739            // If FETCH will be converted to LIMIT and there's also OFFSET,
4740            // emit LIMIT from FETCH BEFORE the OFFSET
4741            if fetch_as_limit && select.offset.is_some() {
4742                if let Some(fetch) = &select.fetch {
4743                    if self.config.pretty {
4744                        self.write_newline();
4745                        self.write_indent();
4746                    } else {
4747                        self.write_space();
4748                    }
4749                    self.write_keyword("LIMIT");
4750                    self.write_space();
4751                    self.generate_expression(fetch.count.as_ref().unwrap())?;
4752                }
4753            }
4754
4755            // OFFSET
4756            // In SQL Server, OFFSET requires ORDER BY and uses different syntax
4757            // OFFSET x ROWS FETCH NEXT y ROWS ONLY
4758            if let Some(offset) = &select.offset {
4759                if self.config.pretty {
4760                    self.write_newline();
4761                    self.write_indent();
4762                } else {
4763                    self.write_space();
4764                }
4765                if matches!(self.config.dialect, Some(DialectType::TSQL)) {
4766                    // SQL Server 2012+ OFFSET ... FETCH syntax
4767                    self.write_keyword("OFFSET");
4768                    self.write_space();
4769                    self.write_limit_expr(&offset.this)?;
4770                    self.write_space();
4771                    self.write_keyword("ROWS");
4772                    // If there was a LIMIT, use FETCH NEXT ... ROWS ONLY
4773                    if let Some(limit) = &select.limit {
4774                        self.write_space();
4775                        self.write_keyword("FETCH NEXT");
4776                        self.write_space();
4777                        self.write_limit_expr(&limit.this)?;
4778                        self.write_space();
4779                        self.write_keyword("ROWS ONLY");
4780                    }
4781                } else {
4782                    self.write_keyword("OFFSET");
4783                    self.write_space();
4784                    self.write_limit_expr(&offset.this)?;
4785                    // Output ROWS keyword if it was in the original SQL
4786                    if offset.rows == Some(true) {
4787                        self.write_space();
4788                        self.write_keyword("ROWS");
4789                    }
4790                }
4791            }
4792        }
4793
4794        // ClickHouse LIMIT BY clause (after LIMIT/OFFSET)
4795        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
4796            if let Some(limit_by) = &select.limit_by {
4797                if !limit_by.is_empty() {
4798                    self.write_space();
4799                    self.write_keyword("BY");
4800                    self.write_space();
4801                    for (i, expr) in limit_by.iter().enumerate() {
4802                        if i > 0 {
4803                            self.write(", ");
4804                        }
4805                        self.generate_expression(expr)?;
4806                    }
4807                }
4808            }
4809        }
4810
4811        // ClickHouse SETTINGS and FORMAT modifiers (after LIMIT/OFFSET)
4812        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
4813            if let Some(settings) = &select.settings {
4814                if self.config.pretty {
4815                    self.write_newline();
4816                    self.write_indent();
4817                } else {
4818                    self.write_space();
4819                }
4820                self.write_keyword("SETTINGS");
4821                self.write_space();
4822                for (i, expr) in settings.iter().enumerate() {
4823                    if i > 0 {
4824                        self.write(", ");
4825                    }
4826                    self.generate_expression(expr)?;
4827                }
4828            }
4829
4830            if let Some(format_expr) = &select.format {
4831                if self.config.pretty {
4832                    self.write_newline();
4833                    self.write_indent();
4834                } else {
4835                    self.write_space();
4836                }
4837                self.write_keyword("FORMAT");
4838                self.write_space();
4839                self.generate_expression(format_expr)?;
4840            }
4841        }
4842
4843        // FETCH FIRST/NEXT
4844        if let Some(fetch) = &select.fetch {
4845            // Check if we already emitted LIMIT from FETCH before OFFSET
4846            let fetch_already_as_limit = select.offset.is_some()
4847                && !fetch.percent
4848                && !fetch.with_ties
4849                && fetch.count.is_some()
4850                && matches!(
4851                    self.config.dialect,
4852                    Some(DialectType::Spark)
4853                        | Some(DialectType::Hive)
4854                        | Some(DialectType::DuckDB)
4855                        | Some(DialectType::SQLite)
4856                        | Some(DialectType::MySQL)
4857                        | Some(DialectType::BigQuery)
4858                        | Some(DialectType::Databricks)
4859                        | Some(DialectType::StarRocks)
4860                        | Some(DialectType::Doris)
4861                        | Some(DialectType::Athena)
4862                        | Some(DialectType::ClickHouse)
4863                        | Some(DialectType::Redshift)
4864                );
4865
4866            if fetch_already_as_limit {
4867                // Already emitted as LIMIT before OFFSET, skip
4868            } else {
4869                if self.config.pretty {
4870                    self.write_newline();
4871                    self.write_indent();
4872                } else {
4873                    self.write_space();
4874                }
4875
4876                // Convert FETCH to LIMIT for dialects that prefer LIMIT syntax
4877                let use_limit = !fetch.percent
4878                    && !fetch.with_ties
4879                    && fetch.count.is_some()
4880                    && matches!(
4881                        self.config.dialect,
4882                        Some(DialectType::Spark)
4883                            | Some(DialectType::Hive)
4884                            | Some(DialectType::DuckDB)
4885                            | Some(DialectType::SQLite)
4886                            | Some(DialectType::MySQL)
4887                            | Some(DialectType::BigQuery)
4888                            | Some(DialectType::Databricks)
4889                            | Some(DialectType::StarRocks)
4890                            | Some(DialectType::Doris)
4891                            | Some(DialectType::Athena)
4892                            | Some(DialectType::ClickHouse)
4893                            | Some(DialectType::Redshift)
4894                    );
4895
4896                if use_limit {
4897                    self.write_keyword("LIMIT");
4898                    self.write_space();
4899                    self.generate_expression(fetch.count.as_ref().unwrap())?;
4900                } else {
4901                    self.write_keyword("FETCH");
4902                    self.write_space();
4903                    self.write_keyword(&fetch.direction);
4904                    if let Some(ref count) = fetch.count {
4905                        self.write_space();
4906                        self.generate_expression(count)?;
4907                    }
4908                    if fetch.percent {
4909                        self.write_space();
4910                        self.write_keyword("PERCENT");
4911                    }
4912                    if fetch.rows {
4913                        self.write_space();
4914                        self.write_keyword("ROWS");
4915                    }
4916                    if fetch.with_ties {
4917                        self.write_space();
4918                        self.write_keyword("WITH TIES");
4919                    } else {
4920                        self.write_space();
4921                        self.write_keyword("ONLY");
4922                    }
4923                }
4924            } // close fetch_already_as_limit else
4925        }
4926
4927        // SAMPLE / TABLESAMPLE
4928        if let Some(sample) = &select.sample {
4929            use crate::dialects::DialectType;
4930            if self.config.pretty {
4931                self.write_newline();
4932            } else {
4933                self.write_space();
4934            }
4935
4936            if sample.is_using_sample {
4937                // DuckDB USING SAMPLE: METHOD (size UNIT) [REPEATABLE (seed)]
4938                self.write_keyword("USING SAMPLE");
4939                self.generate_sample_body(sample)?;
4940            } else {
4941                self.write_keyword("TABLESAMPLE");
4942
4943                // Snowflake defaults to BERNOULLI when no explicit method is given
4944                let snowflake_bernoulli =
4945                    matches!(self.config.dialect, Some(DialectType::Snowflake))
4946                        && !sample.explicit_method;
4947                if snowflake_bernoulli {
4948                    self.write_space();
4949                    self.write_keyword("BERNOULLI");
4950                }
4951
4952                // Handle BUCKET sampling: TABLESAMPLE (BUCKET 1 OUT OF 5 ON x)
4953                if matches!(sample.method, SampleMethod::Bucket) {
4954                    self.write_space();
4955                    self.write("(");
4956                    self.write_keyword("BUCKET");
4957                    self.write_space();
4958                    if let Some(ref num) = sample.bucket_numerator {
4959                        self.generate_expression(num)?;
4960                    }
4961                    self.write_space();
4962                    self.write_keyword("OUT OF");
4963                    self.write_space();
4964                    if let Some(ref denom) = sample.bucket_denominator {
4965                        self.generate_expression(denom)?;
4966                    }
4967                    if let Some(ref field) = sample.bucket_field {
4968                        self.write_space();
4969                        self.write_keyword("ON");
4970                        self.write_space();
4971                        self.generate_expression(field)?;
4972                    }
4973                    self.write(")");
4974                } else if sample.unit_after_size {
4975                    // Syntax: TABLESAMPLE [METHOD] (size ROWS) or TABLESAMPLE [METHOD] (size PERCENT)
4976                    if sample.explicit_method && sample.method_before_size {
4977                        self.write_space();
4978                        match sample.method {
4979                            SampleMethod::Bernoulli => self.write_keyword("BERNOULLI"),
4980                            SampleMethod::System => self.write_keyword("SYSTEM"),
4981                            SampleMethod::Block => self.write_keyword("BLOCK"),
4982                            SampleMethod::Row => self.write_keyword("ROW"),
4983                            SampleMethod::Reservoir => self.write_keyword("RESERVOIR"),
4984                            _ => {}
4985                        }
4986                    }
4987                    self.write(" (");
4988                    self.generate_expression(&sample.size)?;
4989                    self.write_space();
4990                    match sample.method {
4991                        SampleMethod::Percent => self.write_keyword("PERCENT"),
4992                        SampleMethod::Row => self.write_keyword("ROWS"),
4993                        SampleMethod::Reservoir => self.write_keyword("ROWS"),
4994                        _ => {
4995                            self.write_keyword("PERCENT");
4996                        }
4997                    }
4998                    self.write(")");
4999                } else {
5000                    // Syntax: TABLESAMPLE METHOD (size)
5001                    self.write_space();
5002                    match sample.method {
5003                        SampleMethod::Bernoulli => self.write_keyword("BERNOULLI"),
5004                        SampleMethod::System => self.write_keyword("SYSTEM"),
5005                        SampleMethod::Block => self.write_keyword("BLOCK"),
5006                        SampleMethod::Row => self.write_keyword("ROW"),
5007                        SampleMethod::Percent => self.write_keyword("BERNOULLI"),
5008                        SampleMethod::Bucket => {}
5009                        SampleMethod::Reservoir => self.write_keyword("RESERVOIR"),
5010                    }
5011                    self.write(" (");
5012                    self.generate_expression(&sample.size)?;
5013                    if matches!(sample.method, SampleMethod::Percent) {
5014                        self.write_space();
5015                        self.write_keyword("PERCENT");
5016                    }
5017                    self.write(")");
5018                }
5019            }
5020
5021            if let Some(seed) = &sample.seed {
5022                self.write_space();
5023                // Databricks/Spark use REPEATABLE, not SEED
5024                let use_seed = sample.use_seed_keyword
5025                    && !matches!(
5026                        self.config.dialect,
5027                        Some(crate::dialects::DialectType::Databricks)
5028                            | Some(crate::dialects::DialectType::Spark)
5029                    );
5030                if use_seed {
5031                    self.write_keyword("SEED");
5032                } else {
5033                    self.write_keyword("REPEATABLE");
5034                }
5035                self.write(" (");
5036                self.generate_expression(seed)?;
5037                self.write(")");
5038            }
5039        }
5040
5041        // FOR UPDATE/SHARE locks
5042        // Skip locking clauses for dialects that don't support them
5043        if self.config.locking_reads_supported {
5044            for lock in &select.locks {
5045                if self.config.pretty {
5046                    self.write_newline();
5047                    self.write_indent();
5048                } else {
5049                    self.write_space();
5050                }
5051                self.generate_lock(lock)?;
5052            }
5053        }
5054
5055        // FOR XML clause (T-SQL)
5056        if !select.for_xml.is_empty() {
5057            if self.config.pretty {
5058                self.write_newline();
5059                self.write_indent();
5060            } else {
5061                self.write_space();
5062            }
5063            self.write_keyword("FOR XML");
5064            for (i, opt) in select.for_xml.iter().enumerate() {
5065                if self.config.pretty {
5066                    if i > 0 {
5067                        self.write(",");
5068                    }
5069                    self.write_newline();
5070                    self.write_indent();
5071                    self.write("  "); // extra indent for options
5072                } else {
5073                    if i > 0 {
5074                        self.write(",");
5075                    }
5076                    self.write_space();
5077                }
5078                self.generate_for_xml_option(opt)?;
5079            }
5080        }
5081
5082        // FOR JSON clause (T-SQL)
5083        if !select.for_json.is_empty() {
5084            if self.config.pretty {
5085                self.write_newline();
5086                self.write_indent();
5087            } else {
5088                self.write_space();
5089            }
5090            self.write_keyword("FOR JSON");
5091            for (i, opt) in select.for_json.iter().enumerate() {
5092                if self.config.pretty {
5093                    if i > 0 {
5094                        self.write(",");
5095                    }
5096                    self.write_newline();
5097                    self.write_indent();
5098                    self.write("  "); // extra indent for options
5099                } else {
5100                    if i > 0 {
5101                        self.write(",");
5102                    }
5103                    self.write_space();
5104                }
5105                self.generate_for_xml_option(opt)?;
5106            }
5107        }
5108
5109        // TSQL: OPTION clause
5110        if let Some(ref option) = select.option {
5111            if matches!(
5112                self.config.dialect,
5113                Some(crate::dialects::DialectType::TSQL)
5114                    | Some(crate::dialects::DialectType::Fabric)
5115            ) {
5116                self.write_space();
5117                self.write(option);
5118            }
5119        }
5120
5121        Ok(())
5122    }
5123
5124    /// Generate a single FOR XML option
5125    fn generate_for_xml_option(&mut self, opt: &Expression) -> Result<()> {
5126        match opt {
5127            Expression::QueryOption(qo) => {
5128                // Extract the option name from Var
5129                if let Expression::Var(var) = &*qo.this {
5130                    self.write(&var.this);
5131                } else {
5132                    self.generate_expression(&qo.this)?;
5133                }
5134                // If there's an expression (like PATH('element')), output it in parens
5135                if let Some(expr) = &qo.expression {
5136                    self.write("(");
5137                    self.generate_expression(expr)?;
5138                    self.write(")");
5139                }
5140            }
5141            _ => {
5142                self.generate_expression(opt)?;
5143            }
5144        }
5145        Ok(())
5146    }
5147
5148    fn generate_with(&mut self, with: &With) -> Result<()> {
5149        use crate::dialects::DialectType;
5150
5151        // Output leading comments before WITH
5152        for comment in &with.leading_comments {
5153            self.write_formatted_comment(comment);
5154            self.write(" ");
5155        }
5156        self.write_keyword("WITH");
5157        if with.recursive && self.config.cte_recursive_keyword_required {
5158            self.write_space();
5159            self.write_keyword("RECURSIVE");
5160        }
5161        self.write_space();
5162
5163        // BigQuery doesn't support column aliases in CTE definitions
5164        let skip_cte_columns = matches!(self.config.dialect, Some(DialectType::BigQuery));
5165
5166        for (i, cte) in with.ctes.iter().enumerate() {
5167            if i > 0 {
5168                self.write(",");
5169                if self.config.pretty {
5170                    self.write_space();
5171                } else {
5172                    self.write(" ");
5173                }
5174            }
5175            if matches!(self.config.dialect, Some(DialectType::ClickHouse)) && !cte.alias_first {
5176                self.generate_expression(&cte.this)?;
5177                self.write_space();
5178                self.write_keyword("AS");
5179                self.write_space();
5180                self.generate_identifier(&cte.alias)?;
5181                continue;
5182            }
5183            self.generate_identifier(&cte.alias)?;
5184            // Output CTE comments after alias name, before AS
5185            for comment in &cte.comments {
5186                self.write_space();
5187                self.write_formatted_comment(comment);
5188            }
5189            if !cte.columns.is_empty() && !skip_cte_columns {
5190                self.write("(");
5191                for (j, col) in cte.columns.iter().enumerate() {
5192                    if j > 0 {
5193                        self.write(", ");
5194                    }
5195                    self.generate_identifier(col)?;
5196                }
5197                self.write(")");
5198            }
5199            // USING KEY (columns) for DuckDB recursive CTEs
5200            if !cte.key_expressions.is_empty() {
5201                self.write_space();
5202                self.write_keyword("USING KEY");
5203                self.write(" (");
5204                for (i, key) in cte.key_expressions.iter().enumerate() {
5205                    if i > 0 {
5206                        self.write(", ");
5207                    }
5208                    self.generate_identifier(key)?;
5209                }
5210                self.write(")");
5211            }
5212            self.write_space();
5213            self.write_keyword("AS");
5214            // MATERIALIZED / NOT MATERIALIZED
5215            if let Some(materialized) = cte.materialized {
5216                self.write_space();
5217                if materialized {
5218                    self.write_keyword("MATERIALIZED");
5219                } else {
5220                    self.write_keyword("NOT MATERIALIZED");
5221                }
5222            }
5223            self.write(" (");
5224            if self.config.pretty {
5225                self.write_newline();
5226                self.indent_level += 1;
5227                self.write_indent();
5228            }
5229            // For Spark/Databricks, VALUES in a CTE must be wrapped with SELECT * FROM
5230            // e.g., WITH t AS (VALUES ('foo_val') AS t(foo1)) -> WITH t AS (SELECT * FROM VALUES ('foo_val') AS t(foo1))
5231            let wrap_values_in_select = matches!(
5232                self.config.dialect,
5233                Some(DialectType::Spark) | Some(DialectType::Databricks)
5234            ) && matches!(&cte.this, Expression::Values(_));
5235
5236            if wrap_values_in_select {
5237                self.write_keyword("SELECT");
5238                self.write(" * ");
5239                self.write_keyword("FROM");
5240                self.write_space();
5241            }
5242            self.generate_expression(&cte.this)?;
5243            if self.config.pretty {
5244                self.write_newline();
5245                self.indent_level -= 1;
5246                self.write_indent();
5247            }
5248            self.write(")");
5249        }
5250
5251        // Generate SEARCH/CYCLE clause if present
5252        if let Some(search) = &with.search {
5253            self.write_space();
5254            self.generate_expression(search)?;
5255        }
5256
5257        Ok(())
5258    }
5259
5260    /// Generate joins with proper nesting structure for pretty printing.
5261    /// Deferred-condition joins "own" the non-deferred joins that follow them
5262    /// within the same nesting_group.
5263    fn generate_joins_with_nesting(&mut self, joins: &[Join]) -> Result<()> {
5264        let mut i = 0;
5265        while i < joins.len() {
5266            if joins[i].deferred_condition {
5267                let parent_group = joins[i].nesting_group;
5268
5269                // This join owns the following non-deferred joins in the same nesting_group
5270                // First output the join keyword and table (without condition)
5271                self.generate_join_without_condition(&joins[i])?;
5272
5273                // Find the range of child joins: same nesting_group and not deferred
5274                let child_start = i + 1;
5275                let mut child_end = child_start;
5276                while child_end < joins.len()
5277                    && !joins[child_end].deferred_condition
5278                    && joins[child_end].nesting_group == parent_group
5279                {
5280                    child_end += 1;
5281                }
5282
5283                // Output child joins with extra indentation
5284                if child_start < child_end {
5285                    self.indent_level += 1;
5286                    for j in child_start..child_end {
5287                        self.generate_join(&joins[j])?;
5288                    }
5289                    self.indent_level -= 1;
5290                }
5291
5292                // Output the deferred condition at the parent level
5293                self.generate_join_condition(&joins[i])?;
5294
5295                i = child_end;
5296            } else {
5297                // Regular join (no nesting)
5298                self.generate_join(&joins[i])?;
5299                i += 1;
5300            }
5301        }
5302        Ok(())
5303    }
5304
5305    /// Generate a join's keyword and table reference, but not its ON/USING condition.
5306    /// Used for deferred-condition joins where the condition is output after child joins.
5307    fn generate_join_without_condition(&mut self, join: &Join) -> Result<()> {
5308        // Save and temporarily clear the condition to prevent generate_join from outputting it
5309        // We achieve this by creating a modified copy
5310        let mut join_copy = join.clone();
5311        join_copy.on = None;
5312        join_copy.using = Vec::new();
5313        join_copy.deferred_condition = false;
5314        self.generate_join(&join_copy)
5315    }
5316
5317    fn generate_join(&mut self, join: &Join) -> Result<()> {
5318        // Implicit (comma) joins: output as ", table" instead of "CROSS JOIN table"
5319        if join.kind == JoinKind::Implicit {
5320            self.write(",");
5321            if self.config.pretty {
5322                self.write_newline();
5323                self.write_indent();
5324            } else {
5325                self.write_space();
5326            }
5327            self.generate_expression(&join.this)?;
5328            return Ok(());
5329        }
5330
5331        if self.config.pretty {
5332            self.write_newline();
5333            self.write_indent();
5334        } else {
5335            self.write_space();
5336        }
5337
5338        // Helper: format hint suffix (e.g., " LOOP" or "")
5339        // Only include join hints for dialects that support them
5340        let hint_str = if self.config.join_hints {
5341            join.join_hint
5342                .as_ref()
5343                .map(|h| format!(" {}", h))
5344                .unwrap_or_default()
5345        } else {
5346            String::new()
5347        };
5348
5349        let clickhouse_join_keyword =
5350            if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
5351                if let Some(hint) = &join.join_hint {
5352                    let mut global = false;
5353                    let mut strictness: Option<&'static str> = None;
5354                    for part in hint.split_whitespace() {
5355                        if part.eq_ignore_ascii_case("GLOBAL") {
5356                            global = true;
5357                        } else if part.eq_ignore_ascii_case("ANY") {
5358                            strictness = Some("ANY");
5359                        } else if part.eq_ignore_ascii_case("ASOF") {
5360                            strictness = Some("ASOF");
5361                        } else if part.eq_ignore_ascii_case("SEMI") {
5362                            strictness = Some("SEMI");
5363                        } else if part.eq_ignore_ascii_case("ANTI") {
5364                            strictness = Some("ANTI");
5365                        }
5366                    }
5367
5368                    if global || strictness.is_some() {
5369                        let join_type = match join.kind {
5370                            JoinKind::Left => {
5371                                if join.use_outer_keyword {
5372                                    "LEFT OUTER"
5373                                } else if join.use_inner_keyword {
5374                                    "LEFT INNER"
5375                                } else {
5376                                    "LEFT"
5377                                }
5378                            }
5379                            JoinKind::Right => {
5380                                if join.use_outer_keyword {
5381                                    "RIGHT OUTER"
5382                                } else if join.use_inner_keyword {
5383                                    "RIGHT INNER"
5384                                } else {
5385                                    "RIGHT"
5386                                }
5387                            }
5388                            JoinKind::Full => {
5389                                if join.use_outer_keyword {
5390                                    "FULL OUTER"
5391                                } else {
5392                                    "FULL"
5393                                }
5394                            }
5395                            JoinKind::Inner => {
5396                                if join.use_inner_keyword {
5397                                    "INNER"
5398                                } else {
5399                                    ""
5400                                }
5401                            }
5402                            _ => "",
5403                        };
5404
5405                        let mut parts = Vec::new();
5406                        if global {
5407                            parts.push("GLOBAL");
5408                        }
5409                        if !join_type.is_empty() {
5410                            parts.push(join_type);
5411                        }
5412                        if let Some(strict) = strictness {
5413                            parts.push(strict);
5414                        }
5415                        parts.push("JOIN");
5416                        Some(parts.join(" "))
5417                    } else {
5418                        None
5419                    }
5420                } else {
5421                    None
5422                }
5423            } else {
5424                None
5425            };
5426
5427        // Output any comments associated with this join
5428        // In pretty mode, comments go on their own line before the join keyword
5429        // In non-pretty mode, comments go inline before the join keyword
5430        if !join.comments.is_empty() {
5431            if self.config.pretty {
5432                // In pretty mode, go back before the newline+indent we just wrote
5433                // and output comments on their own lines
5434                // We need to output comments BEFORE the join keyword on separate lines
5435                // Trim the trailing newline+indent we already wrote
5436                let trimmed = self.output.trim_end().len();
5437                self.output.truncate(trimmed);
5438                for comment in &join.comments {
5439                    self.write_newline();
5440                    self.write_indent();
5441                    self.write_formatted_comment(comment);
5442                }
5443                self.write_newline();
5444                self.write_indent();
5445            } else {
5446                for comment in &join.comments {
5447                    self.write_formatted_comment(comment);
5448                    self.write_space();
5449                }
5450            }
5451        }
5452
5453        let directed_str = if join.directed { " DIRECTED" } else { "" };
5454
5455        if let Some(keyword) = clickhouse_join_keyword {
5456            self.write_keyword(&keyword);
5457        } else {
5458            match join.kind {
5459                JoinKind::Inner => {
5460                    if join.use_inner_keyword {
5461                        if hint_str.is_empty() && directed_str.is_empty() {
5462                            self.write_keyword("INNER JOIN");
5463                        } else {
5464                            self.write_keyword("INNER");
5465                            if !hint_str.is_empty() {
5466                                self.write_keyword(&hint_str);
5467                            }
5468                            if !directed_str.is_empty() {
5469                                self.write_keyword(directed_str);
5470                            }
5471                            self.write_keyword(" JOIN");
5472                        }
5473                    } else {
5474                        if !hint_str.is_empty() {
5475                            self.write_keyword(hint_str.trim());
5476                            self.write_keyword(" ");
5477                        }
5478                        if !directed_str.is_empty() {
5479                            self.write_keyword("DIRECTED ");
5480                        }
5481                        self.write_keyword("JOIN");
5482                    }
5483                }
5484                JoinKind::Left => {
5485                    if join.use_outer_keyword {
5486                        if hint_str.is_empty() && directed_str.is_empty() {
5487                            self.write_keyword("LEFT OUTER JOIN");
5488                        } else {
5489                            self.write_keyword("LEFT OUTER");
5490                            if !hint_str.is_empty() {
5491                                self.write_keyword(&hint_str);
5492                            }
5493                            if !directed_str.is_empty() {
5494                                self.write_keyword(directed_str);
5495                            }
5496                            self.write_keyword(" JOIN");
5497                        }
5498                    } else if join.use_inner_keyword {
5499                        if hint_str.is_empty() && directed_str.is_empty() {
5500                            self.write_keyword("LEFT INNER JOIN");
5501                        } else {
5502                            self.write_keyword("LEFT INNER");
5503                            if !hint_str.is_empty() {
5504                                self.write_keyword(&hint_str);
5505                            }
5506                            if !directed_str.is_empty() {
5507                                self.write_keyword(directed_str);
5508                            }
5509                            self.write_keyword(" JOIN");
5510                        }
5511                    } else {
5512                        if hint_str.is_empty() && directed_str.is_empty() {
5513                            self.write_keyword("LEFT JOIN");
5514                        } else {
5515                            self.write_keyword("LEFT");
5516                            if !hint_str.is_empty() {
5517                                self.write_keyword(&hint_str);
5518                            }
5519                            if !directed_str.is_empty() {
5520                                self.write_keyword(directed_str);
5521                            }
5522                            self.write_keyword(" JOIN");
5523                        }
5524                    }
5525                }
5526                JoinKind::Right => {
5527                    if join.use_outer_keyword {
5528                        if hint_str.is_empty() && directed_str.is_empty() {
5529                            self.write_keyword("RIGHT OUTER JOIN");
5530                        } else {
5531                            self.write_keyword("RIGHT OUTER");
5532                            if !hint_str.is_empty() {
5533                                self.write_keyword(&hint_str);
5534                            }
5535                            if !directed_str.is_empty() {
5536                                self.write_keyword(directed_str);
5537                            }
5538                            self.write_keyword(" JOIN");
5539                        }
5540                    } else if join.use_inner_keyword {
5541                        if hint_str.is_empty() && directed_str.is_empty() {
5542                            self.write_keyword("RIGHT INNER JOIN");
5543                        } else {
5544                            self.write_keyword("RIGHT INNER");
5545                            if !hint_str.is_empty() {
5546                                self.write_keyword(&hint_str);
5547                            }
5548                            if !directed_str.is_empty() {
5549                                self.write_keyword(directed_str);
5550                            }
5551                            self.write_keyword(" JOIN");
5552                        }
5553                    } else {
5554                        if hint_str.is_empty() && directed_str.is_empty() {
5555                            self.write_keyword("RIGHT JOIN");
5556                        } else {
5557                            self.write_keyword("RIGHT");
5558                            if !hint_str.is_empty() {
5559                                self.write_keyword(&hint_str);
5560                            }
5561                            if !directed_str.is_empty() {
5562                                self.write_keyword(directed_str);
5563                            }
5564                            self.write_keyword(" JOIN");
5565                        }
5566                    }
5567                }
5568                JoinKind::Full => {
5569                    if join.use_outer_keyword {
5570                        if hint_str.is_empty() && directed_str.is_empty() {
5571                            self.write_keyword("FULL OUTER JOIN");
5572                        } else {
5573                            self.write_keyword("FULL OUTER");
5574                            if !hint_str.is_empty() {
5575                                self.write_keyword(&hint_str);
5576                            }
5577                            if !directed_str.is_empty() {
5578                                self.write_keyword(directed_str);
5579                            }
5580                            self.write_keyword(" JOIN");
5581                        }
5582                    } else {
5583                        if hint_str.is_empty() && directed_str.is_empty() {
5584                            self.write_keyword("FULL JOIN");
5585                        } else {
5586                            self.write_keyword("FULL");
5587                            if !hint_str.is_empty() {
5588                                self.write_keyword(&hint_str);
5589                            }
5590                            if !directed_str.is_empty() {
5591                                self.write_keyword(directed_str);
5592                            }
5593                            self.write_keyword(" JOIN");
5594                        }
5595                    }
5596                }
5597                JoinKind::Outer => {
5598                    if directed_str.is_empty() {
5599                        self.write_keyword("OUTER JOIN");
5600                    } else {
5601                        self.write_keyword("OUTER");
5602                        self.write_keyword(directed_str);
5603                        self.write_keyword(" JOIN");
5604                    }
5605                }
5606                JoinKind::Cross => {
5607                    if directed_str.is_empty() {
5608                        self.write_keyword("CROSS JOIN");
5609                    } else {
5610                        self.write_keyword("CROSS");
5611                        self.write_keyword(directed_str);
5612                        self.write_keyword(" JOIN");
5613                    }
5614                }
5615                JoinKind::Natural => {
5616                    if join.use_inner_keyword {
5617                        if directed_str.is_empty() {
5618                            self.write_keyword("NATURAL INNER JOIN");
5619                        } else {
5620                            self.write_keyword("NATURAL INNER");
5621                            self.write_keyword(directed_str);
5622                            self.write_keyword(" JOIN");
5623                        }
5624                    } else {
5625                        if directed_str.is_empty() {
5626                            self.write_keyword("NATURAL JOIN");
5627                        } else {
5628                            self.write_keyword("NATURAL");
5629                            self.write_keyword(directed_str);
5630                            self.write_keyword(" JOIN");
5631                        }
5632                    }
5633                }
5634                JoinKind::NaturalLeft => {
5635                    if join.use_outer_keyword {
5636                        if directed_str.is_empty() {
5637                            self.write_keyword("NATURAL LEFT OUTER JOIN");
5638                        } else {
5639                            self.write_keyword("NATURAL LEFT OUTER");
5640                            self.write_keyword(directed_str);
5641                            self.write_keyword(" JOIN");
5642                        }
5643                    } else {
5644                        if directed_str.is_empty() {
5645                            self.write_keyword("NATURAL LEFT JOIN");
5646                        } else {
5647                            self.write_keyword("NATURAL LEFT");
5648                            self.write_keyword(directed_str);
5649                            self.write_keyword(" JOIN");
5650                        }
5651                    }
5652                }
5653                JoinKind::NaturalRight => {
5654                    if join.use_outer_keyword {
5655                        if directed_str.is_empty() {
5656                            self.write_keyword("NATURAL RIGHT OUTER JOIN");
5657                        } else {
5658                            self.write_keyword("NATURAL RIGHT OUTER");
5659                            self.write_keyword(directed_str);
5660                            self.write_keyword(" JOIN");
5661                        }
5662                    } else {
5663                        if directed_str.is_empty() {
5664                            self.write_keyword("NATURAL RIGHT JOIN");
5665                        } else {
5666                            self.write_keyword("NATURAL RIGHT");
5667                            self.write_keyword(directed_str);
5668                            self.write_keyword(" JOIN");
5669                        }
5670                    }
5671                }
5672                JoinKind::NaturalFull => {
5673                    if join.use_outer_keyword {
5674                        if directed_str.is_empty() {
5675                            self.write_keyword("NATURAL FULL OUTER JOIN");
5676                        } else {
5677                            self.write_keyword("NATURAL FULL OUTER");
5678                            self.write_keyword(directed_str);
5679                            self.write_keyword(" JOIN");
5680                        }
5681                    } else {
5682                        if directed_str.is_empty() {
5683                            self.write_keyword("NATURAL FULL JOIN");
5684                        } else {
5685                            self.write_keyword("NATURAL FULL");
5686                            self.write_keyword(directed_str);
5687                            self.write_keyword(" JOIN");
5688                        }
5689                    }
5690                }
5691                JoinKind::Semi => self.write_keyword("SEMI JOIN"),
5692                JoinKind::Anti => self.write_keyword("ANTI JOIN"),
5693                JoinKind::LeftSemi => self.write_keyword("LEFT SEMI JOIN"),
5694                JoinKind::LeftAnti => self.write_keyword("LEFT ANTI JOIN"),
5695                JoinKind::RightSemi => self.write_keyword("RIGHT SEMI JOIN"),
5696                JoinKind::RightAnti => self.write_keyword("RIGHT ANTI JOIN"),
5697                JoinKind::CrossApply => {
5698                    // CROSS APPLY -> INNER JOIN LATERAL for non-TSQL dialects
5699                    if matches!(self.config.dialect, Some(DialectType::TSQL) | None) {
5700                        self.write_keyword("CROSS APPLY");
5701                    } else {
5702                        self.write_keyword("INNER JOIN LATERAL");
5703                    }
5704                }
5705                JoinKind::OuterApply => {
5706                    // OUTER APPLY -> LEFT JOIN LATERAL for non-TSQL dialects
5707                    if matches!(self.config.dialect, Some(DialectType::TSQL) | None) {
5708                        self.write_keyword("OUTER APPLY");
5709                    } else {
5710                        self.write_keyword("LEFT JOIN LATERAL");
5711                    }
5712                }
5713                JoinKind::AsOf => self.write_keyword("ASOF JOIN"),
5714                JoinKind::AsOfLeft => {
5715                    if join.use_outer_keyword {
5716                        self.write_keyword("ASOF LEFT OUTER JOIN");
5717                    } else {
5718                        self.write_keyword("ASOF LEFT JOIN");
5719                    }
5720                }
5721                JoinKind::AsOfRight => {
5722                    if join.use_outer_keyword {
5723                        self.write_keyword("ASOF RIGHT OUTER JOIN");
5724                    } else {
5725                        self.write_keyword("ASOF RIGHT JOIN");
5726                    }
5727                }
5728                JoinKind::Lateral => self.write_keyword("LATERAL JOIN"),
5729                JoinKind::LeftLateral => {
5730                    if join.use_outer_keyword {
5731                        self.write_keyword("LEFT OUTER LATERAL JOIN");
5732                    } else {
5733                        self.write_keyword("LEFT LATERAL JOIN");
5734                    }
5735                }
5736                JoinKind::Straight => self.write_keyword("STRAIGHT_JOIN"),
5737                JoinKind::Implicit => {
5738                    // BigQuery, Hive, Spark, and Databricks prefer explicit CROSS JOIN over comma syntax
5739                    // But only when source is the same dialect (identity) or source is another CROSS JOIN dialect
5740                    // When source is Generic, keep commas (Python sqlglot: parser marks joins, not generator)
5741                    use crate::dialects::DialectType;
5742                    let is_cj_dialect = matches!(
5743                        self.config.dialect,
5744                        Some(DialectType::BigQuery)
5745                            | Some(DialectType::Hive)
5746                            | Some(DialectType::Spark)
5747                            | Some(DialectType::Databricks)
5748                    );
5749                    let source_is_same = self.config.source_dialect.is_some()
5750                        && self.config.source_dialect == self.config.dialect;
5751                    let source_is_cj = matches!(
5752                        self.config.source_dialect,
5753                        Some(DialectType::BigQuery)
5754                            | Some(DialectType::Hive)
5755                            | Some(DialectType::Spark)
5756                            | Some(DialectType::Databricks)
5757                    );
5758                    if is_cj_dialect
5759                        && (source_is_same || source_is_cj || self.config.source_dialect.is_none())
5760                    {
5761                        self.write_keyword("CROSS JOIN");
5762                    } else {
5763                        // Implicit join uses comma: FROM a, b
5764                        // We already wrote a space before the match, so replace with comma
5765                        // by removing trailing space and writing ", "
5766                        self.output.truncate(self.output.trim_end().len());
5767                        self.write(",");
5768                    }
5769                }
5770                JoinKind::Array => self.write_keyword("ARRAY JOIN"),
5771                JoinKind::LeftArray => self.write_keyword("LEFT ARRAY JOIN"),
5772                JoinKind::Paste => self.write_keyword("PASTE JOIN"),
5773                JoinKind::Positional => self.write_keyword("POSITIONAL JOIN"),
5774            }
5775        }
5776
5777        // ARRAY JOIN items need comma-separated output (Tuple holds multiple items)
5778        if matches!(join.kind, JoinKind::Array | JoinKind::LeftArray) {
5779            self.write_space();
5780            match &join.this {
5781                Expression::Tuple(t) => {
5782                    for (i, item) in t.expressions.iter().enumerate() {
5783                        if i > 0 {
5784                            self.write(", ");
5785                        }
5786                        self.generate_expression(item)?;
5787                    }
5788                }
5789                other => {
5790                    self.generate_expression(other)?;
5791                }
5792            }
5793        } else {
5794            self.write_space();
5795            self.generate_expression(&join.this)?;
5796        }
5797
5798        // Only output MATCH_CONDITION/ON/USING inline if the condition wasn't deferred
5799        if !join.deferred_condition {
5800            // Output MATCH_CONDITION first (Snowflake ASOF JOIN)
5801            if let Some(match_cond) = &join.match_condition {
5802                self.write_space();
5803                self.write_keyword("MATCH_CONDITION");
5804                self.write(" (");
5805                self.generate_expression(match_cond)?;
5806                self.write(")");
5807            }
5808
5809            if let Some(on) = &join.on {
5810                if self.config.pretty {
5811                    self.write_newline();
5812                    self.indent_level += 1;
5813                    self.write_indent();
5814                    self.write_keyword("ON");
5815                    self.write_space();
5816                    self.generate_join_on_condition(on)?;
5817                    self.indent_level -= 1;
5818                } else {
5819                    self.write_space();
5820                    self.write_keyword("ON");
5821                    self.write_space();
5822                    self.generate_expression(on)?;
5823                }
5824            }
5825
5826            if !join.using.is_empty() {
5827                if self.config.pretty {
5828                    self.write_newline();
5829                    self.indent_level += 1;
5830                    self.write_indent();
5831                    self.write_keyword("USING");
5832                    self.write(" (");
5833                    for (i, col) in join.using.iter().enumerate() {
5834                        if i > 0 {
5835                            self.write(", ");
5836                        }
5837                        self.generate_identifier(col)?;
5838                    }
5839                    self.write(")");
5840                    self.indent_level -= 1;
5841                } else {
5842                    self.write_space();
5843                    self.write_keyword("USING");
5844                    self.write(" (");
5845                    for (i, col) in join.using.iter().enumerate() {
5846                        if i > 0 {
5847                            self.write(", ");
5848                        }
5849                        self.generate_identifier(col)?;
5850                    }
5851                    self.write(")");
5852                }
5853            }
5854        }
5855
5856        // Generate PIVOT/UNPIVOT expressions that follow this join
5857        for pivot in &join.pivots {
5858            self.write_space();
5859            self.generate_expression(pivot)?;
5860        }
5861
5862        Ok(())
5863    }
5864
5865    /// Generate just the ON/USING/MATCH_CONDITION for a join (used for deferred conditions)
5866    fn generate_join_condition(&mut self, join: &Join) -> Result<()> {
5867        // Generate MATCH_CONDITION first (Snowflake ASOF JOIN)
5868        if let Some(match_cond) = &join.match_condition {
5869            self.write_space();
5870            self.write_keyword("MATCH_CONDITION");
5871            self.write(" (");
5872            self.generate_expression(match_cond)?;
5873            self.write(")");
5874        }
5875
5876        if let Some(on) = &join.on {
5877            if self.config.pretty {
5878                self.write_newline();
5879                self.indent_level += 1;
5880                self.write_indent();
5881                self.write_keyword("ON");
5882                self.write_space();
5883                // In pretty mode, split AND conditions onto separate lines
5884                self.generate_join_on_condition(on)?;
5885                self.indent_level -= 1;
5886            } else {
5887                self.write_space();
5888                self.write_keyword("ON");
5889                self.write_space();
5890                self.generate_expression(on)?;
5891            }
5892        }
5893
5894        if !join.using.is_empty() {
5895            if self.config.pretty {
5896                self.write_newline();
5897                self.indent_level += 1;
5898                self.write_indent();
5899                self.write_keyword("USING");
5900                self.write(" (");
5901                for (i, col) in join.using.iter().enumerate() {
5902                    if i > 0 {
5903                        self.write(", ");
5904                    }
5905                    self.generate_identifier(col)?;
5906                }
5907                self.write(")");
5908                self.indent_level -= 1;
5909            } else {
5910                self.write_space();
5911                self.write_keyword("USING");
5912                self.write(" (");
5913                for (i, col) in join.using.iter().enumerate() {
5914                    if i > 0 {
5915                        self.write(", ");
5916                    }
5917                    self.generate_identifier(col)?;
5918                }
5919                self.write(")");
5920            }
5921        }
5922
5923        // Generate PIVOT/UNPIVOT expressions that follow this join (for deferred conditions)
5924        for pivot in &join.pivots {
5925            self.write_space();
5926            self.generate_expression(pivot)?;
5927        }
5928
5929        Ok(())
5930    }
5931
5932    /// Generate JOIN ON condition with AND clauses on separate lines in pretty mode
5933    fn generate_join_on_condition(&mut self, expr: &Expression) -> Result<()> {
5934        if let Expression::And(and_op) = expr {
5935            if let Some(conditions) = self.flatten_connector_terms(and_op, ConnectorOperator::And) {
5936                self.generate_expression(conditions[0])?;
5937                for condition in conditions.iter().skip(1) {
5938                    self.write_newline();
5939                    self.write_indent();
5940                    self.write_keyword("AND");
5941                    self.write_space();
5942                    self.generate_expression(condition)?;
5943                }
5944                return Ok(());
5945            }
5946        }
5947
5948        self.generate_expression(expr)
5949    }
5950
5951    fn generate_joined_table(&mut self, jt: &JoinedTable) -> Result<()> {
5952        // Parenthesized join: (tbl1 CROSS JOIN tbl2)
5953        self.write("(");
5954        self.generate_expression(&jt.left)?;
5955
5956        // Generate all joins
5957        for join in &jt.joins {
5958            self.generate_join(join)?;
5959        }
5960
5961        // Generate LATERAL VIEW clauses (Hive/Spark)
5962        for (lv_idx, lv) in jt.lateral_views.iter().enumerate() {
5963            self.generate_lateral_view(lv, lv_idx)?;
5964        }
5965
5966        self.write(")");
5967
5968        // Alias
5969        if let Some(alias) = &jt.alias {
5970            self.write_space();
5971            self.write_keyword("AS");
5972            self.write_space();
5973            self.generate_identifier(alias)?;
5974        }
5975
5976        Ok(())
5977    }
5978
5979    fn generate_lateral_view(&mut self, lv: &LateralView, lv_index: usize) -> Result<()> {
5980        use crate::dialects::DialectType;
5981
5982        if self.config.pretty {
5983            self.write_newline();
5984            self.write_indent();
5985        } else {
5986            self.write_space();
5987        }
5988
5989        // For Hive/Spark/Databricks (or no dialect specified), output native LATERAL VIEW syntax
5990        // For PostgreSQL and other specific dialects, convert to CROSS JOIN (LATERAL or UNNEST)
5991        let use_lateral_join = matches!(
5992            self.config.dialect,
5993            Some(DialectType::PostgreSQL)
5994                | Some(DialectType::DuckDB)
5995                | Some(DialectType::Snowflake)
5996                | Some(DialectType::TSQL)
5997                | Some(DialectType::Presto)
5998                | Some(DialectType::Trino)
5999                | Some(DialectType::Athena)
6000        );
6001
6002        // Check if target dialect should use UNNEST instead of EXPLODE
6003        let use_unnest = matches!(
6004            self.config.dialect,
6005            Some(DialectType::DuckDB)
6006                | Some(DialectType::Presto)
6007                | Some(DialectType::Trino)
6008                | Some(DialectType::Athena)
6009        );
6010
6011        // Check if we need POSEXPLODE -> UNNEST WITH ORDINALITY
6012        let (is_posexplode, is_inline, func_args) = match &lv.this {
6013            Expression::Explode(uf) => {
6014                // Expression::Explode is the dedicated EXPLODE expression type
6015                (false, false, vec![uf.this.clone()])
6016            }
6017            Expression::Unnest(uf) => {
6018                let mut args = vec![uf.this.clone()];
6019                args.extend(uf.expressions.clone());
6020                (false, false, args)
6021            }
6022            Expression::Function(func) => {
6023                if func.name.eq_ignore_ascii_case("POSEXPLODE")
6024                    || func.name.eq_ignore_ascii_case("POSEXPLODE_OUTER")
6025                {
6026                    (true, false, func.args.clone())
6027                } else if func.name.eq_ignore_ascii_case("INLINE") {
6028                    (false, true, func.args.clone())
6029                } else if func.name.eq_ignore_ascii_case("EXPLODE")
6030                    || func.name.eq_ignore_ascii_case("EXPLODE_OUTER")
6031                {
6032                    (false, false, func.args.clone())
6033                } else {
6034                    (false, false, vec![])
6035                }
6036            }
6037            _ => (false, false, vec![]),
6038        };
6039
6040        if use_lateral_join {
6041            // Convert to CROSS JOIN for PostgreSQL-like dialects
6042            if lv.outer {
6043                self.write_keyword("LEFT JOIN LATERAL");
6044            } else {
6045                self.write_keyword("CROSS JOIN");
6046            }
6047            self.write_space();
6048
6049            if use_unnest && !func_args.is_empty() {
6050                // Convert EXPLODE(y) -> UNNEST(y), POSEXPLODE(y) -> UNNEST(y)
6051                // For DuckDB, also convert ARRAY(y) -> [y]
6052                let unnest_args = if matches!(self.config.dialect, Some(DialectType::DuckDB)) {
6053                    // DuckDB: ARRAY(y) -> [y]
6054                    func_args
6055                        .iter()
6056                        .map(|a| {
6057                            if let Expression::Function(ref f) = a {
6058                                if f.name.eq_ignore_ascii_case("ARRAY") && f.args.len() == 1 {
6059                                    return Expression::ArrayFunc(Box::new(
6060                                        crate::expressions::ArrayConstructor {
6061                                            expressions: f.args.clone(),
6062                                            bracket_notation: true,
6063                                            use_list_keyword: false,
6064                                        },
6065                                    ));
6066                                }
6067                            }
6068                            a.clone()
6069                        })
6070                        .collect::<Vec<_>>()
6071                } else if matches!(
6072                    self.config.dialect,
6073                    Some(DialectType::Presto)
6074                        | Some(DialectType::Trino)
6075                        | Some(DialectType::Athena)
6076                ) {
6077                    // Presto: ARRAY(y) -> ARRAY[y]
6078                    func_args
6079                        .iter()
6080                        .map(|a| {
6081                            if let Expression::Function(ref f) = a {
6082                                if f.name.eq_ignore_ascii_case("ARRAY") && f.args.len() >= 1 {
6083                                    return Expression::ArrayFunc(Box::new(
6084                                        crate::expressions::ArrayConstructor {
6085                                            expressions: f.args.clone(),
6086                                            bracket_notation: true,
6087                                            use_list_keyword: false,
6088                                        },
6089                                    ));
6090                                }
6091                            }
6092                            a.clone()
6093                        })
6094                        .collect::<Vec<_>>()
6095                } else {
6096                    func_args
6097                };
6098
6099                // POSEXPLODE -> LATERAL (SELECT pos - 1 AS pos, col FROM UNNEST(y) WITH ORDINALITY AS t(col, pos))
6100                if is_posexplode {
6101                    self.write_keyword("LATERAL");
6102                    self.write(" (");
6103                    self.write_keyword("SELECT");
6104                    self.write_space();
6105
6106                    // Build the outer SELECT list: pos - 1 AS pos, then data columns
6107                    // column_aliases[0] is the position column, rest are data columns
6108                    let pos_alias = if !lv.column_aliases.is_empty() {
6109                        lv.column_aliases[0].clone()
6110                    } else {
6111                        Identifier::new("pos")
6112                    };
6113                    let data_aliases: Vec<Identifier> = if lv.column_aliases.len() > 1 {
6114                        lv.column_aliases[1..].to_vec()
6115                    } else {
6116                        vec![Identifier::new("col")]
6117                    };
6118
6119                    // pos - 1 AS pos
6120                    self.generate_identifier(&pos_alias)?;
6121                    self.write(" - 1");
6122                    self.write_space();
6123                    self.write_keyword("AS");
6124                    self.write_space();
6125                    self.generate_identifier(&pos_alias)?;
6126
6127                    // , col [, key, value ...]
6128                    for data_col in &data_aliases {
6129                        self.write(", ");
6130                        self.generate_identifier(data_col)?;
6131                    }
6132
6133                    self.write_space();
6134                    self.write_keyword("FROM");
6135                    self.write_space();
6136                    self.write_keyword("UNNEST");
6137                    self.write("(");
6138                    for (i, arg) in unnest_args.iter().enumerate() {
6139                        if i > 0 {
6140                            self.write(", ");
6141                        }
6142                        self.generate_expression(arg)?;
6143                    }
6144                    self.write(")");
6145                    self.write_space();
6146                    self.write_keyword("WITH ORDINALITY");
6147                    self.write_space();
6148                    self.write_keyword("AS");
6149                    self.write_space();
6150
6151                    // Inner alias: t(data_cols..., pos) - data columns first, pos last
6152                    let table_alias_ident = lv
6153                        .table_alias
6154                        .clone()
6155                        .unwrap_or_else(|| Identifier::new("t"));
6156                    self.generate_identifier(&table_alias_ident)?;
6157                    self.write("(");
6158                    for (i, data_col) in data_aliases.iter().enumerate() {
6159                        if i > 0 {
6160                            self.write(", ");
6161                        }
6162                        self.generate_identifier(data_col)?;
6163                    }
6164                    self.write(", ");
6165                    self.generate_identifier(&pos_alias)?;
6166                    self.write("))");
6167                } else if is_inline && matches!(self.config.dialect, Some(DialectType::DuckDB)) {
6168                    // INLINE -> LATERAL (SELECT UNNEST(arg, max_depth => 2)) AS alias
6169                    self.write_keyword("LATERAL");
6170                    self.write(" (");
6171                    self.write_keyword("SELECT");
6172                    self.write_space();
6173                    self.write_keyword("UNNEST");
6174                    self.write("(");
6175                    for (i, arg) in unnest_args.iter().enumerate() {
6176                        if i > 0 {
6177                            self.write(", ");
6178                        }
6179                        self.generate_expression(arg)?;
6180                    }
6181                    self.write(", ");
6182                    self.write_keyword("max_depth");
6183                    self.write(" => 2))");
6184
6185                    // Add table and column aliases
6186                    if let Some(alias) = &lv.table_alias {
6187                        self.write_space();
6188                        self.write_keyword("AS");
6189                        self.write_space();
6190                        self.generate_identifier(alias)?;
6191                        if !lv.column_aliases.is_empty() {
6192                            self.write("(");
6193                            for (i, col) in lv.column_aliases.iter().enumerate() {
6194                                if i > 0 {
6195                                    self.write(", ");
6196                                }
6197                                self.generate_identifier(col)?;
6198                            }
6199                            self.write(")");
6200                        }
6201                    } else if !lv.column_aliases.is_empty() {
6202                        // Auto-generate alias like _u_N
6203                        self.write_space();
6204                        self.write_keyword("AS");
6205                        self.write_space();
6206                        self.write(&format!("_u_{}", lv_index));
6207                        self.write("(");
6208                        for (i, col) in lv.column_aliases.iter().enumerate() {
6209                            if i > 0 {
6210                                self.write(", ");
6211                            }
6212                            self.generate_identifier(col)?;
6213                        }
6214                        self.write(")");
6215                    }
6216                } else {
6217                    self.write_keyword("UNNEST");
6218                    self.write("(");
6219                    for (i, arg) in unnest_args.iter().enumerate() {
6220                        if i > 0 {
6221                            self.write(", ");
6222                        }
6223                        self.generate_expression(arg)?;
6224                    }
6225                    self.write(")");
6226
6227                    // Add table and column aliases for non-POSEXPLODE
6228                    if let Some(alias) = &lv.table_alias {
6229                        self.write_space();
6230                        self.write_keyword("AS");
6231                        self.write_space();
6232                        self.generate_identifier(alias)?;
6233                        if !lv.column_aliases.is_empty() {
6234                            self.write("(");
6235                            for (i, col) in lv.column_aliases.iter().enumerate() {
6236                                if i > 0 {
6237                                    self.write(", ");
6238                                }
6239                                self.generate_identifier(col)?;
6240                            }
6241                            self.write(")");
6242                        }
6243                    } else if !lv.column_aliases.is_empty() {
6244                        self.write_space();
6245                        self.write_keyword("AS");
6246                        self.write(" t(");
6247                        for (i, col) in lv.column_aliases.iter().enumerate() {
6248                            if i > 0 {
6249                                self.write(", ");
6250                            }
6251                            self.generate_identifier(col)?;
6252                        }
6253                        self.write(")");
6254                    }
6255                }
6256            } else {
6257                // Not EXPLODE/POSEXPLODE or not using UNNEST, use LATERAL
6258                if !lv.outer {
6259                    self.write_keyword("LATERAL");
6260                    self.write_space();
6261                }
6262                self.generate_expression(&lv.this)?;
6263
6264                // Add table and column aliases
6265                if let Some(alias) = &lv.table_alias {
6266                    self.write_space();
6267                    self.write_keyword("AS");
6268                    self.write_space();
6269                    self.generate_identifier(alias)?;
6270                    if !lv.column_aliases.is_empty() {
6271                        self.write("(");
6272                        for (i, col) in lv.column_aliases.iter().enumerate() {
6273                            if i > 0 {
6274                                self.write(", ");
6275                            }
6276                            self.generate_identifier(col)?;
6277                        }
6278                        self.write(")");
6279                    }
6280                } else if !lv.column_aliases.is_empty() {
6281                    self.write_space();
6282                    self.write_keyword("AS");
6283                    self.write(" t(");
6284                    for (i, col) in lv.column_aliases.iter().enumerate() {
6285                        if i > 0 {
6286                            self.write(", ");
6287                        }
6288                        self.generate_identifier(col)?;
6289                    }
6290                    self.write(")");
6291                }
6292            }
6293
6294            // For LEFT JOIN LATERAL, need ON TRUE
6295            if lv.outer {
6296                self.write_space();
6297                self.write_keyword("ON TRUE");
6298            }
6299        } else {
6300            // Output native LATERAL VIEW syntax (Hive/Spark/Databricks or default)
6301            self.write_keyword("LATERAL VIEW");
6302            if lv.outer {
6303                self.write_space();
6304                self.write_keyword("OUTER");
6305            }
6306            if self.config.pretty {
6307                self.write_newline();
6308                self.write_indent();
6309            } else {
6310                self.write_space();
6311            }
6312            self.generate_expression(&lv.this)?;
6313
6314            // Table alias
6315            if let Some(alias) = &lv.table_alias {
6316                self.write_space();
6317                self.generate_identifier(alias)?;
6318            }
6319
6320            // Column aliases
6321            if !lv.column_aliases.is_empty() {
6322                self.write_space();
6323                self.write_keyword("AS");
6324                self.write_space();
6325                for (i, col) in lv.column_aliases.iter().enumerate() {
6326                    if i > 0 {
6327                        self.write(", ");
6328                    }
6329                    self.generate_identifier(col)?;
6330                }
6331            }
6332        }
6333
6334        Ok(())
6335    }
6336
6337    fn generate_union(&mut self, outermost: &Union) -> Result<()> {
6338        // Collect the left-recursive chain of Union nodes iteratively.
6339        // This avoids stack overflow for deeply nested chains like
6340        // SELECT 1 UNION ALL SELECT 2 UNION ALL ... UNION ALL SELECT N
6341        // where the parser builds: Union(Union(Union(A, B), C), D)
6342        let mut chain: Vec<&Union> = vec![outermost];
6343        let mut leftmost: &Expression = &outermost.left;
6344        while let Expression::Union(inner) = leftmost {
6345            chain.push(inner);
6346            leftmost = &inner.left;
6347        }
6348        // chain[0] = outermost, chain[last] = innermost
6349        // leftmost = innermost.left (a non-Union expression, typically Select)
6350
6351        // WITH clause (only on outermost)
6352        if let Some(with) = &outermost.with {
6353            self.generate_with(with)?;
6354            self.write_space();
6355        }
6356
6357        // Generate the base (leftmost) expression
6358        self.generate_expression(leftmost)?;
6359
6360        // Generate each union step from innermost to outermost
6361        for union in chain.iter().rev() {
6362            self.generate_union_step(union)?;
6363        }
6364        Ok(())
6365    }
6366
6367    /// Generate a single UNION step: keyword, right expression, and trailing modifiers.
6368    fn generate_union_step(&mut self, union: &Union) -> Result<()> {
6369        if self.config.pretty {
6370            self.write_newline();
6371            self.write_indent();
6372        } else {
6373            self.write_space();
6374        }
6375
6376        // BigQuery set operation modifiers: [side] [kind] UNION
6377        if let Some(side) = &union.side {
6378            self.write_keyword(side);
6379            self.write_space();
6380        }
6381        if let Some(kind) = &union.kind {
6382            self.write_keyword(kind);
6383            self.write_space();
6384        }
6385
6386        self.write_keyword("UNION");
6387        if union.all {
6388            self.write_space();
6389            self.write_keyword("ALL");
6390        } else if union.distinct {
6391            self.write_space();
6392            self.write_keyword("DISTINCT");
6393        }
6394
6395        // BigQuery: CORRESPONDING/STRICT CORRESPONDING -> BY NAME, BY (cols) -> ON (cols)
6396        // DuckDB: BY NAME
6397        if union.corresponding || union.by_name {
6398            self.write_space();
6399            self.write_keyword("BY NAME");
6400        }
6401        if !union.on_columns.is_empty() {
6402            self.write_space();
6403            self.write_keyword("ON");
6404            self.write(" (");
6405            for (i, col) in union.on_columns.iter().enumerate() {
6406                if i > 0 {
6407                    self.write(", ");
6408                }
6409                self.generate_expression(col)?;
6410            }
6411            self.write(")");
6412        }
6413
6414        if self.config.pretty {
6415            self.write_newline();
6416            self.write_indent();
6417        } else {
6418            self.write_space();
6419        }
6420        self.generate_expression(&union.right)?;
6421        // ORDER BY, LIMIT, OFFSET for the set operation
6422        if let Some(order_by) = &union.order_by {
6423            if self.config.pretty {
6424                self.write_newline();
6425            } else {
6426                self.write_space();
6427            }
6428            self.write_keyword("ORDER BY");
6429            self.write_space();
6430            for (i, ordered) in order_by.expressions.iter().enumerate() {
6431                if i > 0 {
6432                    self.write(", ");
6433                }
6434                self.generate_ordered(ordered)?;
6435            }
6436        }
6437        if let Some(limit) = &union.limit {
6438            if self.config.pretty {
6439                self.write_newline();
6440            } else {
6441                self.write_space();
6442            }
6443            self.write_keyword("LIMIT");
6444            self.write_space();
6445            self.generate_expression(limit)?;
6446        }
6447        if let Some(offset) = &union.offset {
6448            if self.config.pretty {
6449                self.write_newline();
6450            } else {
6451                self.write_space();
6452            }
6453            self.write_keyword("OFFSET");
6454            self.write_space();
6455            self.generate_expression(offset)?;
6456        }
6457        // DISTRIBUTE BY (Hive/Spark)
6458        if let Some(distribute_by) = &union.distribute_by {
6459            self.write_space();
6460            self.write_keyword("DISTRIBUTE BY");
6461            self.write_space();
6462            for (i, expr) in distribute_by.expressions.iter().enumerate() {
6463                if i > 0 {
6464                    self.write(", ");
6465                }
6466                self.generate_expression(expr)?;
6467            }
6468        }
6469        // SORT BY (Hive/Spark)
6470        if let Some(sort_by) = &union.sort_by {
6471            self.write_space();
6472            self.write_keyword("SORT BY");
6473            self.write_space();
6474            for (i, ord) in sort_by.expressions.iter().enumerate() {
6475                if i > 0 {
6476                    self.write(", ");
6477                }
6478                self.generate_ordered(ord)?;
6479            }
6480        }
6481        // CLUSTER BY (Hive/Spark)
6482        if let Some(cluster_by) = &union.cluster_by {
6483            self.write_space();
6484            self.write_keyword("CLUSTER BY");
6485            self.write_space();
6486            for (i, ord) in cluster_by.expressions.iter().enumerate() {
6487                if i > 0 {
6488                    self.write(", ");
6489                }
6490                self.generate_ordered(ord)?;
6491            }
6492        }
6493        Ok(())
6494    }
6495
6496    fn generate_intersect(&mut self, outermost: &Intersect) -> Result<()> {
6497        // Collect the left-recursive chain iteratively to avoid stack overflow
6498        let mut chain: Vec<&Intersect> = vec![outermost];
6499        let mut leftmost: &Expression = &outermost.left;
6500        while let Expression::Intersect(inner) = leftmost {
6501            chain.push(inner);
6502            leftmost = &inner.left;
6503        }
6504
6505        if let Some(with) = &outermost.with {
6506            self.generate_with(with)?;
6507            self.write_space();
6508        }
6509
6510        self.generate_expression(leftmost)?;
6511
6512        for intersect in chain.iter().rev() {
6513            self.generate_intersect_step(intersect)?;
6514        }
6515        Ok(())
6516    }
6517
6518    /// Generate a single INTERSECT step: keyword, right expression, and trailing modifiers.
6519    fn generate_intersect_step(&mut self, intersect: &Intersect) -> Result<()> {
6520        if self.config.pretty {
6521            self.write_newline();
6522            self.write_indent();
6523        } else {
6524            self.write_space();
6525        }
6526
6527        // BigQuery set operation modifiers: [side] [kind] INTERSECT
6528        if let Some(side) = &intersect.side {
6529            self.write_keyword(side);
6530            self.write_space();
6531        }
6532        if let Some(kind) = &intersect.kind {
6533            self.write_keyword(kind);
6534            self.write_space();
6535        }
6536
6537        self.write_keyword("INTERSECT");
6538        if intersect.all {
6539            self.write_space();
6540            self.write_keyword("ALL");
6541        } else if intersect.distinct {
6542            self.write_space();
6543            self.write_keyword("DISTINCT");
6544        }
6545
6546        // BigQuery: CORRESPONDING/STRICT CORRESPONDING -> BY NAME, BY (cols) -> ON (cols)
6547        // DuckDB: BY NAME
6548        if intersect.corresponding || intersect.by_name {
6549            self.write_space();
6550            self.write_keyword("BY NAME");
6551        }
6552        if !intersect.on_columns.is_empty() {
6553            self.write_space();
6554            self.write_keyword("ON");
6555            self.write(" (");
6556            for (i, col) in intersect.on_columns.iter().enumerate() {
6557                if i > 0 {
6558                    self.write(", ");
6559                }
6560                self.generate_expression(col)?;
6561            }
6562            self.write(")");
6563        }
6564
6565        if self.config.pretty {
6566            self.write_newline();
6567            self.write_indent();
6568        } else {
6569            self.write_space();
6570        }
6571        self.generate_expression(&intersect.right)?;
6572        // ORDER BY, LIMIT, OFFSET for the set operation
6573        if let Some(order_by) = &intersect.order_by {
6574            if self.config.pretty {
6575                self.write_newline();
6576            } else {
6577                self.write_space();
6578            }
6579            self.write_keyword("ORDER BY");
6580            self.write_space();
6581            for (i, ordered) in order_by.expressions.iter().enumerate() {
6582                if i > 0 {
6583                    self.write(", ");
6584                }
6585                self.generate_ordered(ordered)?;
6586            }
6587        }
6588        if let Some(limit) = &intersect.limit {
6589            if self.config.pretty {
6590                self.write_newline();
6591            } else {
6592                self.write_space();
6593            }
6594            self.write_keyword("LIMIT");
6595            self.write_space();
6596            self.generate_expression(limit)?;
6597        }
6598        if let Some(offset) = &intersect.offset {
6599            if self.config.pretty {
6600                self.write_newline();
6601            } else {
6602                self.write_space();
6603            }
6604            self.write_keyword("OFFSET");
6605            self.write_space();
6606            self.generate_expression(offset)?;
6607        }
6608        // DISTRIBUTE BY (Hive/Spark)
6609        if let Some(distribute_by) = &intersect.distribute_by {
6610            self.write_space();
6611            self.write_keyword("DISTRIBUTE BY");
6612            self.write_space();
6613            for (i, expr) in distribute_by.expressions.iter().enumerate() {
6614                if i > 0 {
6615                    self.write(", ");
6616                }
6617                self.generate_expression(expr)?;
6618            }
6619        }
6620        // SORT BY (Hive/Spark)
6621        if let Some(sort_by) = &intersect.sort_by {
6622            self.write_space();
6623            self.write_keyword("SORT BY");
6624            self.write_space();
6625            for (i, ord) in sort_by.expressions.iter().enumerate() {
6626                if i > 0 {
6627                    self.write(", ");
6628                }
6629                self.generate_ordered(ord)?;
6630            }
6631        }
6632        // CLUSTER BY (Hive/Spark)
6633        if let Some(cluster_by) = &intersect.cluster_by {
6634            self.write_space();
6635            self.write_keyword("CLUSTER BY");
6636            self.write_space();
6637            for (i, ord) in cluster_by.expressions.iter().enumerate() {
6638                if i > 0 {
6639                    self.write(", ");
6640                }
6641                self.generate_ordered(ord)?;
6642            }
6643        }
6644        Ok(())
6645    }
6646
6647    fn generate_except(&mut self, outermost: &Except) -> Result<()> {
6648        // Collect the left-recursive chain iteratively to avoid stack overflow
6649        let mut chain: Vec<&Except> = vec![outermost];
6650        let mut leftmost: &Expression = &outermost.left;
6651        while let Expression::Except(inner) = leftmost {
6652            chain.push(inner);
6653            leftmost = &inner.left;
6654        }
6655
6656        if let Some(with) = &outermost.with {
6657            self.generate_with(with)?;
6658            self.write_space();
6659        }
6660
6661        self.generate_expression(leftmost)?;
6662
6663        for except in chain.iter().rev() {
6664            self.generate_except_step(except)?;
6665        }
6666        Ok(())
6667    }
6668
6669    /// Generate a single EXCEPT step: keyword, right expression, and trailing modifiers.
6670    fn generate_except_step(&mut self, except: &Except) -> Result<()> {
6671        use crate::dialects::DialectType;
6672
6673        if self.config.pretty {
6674            self.write_newline();
6675            self.write_indent();
6676        } else {
6677            self.write_space();
6678        }
6679
6680        // BigQuery set operation modifiers: [side] [kind] EXCEPT
6681        if let Some(side) = &except.side {
6682            self.write_keyword(side);
6683            self.write_space();
6684        }
6685        if let Some(kind) = &except.kind {
6686            self.write_keyword(kind);
6687            self.write_space();
6688        }
6689
6690        // Oracle uses MINUS instead of EXCEPT (but not for EXCEPT ALL)
6691        match self.config.dialect {
6692            Some(DialectType::Oracle) if !except.all => {
6693                self.write_keyword("MINUS");
6694            }
6695            Some(DialectType::ClickHouse) => {
6696                // ClickHouse: drop ALL from EXCEPT ALL
6697                self.write_keyword("EXCEPT");
6698                if except.distinct {
6699                    self.write_space();
6700                    self.write_keyword("DISTINCT");
6701                }
6702            }
6703            Some(DialectType::BigQuery) => {
6704                // BigQuery: bare EXCEPT defaults to EXCEPT DISTINCT
6705                self.write_keyword("EXCEPT");
6706                if except.all {
6707                    self.write_space();
6708                    self.write_keyword("ALL");
6709                } else {
6710                    self.write_space();
6711                    self.write_keyword("DISTINCT");
6712                }
6713            }
6714            _ => {
6715                self.write_keyword("EXCEPT");
6716                if except.all {
6717                    self.write_space();
6718                    self.write_keyword("ALL");
6719                } else if except.distinct {
6720                    self.write_space();
6721                    self.write_keyword("DISTINCT");
6722                }
6723            }
6724        }
6725
6726        // BigQuery: CORRESPONDING/STRICT CORRESPONDING -> BY NAME, BY (cols) -> ON (cols)
6727        // DuckDB: BY NAME
6728        if except.corresponding || except.by_name {
6729            self.write_space();
6730            self.write_keyword("BY NAME");
6731        }
6732        if !except.on_columns.is_empty() {
6733            self.write_space();
6734            self.write_keyword("ON");
6735            self.write(" (");
6736            for (i, col) in except.on_columns.iter().enumerate() {
6737                if i > 0 {
6738                    self.write(", ");
6739                }
6740                self.generate_expression(col)?;
6741            }
6742            self.write(")");
6743        }
6744
6745        if self.config.pretty {
6746            self.write_newline();
6747            self.write_indent();
6748        } else {
6749            self.write_space();
6750        }
6751        self.generate_expression(&except.right)?;
6752        // ORDER BY, LIMIT, OFFSET for the set operation
6753        if let Some(order_by) = &except.order_by {
6754            if self.config.pretty {
6755                self.write_newline();
6756            } else {
6757                self.write_space();
6758            }
6759            self.write_keyword("ORDER BY");
6760            self.write_space();
6761            for (i, ordered) in order_by.expressions.iter().enumerate() {
6762                if i > 0 {
6763                    self.write(", ");
6764                }
6765                self.generate_ordered(ordered)?;
6766            }
6767        }
6768        if let Some(limit) = &except.limit {
6769            if self.config.pretty {
6770                self.write_newline();
6771            } else {
6772                self.write_space();
6773            }
6774            self.write_keyword("LIMIT");
6775            self.write_space();
6776            self.generate_expression(limit)?;
6777        }
6778        if let Some(offset) = &except.offset {
6779            if self.config.pretty {
6780                self.write_newline();
6781            } else {
6782                self.write_space();
6783            }
6784            self.write_keyword("OFFSET");
6785            self.write_space();
6786            self.generate_expression(offset)?;
6787        }
6788        // DISTRIBUTE BY (Hive/Spark)
6789        if let Some(distribute_by) = &except.distribute_by {
6790            self.write_space();
6791            self.write_keyword("DISTRIBUTE BY");
6792            self.write_space();
6793            for (i, expr) in distribute_by.expressions.iter().enumerate() {
6794                if i > 0 {
6795                    self.write(", ");
6796                }
6797                self.generate_expression(expr)?;
6798            }
6799        }
6800        // SORT BY (Hive/Spark)
6801        if let Some(sort_by) = &except.sort_by {
6802            self.write_space();
6803            self.write_keyword("SORT BY");
6804            self.write_space();
6805            for (i, ord) in sort_by.expressions.iter().enumerate() {
6806                if i > 0 {
6807                    self.write(", ");
6808                }
6809                self.generate_ordered(ord)?;
6810            }
6811        }
6812        // CLUSTER BY (Hive/Spark)
6813        if let Some(cluster_by) = &except.cluster_by {
6814            self.write_space();
6815            self.write_keyword("CLUSTER BY");
6816            self.write_space();
6817            for (i, ord) in cluster_by.expressions.iter().enumerate() {
6818                if i > 0 {
6819                    self.write(", ");
6820                }
6821                self.generate_ordered(ord)?;
6822            }
6823        }
6824        Ok(())
6825    }
6826
6827    fn generate_insert(&mut self, insert: &Insert) -> Result<()> {
6828        // For TSQL/Fabric/Spark/Hive/Databricks, CTEs must be prepended before INSERT
6829        let prepend_query_cte = if insert.with.is_none() {
6830            use crate::dialects::DialectType;
6831            let should_prepend = matches!(
6832                self.config.dialect,
6833                Some(DialectType::TSQL)
6834                    | Some(DialectType::Fabric)
6835                    | Some(DialectType::Spark)
6836                    | Some(DialectType::Databricks)
6837                    | Some(DialectType::Hive)
6838            );
6839            if should_prepend {
6840                if let Some(Expression::Select(select)) = &insert.query {
6841                    select.with.clone()
6842                } else {
6843                    None
6844                }
6845            } else {
6846                None
6847            }
6848        } else {
6849            None
6850        };
6851
6852        // Output WITH clause if on INSERT (e.g., WITH ... INSERT INTO ...)
6853        if let Some(with) = &insert.with {
6854            self.generate_with(with)?;
6855            self.write_space();
6856        } else if let Some(with) = &prepend_query_cte {
6857            self.generate_with(with)?;
6858            self.write_space();
6859        }
6860
6861        // Output leading comments before INSERT
6862        for comment in &insert.leading_comments {
6863            self.write_formatted_comment(comment);
6864            self.write(" ");
6865        }
6866
6867        // Handle directory insert (INSERT OVERWRITE DIRECTORY)
6868        if let Some(dir) = &insert.directory {
6869            self.write_keyword("INSERT OVERWRITE");
6870            if dir.local {
6871                self.write_space();
6872                self.write_keyword("LOCAL");
6873            }
6874            self.write_space();
6875            self.write_keyword("DIRECTORY");
6876            self.write_space();
6877            self.write("'");
6878            self.write(&dir.path);
6879            self.write("'");
6880
6881            // ROW FORMAT clause
6882            if let Some(row_format) = &dir.row_format {
6883                self.write_space();
6884                self.write_keyword("ROW FORMAT");
6885                if row_format.delimited {
6886                    self.write_space();
6887                    self.write_keyword("DELIMITED");
6888                }
6889                if let Some(val) = &row_format.fields_terminated_by {
6890                    self.write_space();
6891                    self.write_keyword("FIELDS TERMINATED BY");
6892                    self.write_space();
6893                    self.write("'");
6894                    self.write(val);
6895                    self.write("'");
6896                }
6897                if let Some(val) = &row_format.collection_items_terminated_by {
6898                    self.write_space();
6899                    self.write_keyword("COLLECTION ITEMS TERMINATED BY");
6900                    self.write_space();
6901                    self.write("'");
6902                    self.write(val);
6903                    self.write("'");
6904                }
6905                if let Some(val) = &row_format.map_keys_terminated_by {
6906                    self.write_space();
6907                    self.write_keyword("MAP KEYS TERMINATED BY");
6908                    self.write_space();
6909                    self.write("'");
6910                    self.write(val);
6911                    self.write("'");
6912                }
6913                if let Some(val) = &row_format.lines_terminated_by {
6914                    self.write_space();
6915                    self.write_keyword("LINES TERMINATED BY");
6916                    self.write_space();
6917                    self.write("'");
6918                    self.write(val);
6919                    self.write("'");
6920                }
6921                if let Some(val) = &row_format.null_defined_as {
6922                    self.write_space();
6923                    self.write_keyword("NULL DEFINED AS");
6924                    self.write_space();
6925                    self.write("'");
6926                    self.write(val);
6927                    self.write("'");
6928                }
6929            }
6930
6931            // STORED AS clause
6932            if let Some(format) = &dir.stored_as {
6933                self.write_space();
6934                self.write_keyword("STORED AS");
6935                self.write_space();
6936                self.write_keyword(format);
6937            }
6938
6939            // Query (SELECT statement)
6940            if let Some(query) = &insert.query {
6941                self.write_space();
6942                self.generate_expression(query)?;
6943            }
6944
6945            return Ok(());
6946        }
6947
6948        if insert.is_replace {
6949            // MySQL/SQLite REPLACE INTO statement
6950            self.write_keyword("REPLACE INTO");
6951        } else if insert.overwrite {
6952            // Use dialect-specific INSERT OVERWRITE format
6953            self.write_keyword("INSERT");
6954            // Output hint if present (Oracle: INSERT /*+ APPEND */ INTO)
6955            if let Some(ref hint) = insert.hint {
6956                self.generate_hint(hint)?;
6957            }
6958            self.write(&self.config.insert_overwrite.to_ascii_uppercase());
6959        } else if let Some(ref action) = insert.conflict_action {
6960            // SQLite conflict action: INSERT OR ABORT|FAIL|IGNORE|REPLACE|ROLLBACK INTO
6961            self.write_keyword("INSERT OR");
6962            self.write_space();
6963            self.write_keyword(action);
6964            self.write_space();
6965            self.write_keyword("INTO");
6966        } else if insert.ignore {
6967            // MySQL INSERT IGNORE syntax
6968            self.write_keyword("INSERT IGNORE INTO");
6969        } else {
6970            self.write_keyword("INSERT");
6971            // Output hint if present (Oracle: INSERT /*+ APPEND */ INTO)
6972            if let Some(ref hint) = insert.hint {
6973                self.generate_hint(hint)?;
6974            }
6975            self.write_space();
6976            self.write_keyword("INTO");
6977        }
6978        // ClickHouse: INSERT INTO FUNCTION func_name(args...)
6979        if let Some(ref func) = insert.function_target {
6980            self.write_space();
6981            self.write_keyword("FUNCTION");
6982            self.write_space();
6983            self.generate_expression(func)?;
6984        } else {
6985            self.write_space();
6986            self.generate_table(&insert.table)?;
6987        }
6988
6989        // Table alias (PostgreSQL: INSERT INTO table AS t(...), Oracle: INSERT INTO table t ...)
6990        if let Some(ref alias) = insert.alias {
6991            self.write_space();
6992            if insert.alias_explicit_as {
6993                self.write_keyword("AS");
6994                self.write_space();
6995            }
6996            self.generate_identifier(alias)?;
6997        }
6998
6999        // IF EXISTS clause (Hive)
7000        if insert.if_exists {
7001            self.write_space();
7002            self.write_keyword("IF EXISTS");
7003        }
7004
7005        // REPLACE WHERE clause (Databricks)
7006        if let Some(ref replace_where) = insert.replace_where {
7007            if self.config.pretty {
7008                self.write_newline();
7009                self.write_indent();
7010            } else {
7011                self.write_space();
7012            }
7013            self.write_keyword("REPLACE WHERE");
7014            self.write_space();
7015            self.generate_expression(replace_where)?;
7016        }
7017
7018        // Generate PARTITION clause if present
7019        if !insert.partition.is_empty() {
7020            self.write_space();
7021            self.write_keyword("PARTITION");
7022            self.write("(");
7023            for (i, (col, val)) in insert.partition.iter().enumerate() {
7024                if i > 0 {
7025                    self.write(", ");
7026                }
7027                self.generate_identifier(col)?;
7028                if let Some(v) = val {
7029                    self.write(" = ");
7030                    self.generate_expression(v)?;
7031                }
7032            }
7033            self.write(")");
7034        }
7035
7036        // ClickHouse: PARTITION BY expr
7037        if let Some(ref partition_by) = insert.partition_by {
7038            self.write_space();
7039            self.write_keyword("PARTITION BY");
7040            self.write_space();
7041            self.generate_expression(partition_by)?;
7042        }
7043
7044        // ClickHouse: SETTINGS key = val, ...
7045        if !insert.settings.is_empty() {
7046            self.write_space();
7047            self.write_keyword("SETTINGS");
7048            self.write_space();
7049            for (i, setting) in insert.settings.iter().enumerate() {
7050                if i > 0 {
7051                    self.write(", ");
7052                }
7053                self.generate_expression(setting)?;
7054            }
7055        }
7056
7057        if !insert.columns.is_empty() {
7058            if insert.alias.is_some() && insert.alias_explicit_as {
7059                // No space when explicit AS alias is present: INSERT INTO table AS t(a, b, c)
7060                self.write("(");
7061            } else {
7062                // Space for implicit alias or no alias: INSERT INTO dest d (i, value)
7063                self.write(" (");
7064            }
7065            for (i, col) in insert.columns.iter().enumerate() {
7066                if i > 0 {
7067                    self.write(", ");
7068                }
7069                self.generate_identifier(col)?;
7070            }
7071            self.write(")");
7072        }
7073
7074        // OUTPUT clause (TSQL)
7075        if let Some(ref output) = insert.output {
7076            self.generate_output_clause(output)?;
7077        }
7078
7079        // BY NAME modifier (DuckDB)
7080        if insert.by_name {
7081            self.write_space();
7082            self.write_keyword("BY NAME");
7083        }
7084
7085        if insert.default_values {
7086            self.write_space();
7087            self.write_keyword("DEFAULT VALUES");
7088        } else if let Some(query) = &insert.query {
7089            if self.config.pretty {
7090                self.write_newline();
7091            } else {
7092                self.write_space();
7093            }
7094            // If we prepended CTEs from nested SELECT (TSQL), strip the WITH from SELECT
7095            if prepend_query_cte.is_some() {
7096                if let Expression::Select(select) = query {
7097                    let mut select_no_with = select.clone();
7098                    select_no_with.with = None;
7099                    self.generate_select(&select_no_with)?;
7100                } else {
7101                    self.generate_expression(query)?;
7102                }
7103            } else {
7104                self.generate_expression(query)?;
7105            }
7106        } else if !insert.values.is_empty() {
7107            if self.config.pretty {
7108                // Pretty printing: VALUES on new line, each tuple indented
7109                self.write_newline();
7110                self.write_keyword("VALUES");
7111                self.write_newline();
7112                self.indent_level += 1;
7113                for (i, row) in insert.values.iter().enumerate() {
7114                    if i > 0 {
7115                        self.write(",");
7116                        self.write_newline();
7117                    }
7118                    self.write_indent();
7119                    self.write("(");
7120                    for (j, val) in row.iter().enumerate() {
7121                        if j > 0 {
7122                            self.write(", ");
7123                        }
7124                        self.generate_expression(val)?;
7125                    }
7126                    self.write(")");
7127                }
7128                self.indent_level -= 1;
7129            } else {
7130                // Non-pretty: single line
7131                self.write_space();
7132                self.write_keyword("VALUES");
7133                for (i, row) in insert.values.iter().enumerate() {
7134                    if i > 0 {
7135                        self.write(",");
7136                    }
7137                    self.write(" (");
7138                    for (j, val) in row.iter().enumerate() {
7139                        if j > 0 {
7140                            self.write(", ");
7141                        }
7142                        self.generate_expression(val)?;
7143                    }
7144                    self.write(")");
7145                }
7146            }
7147        }
7148
7149        // Source table (Hive/Spark): INSERT OVERWRITE TABLE target TABLE source
7150        if let Some(ref source) = insert.source {
7151            self.write_space();
7152            self.write_keyword("TABLE");
7153            self.write_space();
7154            self.generate_expression(source)?;
7155        }
7156
7157        // Source alias (MySQL: VALUES (...) AS new_data)
7158        if let Some(alias) = &insert.source_alias {
7159            self.write_space();
7160            self.write_keyword("AS");
7161            self.write_space();
7162            self.generate_identifier(alias)?;
7163        }
7164
7165        // ON CONFLICT clause (Materialize doesn't support ON CONFLICT)
7166        if let Some(on_conflict) = &insert.on_conflict {
7167            if !matches!(self.config.dialect, Some(DialectType::Materialize)) {
7168                self.write_space();
7169                self.generate_expression(on_conflict)?;
7170            }
7171        }
7172
7173        // RETURNING clause
7174        if !insert.returning.is_empty() {
7175            self.write_space();
7176            self.write_keyword("RETURNING");
7177            self.write_space();
7178            for (i, expr) in insert.returning.iter().enumerate() {
7179                if i > 0 {
7180                    self.write(", ");
7181                }
7182                self.generate_expression(expr)?;
7183            }
7184        }
7185
7186        Ok(())
7187    }
7188
7189    fn generate_update(&mut self, update: &Update) -> Result<()> {
7190        // Output leading comments before UPDATE
7191        for comment in &update.leading_comments {
7192            self.write_formatted_comment(comment);
7193            self.write(" ");
7194        }
7195
7196        // WITH clause (CTEs)
7197        if let Some(ref with) = update.with {
7198            self.generate_with(with)?;
7199            self.write_space();
7200        }
7201
7202        self.write_keyword("UPDATE");
7203        if let Some(hint) = &update.hint {
7204            self.generate_hint(hint)?;
7205        }
7206        self.write_space();
7207        self.generate_table(&update.table)?;
7208
7209        let mysql_like_update_from = matches!(
7210            self.config.dialect,
7211            Some(DialectType::MySQL) | Some(DialectType::SingleStore)
7212        ) && update.from_clause.is_some();
7213
7214        let mut set_pairs = update.set.clone();
7215
7216        // MySQL-style UPDATE doesn't support FROM after SET. Convert FROM tables to JOIN ... ON TRUE.
7217        let mut pre_set_joins = update.table_joins.clone();
7218        if mysql_like_update_from {
7219            let target_name = update
7220                .table
7221                .alias
7222                .as_ref()
7223                .map(|a| a.name.clone())
7224                .unwrap_or_else(|| update.table.name.name.clone());
7225
7226            for (col, _) in &mut set_pairs {
7227                if !col.name.contains('.') {
7228                    col.name = format!("{}.{}", target_name, col.name);
7229                }
7230            }
7231
7232            if let Some(from_clause) = &update.from_clause {
7233                for table_expr in &from_clause.expressions {
7234                    pre_set_joins.push(crate::expressions::Join {
7235                        this: table_expr.clone(),
7236                        on: Some(Expression::Boolean(crate::expressions::BooleanLiteral {
7237                            value: true,
7238                        })),
7239                        using: Vec::new(),
7240                        kind: crate::expressions::JoinKind::Inner,
7241                        use_inner_keyword: false,
7242                        use_outer_keyword: false,
7243                        deferred_condition: false,
7244                        join_hint: None,
7245                        match_condition: None,
7246                        pivots: Vec::new(),
7247                        comments: Vec::new(),
7248                        nesting_group: 0,
7249                        directed: false,
7250                    });
7251                }
7252            }
7253            for join in &update.from_joins {
7254                let mut join = join.clone();
7255                if join.on.is_none() && join.using.is_empty() {
7256                    join.on = Some(Expression::Boolean(crate::expressions::BooleanLiteral {
7257                        value: true,
7258                    }));
7259                }
7260                pre_set_joins.push(join);
7261            }
7262        }
7263
7264        // Extra tables for multi-table UPDATE (MySQL syntax)
7265        for extra_table in &update.extra_tables {
7266            self.write(", ");
7267            self.generate_table(extra_table)?;
7268        }
7269
7270        // JOINs attached to the table list (MySQL multi-table syntax)
7271        for join in &pre_set_joins {
7272            // generate_join already adds a leading space
7273            self.generate_join(join)?;
7274        }
7275
7276        // Teradata: FROM clause comes before SET
7277        let teradata_from_before_set = matches!(self.config.dialect, Some(DialectType::Teradata));
7278        if teradata_from_before_set && !mysql_like_update_from {
7279            if let Some(ref from_clause) = update.from_clause {
7280                self.write_space();
7281                self.write_keyword("FROM");
7282                self.write_space();
7283                for (i, table_expr) in from_clause.expressions.iter().enumerate() {
7284                    if i > 0 {
7285                        self.write(", ");
7286                    }
7287                    self.generate_expression(table_expr)?;
7288                }
7289            }
7290            for join in &update.from_joins {
7291                self.generate_join(join)?;
7292            }
7293        }
7294
7295        self.write_space();
7296        self.write_keyword("SET");
7297        self.write_space();
7298
7299        for (i, (col, val)) in set_pairs.iter().enumerate() {
7300            if i > 0 {
7301                self.write(", ");
7302            }
7303            self.generate_identifier(col)?;
7304            self.write(" = ");
7305            self.generate_expression(val)?;
7306        }
7307
7308        // OUTPUT clause (TSQL)
7309        if let Some(ref output) = update.output {
7310            self.generate_output_clause(output)?;
7311        }
7312
7313        // FROM clause (after SET for non-Teradata, non-MySQL dialects)
7314        if !mysql_like_update_from && !teradata_from_before_set {
7315            if let Some(ref from_clause) = update.from_clause {
7316                self.write_space();
7317                self.write_keyword("FROM");
7318                self.write_space();
7319                // Generate each table in the FROM clause
7320                for (i, table_expr) in from_clause.expressions.iter().enumerate() {
7321                    if i > 0 {
7322                        self.write(", ");
7323                    }
7324                    self.generate_expression(table_expr)?;
7325                }
7326            }
7327        }
7328
7329        if !mysql_like_update_from && !teradata_from_before_set {
7330            // JOINs after FROM clause (PostgreSQL, Snowflake, SQL Server syntax)
7331            for join in &update.from_joins {
7332                self.generate_join(join)?;
7333            }
7334        }
7335
7336        if let Some(where_clause) = &update.where_clause {
7337            self.write_space();
7338            self.write_keyword("WHERE");
7339            self.write_space();
7340            self.generate_expression(&where_clause.this)?;
7341        }
7342
7343        // RETURNING clause
7344        if !update.returning.is_empty() {
7345            self.write_space();
7346            self.write_keyword("RETURNING");
7347            self.write_space();
7348            for (i, expr) in update.returning.iter().enumerate() {
7349                if i > 0 {
7350                    self.write(", ");
7351                }
7352                self.generate_expression(expr)?;
7353            }
7354        }
7355
7356        // ORDER BY clause (MySQL)
7357        if let Some(ref order_by) = update.order_by {
7358            self.write_space();
7359            self.generate_order_by(order_by)?;
7360        }
7361
7362        // LIMIT clause (MySQL)
7363        if let Some(ref limit) = update.limit {
7364            self.write_space();
7365            self.write_keyword("LIMIT");
7366            self.write_space();
7367            self.generate_expression(limit)?;
7368        }
7369
7370        Ok(())
7371    }
7372
7373    fn generate_delete(&mut self, delete: &Delete) -> Result<()> {
7374        // Output WITH clause if present
7375        if let Some(with) = &delete.with {
7376            self.generate_with(with)?;
7377            self.write_space();
7378        }
7379
7380        // Output leading comments before DELETE
7381        for comment in &delete.leading_comments {
7382            self.write_formatted_comment(comment);
7383            self.write(" ");
7384        }
7385
7386        // MySQL multi-table DELETE or TSQL DELETE with OUTPUT before FROM
7387        if !delete.tables.is_empty() && !delete.tables_from_using {
7388            // DELETE t1[, t2] [OUTPUT ...] FROM ... syntax (tables before FROM)
7389            self.write_keyword("DELETE");
7390            if let Some(hint) = &delete.hint {
7391                self.generate_hint(hint)?;
7392            }
7393            self.write_space();
7394            for (i, tbl) in delete.tables.iter().enumerate() {
7395                if i > 0 {
7396                    self.write(", ");
7397                }
7398                self.generate_table(tbl)?;
7399            }
7400            // TSQL: OUTPUT clause between target table and FROM
7401            if let Some(ref output) = delete.output {
7402                self.generate_output_clause(output)?;
7403            }
7404            self.write_space();
7405            self.write_keyword("FROM");
7406            self.write_space();
7407            self.generate_table(&delete.table)?;
7408        } else if !delete.tables.is_empty() && delete.tables_from_using {
7409            // DELETE FROM t1, t2 USING ... syntax (tables after FROM)
7410            self.write_keyword("DELETE");
7411            if let Some(hint) = &delete.hint {
7412                self.generate_hint(hint)?;
7413            }
7414            self.write_space();
7415            self.write_keyword("FROM");
7416            self.write_space();
7417            for (i, tbl) in delete.tables.iter().enumerate() {
7418                if i > 0 {
7419                    self.write(", ");
7420                }
7421                self.generate_table(tbl)?;
7422            }
7423        } else if delete.no_from && matches!(self.config.dialect, Some(DialectType::BigQuery)) {
7424            // BigQuery-style DELETE without FROM keyword
7425            self.write_keyword("DELETE");
7426            if let Some(hint) = &delete.hint {
7427                self.generate_hint(hint)?;
7428            }
7429            self.write_space();
7430            self.generate_table(&delete.table)?;
7431        } else {
7432            self.write_keyword("DELETE");
7433            if let Some(hint) = &delete.hint {
7434                self.generate_hint(hint)?;
7435            }
7436            self.write_space();
7437            self.write_keyword("FROM");
7438            self.write_space();
7439            self.generate_table(&delete.table)?;
7440        }
7441
7442        // ClickHouse: ON CLUSTER clause
7443        if let Some(ref on_cluster) = delete.on_cluster {
7444            self.write_space();
7445            self.generate_on_cluster(on_cluster)?;
7446        }
7447
7448        // FORCE INDEX hint (MySQL)
7449        if let Some(ref idx) = delete.force_index {
7450            self.write_space();
7451            self.write_keyword("FORCE INDEX");
7452            self.write(" (");
7453            self.write(idx);
7454            self.write(")");
7455        }
7456
7457        // Optional alias
7458        if let Some(ref alias) = delete.alias {
7459            self.write_space();
7460            if delete.alias_explicit_as
7461                || matches!(self.config.dialect, Some(DialectType::BigQuery))
7462            {
7463                self.write_keyword("AS");
7464                self.write_space();
7465            }
7466            self.generate_identifier(alias)?;
7467        }
7468
7469        // JOINs (MySQL multi-table) - when NOT tables_from_using, JOINs come before USING
7470        if !delete.tables_from_using {
7471            for join in &delete.joins {
7472                self.generate_join(join)?;
7473            }
7474        }
7475
7476        // USING clause (PostgreSQL/DuckDB/MySQL)
7477        if !delete.using.is_empty() {
7478            self.write_space();
7479            self.write_keyword("USING");
7480            for (i, table) in delete.using.iter().enumerate() {
7481                if i > 0 {
7482                    self.write(",");
7483                }
7484                self.write_space();
7485                // Check if the table has subquery hints (DuckDB USING with subquery)
7486                if !table.hints.is_empty() && table.name.is_empty() {
7487                    // Subquery in USING: (VALUES ...) AS alias(cols)
7488                    self.generate_expression(&table.hints[0])?;
7489                    if let Some(ref alias) = table.alias {
7490                        self.write_space();
7491                        if table.alias_explicit_as {
7492                            self.write_keyword("AS");
7493                            self.write_space();
7494                        }
7495                        self.generate_identifier(alias)?;
7496                        if !table.column_aliases.is_empty() {
7497                            self.write("(");
7498                            for (j, col_alias) in table.column_aliases.iter().enumerate() {
7499                                if j > 0 {
7500                                    self.write(", ");
7501                                }
7502                                self.generate_identifier(col_alias)?;
7503                            }
7504                            self.write(")");
7505                        }
7506                    }
7507                } else {
7508                    self.generate_table(table)?;
7509                }
7510            }
7511        }
7512
7513        // JOINs (MySQL multi-table) - when tables_from_using, JOINs come after USING
7514        if delete.tables_from_using {
7515            for join in &delete.joins {
7516                self.generate_join(join)?;
7517            }
7518        }
7519
7520        // OUTPUT clause (TSQL) - only if not already emitted in the early position
7521        let output_already_emitted =
7522            !delete.tables.is_empty() && !delete.tables_from_using && delete.output.is_some();
7523        if !output_already_emitted {
7524            if let Some(ref output) = delete.output {
7525                self.generate_output_clause(output)?;
7526            }
7527        }
7528
7529        if let Some(where_clause) = &delete.where_clause {
7530            self.write_space();
7531            self.write_keyword("WHERE");
7532            self.write_space();
7533            self.generate_expression(&where_clause.this)?;
7534        }
7535
7536        // ORDER BY clause (MySQL)
7537        if let Some(ref order_by) = delete.order_by {
7538            self.write_space();
7539            self.generate_order_by(order_by)?;
7540        }
7541
7542        // LIMIT clause (MySQL)
7543        if let Some(ref limit) = delete.limit {
7544            self.write_space();
7545            self.write_keyword("LIMIT");
7546            self.write_space();
7547            self.generate_expression(limit)?;
7548        }
7549
7550        // RETURNING clause (PostgreSQL)
7551        if !delete.returning.is_empty() {
7552            self.write_space();
7553            self.write_keyword("RETURNING");
7554            self.write_space();
7555            for (i, expr) in delete.returning.iter().enumerate() {
7556                if i > 0 {
7557                    self.write(", ");
7558                }
7559                self.generate_expression(expr)?;
7560            }
7561        }
7562
7563        Ok(())
7564    }
7565
7566    // ==================== DDL Generation ====================
7567
7568    fn generate_create_table(&mut self, ct: &CreateTable) -> Result<()> {
7569        // Athena: Determine if this is Hive-style DDL or Trino-style DML
7570        // CREATE TABLE AS SELECT uses Trino (double quotes)
7571        // CREATE TABLE (without AS SELECT) and CREATE EXTERNAL TABLE use Hive (backticks)
7572        let saved_athena_hive_context = self.athena_hive_context;
7573        let is_clickhouse = matches!(self.config.dialect, Some(DialectType::ClickHouse));
7574        if matches!(
7575            self.config.dialect,
7576            Some(crate::dialects::DialectType::Athena)
7577        ) {
7578            // Use Hive context if:
7579            // 1. It's an EXTERNAL table, OR
7580            // 2. There's no AS SELECT clause
7581            let is_external = ct
7582                .table_modifier
7583                .as_ref()
7584                .map(|m| m.eq_ignore_ascii_case("EXTERNAL"))
7585                .unwrap_or(false);
7586            let has_as_select = ct.as_select.is_some();
7587            self.athena_hive_context = is_external || !has_as_select;
7588        }
7589
7590        // TSQL: Convert CREATE TABLE AS SELECT to SELECT * INTO table FROM (subquery) AS temp
7591        if matches!(
7592            self.config.dialect,
7593            Some(crate::dialects::DialectType::TSQL)
7594        ) {
7595            if let Some(ref query) = ct.as_select {
7596                // Output WITH CTE clause if present
7597                if let Some(with_cte) = &ct.with_cte {
7598                    self.generate_with(with_cte)?;
7599                    self.write_space();
7600                }
7601
7602                // Generate: SELECT * INTO [table] FROM (subquery) AS temp
7603                self.write_keyword("SELECT");
7604                self.write(" * ");
7605                self.write_keyword("INTO");
7606                self.write_space();
7607
7608                // If temporary, prefix with # for TSQL temp table
7609                if ct.temporary {
7610                    self.write("#");
7611                }
7612                self.generate_table(&ct.name)?;
7613
7614                self.write_space();
7615                self.write_keyword("FROM");
7616                self.write(" (");
7617                // For TSQL, add aliases to select columns to preserve column names
7618                let aliased_query = Self::add_column_aliases_to_query(query.clone());
7619                self.generate_expression(&aliased_query)?;
7620                self.write(") ");
7621                self.write_keyword("AS");
7622                self.write(" temp");
7623                return Ok(());
7624            }
7625        }
7626
7627        // Output WITH CTE clause if present
7628        if let Some(with_cte) = &ct.with_cte {
7629            self.generate_with(with_cte)?;
7630            self.write_space();
7631        }
7632
7633        // Output leading comments before CREATE
7634        for comment in &ct.leading_comments {
7635            self.write_formatted_comment(comment);
7636            self.write(" ");
7637        }
7638        self.write_keyword("CREATE");
7639
7640        if ct.or_replace {
7641            self.write_space();
7642            self.write_keyword("OR REPLACE");
7643        }
7644
7645        if ct.temporary {
7646            self.write_space();
7647            // Oracle uses GLOBAL TEMPORARY TABLE syntax
7648            if matches!(self.config.dialect, Some(DialectType::Oracle)) {
7649                self.write_keyword("GLOBAL TEMPORARY");
7650            } else {
7651                self.write_keyword("TEMPORARY");
7652            }
7653        }
7654
7655        // Table modifier: DYNAMIC, ICEBERG, EXTERNAL, HYBRID, TRANSIENT
7656        let is_dictionary = ct
7657            .table_modifier
7658            .as_ref()
7659            .map(|m| m.eq_ignore_ascii_case("DICTIONARY"))
7660            .unwrap_or(false);
7661        if let Some(ref modifier) = ct.table_modifier {
7662            // TRANSIENT is Snowflake-specific - skip for other dialects
7663            let skip_transient = modifier.eq_ignore_ascii_case("TRANSIENT")
7664                && !matches!(self.config.dialect, Some(DialectType::Snowflake) | None);
7665            // Teradata-specific modifiers: VOLATILE, SET, MULTISET, SET TABLE combinations
7666            let is_teradata_modifier = modifier.eq_ignore_ascii_case("VOLATILE")
7667                || modifier.eq_ignore_ascii_case("SET")
7668                || modifier.eq_ignore_ascii_case("MULTISET")
7669                || modifier.to_ascii_uppercase().contains("VOLATILE")
7670                || modifier.to_ascii_uppercase().starts_with("SET ")
7671                || modifier.to_ascii_uppercase().starts_with("MULTISET ");
7672            let skip_teradata =
7673                is_teradata_modifier && !matches!(self.config.dialect, Some(DialectType::Teradata));
7674            if !skip_transient && !skip_teradata {
7675                self.write_space();
7676                self.write_keyword(modifier);
7677            }
7678        }
7679
7680        if !is_dictionary {
7681            self.write_space();
7682            self.write_keyword("TABLE");
7683        }
7684
7685        if ct.if_not_exists {
7686            self.write_space();
7687            self.write_keyword("IF NOT EXISTS");
7688        }
7689
7690        self.write_space();
7691        self.generate_table(&ct.name)?;
7692
7693        // ClickHouse: UUID 'xxx' clause after table name
7694        if let Some(ref uuid) = ct.uuid {
7695            self.write_space();
7696            self.write_keyword("UUID");
7697            self.write(" '");
7698            self.write(uuid);
7699            self.write("'");
7700        }
7701
7702        // ClickHouse: ON CLUSTER clause
7703        if let Some(ref on_cluster) = ct.on_cluster {
7704            self.write_space();
7705            self.generate_on_cluster(on_cluster)?;
7706        }
7707
7708        // Teradata: options after table name before column list (comma-separated)
7709        if matches!(
7710            self.config.dialect,
7711            Some(crate::dialects::DialectType::Teradata)
7712        ) && !ct.teradata_post_name_options.is_empty()
7713        {
7714            for opt in &ct.teradata_post_name_options {
7715                self.write(", ");
7716                self.write(opt);
7717            }
7718        }
7719
7720        // Snowflake: COPY GRANTS clause
7721        if ct.copy_grants {
7722            self.write_space();
7723            self.write_keyword("COPY GRANTS");
7724        }
7725
7726        // Snowflake: USING TEMPLATE clause (before columns or AS SELECT)
7727        if let Some(ref using_template) = ct.using_template {
7728            self.write_space();
7729            self.write_keyword("USING TEMPLATE");
7730            self.write_space();
7731            self.generate_expression(using_template)?;
7732            return Ok(());
7733        }
7734
7735        // Handle [SHALLOW | DEEP] CLONE/COPY source_table [AT(...) | BEFORE(...)]
7736        if let Some(ref clone_source) = ct.clone_source {
7737            self.write_space();
7738            if ct.is_copy && self.config.supports_table_copy {
7739                // BigQuery uses COPY
7740                self.write_keyword("COPY");
7741            } else if ct.shallow_clone {
7742                self.write_keyword("SHALLOW CLONE");
7743            } else if ct.deep_clone {
7744                self.write_keyword("DEEP CLONE");
7745            } else {
7746                self.write_keyword("CLONE");
7747            }
7748            self.write_space();
7749            self.generate_table(clone_source)?;
7750            // Generate AT/BEFORE time travel clause (stored as Raw expression)
7751            if let Some(ref at_clause) = ct.clone_at_clause {
7752                self.write_space();
7753                self.generate_expression(at_clause)?;
7754            }
7755            return Ok(());
7756        }
7757
7758        // Handle PARTITION OF property
7759        // Output order: PARTITION OF <table> (<columns/constraints>) FOR VALUES ...
7760        // Columns/constraints must appear BETWEEN the table name and the partition bound spec
7761        if let Some(ref partition_of) = ct.partition_of {
7762            self.write_space();
7763
7764            // Extract the PartitionedOfProperty parts to generate them separately
7765            if let Expression::PartitionedOfProperty(ref pop) = partition_of {
7766                // Output: PARTITION OF <table>
7767                self.write_keyword("PARTITION OF");
7768                self.write_space();
7769                self.generate_expression(&pop.this)?;
7770
7771                // Output columns/constraints if present (e.g., (unitsales DEFAULT 0) or (CONSTRAINT ...))
7772                if !ct.columns.is_empty() || !ct.constraints.is_empty() {
7773                    self.write(" (");
7774                    let mut first = true;
7775                    for col in &ct.columns {
7776                        if !first {
7777                            self.write(", ");
7778                        }
7779                        first = false;
7780                        self.generate_column_def(col)?;
7781                    }
7782                    for constraint in &ct.constraints {
7783                        if !first {
7784                            self.write(", ");
7785                        }
7786                        first = false;
7787                        self.generate_table_constraint(constraint)?;
7788                    }
7789                    self.write(")");
7790                }
7791
7792                // Output partition bound spec: FOR VALUES ... or DEFAULT
7793                if let Expression::PartitionBoundSpec(_) = pop.expression.as_ref() {
7794                    self.write_space();
7795                    self.write_keyword("FOR VALUES");
7796                    self.write_space();
7797                    self.generate_expression(&pop.expression)?;
7798                } else {
7799                    self.write_space();
7800                    self.write_keyword("DEFAULT");
7801                }
7802            } else {
7803                // Fallback: generate the whole expression if it's not a PartitionedOfProperty
7804                self.generate_expression(partition_of)?;
7805
7806                // Output columns/constraints if present
7807                if !ct.columns.is_empty() || !ct.constraints.is_empty() {
7808                    self.write(" (");
7809                    let mut first = true;
7810                    for col in &ct.columns {
7811                        if !first {
7812                            self.write(", ");
7813                        }
7814                        first = false;
7815                        self.generate_column_def(col)?;
7816                    }
7817                    for constraint in &ct.constraints {
7818                        if !first {
7819                            self.write(", ");
7820                        }
7821                        first = false;
7822                        self.generate_table_constraint(constraint)?;
7823                    }
7824                    self.write(")");
7825                }
7826            }
7827
7828            // Output table properties (e.g., PARTITION BY RANGE(population))
7829            for prop in &ct.properties {
7830                self.write_space();
7831                self.generate_expression(prop)?;
7832            }
7833
7834            return Ok(());
7835        }
7836
7837        // SQLite: Inline single-column PRIMARY KEY constraints into column definition
7838        // This matches Python sqlglot's behavior for SQLite dialect
7839        self.sqlite_inline_pk_columns.clear();
7840        if matches!(
7841            self.config.dialect,
7842            Some(crate::dialects::DialectType::SQLite)
7843        ) {
7844            for constraint in &ct.constraints {
7845                if let TableConstraint::PrimaryKey { columns, name, .. } = constraint {
7846                    // Only inline if: single column, no constraint name, and column exists in table
7847                    if columns.len() == 1 && name.is_none() {
7848                        let pk_col_name = columns[0].name.to_ascii_lowercase();
7849                        // Check if this column exists in the table
7850                        if ct
7851                            .columns
7852                            .iter()
7853                            .any(|c| c.name.name.to_ascii_lowercase() == pk_col_name)
7854                        {
7855                            self.sqlite_inline_pk_columns.insert(pk_col_name);
7856                        }
7857                    }
7858                }
7859            }
7860        }
7861
7862        // Output columns if present (even for CTAS with columns)
7863        if !ct.columns.is_empty() {
7864            if self.config.pretty {
7865                // Pretty print: each column on new line
7866                self.write(" (");
7867                self.write_newline();
7868                self.indent_level += 1;
7869                for (i, col) in ct.columns.iter().enumerate() {
7870                    if i > 0 {
7871                        self.write(",");
7872                        self.write_newline();
7873                    }
7874                    self.write_indent();
7875                    self.generate_column_def(col)?;
7876                }
7877                // Table constraints (skip inlined PRIMARY KEY for SQLite)
7878                for constraint in &ct.constraints {
7879                    // Skip single-column PRIMARY KEY that was inlined for SQLite
7880                    if let TableConstraint::PrimaryKey { columns, name, .. } = constraint {
7881                        if columns.len() == 1
7882                            && name.is_none()
7883                            && self
7884                                .sqlite_inline_pk_columns
7885                                .contains(&columns[0].name.to_ascii_lowercase())
7886                        {
7887                            continue;
7888                        }
7889                    }
7890                    self.write(",");
7891                    self.write_newline();
7892                    self.write_indent();
7893                    self.generate_table_constraint(constraint)?;
7894                }
7895                self.indent_level -= 1;
7896                self.write_newline();
7897                self.write(")");
7898            } else {
7899                self.write(" (");
7900                for (i, col) in ct.columns.iter().enumerate() {
7901                    if i > 0 {
7902                        self.write(", ");
7903                    }
7904                    self.generate_column_def(col)?;
7905                }
7906                // Table constraints (skip inlined PRIMARY KEY for SQLite)
7907                let mut first_constraint = true;
7908                for constraint in &ct.constraints {
7909                    // Skip single-column PRIMARY KEY that was inlined for SQLite
7910                    if let TableConstraint::PrimaryKey { columns, name, .. } = constraint {
7911                        if columns.len() == 1
7912                            && name.is_none()
7913                            && self
7914                                .sqlite_inline_pk_columns
7915                                .contains(&columns[0].name.to_ascii_lowercase())
7916                        {
7917                            continue;
7918                        }
7919                    }
7920                    if first_constraint {
7921                        self.write(", ");
7922                        first_constraint = false;
7923                    } else {
7924                        self.write(", ");
7925                    }
7926                    self.generate_table_constraint(constraint)?;
7927                }
7928                self.write(")");
7929            }
7930        } else if !ct.constraints.is_empty() {
7931            // No columns but constraints exist (e.g., CREATE TABLE A LIKE B or CREATE TABLE A TAG (...))
7932            let has_like_only = ct
7933                .constraints
7934                .iter()
7935                .all(|c| matches!(c, TableConstraint::Like { .. }));
7936            let has_tags_only = ct
7937                .constraints
7938                .iter()
7939                .all(|c| matches!(c, TableConstraint::Tags(_)));
7940            // PostgreSQL: CREATE TABLE A (LIKE B INCLUDING ALL) (with parens)
7941            // Most dialects: CREATE TABLE A LIKE B (no parens)
7942            // Snowflake: CREATE TABLE A TAG (...) (no outer parens, but TAG has its own)
7943            let is_pg_like = matches!(
7944                self.config.dialect,
7945                Some(crate::dialects::DialectType::PostgreSQL)
7946                    | Some(crate::dialects::DialectType::CockroachDB)
7947                    | Some(crate::dialects::DialectType::Materialize)
7948                    | Some(crate::dialects::DialectType::RisingWave)
7949                    | Some(crate::dialects::DialectType::Redshift)
7950                    | Some(crate::dialects::DialectType::Presto)
7951                    | Some(crate::dialects::DialectType::Trino)
7952                    | Some(crate::dialects::DialectType::Athena)
7953            );
7954            let use_parens = if has_like_only {
7955                is_pg_like
7956            } else {
7957                !has_tags_only
7958            };
7959            if self.config.pretty && use_parens {
7960                self.write(" (");
7961                self.write_newline();
7962                self.indent_level += 1;
7963                for (i, constraint) in ct.constraints.iter().enumerate() {
7964                    if i > 0 {
7965                        self.write(",");
7966                        self.write_newline();
7967                    }
7968                    self.write_indent();
7969                    self.generate_table_constraint(constraint)?;
7970                }
7971                self.indent_level -= 1;
7972                self.write_newline();
7973                self.write(")");
7974            } else {
7975                if use_parens {
7976                    self.write(" (");
7977                } else {
7978                    self.write_space();
7979                }
7980                for (i, constraint) in ct.constraints.iter().enumerate() {
7981                    if i > 0 {
7982                        self.write(", ");
7983                    }
7984                    self.generate_table_constraint(constraint)?;
7985                }
7986                if use_parens {
7987                    self.write(")");
7988                }
7989            }
7990        }
7991
7992        // TSQL ON filegroup or ON filegroup (partition_column) clause
7993        if let Some(ref on_prop) = ct.on_property {
7994            self.write(" ");
7995            self.write_keyword("ON");
7996            self.write(" ");
7997            self.generate_expression(&on_prop.this)?;
7998        }
7999
8000        // BigQuery: WITH PARTITION COLUMNS (col_name col_type, ...)
8001        if !ct.with_partition_columns.is_empty() {
8002            if self.config.pretty {
8003                self.write_newline();
8004            } else {
8005                self.write_space();
8006            }
8007            self.write_keyword("WITH PARTITION COLUMNS");
8008            self.write(" (");
8009            if self.config.pretty {
8010                self.write_newline();
8011                self.indent_level += 1;
8012                for (i, col) in ct.with_partition_columns.iter().enumerate() {
8013                    if i > 0 {
8014                        self.write(",");
8015                        self.write_newline();
8016                    }
8017                    self.write_indent();
8018                    self.generate_column_def(col)?;
8019                }
8020                self.indent_level -= 1;
8021                self.write_newline();
8022            } else {
8023                for (i, col) in ct.with_partition_columns.iter().enumerate() {
8024                    if i > 0 {
8025                        self.write(", ");
8026                    }
8027                    self.generate_column_def(col)?;
8028                }
8029            }
8030            self.write(")");
8031        }
8032
8033        // BigQuery: WITH CONNECTION `project.region.connection`
8034        if let Some(ref conn) = ct.with_connection {
8035            if self.config.pretty {
8036                self.write_newline();
8037            } else {
8038                self.write_space();
8039            }
8040            self.write_keyword("WITH CONNECTION");
8041            self.write_space();
8042            self.generate_table(conn)?;
8043        }
8044
8045        // Output SchemaCommentProperty BEFORE WITH properties (Presto/Hive/Spark style)
8046        // For ClickHouse, SchemaCommentProperty goes after AS SELECT, handled later
8047        if !is_clickhouse {
8048            for prop in &ct.properties {
8049                if let Expression::SchemaCommentProperty(_) = prop {
8050                    if self.config.pretty {
8051                        self.write_newline();
8052                    } else {
8053                        self.write_space();
8054                    }
8055                    self.generate_expression(prop)?;
8056                }
8057            }
8058        }
8059
8060        // WITH properties (output after columns if columns exist, otherwise before AS)
8061        if !ct.with_properties.is_empty() {
8062            // Snowflake ICEBERG/DYNAMIC TABLE: output properties inline (space-separated, no WITH wrapper)
8063            let is_snowflake_special_table = matches!(
8064                self.config.dialect,
8065                Some(crate::dialects::DialectType::Snowflake)
8066            ) && (ct.table_modifier.as_deref() == Some("ICEBERG")
8067                || ct.table_modifier.as_deref() == Some("DYNAMIC"));
8068            if is_snowflake_special_table {
8069                for (key, value) in &ct.with_properties {
8070                    self.write_space();
8071                    self.write(key);
8072                    self.write("=");
8073                    self.write(value);
8074                }
8075            } else if self.config.pretty {
8076                self.write_newline();
8077                self.write_keyword("WITH");
8078                self.write(" (");
8079                self.write_newline();
8080                self.indent_level += 1;
8081                for (i, (key, value)) in ct.with_properties.iter().enumerate() {
8082                    if i > 0 {
8083                        self.write(",");
8084                        self.write_newline();
8085                    }
8086                    self.write_indent();
8087                    self.write(key);
8088                    self.write("=");
8089                    self.write(value);
8090                }
8091                self.indent_level -= 1;
8092                self.write_newline();
8093                self.write(")");
8094            } else {
8095                self.write_space();
8096                self.write_keyword("WITH");
8097                self.write(" (");
8098                for (i, (key, value)) in ct.with_properties.iter().enumerate() {
8099                    if i > 0 {
8100                        self.write(", ");
8101                    }
8102                    self.write(key);
8103                    self.write("=");
8104                    self.write(value);
8105                }
8106                self.write(")");
8107            }
8108        }
8109
8110        let (pre_as_properties, post_as_properties): (Vec<&Expression>, Vec<&Expression>) =
8111            if is_clickhouse && ct.as_select.is_some() {
8112                let mut pre = Vec::new();
8113                let mut post = Vec::new();
8114                for prop in &ct.properties {
8115                    if matches!(prop, Expression::SchemaCommentProperty(_)) {
8116                        post.push(prop);
8117                    } else {
8118                        pre.push(prop);
8119                    }
8120                }
8121                (pre, post)
8122            } else {
8123                (ct.properties.iter().collect(), Vec::new())
8124            };
8125
8126        // Table properties like DEFAULT COLLATE (BigQuery), OPTIONS (...), TBLPROPERTIES (...), or PROPERTIES (...)
8127        for prop in pre_as_properties {
8128            // SchemaCommentProperty was already output before WITH properties (except for ClickHouse)
8129            if !is_clickhouse && matches!(prop, Expression::SchemaCommentProperty(_)) {
8130                continue;
8131            }
8132            if self.config.pretty {
8133                self.write_newline();
8134            } else {
8135                self.write_space();
8136            }
8137            // BigQuery: Properties containing OPTIONS should be wrapped with OPTIONS (...)
8138            // Hive: Properties should be wrapped with TBLPROPERTIES (...)
8139            // Doris/StarRocks: Properties should be wrapped with PROPERTIES (...)
8140            if let Expression::Properties(props) = prop {
8141                let is_hive_dialect = matches!(
8142                    self.config.dialect,
8143                    Some(crate::dialects::DialectType::Hive)
8144                        | Some(crate::dialects::DialectType::Spark)
8145                        | Some(crate::dialects::DialectType::Databricks)
8146                        | Some(crate::dialects::DialectType::Athena)
8147                );
8148                let is_doris_starrocks = matches!(
8149                    self.config.dialect,
8150                    Some(crate::dialects::DialectType::Doris)
8151                        | Some(crate::dialects::DialectType::StarRocks)
8152                );
8153                if is_hive_dialect {
8154                    self.generate_tblproperties_clause(&props.expressions)?;
8155                } else if is_doris_starrocks {
8156                    self.generate_properties_clause(&props.expressions)?;
8157                } else {
8158                    self.generate_options_clause(&props.expressions)?;
8159                }
8160            } else {
8161                self.generate_expression(prop)?;
8162            }
8163        }
8164
8165        // Post-table properties like TSQL WITH(SYSTEM_VERSIONING=ON(...)) or Doris PROPERTIES
8166        for prop in &ct.post_table_properties {
8167            if let Expression::WithSystemVersioningProperty(ref svp) = prop {
8168                self.write(" WITH(");
8169                self.generate_system_versioning_content(svp)?;
8170                self.write(")");
8171            } else if let Expression::Properties(props) = prop {
8172                // Doris/StarRocks: PROPERTIES ('key'='value', ...) in post_table_properties
8173                let is_doris_starrocks = matches!(
8174                    self.config.dialect,
8175                    Some(crate::dialects::DialectType::Doris)
8176                        | Some(crate::dialects::DialectType::StarRocks)
8177                );
8178                self.write_space();
8179                if is_doris_starrocks {
8180                    self.generate_properties_clause(&props.expressions)?;
8181                } else {
8182                    self.generate_options_clause(&props.expressions)?;
8183                }
8184            } else {
8185                self.write_space();
8186                self.generate_expression(prop)?;
8187            }
8188        }
8189
8190        // StarRocks ROLLUP property: ROLLUP (r1(col1, col2), r2(col1))
8191        // Only output for StarRocks target
8192        if let Some(ref rollup) = ct.rollup {
8193            if matches!(self.config.dialect, Some(DialectType::StarRocks)) {
8194                self.write_space();
8195                self.generate_rollup_property(rollup)?;
8196            }
8197        }
8198
8199        // MySQL table options (ENGINE=val, AUTO_INCREMENT=val, etc.)
8200        // Only output for MySQL-compatible dialects; strip for others during transpilation
8201        // COMMENT is also used by Hive/Spark so we selectively preserve it
8202        let is_mysql_compatible = matches!(
8203            self.config.dialect,
8204            Some(DialectType::MySQL)
8205                | Some(DialectType::SingleStore)
8206                | Some(DialectType::Doris)
8207                | Some(DialectType::StarRocks)
8208                | None
8209        );
8210        let is_hive_compatible = matches!(
8211            self.config.dialect,
8212            Some(DialectType::Hive)
8213                | Some(DialectType::Spark)
8214                | Some(DialectType::Databricks)
8215                | Some(DialectType::Athena)
8216        );
8217        let mysql_pretty_options =
8218            self.config.pretty && matches!(self.config.dialect, Some(DialectType::MySQL));
8219        for (key, value) in &ct.mysql_table_options {
8220            // Skip non-MySQL-specific options for non-MySQL targets
8221            let should_output = if is_mysql_compatible {
8222                true
8223            } else if is_hive_compatible && key == "COMMENT" {
8224                true // COMMENT is valid in Hive/Spark table definitions
8225            } else {
8226                false
8227            };
8228            if should_output {
8229                if mysql_pretty_options {
8230                    self.write_newline();
8231                    self.write_indent();
8232                } else {
8233                    self.write_space();
8234                }
8235                self.write_keyword(key);
8236                // StarRocks/Doris: COMMENT 'value' (no =), others: COMMENT='value'
8237                if key == "COMMENT" && !self.config.schema_comment_with_eq {
8238                    self.write_space();
8239                } else {
8240                    self.write("=");
8241                }
8242                self.write(value);
8243            }
8244        }
8245
8246        // Spark/Databricks: USING PARQUET for temporary tables that don't already have a storage format
8247        if ct.temporary
8248            && matches!(
8249                self.config.dialect,
8250                Some(DialectType::Spark) | Some(DialectType::Databricks)
8251            )
8252            && ct.as_select.is_none()
8253        {
8254            self.write_space();
8255            self.write_keyword("USING PARQUET");
8256        }
8257
8258        // PostgreSQL INHERITS clause
8259        if !ct.inherits.is_empty() {
8260            self.write_space();
8261            self.write_keyword("INHERITS");
8262            self.write(" (");
8263            for (i, parent) in ct.inherits.iter().enumerate() {
8264                if i > 0 {
8265                    self.write(", ");
8266                }
8267                self.generate_table(parent)?;
8268            }
8269            self.write(")");
8270        }
8271
8272        // CREATE TABLE AS SELECT
8273        if let Some(ref query) = ct.as_select {
8274            self.write_space();
8275            self.write_keyword("AS");
8276            self.write_space();
8277            if ct.as_select_parenthesized {
8278                self.write("(");
8279            }
8280            self.generate_expression(query)?;
8281            if ct.as_select_parenthesized {
8282                self.write(")");
8283            }
8284
8285            // Teradata: WITH DATA / WITH NO DATA
8286            if let Some(with_data) = ct.with_data {
8287                self.write_space();
8288                self.write_keyword("WITH");
8289                if !with_data {
8290                    self.write_space();
8291                    self.write_keyword("NO");
8292                }
8293                self.write_space();
8294                self.write_keyword("DATA");
8295            }
8296
8297            // Teradata: AND STATISTICS / AND NO STATISTICS
8298            if let Some(with_statistics) = ct.with_statistics {
8299                self.write_space();
8300                self.write_keyword("AND");
8301                if !with_statistics {
8302                    self.write_space();
8303                    self.write_keyword("NO");
8304                }
8305                self.write_space();
8306                self.write_keyword("STATISTICS");
8307            }
8308
8309            // Teradata: Index specifications
8310            for index in &ct.teradata_indexes {
8311                self.write_space();
8312                match index.kind {
8313                    TeradataIndexKind::NoPrimary => {
8314                        self.write_keyword("NO PRIMARY INDEX");
8315                    }
8316                    TeradataIndexKind::Primary => {
8317                        self.write_keyword("PRIMARY INDEX");
8318                    }
8319                    TeradataIndexKind::PrimaryAmp => {
8320                        self.write_keyword("PRIMARY AMP INDEX");
8321                    }
8322                    TeradataIndexKind::Unique => {
8323                        self.write_keyword("UNIQUE INDEX");
8324                    }
8325                    TeradataIndexKind::UniquePrimary => {
8326                        self.write_keyword("UNIQUE PRIMARY INDEX");
8327                    }
8328                    TeradataIndexKind::Secondary => {
8329                        self.write_keyword("INDEX");
8330                    }
8331                }
8332                // Output index name if present
8333                if let Some(ref name) = index.name {
8334                    self.write_space();
8335                    self.write(name);
8336                }
8337                // Output columns if present
8338                if !index.columns.is_empty() {
8339                    self.write(" (");
8340                    for (i, col) in index.columns.iter().enumerate() {
8341                        if i > 0 {
8342                            self.write(", ");
8343                        }
8344                        self.write(col);
8345                    }
8346                    self.write(")");
8347                }
8348            }
8349
8350            // Teradata: ON COMMIT behavior for volatile tables
8351            if let Some(ref on_commit) = ct.on_commit {
8352                self.write_space();
8353                self.write_keyword("ON COMMIT");
8354                self.write_space();
8355                match on_commit {
8356                    OnCommit::PreserveRows => self.write_keyword("PRESERVE ROWS"),
8357                    OnCommit::DeleteRows => self.write_keyword("DELETE ROWS"),
8358                }
8359            }
8360
8361            if !post_as_properties.is_empty() {
8362                for prop in post_as_properties {
8363                    self.write_space();
8364                    self.generate_expression(prop)?;
8365                }
8366            }
8367
8368            // Restore Athena Hive context before early return
8369            self.athena_hive_context = saved_athena_hive_context;
8370            return Ok(());
8371        }
8372
8373        // ON COMMIT behavior (for non-CTAS tables)
8374        if let Some(ref on_commit) = ct.on_commit {
8375            self.write_space();
8376            self.write_keyword("ON COMMIT");
8377            self.write_space();
8378            match on_commit {
8379                OnCommit::PreserveRows => self.write_keyword("PRESERVE ROWS"),
8380                OnCommit::DeleteRows => self.write_keyword("DELETE ROWS"),
8381            }
8382        }
8383
8384        // Restore Athena Hive context
8385        self.athena_hive_context = saved_athena_hive_context;
8386
8387        Ok(())
8388    }
8389
8390    /// Generate column definition as an expression (for ROWS FROM alias columns, XMLTABLE/JSON_TABLE)
8391    /// Outputs: "col_name" TYPE [PATH 'xpath'] (not the full CREATE TABLE column definition)
8392    fn generate_column_def_expr(&mut self, col: &ColumnDef) -> Result<()> {
8393        // Output column name
8394        self.generate_identifier(&col.name)?;
8395        // Output data type if known
8396        if !matches!(col.data_type, DataType::Unknown) {
8397            self.write_space();
8398            self.generate_data_type(&col.data_type)?;
8399        }
8400        // Output PATH constraint if present (for XMLTABLE/JSON_TABLE columns)
8401        for constraint in &col.constraints {
8402            if let ColumnConstraint::Path(path_expr) = constraint {
8403                self.write_space();
8404                self.write_keyword("PATH");
8405                self.write_space();
8406                self.generate_expression(path_expr)?;
8407            }
8408        }
8409        Ok(())
8410    }
8411
8412    fn generate_column_def(&mut self, col: &ColumnDef) -> Result<()> {
8413        // Check if this is a TSQL computed column (no data type)
8414        let has_computed_no_type = matches!(&col.data_type, DataType::Custom { name } if name.is_empty())
8415            && col
8416                .constraints
8417                .iter()
8418                .any(|c| matches!(c, ColumnConstraint::ComputedColumn(_)));
8419        // Some dialects (notably TSQL/Fabric) do not include an explicit type for computed columns.
8420        let omit_computed_type = !self.config.computed_column_with_type
8421            && col
8422                .constraints
8423                .iter()
8424                .any(|c| matches!(c, ColumnConstraint::ComputedColumn(_)));
8425
8426        // Check if this is a partition column spec (no data type, type is Unknown)
8427        // This is used in PostgreSQL PARTITION OF syntax where columns only have constraints
8428        let is_partition_column_spec = matches!(col.data_type, DataType::Unknown);
8429
8430        // Check if this is a DYNAMIC TABLE column (no data type, empty Custom name, no constraints)
8431        // Also check the no_type flag for SQLite columns without types
8432        let has_no_type = col.no_type
8433            || (matches!(&col.data_type, DataType::Custom { name } if name.is_empty())
8434                && col.constraints.is_empty());
8435
8436        self.generate_identifier(&col.name)?;
8437
8438        // Check for SERIAL/BIGSERIAL/SMALLSERIAL expansion for Materialize and PostgreSQL
8439        let serial_expansion = if matches!(
8440            self.config.dialect,
8441            Some(DialectType::Materialize) | Some(DialectType::PostgreSQL)
8442        ) {
8443            if let DataType::Custom { ref name } = col.data_type {
8444                if name.eq_ignore_ascii_case("SERIAL") {
8445                    Some("INT")
8446                } else if name.eq_ignore_ascii_case("BIGSERIAL") {
8447                    Some("BIGINT")
8448                } else if name.eq_ignore_ascii_case("SMALLSERIAL") {
8449                    Some("SMALLINT")
8450                } else {
8451                    None
8452                }
8453            } else {
8454                None
8455            }
8456        } else {
8457            None
8458        };
8459
8460        if !has_computed_no_type && !omit_computed_type && !is_partition_column_spec && !has_no_type
8461        {
8462            self.write_space();
8463            // ClickHouse CREATE TABLE column types: suppress automatic Nullable wrapping
8464            // since ClickHouse uses explicit Nullable() in its type system.
8465            let saved_nullable_depth = self.clickhouse_nullable_depth;
8466            if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
8467                self.clickhouse_nullable_depth = -1;
8468            }
8469            if let Some(int_type) = serial_expansion {
8470                // SERIAL -> INT (+ constraints added below)
8471                self.write_keyword(int_type);
8472            } else if col.unsigned && matches!(self.config.dialect, Some(DialectType::DuckDB)) {
8473                // For DuckDB: convert unsigned integer types to their unsigned equivalents
8474                let unsigned_type = match &col.data_type {
8475                    DataType::Int { .. } => Some("UINTEGER"),
8476                    DataType::BigInt { .. } => Some("UBIGINT"),
8477                    DataType::SmallInt { .. } => Some("USMALLINT"),
8478                    DataType::TinyInt { .. } => Some("UTINYINT"),
8479                    _ => None,
8480                };
8481                if let Some(utype) = unsigned_type {
8482                    self.write_keyword(utype);
8483                } else {
8484                    self.generate_data_type(&col.data_type)?;
8485                }
8486            } else {
8487                self.generate_data_type(&col.data_type)?;
8488            }
8489            self.clickhouse_nullable_depth = saved_nullable_depth;
8490        }
8491
8492        // MySQL type modifiers (must come right after data type)
8493        // Skip UNSIGNED for DuckDB (already mapped to unsigned type above)
8494        if col.unsigned && !matches!(self.config.dialect, Some(DialectType::DuckDB)) {
8495            self.write_space();
8496            self.write_keyword("UNSIGNED");
8497        }
8498        if col.zerofill {
8499            self.write_space();
8500            self.write_keyword("ZEROFILL");
8501        }
8502
8503        // Teradata column attributes (must come right after data type, in specific order)
8504        // ORDER: CHARACTER SET, UPPERCASE, CASESPECIFIC, FORMAT, TITLE, INLINE LENGTH, COMPRESS
8505
8506        if let Some(ref charset) = col.character_set {
8507            self.write_space();
8508            self.write_keyword("CHARACTER SET");
8509            self.write_space();
8510            self.write(charset);
8511        }
8512
8513        if col.uppercase {
8514            self.write_space();
8515            self.write_keyword("UPPERCASE");
8516        }
8517
8518        if let Some(casespecific) = col.casespecific {
8519            self.write_space();
8520            if casespecific {
8521                self.write_keyword("CASESPECIFIC");
8522            } else {
8523                self.write_keyword("NOT CASESPECIFIC");
8524            }
8525        }
8526
8527        if let Some(ref format) = col.format {
8528            self.write_space();
8529            self.write_keyword("FORMAT");
8530            self.write(" '");
8531            self.write(format);
8532            self.write("'");
8533        }
8534
8535        if let Some(ref title) = col.title {
8536            self.write_space();
8537            self.write_keyword("TITLE");
8538            self.write(" '");
8539            self.write(title);
8540            self.write("'");
8541        }
8542
8543        if let Some(length) = col.inline_length {
8544            self.write_space();
8545            self.write_keyword("INLINE LENGTH");
8546            self.write(" ");
8547            self.write(&length.to_string());
8548        }
8549
8550        if let Some(ref compress) = col.compress {
8551            self.write_space();
8552            self.write_keyword("COMPRESS");
8553            if !compress.is_empty() {
8554                // Single string literal: output without parentheses (Teradata syntax)
8555                if compress.len() == 1 {
8556                    if let Expression::Literal(lit) = &compress[0] {
8557                        if let Literal::String(_) = lit.as_ref() {
8558                            self.write_space();
8559                            self.generate_expression(&compress[0])?;
8560                        }
8561                    } else {
8562                        self.write(" (");
8563                        self.generate_expression(&compress[0])?;
8564                        self.write(")");
8565                    }
8566                } else {
8567                    self.write(" (");
8568                    for (i, val) in compress.iter().enumerate() {
8569                        if i > 0 {
8570                            self.write(", ");
8571                        }
8572                        self.generate_expression(val)?;
8573                    }
8574                    self.write(")");
8575                }
8576            }
8577        }
8578
8579        // Column constraints - output in original order if constraint_order is populated
8580        // Otherwise fall back to legacy fixed order for backward compatibility
8581        if !col.constraint_order.is_empty() {
8582            // Use constraint_order for original ordering
8583            // Track indices for constraints stored in the constraints Vec
8584            let mut references_idx = 0;
8585            let mut check_idx = 0;
8586            let mut generated_idx = 0;
8587            let mut collate_idx = 0;
8588            let mut comment_idx = 0;
8589            // The preprocessing in dialects/mod.rs now handles the correct ordering of
8590            // NOT NULL relative to IDENTITY for PostgreSQL, so no deferral needed here.
8591            let defer_not_null_after_identity = false;
8592            let mut pending_not_null_after_identity = false;
8593
8594            for constraint_type in &col.constraint_order {
8595                match constraint_type {
8596                    ConstraintType::PrimaryKey => {
8597                        // Materialize doesn't support PRIMARY KEY column constraints
8598                        if col.primary_key
8599                            && !matches!(self.config.dialect, Some(DialectType::Materialize))
8600                        {
8601                            if let Some(ref cname) = col.primary_key_constraint_name {
8602                                self.write_space();
8603                                self.write_keyword("CONSTRAINT");
8604                                self.write_space();
8605                                self.write(cname);
8606                            }
8607                            self.write_space();
8608                            self.write_keyword("PRIMARY KEY");
8609                            if let Some(ref order) = col.primary_key_order {
8610                                self.write_space();
8611                                match order {
8612                                    SortOrder::Asc => self.write_keyword("ASC"),
8613                                    SortOrder::Desc => self.write_keyword("DESC"),
8614                                }
8615                            }
8616                        }
8617                    }
8618                    ConstraintType::Unique => {
8619                        if col.unique {
8620                            if let Some(ref cname) = col.unique_constraint_name {
8621                                self.write_space();
8622                                self.write_keyword("CONSTRAINT");
8623                                self.write_space();
8624                                self.write(cname);
8625                            }
8626                            self.write_space();
8627                            self.write_keyword("UNIQUE");
8628                            // PostgreSQL 15+: NULLS NOT DISTINCT
8629                            if col.unique_nulls_not_distinct {
8630                                self.write(" NULLS NOT DISTINCT");
8631                            }
8632                        }
8633                    }
8634                    ConstraintType::NotNull => {
8635                        if col.nullable == Some(false) {
8636                            if defer_not_null_after_identity {
8637                                pending_not_null_after_identity = true;
8638                                continue;
8639                            }
8640                            if let Some(ref cname) = col.not_null_constraint_name {
8641                                self.write_space();
8642                                self.write_keyword("CONSTRAINT");
8643                                self.write_space();
8644                                self.write(cname);
8645                            }
8646                            self.write_space();
8647                            self.write_keyword("NOT NULL");
8648                        }
8649                    }
8650                    ConstraintType::Null => {
8651                        if col.nullable == Some(true) {
8652                            self.write_space();
8653                            self.write_keyword("NULL");
8654                        }
8655                    }
8656                    ConstraintType::Default => {
8657                        if let Some(ref default) = col.default {
8658                            self.write_space();
8659                            self.write_keyword("DEFAULT");
8660                            self.write_space();
8661                            self.generate_expression(default)?;
8662                        }
8663                    }
8664                    ConstraintType::AutoIncrement => {
8665                        if col.auto_increment {
8666                            // DuckDB doesn't support AUTO_INCREMENT - skip entirely
8667                            if matches!(
8668                                self.config.dialect,
8669                                Some(crate::dialects::DialectType::DuckDB)
8670                            ) {
8671                                // Skip - DuckDB uses sequences or rowid instead
8672                            } else if matches!(
8673                                self.config.dialect,
8674                                Some(crate::dialects::DialectType::Materialize)
8675                            ) {
8676                                // Materialize strips AUTO_INCREMENT but adds NOT NULL
8677                                if !matches!(col.nullable, Some(false)) {
8678                                    self.write_space();
8679                                    self.write_keyword("NOT NULL");
8680                                }
8681                            } else if matches!(
8682                                self.config.dialect,
8683                                Some(crate::dialects::DialectType::PostgreSQL)
8684                            ) {
8685                                // PostgreSQL: AUTO_INCREMENT -> GENERATED BY DEFAULT AS IDENTITY
8686                                self.write_space();
8687                                self.generate_auto_increment_keyword(col)?;
8688                            } else {
8689                                self.write_space();
8690                                self.generate_auto_increment_keyword(col)?;
8691                                if pending_not_null_after_identity {
8692                                    self.write_space();
8693                                    self.write_keyword("NOT NULL");
8694                                    pending_not_null_after_identity = false;
8695                                }
8696                            }
8697                        } // close else for DuckDB skip
8698                    }
8699                    ConstraintType::References => {
8700                        // Find next References constraint
8701                        while references_idx < col.constraints.len() {
8702                            if let ColumnConstraint::References(fk_ref) =
8703                                &col.constraints[references_idx]
8704                            {
8705                                // CONSTRAINT name if present
8706                                if let Some(ref name) = fk_ref.constraint_name {
8707                                    self.write_space();
8708                                    self.write_keyword("CONSTRAINT");
8709                                    self.write_space();
8710                                    self.write(name);
8711                                }
8712                                self.write_space();
8713                                if fk_ref.has_foreign_key_keywords {
8714                                    self.write_keyword("FOREIGN KEY");
8715                                    self.write_space();
8716                                }
8717                                self.write_keyword("REFERENCES");
8718                                self.write_space();
8719                                self.generate_table(&fk_ref.table)?;
8720                                if !fk_ref.columns.is_empty() {
8721                                    self.write(" (");
8722                                    for (i, c) in fk_ref.columns.iter().enumerate() {
8723                                        if i > 0 {
8724                                            self.write(", ");
8725                                        }
8726                                        self.generate_identifier(c)?;
8727                                    }
8728                                    self.write(")");
8729                                }
8730                                self.generate_referential_actions(fk_ref)?;
8731                                references_idx += 1;
8732                                break;
8733                            }
8734                            references_idx += 1;
8735                        }
8736                    }
8737                    ConstraintType::Check => {
8738                        // Find next Check constraint
8739                        while check_idx < col.constraints.len() {
8740                            if let ColumnConstraint::Check(expr) = &col.constraints[check_idx] {
8741                                // Output CONSTRAINT name if present (only for first CHECK)
8742                                if check_idx == 0 {
8743                                    if let Some(ref cname) = col.check_constraint_name {
8744                                        self.write_space();
8745                                        self.write_keyword("CONSTRAINT");
8746                                        self.write_space();
8747                                        self.write(cname);
8748                                    }
8749                                }
8750                                self.write_space();
8751                                self.write_keyword("CHECK");
8752                                self.write(" (");
8753                                self.generate_expression(expr)?;
8754                                self.write(")");
8755                                check_idx += 1;
8756                                break;
8757                            }
8758                            check_idx += 1;
8759                        }
8760                    }
8761                    ConstraintType::GeneratedAsIdentity => {
8762                        // Find next GeneratedAsIdentity constraint
8763                        while generated_idx < col.constraints.len() {
8764                            if let ColumnConstraint::GeneratedAsIdentity(gen) =
8765                                &col.constraints[generated_idx]
8766                            {
8767                                self.write_space();
8768                                // Redshift uses IDENTITY(start, increment) syntax
8769                                if matches!(
8770                                    self.config.dialect,
8771                                    Some(crate::dialects::DialectType::Redshift)
8772                                ) {
8773                                    self.write_keyword("IDENTITY");
8774                                    self.write("(");
8775                                    if let Some(ref start) = gen.start {
8776                                        self.generate_expression(start)?;
8777                                    } else {
8778                                        self.write("0");
8779                                    }
8780                                    self.write(", ");
8781                                    if let Some(ref incr) = gen.increment {
8782                                        self.generate_expression(incr)?;
8783                                    } else {
8784                                        self.write("1");
8785                                    }
8786                                    self.write(")");
8787                                } else {
8788                                    self.write_keyword("GENERATED");
8789                                    if gen.always {
8790                                        self.write_space();
8791                                        self.write_keyword("ALWAYS");
8792                                    } else {
8793                                        self.write_space();
8794                                        self.write_keyword("BY DEFAULT");
8795                                        if gen.on_null {
8796                                            self.write_space();
8797                                            self.write_keyword("ON NULL");
8798                                        }
8799                                    }
8800                                    self.write_space();
8801                                    self.write_keyword("AS IDENTITY");
8802
8803                                    let has_options = gen.start.is_some()
8804                                        || gen.increment.is_some()
8805                                        || gen.minvalue.is_some()
8806                                        || gen.maxvalue.is_some()
8807                                        || gen.cycle.is_some();
8808                                    if has_options {
8809                                        self.write(" (");
8810                                        let mut first = true;
8811                                        if let Some(ref start) = gen.start {
8812                                            if !first {
8813                                                self.write(" ");
8814                                            }
8815                                            first = false;
8816                                            self.write_keyword("START WITH");
8817                                            self.write_space();
8818                                            self.generate_expression(start)?;
8819                                        }
8820                                        if let Some(ref incr) = gen.increment {
8821                                            if !first {
8822                                                self.write(" ");
8823                                            }
8824                                            first = false;
8825                                            self.write_keyword("INCREMENT BY");
8826                                            self.write_space();
8827                                            self.generate_expression(incr)?;
8828                                        }
8829                                        if let Some(ref minv) = gen.minvalue {
8830                                            if !first {
8831                                                self.write(" ");
8832                                            }
8833                                            first = false;
8834                                            self.write_keyword("MINVALUE");
8835                                            self.write_space();
8836                                            self.generate_expression(minv)?;
8837                                        }
8838                                        if let Some(ref maxv) = gen.maxvalue {
8839                                            if !first {
8840                                                self.write(" ");
8841                                            }
8842                                            first = false;
8843                                            self.write_keyword("MAXVALUE");
8844                                            self.write_space();
8845                                            self.generate_expression(maxv)?;
8846                                        }
8847                                        if let Some(cycle) = gen.cycle {
8848                                            if !first {
8849                                                self.write(" ");
8850                                            }
8851                                            if cycle {
8852                                                self.write_keyword("CYCLE");
8853                                            } else {
8854                                                self.write_keyword("NO CYCLE");
8855                                            }
8856                                        }
8857                                        self.write(")");
8858                                    }
8859                                }
8860                                generated_idx += 1;
8861                                break;
8862                            }
8863                            generated_idx += 1;
8864                        }
8865                    }
8866                    ConstraintType::Collate => {
8867                        // Find next Collate constraint
8868                        while collate_idx < col.constraints.len() {
8869                            if let ColumnConstraint::Collate(collation) =
8870                                &col.constraints[collate_idx]
8871                            {
8872                                self.write_space();
8873                                self.write_keyword("COLLATE");
8874                                self.write_space();
8875                                self.generate_identifier(collation)?;
8876                                collate_idx += 1;
8877                                break;
8878                            }
8879                            collate_idx += 1;
8880                        }
8881                    }
8882                    ConstraintType::Comment => {
8883                        // Find next Comment constraint
8884                        while comment_idx < col.constraints.len() {
8885                            if let ColumnConstraint::Comment(comment) =
8886                                &col.constraints[comment_idx]
8887                            {
8888                                self.write_space();
8889                                self.write_keyword("COMMENT");
8890                                self.write_space();
8891                                self.generate_string_literal(comment)?;
8892                                comment_idx += 1;
8893                                break;
8894                            }
8895                            comment_idx += 1;
8896                        }
8897                    }
8898                    ConstraintType::Tags => {
8899                        // Find next Tags constraint (Snowflake)
8900                        for constraint in &col.constraints {
8901                            if let ColumnConstraint::Tags(tags) = constraint {
8902                                self.write_space();
8903                                self.write_keyword("TAG");
8904                                self.write(" (");
8905                                for (i, expr) in tags.expressions.iter().enumerate() {
8906                                    if i > 0 {
8907                                        self.write(", ");
8908                                    }
8909                                    self.generate_expression(expr)?;
8910                                }
8911                                self.write(")");
8912                                break;
8913                            }
8914                        }
8915                    }
8916                    ConstraintType::ComputedColumn => {
8917                        // Find next ComputedColumn constraint
8918                        for constraint in &col.constraints {
8919                            if let ColumnConstraint::ComputedColumn(cc) = constraint {
8920                                self.write_space();
8921                                self.generate_computed_column_inline(cc)?;
8922                                break;
8923                            }
8924                        }
8925                    }
8926                    ConstraintType::GeneratedAsRow => {
8927                        // Find next GeneratedAsRow constraint
8928                        for constraint in &col.constraints {
8929                            if let ColumnConstraint::GeneratedAsRow(gar) = constraint {
8930                                self.write_space();
8931                                self.generate_generated_as_row_inline(gar)?;
8932                                break;
8933                            }
8934                        }
8935                    }
8936                    ConstraintType::OnUpdate => {
8937                        if let Some(ref expr) = col.on_update {
8938                            self.write_space();
8939                            self.write_keyword("ON UPDATE");
8940                            self.write_space();
8941                            self.generate_expression(expr)?;
8942                        }
8943                    }
8944                    ConstraintType::Encode => {
8945                        if let Some(ref encoding) = col.encoding {
8946                            self.write_space();
8947                            self.write_keyword("ENCODE");
8948                            self.write_space();
8949                            self.write(encoding);
8950                        }
8951                    }
8952                    ConstraintType::Path => {
8953                        // Find next Path constraint
8954                        for constraint in &col.constraints {
8955                            if let ColumnConstraint::Path(path_expr) = constraint {
8956                                self.write_space();
8957                                self.write_keyword("PATH");
8958                                self.write_space();
8959                                self.generate_expression(path_expr)?;
8960                                break;
8961                            }
8962                        }
8963                    }
8964                }
8965            }
8966            if pending_not_null_after_identity {
8967                self.write_space();
8968                self.write_keyword("NOT NULL");
8969            }
8970        } else {
8971            // Legacy fixed order for backward compatibility
8972            if col.primary_key {
8973                self.write_space();
8974                self.write_keyword("PRIMARY KEY");
8975                if let Some(ref order) = col.primary_key_order {
8976                    self.write_space();
8977                    match order {
8978                        SortOrder::Asc => self.write_keyword("ASC"),
8979                        SortOrder::Desc => self.write_keyword("DESC"),
8980                    }
8981                }
8982            }
8983
8984            if col.unique {
8985                self.write_space();
8986                self.write_keyword("UNIQUE");
8987                // PostgreSQL 15+: NULLS NOT DISTINCT
8988                if col.unique_nulls_not_distinct {
8989                    self.write(" NULLS NOT DISTINCT");
8990                }
8991            }
8992
8993            match col.nullable {
8994                Some(false) => {
8995                    self.write_space();
8996                    self.write_keyword("NOT NULL");
8997                }
8998                Some(true) => {
8999                    self.write_space();
9000                    self.write_keyword("NULL");
9001                }
9002                None => {}
9003            }
9004
9005            if let Some(ref default) = col.default {
9006                self.write_space();
9007                self.write_keyword("DEFAULT");
9008                self.write_space();
9009                self.generate_expression(default)?;
9010            }
9011
9012            if col.auto_increment {
9013                self.write_space();
9014                self.generate_auto_increment_keyword(col)?;
9015            }
9016
9017            // Column-level constraints from Vec
9018            for constraint in &col.constraints {
9019                match constraint {
9020                    ColumnConstraint::References(fk_ref) => {
9021                        self.write_space();
9022                        if fk_ref.has_foreign_key_keywords {
9023                            self.write_keyword("FOREIGN KEY");
9024                            self.write_space();
9025                        }
9026                        self.write_keyword("REFERENCES");
9027                        self.write_space();
9028                        self.generate_table(&fk_ref.table)?;
9029                        if !fk_ref.columns.is_empty() {
9030                            self.write(" (");
9031                            for (i, c) in fk_ref.columns.iter().enumerate() {
9032                                if i > 0 {
9033                                    self.write(", ");
9034                                }
9035                                self.generate_identifier(c)?;
9036                            }
9037                            self.write(")");
9038                        }
9039                        self.generate_referential_actions(fk_ref)?;
9040                    }
9041                    ColumnConstraint::Check(expr) => {
9042                        self.write_space();
9043                        self.write_keyword("CHECK");
9044                        self.write(" (");
9045                        self.generate_expression(expr)?;
9046                        self.write(")");
9047                    }
9048                    ColumnConstraint::GeneratedAsIdentity(gen) => {
9049                        self.write_space();
9050                        // Redshift uses IDENTITY(start, increment) syntax
9051                        if matches!(
9052                            self.config.dialect,
9053                            Some(crate::dialects::DialectType::Redshift)
9054                        ) {
9055                            self.write_keyword("IDENTITY");
9056                            self.write("(");
9057                            if let Some(ref start) = gen.start {
9058                                self.generate_expression(start)?;
9059                            } else {
9060                                self.write("0");
9061                            }
9062                            self.write(", ");
9063                            if let Some(ref incr) = gen.increment {
9064                                self.generate_expression(incr)?;
9065                            } else {
9066                                self.write("1");
9067                            }
9068                            self.write(")");
9069                        } else {
9070                            self.write_keyword("GENERATED");
9071                            if gen.always {
9072                                self.write_space();
9073                                self.write_keyword("ALWAYS");
9074                            } else {
9075                                self.write_space();
9076                                self.write_keyword("BY DEFAULT");
9077                                if gen.on_null {
9078                                    self.write_space();
9079                                    self.write_keyword("ON NULL");
9080                                }
9081                            }
9082                            self.write_space();
9083                            self.write_keyword("AS IDENTITY");
9084
9085                            let has_options = gen.start.is_some()
9086                                || gen.increment.is_some()
9087                                || gen.minvalue.is_some()
9088                                || gen.maxvalue.is_some()
9089                                || gen.cycle.is_some();
9090                            if has_options {
9091                                self.write(" (");
9092                                let mut first = true;
9093                                if let Some(ref start) = gen.start {
9094                                    if !first {
9095                                        self.write(" ");
9096                                    }
9097                                    first = false;
9098                                    self.write_keyword("START WITH");
9099                                    self.write_space();
9100                                    self.generate_expression(start)?;
9101                                }
9102                                if let Some(ref incr) = gen.increment {
9103                                    if !first {
9104                                        self.write(" ");
9105                                    }
9106                                    first = false;
9107                                    self.write_keyword("INCREMENT BY");
9108                                    self.write_space();
9109                                    self.generate_expression(incr)?;
9110                                }
9111                                if let Some(ref minv) = gen.minvalue {
9112                                    if !first {
9113                                        self.write(" ");
9114                                    }
9115                                    first = false;
9116                                    self.write_keyword("MINVALUE");
9117                                    self.write_space();
9118                                    self.generate_expression(minv)?;
9119                                }
9120                                if let Some(ref maxv) = gen.maxvalue {
9121                                    if !first {
9122                                        self.write(" ");
9123                                    }
9124                                    first = false;
9125                                    self.write_keyword("MAXVALUE");
9126                                    self.write_space();
9127                                    self.generate_expression(maxv)?;
9128                                }
9129                                if let Some(cycle) = gen.cycle {
9130                                    if !first {
9131                                        self.write(" ");
9132                                    }
9133                                    if cycle {
9134                                        self.write_keyword("CYCLE");
9135                                    } else {
9136                                        self.write_keyword("NO CYCLE");
9137                                    }
9138                                }
9139                                self.write(")");
9140                            }
9141                        }
9142                    }
9143                    ColumnConstraint::Collate(collation) => {
9144                        self.write_space();
9145                        self.write_keyword("COLLATE");
9146                        self.write_space();
9147                        self.generate_identifier(collation)?;
9148                    }
9149                    ColumnConstraint::Comment(comment) => {
9150                        self.write_space();
9151                        self.write_keyword("COMMENT");
9152                        self.write_space();
9153                        self.generate_string_literal(comment)?;
9154                    }
9155                    ColumnConstraint::Path(path_expr) => {
9156                        self.write_space();
9157                        self.write_keyword("PATH");
9158                        self.write_space();
9159                        self.generate_expression(path_expr)?;
9160                    }
9161                    _ => {} // Other constraints handled above
9162                }
9163            }
9164
9165            // Redshift: ENCODE encoding_type (legacy path)
9166            if let Some(ref encoding) = col.encoding {
9167                self.write_space();
9168                self.write_keyword("ENCODE");
9169                self.write_space();
9170                self.write(encoding);
9171            }
9172        }
9173
9174        // ClickHouse: CODEC(...)
9175        if let Some(ref codec) = col.codec {
9176            self.write_space();
9177            self.write_keyword("CODEC");
9178            self.write("(");
9179            self.write(codec);
9180            self.write(")");
9181        }
9182
9183        if let Some(visible) = col.visible {
9184            self.write_space();
9185            if visible {
9186                self.write_keyword("VISIBLE");
9187            } else {
9188                self.write_keyword("INVISIBLE");
9189            }
9190        }
9191
9192        // ClickHouse: EPHEMERAL [expr]
9193        if let Some(ref ephemeral) = col.ephemeral {
9194            self.write_space();
9195            self.write_keyword("EPHEMERAL");
9196            if let Some(ref expr) = ephemeral {
9197                self.write_space();
9198                self.generate_expression(expr)?;
9199            }
9200        }
9201
9202        // ClickHouse: MATERIALIZED expr
9203        if let Some(ref mat_expr) = col.materialized_expr {
9204            self.write_space();
9205            self.write_keyword("MATERIALIZED");
9206            self.write_space();
9207            self.generate_expression(mat_expr)?;
9208        }
9209
9210        // ClickHouse: ALIAS expr
9211        if let Some(ref alias_expr) = col.alias_expr {
9212            self.write_space();
9213            self.write_keyword("ALIAS");
9214            self.write_space();
9215            self.generate_expression(alias_expr)?;
9216        }
9217
9218        // ClickHouse: TTL expr
9219        if let Some(ref ttl_expr) = col.ttl_expr {
9220            self.write_space();
9221            self.write_keyword("TTL");
9222            self.write_space();
9223            self.generate_expression(ttl_expr)?;
9224        }
9225
9226        // TSQL: NOT FOR REPLICATION
9227        if col.not_for_replication
9228            && matches!(
9229                self.config.dialect,
9230                Some(crate::dialects::DialectType::TSQL)
9231                    | Some(crate::dialects::DialectType::Fabric)
9232            )
9233        {
9234            self.write_space();
9235            self.write_keyword("NOT FOR REPLICATION");
9236        }
9237
9238        // BigQuery: OPTIONS (key=value, ...) on column - comes after all constraints
9239        if !col.options.is_empty() {
9240            self.write_space();
9241            self.generate_options_clause(&col.options)?;
9242        }
9243
9244        // SQLite: Inline PRIMARY KEY from table constraint
9245        // This comes at the end, after all existing column constraints
9246        if !col.primary_key
9247            && self
9248                .sqlite_inline_pk_columns
9249                .contains(&col.name.name.to_ascii_lowercase())
9250        {
9251            self.write_space();
9252            self.write_keyword("PRIMARY KEY");
9253        }
9254
9255        // SERIAL expansion: add GENERATED BY DEFAULT AS IDENTITY NOT NULL for PostgreSQL,
9256        // just NOT NULL for Materialize (which strips GENERATED AS IDENTITY)
9257        if serial_expansion.is_some() {
9258            if matches!(self.config.dialect, Some(DialectType::PostgreSQL)) {
9259                self.write_space();
9260                self.write_keyword("GENERATED BY DEFAULT AS IDENTITY NOT NULL");
9261            } else if matches!(self.config.dialect, Some(DialectType::Materialize)) {
9262                self.write_space();
9263                self.write_keyword("NOT NULL");
9264            }
9265        }
9266
9267        Ok(())
9268    }
9269
9270    fn generate_table_constraint(&mut self, constraint: &TableConstraint) -> Result<()> {
9271        match constraint {
9272            TableConstraint::PrimaryKey {
9273                name,
9274                columns,
9275                include_columns,
9276                modifiers,
9277                has_constraint_keyword,
9278            } => {
9279                if let Some(ref n) = name {
9280                    if *has_constraint_keyword {
9281                        self.write_keyword("CONSTRAINT");
9282                        self.write_space();
9283                        self.generate_identifier(n)?;
9284                        self.write_space();
9285                    }
9286                }
9287                self.write_keyword("PRIMARY KEY");
9288                // TSQL CLUSTERED/NONCLUSTERED modifier (before columns)
9289                if let Some(ref clustered) = modifiers.clustered {
9290                    self.write_space();
9291                    self.write_keyword(clustered);
9292                }
9293                // MySQL format: PRIMARY KEY name (cols) when no CONSTRAINT keyword
9294                if let Some(ref n) = name {
9295                    if !*has_constraint_keyword {
9296                        self.write_space();
9297                        self.generate_identifier(n)?;
9298                    }
9299                }
9300                self.write(" (");
9301                for (i, col) in columns.iter().enumerate() {
9302                    if i > 0 {
9303                        self.write(", ");
9304                    }
9305                    self.generate_identifier(col)?;
9306                }
9307                self.write(")");
9308                if !include_columns.is_empty() {
9309                    self.write_space();
9310                    self.write_keyword("INCLUDE");
9311                    self.write(" (");
9312                    for (i, col) in include_columns.iter().enumerate() {
9313                        if i > 0 {
9314                            self.write(", ");
9315                        }
9316                        self.generate_identifier(col)?;
9317                    }
9318                    self.write(")");
9319                }
9320                self.generate_constraint_modifiers(modifiers);
9321            }
9322            TableConstraint::Unique {
9323                name,
9324                columns,
9325                columns_parenthesized,
9326                modifiers,
9327                has_constraint_keyword,
9328                nulls_not_distinct,
9329            } => {
9330                if let Some(ref n) = name {
9331                    if *has_constraint_keyword {
9332                        self.write_keyword("CONSTRAINT");
9333                        self.write_space();
9334                        self.generate_identifier(n)?;
9335                        self.write_space();
9336                    }
9337                }
9338                self.write_keyword("UNIQUE");
9339                // TSQL CLUSTERED/NONCLUSTERED modifier (before columns)
9340                if let Some(ref clustered) = modifiers.clustered {
9341                    self.write_space();
9342                    self.write_keyword(clustered);
9343                }
9344                // PostgreSQL 15+: NULLS NOT DISTINCT
9345                if *nulls_not_distinct {
9346                    self.write(" NULLS NOT DISTINCT");
9347                }
9348                // MySQL format: UNIQUE name (cols) when no CONSTRAINT keyword
9349                if let Some(ref n) = name {
9350                    if !*has_constraint_keyword {
9351                        self.write_space();
9352                        self.generate_identifier(n)?;
9353                    }
9354                }
9355                if *columns_parenthesized {
9356                    self.write(" (");
9357                    for (i, col) in columns.iter().enumerate() {
9358                        if i > 0 {
9359                            self.write(", ");
9360                        }
9361                        self.generate_identifier(col)?;
9362                    }
9363                    self.write(")");
9364                } else {
9365                    // UNIQUE without parentheses (e.g., UNIQUE idx_name)
9366                    for col in columns.iter() {
9367                        self.write_space();
9368                        self.generate_identifier(col)?;
9369                    }
9370                }
9371                self.generate_constraint_modifiers(modifiers);
9372            }
9373            TableConstraint::ForeignKey {
9374                name,
9375                columns,
9376                references,
9377                on_delete,
9378                on_update,
9379                modifiers,
9380            } => {
9381                if let Some(ref n) = name {
9382                    self.write_keyword("CONSTRAINT");
9383                    self.write_space();
9384                    self.generate_identifier(n)?;
9385                    self.write_space();
9386                }
9387                self.write_keyword("FOREIGN KEY");
9388                self.write(" (");
9389                for (i, col) in columns.iter().enumerate() {
9390                    if i > 0 {
9391                        self.write(", ");
9392                    }
9393                    self.generate_identifier(col)?;
9394                }
9395                self.write(")");
9396                if let Some(ref refs) = references {
9397                    self.write(" ");
9398                    self.write_keyword("REFERENCES");
9399                    self.write_space();
9400                    self.generate_table(&refs.table)?;
9401                    if !refs.columns.is_empty() {
9402                        if self.config.pretty {
9403                            self.write(" (");
9404                            self.write_newline();
9405                            self.indent_level += 1;
9406                            for (i, col) in refs.columns.iter().enumerate() {
9407                                if i > 0 {
9408                                    self.write(",");
9409                                    self.write_newline();
9410                                }
9411                                self.write_indent();
9412                                self.generate_identifier(col)?;
9413                            }
9414                            self.indent_level -= 1;
9415                            self.write_newline();
9416                            self.write_indent();
9417                            self.write(")");
9418                        } else {
9419                            self.write(" (");
9420                            for (i, col) in refs.columns.iter().enumerate() {
9421                                if i > 0 {
9422                                    self.write(", ");
9423                                }
9424                                self.generate_identifier(col)?;
9425                            }
9426                            self.write(")");
9427                        }
9428                    }
9429                    self.generate_referential_actions(refs)?;
9430                } else {
9431                    // No REFERENCES - output ON DELETE/ON UPDATE directly
9432                    if let Some(ref action) = on_delete {
9433                        self.write_space();
9434                        self.write_keyword("ON DELETE");
9435                        self.write_space();
9436                        self.generate_referential_action(action);
9437                    }
9438                    if let Some(ref action) = on_update {
9439                        self.write_space();
9440                        self.write_keyword("ON UPDATE");
9441                        self.write_space();
9442                        self.generate_referential_action(action);
9443                    }
9444                }
9445                self.generate_constraint_modifiers(modifiers);
9446            }
9447            TableConstraint::Check {
9448                name,
9449                expression,
9450                modifiers,
9451            } => {
9452                if let Some(ref n) = name {
9453                    self.write_keyword("CONSTRAINT");
9454                    self.write_space();
9455                    self.generate_identifier(n)?;
9456                    self.write_space();
9457                }
9458                self.write_keyword("CHECK");
9459                self.write(" (");
9460                self.generate_expression(expression)?;
9461                self.write(")");
9462                self.generate_constraint_modifiers(modifiers);
9463            }
9464            TableConstraint::Assume { name, expression } => {
9465                if let Some(ref n) = name {
9466                    self.write_keyword("CONSTRAINT");
9467                    self.write_space();
9468                    self.generate_identifier(n)?;
9469                    self.write_space();
9470                }
9471                self.write_keyword("ASSUME");
9472                self.write(" (");
9473                self.generate_expression(expression)?;
9474                self.write(")");
9475            }
9476            TableConstraint::Default {
9477                name,
9478                expression,
9479                column,
9480            } => {
9481                if let Some(ref n) = name {
9482                    self.write_keyword("CONSTRAINT");
9483                    self.write_space();
9484                    self.generate_identifier(n)?;
9485                    self.write_space();
9486                }
9487                self.write_keyword("DEFAULT");
9488                self.write_space();
9489                self.generate_expression(expression)?;
9490                self.write_space();
9491                self.write_keyword("FOR");
9492                self.write_space();
9493                self.generate_identifier(column)?;
9494            }
9495            TableConstraint::Index {
9496                name,
9497                columns,
9498                kind,
9499                modifiers,
9500                use_key_keyword,
9501                expression,
9502                index_type,
9503                granularity,
9504            } => {
9505                // ClickHouse-style INDEX: INDEX name expr TYPE type_func GRANULARITY n
9506                if expression.is_some() {
9507                    self.write_keyword("INDEX");
9508                    if let Some(ref n) = name {
9509                        self.write_space();
9510                        self.generate_identifier(n)?;
9511                    }
9512                    if let Some(ref expr) = expression {
9513                        self.write_space();
9514                        self.generate_expression(expr)?;
9515                    }
9516                    if let Some(ref idx_type) = index_type {
9517                        self.write_space();
9518                        self.write_keyword("TYPE");
9519                        self.write_space();
9520                        self.generate_expression(idx_type)?;
9521                    }
9522                    if let Some(ref gran) = granularity {
9523                        self.write_space();
9524                        self.write_keyword("GRANULARITY");
9525                        self.write_space();
9526                        self.generate_expression(gran)?;
9527                    }
9528                } else {
9529                    // Standard INDEX syntax
9530                    // Determine the index keyword to use
9531                    // MySQL normalizes KEY to INDEX
9532                    use crate::dialects::DialectType;
9533                    let index_keyword = if *use_key_keyword
9534                        && !matches!(self.config.dialect, Some(DialectType::MySQL))
9535                    {
9536                        "KEY"
9537                    } else {
9538                        "INDEX"
9539                    };
9540
9541                    // Output kind (UNIQUE, FULLTEXT, SPATIAL) if present
9542                    if let Some(ref k) = kind {
9543                        self.write_keyword(k);
9544                        // For UNIQUE, don't add INDEX/KEY keyword
9545                        if k != "UNIQUE" {
9546                            self.write_space();
9547                            self.write_keyword(index_keyword);
9548                        }
9549                    } else {
9550                        self.write_keyword(index_keyword);
9551                    }
9552
9553                    // Output USING before name if using_before_columns is true and there's no name
9554                    if modifiers.using_before_columns && name.is_none() {
9555                        if let Some(ref using) = modifiers.using {
9556                            self.write_space();
9557                            self.write_keyword("USING");
9558                            self.write_space();
9559                            self.write_keyword(using);
9560                        }
9561                    }
9562
9563                    // Output index name if present
9564                    if let Some(ref n) = name {
9565                        self.write_space();
9566                        self.generate_identifier(n)?;
9567                    }
9568
9569                    // Output USING after name but before columns if using_before_columns and there's a name
9570                    if modifiers.using_before_columns && name.is_some() {
9571                        if let Some(ref using) = modifiers.using {
9572                            self.write_space();
9573                            self.write_keyword("USING");
9574                            self.write_space();
9575                            self.write_keyword(using);
9576                        }
9577                    }
9578
9579                    // Output columns
9580                    self.write(" (");
9581                    for (i, col) in columns.iter().enumerate() {
9582                        if i > 0 {
9583                            self.write(", ");
9584                        }
9585                        self.generate_identifier(col)?;
9586                    }
9587                    self.write(")");
9588
9589                    // Output USING after columns if not using_before_columns
9590                    if !modifiers.using_before_columns {
9591                        if let Some(ref using) = modifiers.using {
9592                            self.write_space();
9593                            self.write_keyword("USING");
9594                            self.write_space();
9595                            self.write_keyword(using);
9596                        }
9597                    }
9598
9599                    // Output other constraint modifiers (but skip USING since we already handled it)
9600                    self.generate_constraint_modifiers_without_using(modifiers);
9601                }
9602            }
9603            TableConstraint::Projection { name, expression } => {
9604                // ClickHouse: PROJECTION name (SELECT ...)
9605                self.write_keyword("PROJECTION");
9606                self.write_space();
9607                self.generate_identifier(name)?;
9608                self.write(" (");
9609                self.generate_expression(expression)?;
9610                self.write(")");
9611            }
9612            TableConstraint::Like { source, options } => {
9613                self.write_keyword("LIKE");
9614                self.write_space();
9615                self.generate_table(source)?;
9616                for (action, prop) in options {
9617                    self.write_space();
9618                    match action {
9619                        LikeOptionAction::Including => self.write_keyword("INCLUDING"),
9620                        LikeOptionAction::Excluding => self.write_keyword("EXCLUDING"),
9621                    }
9622                    self.write_space();
9623                    self.write_keyword(prop);
9624                }
9625            }
9626            TableConstraint::PeriodForSystemTime { start_col, end_col } => {
9627                self.write_keyword("PERIOD FOR SYSTEM_TIME");
9628                self.write(" (");
9629                self.generate_identifier(start_col)?;
9630                self.write(", ");
9631                self.generate_identifier(end_col)?;
9632                self.write(")");
9633            }
9634            TableConstraint::Exclude {
9635                name,
9636                using,
9637                elements,
9638                include_columns,
9639                where_clause,
9640                with_params,
9641                using_index_tablespace,
9642                modifiers: _,
9643            } => {
9644                if let Some(ref n) = name {
9645                    self.write_keyword("CONSTRAINT");
9646                    self.write_space();
9647                    self.generate_identifier(n)?;
9648                    self.write_space();
9649                }
9650                self.write_keyword("EXCLUDE");
9651                if let Some(ref method) = using {
9652                    self.write_space();
9653                    self.write_keyword("USING");
9654                    self.write_space();
9655                    self.write(method);
9656                    self.write("(");
9657                } else {
9658                    self.write(" (");
9659                }
9660                for (i, elem) in elements.iter().enumerate() {
9661                    if i > 0 {
9662                        self.write(", ");
9663                    }
9664                    self.write(&elem.expression);
9665                    self.write_space();
9666                    self.write_keyword("WITH");
9667                    self.write_space();
9668                    self.write(&elem.operator);
9669                }
9670                self.write(")");
9671                if !include_columns.is_empty() {
9672                    self.write_space();
9673                    self.write_keyword("INCLUDE");
9674                    self.write(" (");
9675                    for (i, col) in include_columns.iter().enumerate() {
9676                        if i > 0 {
9677                            self.write(", ");
9678                        }
9679                        self.generate_identifier(col)?;
9680                    }
9681                    self.write(")");
9682                }
9683                if !with_params.is_empty() {
9684                    self.write_space();
9685                    self.write_keyword("WITH");
9686                    self.write(" (");
9687                    for (i, (key, val)) in with_params.iter().enumerate() {
9688                        if i > 0 {
9689                            self.write(", ");
9690                        }
9691                        self.write(key);
9692                        self.write("=");
9693                        self.write(val);
9694                    }
9695                    self.write(")");
9696                }
9697                if let Some(ref tablespace) = using_index_tablespace {
9698                    self.write_space();
9699                    self.write_keyword("USING INDEX TABLESPACE");
9700                    self.write_space();
9701                    self.write(tablespace);
9702                }
9703                if let Some(ref where_expr) = where_clause {
9704                    self.write_space();
9705                    self.write_keyword("WHERE");
9706                    self.write(" (");
9707                    self.generate_expression(where_expr)?;
9708                    self.write(")");
9709                }
9710            }
9711            TableConstraint::Tags(tags) => {
9712                self.write_keyword("TAG");
9713                self.write(" (");
9714                for (i, expr) in tags.expressions.iter().enumerate() {
9715                    if i > 0 {
9716                        self.write(", ");
9717                    }
9718                    self.generate_expression(expr)?;
9719                }
9720                self.write(")");
9721            }
9722            TableConstraint::InitiallyDeferred { deferred } => {
9723                self.write_keyword("INITIALLY");
9724                self.write_space();
9725                if *deferred {
9726                    self.write_keyword("DEFERRED");
9727                } else {
9728                    self.write_keyword("IMMEDIATE");
9729                }
9730            }
9731        }
9732        Ok(())
9733    }
9734
9735    fn generate_constraint_modifiers(&mut self, modifiers: &ConstraintModifiers) {
9736        // Output USING BTREE/HASH (MySQL) - comes first
9737        if let Some(using) = &modifiers.using {
9738            self.write_space();
9739            self.write_keyword("USING");
9740            self.write_space();
9741            self.write_keyword(using);
9742        }
9743        // Output ENFORCED/NOT ENFORCED
9744        if let Some(enforced) = modifiers.enforced {
9745            self.write_space();
9746            if enforced {
9747                self.write_keyword("ENFORCED");
9748            } else {
9749                self.write_keyword("NOT ENFORCED");
9750            }
9751        }
9752        // Output DEFERRABLE/NOT DEFERRABLE
9753        if let Some(deferrable) = modifiers.deferrable {
9754            self.write_space();
9755            if deferrable {
9756                self.write_keyword("DEFERRABLE");
9757            } else {
9758                self.write_keyword("NOT DEFERRABLE");
9759            }
9760        }
9761        // Output INITIALLY DEFERRED/INITIALLY IMMEDIATE
9762        if let Some(initially_deferred) = modifiers.initially_deferred {
9763            self.write_space();
9764            if initially_deferred {
9765                self.write_keyword("INITIALLY DEFERRED");
9766            } else {
9767                self.write_keyword("INITIALLY IMMEDIATE");
9768            }
9769        }
9770        // Output NORELY
9771        if modifiers.norely {
9772            self.write_space();
9773            self.write_keyword("NORELY");
9774        }
9775        // Output RELY
9776        if modifiers.rely {
9777            self.write_space();
9778            self.write_keyword("RELY");
9779        }
9780        // Output NOT VALID (PostgreSQL)
9781        if modifiers.not_valid {
9782            self.write_space();
9783            self.write_keyword("NOT VALID");
9784        }
9785        // Output ON CONFLICT (SQLite)
9786        if let Some(on_conflict) = &modifiers.on_conflict {
9787            self.write_space();
9788            self.write_keyword("ON CONFLICT");
9789            self.write_space();
9790            self.write_keyword(on_conflict);
9791        }
9792        // Output TSQL WITH options (PAD_INDEX=ON, STATISTICS_NORECOMPUTE=OFF, ...)
9793        if !modifiers.with_options.is_empty() {
9794            self.write_space();
9795            self.write_keyword("WITH");
9796            self.write(" (");
9797            for (i, (key, value)) in modifiers.with_options.iter().enumerate() {
9798                if i > 0 {
9799                    self.write(", ");
9800                }
9801                self.write(key);
9802                self.write("=");
9803                self.write(value);
9804            }
9805            self.write(")");
9806        }
9807        // Output TSQL ON filegroup
9808        if let Some(ref fg) = modifiers.on_filegroup {
9809            self.write_space();
9810            self.write_keyword("ON");
9811            self.write_space();
9812            let _ = self.generate_identifier(fg);
9813        }
9814    }
9815
9816    /// Generate constraint modifiers without USING (for Index constraints where USING is handled separately)
9817    fn generate_constraint_modifiers_without_using(&mut self, modifiers: &ConstraintModifiers) {
9818        // Output ENFORCED/NOT ENFORCED
9819        if let Some(enforced) = modifiers.enforced {
9820            self.write_space();
9821            if enforced {
9822                self.write_keyword("ENFORCED");
9823            } else {
9824                self.write_keyword("NOT ENFORCED");
9825            }
9826        }
9827        // Output DEFERRABLE/NOT DEFERRABLE
9828        if let Some(deferrable) = modifiers.deferrable {
9829            self.write_space();
9830            if deferrable {
9831                self.write_keyword("DEFERRABLE");
9832            } else {
9833                self.write_keyword("NOT DEFERRABLE");
9834            }
9835        }
9836        // Output INITIALLY DEFERRED/INITIALLY IMMEDIATE
9837        if let Some(initially_deferred) = modifiers.initially_deferred {
9838            self.write_space();
9839            if initially_deferred {
9840                self.write_keyword("INITIALLY DEFERRED");
9841            } else {
9842                self.write_keyword("INITIALLY IMMEDIATE");
9843            }
9844        }
9845        // Output NORELY
9846        if modifiers.norely {
9847            self.write_space();
9848            self.write_keyword("NORELY");
9849        }
9850        // Output RELY
9851        if modifiers.rely {
9852            self.write_space();
9853            self.write_keyword("RELY");
9854        }
9855        // Output NOT VALID (PostgreSQL)
9856        if modifiers.not_valid {
9857            self.write_space();
9858            self.write_keyword("NOT VALID");
9859        }
9860        // Output ON CONFLICT (SQLite)
9861        if let Some(on_conflict) = &modifiers.on_conflict {
9862            self.write_space();
9863            self.write_keyword("ON CONFLICT");
9864            self.write_space();
9865            self.write_keyword(on_conflict);
9866        }
9867        // Output MySQL index-specific modifiers
9868        self.generate_index_specific_modifiers(modifiers);
9869    }
9870
9871    /// Generate MySQL index-specific modifiers (COMMENT, VISIBLE, ENGINE_ATTRIBUTE, WITH PARSER)
9872    fn generate_index_specific_modifiers(&mut self, modifiers: &ConstraintModifiers) {
9873        if let Some(ref comment) = modifiers.comment {
9874            self.write_space();
9875            self.write_keyword("COMMENT");
9876            self.write(" '");
9877            self.write(comment);
9878            self.write("'");
9879        }
9880        if let Some(visible) = modifiers.visible {
9881            self.write_space();
9882            if visible {
9883                self.write_keyword("VISIBLE");
9884            } else {
9885                self.write_keyword("INVISIBLE");
9886            }
9887        }
9888        if let Some(ref attr) = modifiers.engine_attribute {
9889            self.write_space();
9890            self.write_keyword("ENGINE_ATTRIBUTE");
9891            self.write(" = '");
9892            self.write(attr);
9893            self.write("'");
9894        }
9895        if let Some(ref parser) = modifiers.with_parser {
9896            self.write_space();
9897            self.write_keyword("WITH PARSER");
9898            self.write_space();
9899            self.write(parser);
9900        }
9901    }
9902
9903    fn generate_referential_actions(&mut self, fk_ref: &ForeignKeyRef) -> Result<()> {
9904        // MATCH clause before ON DELETE/ON UPDATE (default position, e.g. PostgreSQL)
9905        if !fk_ref.match_after_actions {
9906            if let Some(ref match_type) = fk_ref.match_type {
9907                self.write_space();
9908                self.write_keyword("MATCH");
9909                self.write_space();
9910                match match_type {
9911                    MatchType::Full => self.write_keyword("FULL"),
9912                    MatchType::Partial => self.write_keyword("PARTIAL"),
9913                    MatchType::Simple => self.write_keyword("SIMPLE"),
9914                }
9915            }
9916        }
9917
9918        // Output ON UPDATE and ON DELETE in the original order
9919        if fk_ref.on_update_first {
9920            if let Some(ref action) = fk_ref.on_update {
9921                self.write_space();
9922                self.write_keyword("ON UPDATE");
9923                self.write_space();
9924                self.generate_referential_action(action);
9925            }
9926            if let Some(ref action) = fk_ref.on_delete {
9927                self.write_space();
9928                self.write_keyword("ON DELETE");
9929                self.write_space();
9930                self.generate_referential_action(action);
9931            }
9932        } else {
9933            if let Some(ref action) = fk_ref.on_delete {
9934                self.write_space();
9935                self.write_keyword("ON DELETE");
9936                self.write_space();
9937                self.generate_referential_action(action);
9938            }
9939            if let Some(ref action) = fk_ref.on_update {
9940                self.write_space();
9941                self.write_keyword("ON UPDATE");
9942                self.write_space();
9943                self.generate_referential_action(action);
9944            }
9945        }
9946
9947        // MATCH clause after ON DELETE/ON UPDATE (when original SQL had it after)
9948        if fk_ref.match_after_actions {
9949            if let Some(ref match_type) = fk_ref.match_type {
9950                self.write_space();
9951                self.write_keyword("MATCH");
9952                self.write_space();
9953                match match_type {
9954                    MatchType::Full => self.write_keyword("FULL"),
9955                    MatchType::Partial => self.write_keyword("PARTIAL"),
9956                    MatchType::Simple => self.write_keyword("SIMPLE"),
9957                }
9958            }
9959        }
9960
9961        // DEFERRABLE / NOT DEFERRABLE
9962        if let Some(deferrable) = fk_ref.deferrable {
9963            self.write_space();
9964            if deferrable {
9965                self.write_keyword("DEFERRABLE");
9966            } else {
9967                self.write_keyword("NOT DEFERRABLE");
9968            }
9969        }
9970
9971        Ok(())
9972    }
9973
9974    fn generate_referential_action(&mut self, action: &ReferentialAction) {
9975        match action {
9976            ReferentialAction::Cascade => self.write_keyword("CASCADE"),
9977            ReferentialAction::SetNull => self.write_keyword("SET NULL"),
9978            ReferentialAction::SetDefault => self.write_keyword("SET DEFAULT"),
9979            ReferentialAction::Restrict => self.write_keyword("RESTRICT"),
9980            ReferentialAction::NoAction => self.write_keyword("NO ACTION"),
9981        }
9982    }
9983
9984    fn generate_drop_table(&mut self, dt: &DropTable) -> Result<()> {
9985        // TSQL: IF NOT OBJECT_ID(...) IS NULL BEGIN DROP TABLE ...; END
9986        if let Some(ref object_id_args) = dt.object_id_args {
9987            if matches!(
9988                self.config.dialect,
9989                Some(crate::dialects::DialectType::TSQL)
9990                    | Some(crate::dialects::DialectType::Fabric)
9991            ) {
9992                self.write_keyword("IF NOT OBJECT_ID");
9993                self.write("(");
9994                self.write(object_id_args);
9995                self.write(")");
9996                self.write_space();
9997                self.write_keyword("IS NULL BEGIN DROP TABLE");
9998                self.write_space();
9999                for (i, table) in dt.names.iter().enumerate() {
10000                    if i > 0 {
10001                        self.write(", ");
10002                    }
10003                    self.generate_table(table)?;
10004                }
10005                self.write("; ");
10006                self.write_keyword("END");
10007                return Ok(());
10008            }
10009        }
10010
10011        // Athena: DROP TABLE uses Hive engine (backticks)
10012        let saved_athena_hive_context = self.athena_hive_context;
10013        if matches!(
10014            self.config.dialect,
10015            Some(crate::dialects::DialectType::Athena)
10016        ) {
10017            self.athena_hive_context = true;
10018        }
10019
10020        // Output leading comments (e.g., "-- comment\nDROP TABLE ...")
10021        for comment in &dt.leading_comments {
10022            self.write_formatted_comment(comment);
10023            self.write_space();
10024        }
10025        if dt.iceberg {
10026            self.write_keyword("DROP ICEBERG TABLE");
10027        } else {
10028            self.write_keyword("DROP TABLE");
10029        }
10030
10031        if dt.if_exists {
10032            self.write_space();
10033            self.write_keyword("IF EXISTS");
10034        }
10035
10036        self.write_space();
10037        for (i, table) in dt.names.iter().enumerate() {
10038            if i > 0 {
10039                self.write(", ");
10040            }
10041            self.generate_table(table)?;
10042        }
10043
10044        if dt.cascade_constraints {
10045            self.write_space();
10046            self.write_keyword("CASCADE CONSTRAINTS");
10047        } else if dt.cascade {
10048            self.write_space();
10049            self.write_keyword("CASCADE");
10050        }
10051
10052        if dt.restrict {
10053            self.write_space();
10054            self.write_keyword("RESTRICT");
10055        }
10056
10057        if dt.purge {
10058            self.write_space();
10059            self.write_keyword("PURGE");
10060        }
10061
10062        if dt.sync {
10063            self.write_space();
10064            self.write_keyword("SYNC");
10065        }
10066
10067        // Restore Athena Hive context
10068        self.athena_hive_context = saved_athena_hive_context;
10069
10070        Ok(())
10071    }
10072
10073    fn generate_undrop(&mut self, u: &Undrop) -> Result<()> {
10074        self.write_keyword("UNDROP");
10075        self.write_space();
10076        self.write_keyword(&u.kind);
10077        if u.if_exists {
10078            self.write_space();
10079            self.write_keyword("IF EXISTS");
10080        }
10081        self.write_space();
10082        self.generate_table(&u.name)?;
10083        Ok(())
10084    }
10085
10086    fn generate_alter_table(&mut self, at: &AlterTable) -> Result<()> {
10087        // Athena: ALTER TABLE uses Hive engine (backticks)
10088        let saved_athena_hive_context = self.athena_hive_context;
10089        if matches!(
10090            self.config.dialect,
10091            Some(crate::dialects::DialectType::Athena)
10092        ) {
10093            self.athena_hive_context = true;
10094        }
10095
10096        self.write_keyword("ALTER");
10097        // Write table modifier (e.g., ICEBERG) unless target is DuckDB
10098        if let Some(ref modifier) = at.table_modifier {
10099            if !matches!(
10100                self.config.dialect,
10101                Some(crate::dialects::DialectType::DuckDB)
10102            ) {
10103                self.write_space();
10104                self.write_keyword(modifier);
10105            }
10106        }
10107        self.write(" ");
10108        self.write_keyword("TABLE");
10109        if at.if_exists {
10110            self.write_space();
10111            self.write_keyword("IF EXISTS");
10112        }
10113        self.write_space();
10114        self.generate_table(&at.name)?;
10115
10116        // ClickHouse: ON CLUSTER clause
10117        if let Some(ref on_cluster) = at.on_cluster {
10118            self.write_space();
10119            self.generate_on_cluster(on_cluster)?;
10120        }
10121
10122        // Hive: PARTITION(key=value, ...) clause
10123        if let Some(ref partition) = at.partition {
10124            self.write_space();
10125            self.write_keyword("PARTITION");
10126            self.write("(");
10127            for (i, (key, value)) in partition.iter().enumerate() {
10128                if i > 0 {
10129                    self.write(", ");
10130                }
10131                self.generate_identifier(key)?;
10132                self.write(" = ");
10133                self.generate_expression(value)?;
10134            }
10135            self.write(")");
10136        }
10137
10138        // TSQL: WITH CHECK / WITH NOCHECK modifier
10139        if let Some(ref with_check) = at.with_check {
10140            self.write_space();
10141            self.write_keyword(with_check);
10142        }
10143
10144        if self.config.pretty {
10145            // In pretty mode, format actions with newlines and indentation
10146            self.write_newline();
10147            self.indent_level += 1;
10148            for (i, action) in at.actions.iter().enumerate() {
10149                // Check if this is a continuation of previous ADD COLUMN or ADD CONSTRAINT
10150                let is_continuation = i > 0
10151                    && matches!(
10152                        (&at.actions[i - 1], action),
10153                        (
10154                            AlterTableAction::AddColumn { .. },
10155                            AlterTableAction::AddColumn { .. }
10156                        ) | (
10157                            AlterTableAction::AddConstraint(_),
10158                            AlterTableAction::AddConstraint(_)
10159                        )
10160                    );
10161                if i > 0 {
10162                    self.write(",");
10163                    self.write_newline();
10164                }
10165                self.write_indent();
10166                self.generate_alter_action_with_continuation(action, is_continuation)?;
10167            }
10168            self.indent_level -= 1;
10169        } else {
10170            for (i, action) in at.actions.iter().enumerate() {
10171                // Check if this is a continuation of previous ADD COLUMN or ADD CONSTRAINT
10172                let is_continuation = i > 0
10173                    && matches!(
10174                        (&at.actions[i - 1], action),
10175                        (
10176                            AlterTableAction::AddColumn { .. },
10177                            AlterTableAction::AddColumn { .. }
10178                        ) | (
10179                            AlterTableAction::AddConstraint(_),
10180                            AlterTableAction::AddConstraint(_)
10181                        )
10182                    );
10183                if i > 0 {
10184                    self.write(",");
10185                }
10186                self.write_space();
10187                self.generate_alter_action_with_continuation(action, is_continuation)?;
10188            }
10189        }
10190
10191        // MySQL ALTER TABLE trailing options
10192        if let Some(ref algorithm) = at.algorithm {
10193            self.write(", ");
10194            self.write_keyword("ALGORITHM");
10195            self.write("=");
10196            self.write_keyword(algorithm);
10197        }
10198        if let Some(ref lock) = at.lock {
10199            self.write(", ");
10200            self.write_keyword("LOCK");
10201            self.write("=");
10202            self.write_keyword(lock);
10203        }
10204
10205        // Restore Athena Hive context
10206        self.athena_hive_context = saved_athena_hive_context;
10207
10208        Ok(())
10209    }
10210
10211    fn generate_alter_action_with_continuation(
10212        &mut self,
10213        action: &AlterTableAction,
10214        is_continuation: bool,
10215    ) -> Result<()> {
10216        match action {
10217            AlterTableAction::AddColumn {
10218                column,
10219                if_not_exists,
10220                position,
10221            } => {
10222                use crate::dialects::DialectType;
10223                // For Snowflake: consecutive ADD COLUMN actions are combined with commas
10224                // e.g., "ADD col1, col2" instead of "ADD col1, ADD col2"
10225                // For other dialects, repeat ADD COLUMN for each
10226                let is_snowflake = matches!(self.config.dialect, Some(DialectType::Snowflake));
10227                let is_tsql_like = matches!(
10228                    self.config.dialect,
10229                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
10230                );
10231                // Athena uses "ADD COLUMNS (col_def)" instead of "ADD COLUMN col_def"
10232                let is_athena = matches!(self.config.dialect, Some(DialectType::Athena));
10233
10234                if is_continuation && (is_snowflake || is_tsql_like) {
10235                    // Don't write ADD keyword for continuation in Snowflake/TSQL
10236                } else if is_snowflake {
10237                    self.write_keyword("ADD");
10238                    self.write_space();
10239                } else if is_athena {
10240                    // Athena uses ADD COLUMNS (col_def) syntax
10241                    self.write_keyword("ADD COLUMNS");
10242                    self.write(" (");
10243                } else if self.config.alter_table_include_column_keyword {
10244                    self.write_keyword("ADD COLUMN");
10245                    self.write_space();
10246                } else {
10247                    // Dialects like Oracle and TSQL don't use COLUMN keyword
10248                    self.write_keyword("ADD");
10249                    self.write_space();
10250                }
10251
10252                if *if_not_exists {
10253                    self.write_keyword("IF NOT EXISTS");
10254                    self.write_space();
10255                }
10256                self.generate_column_def(column)?;
10257
10258                // Close parenthesis for Athena
10259                if is_athena {
10260                    self.write(")");
10261                }
10262
10263                // Column position (FIRST or AFTER)
10264                if let Some(pos) = position {
10265                    self.write_space();
10266                    match pos {
10267                        ColumnPosition::First => self.write_keyword("FIRST"),
10268                        ColumnPosition::After(col_name) => {
10269                            self.write_keyword("AFTER");
10270                            self.write_space();
10271                            self.generate_identifier(col_name)?;
10272                        }
10273                    }
10274                }
10275            }
10276            AlterTableAction::DropColumn {
10277                name,
10278                if_exists,
10279                cascade,
10280            } => {
10281                self.write_keyword("DROP COLUMN");
10282                if *if_exists {
10283                    self.write_space();
10284                    self.write_keyword("IF EXISTS");
10285                }
10286                self.write_space();
10287                self.generate_identifier(name)?;
10288                if *cascade {
10289                    self.write_space();
10290                    self.write_keyword("CASCADE");
10291                }
10292            }
10293            AlterTableAction::DropColumns { names } => {
10294                self.write_keyword("DROP COLUMNS");
10295                self.write(" (");
10296                for (i, name) in names.iter().enumerate() {
10297                    if i > 0 {
10298                        self.write(", ");
10299                    }
10300                    self.generate_identifier(name)?;
10301                }
10302                self.write(")");
10303            }
10304            AlterTableAction::RenameColumn {
10305                old_name,
10306                new_name,
10307                if_exists,
10308            } => {
10309                self.write_keyword("RENAME COLUMN");
10310                if *if_exists {
10311                    self.write_space();
10312                    self.write_keyword("IF EXISTS");
10313                }
10314                self.write_space();
10315                self.generate_identifier(old_name)?;
10316                self.write_space();
10317                self.write_keyword("TO");
10318                self.write_space();
10319                self.generate_identifier(new_name)?;
10320            }
10321            AlterTableAction::AlterColumn {
10322                name,
10323                action,
10324                use_modify_keyword,
10325            } => {
10326                use crate::dialects::DialectType;
10327                // MySQL uses MODIFY COLUMN for type changes (SetDataType)
10328                // but ALTER COLUMN for SET DEFAULT, DROP DEFAULT, etc.
10329                let use_modify = *use_modify_keyword
10330                    || (matches!(self.config.dialect, Some(DialectType::MySQL))
10331                        && matches!(action, AlterColumnAction::SetDataType { .. }));
10332                if use_modify {
10333                    self.write_keyword("MODIFY COLUMN");
10334                    self.write_space();
10335                    self.generate_identifier(name)?;
10336                    // For MODIFY COLUMN, output the type directly
10337                    if let AlterColumnAction::SetDataType {
10338                        data_type,
10339                        using: _,
10340                        collate,
10341                    } = action
10342                    {
10343                        self.write_space();
10344                        self.generate_data_type(data_type)?;
10345                        // Output COLLATE clause if present
10346                        if let Some(collate_name) = collate {
10347                            self.write_space();
10348                            self.write_keyword("COLLATE");
10349                            self.write_space();
10350                            // Output as single-quoted string
10351                            self.write(&format!("'{}'", collate_name));
10352                        }
10353                    } else {
10354                        self.write_space();
10355                        self.generate_alter_column_action(action)?;
10356                    }
10357                } else if matches!(self.config.dialect, Some(DialectType::Hive))
10358                    && matches!(action, AlterColumnAction::SetDataType { .. })
10359                {
10360                    // Hive uses CHANGE COLUMN col_name col_name NEW_TYPE
10361                    self.write_keyword("CHANGE COLUMN");
10362                    self.write_space();
10363                    self.generate_identifier(name)?;
10364                    self.write_space();
10365                    self.generate_identifier(name)?;
10366                    if let AlterColumnAction::SetDataType { data_type, .. } = action {
10367                        self.write_space();
10368                        self.generate_data_type(data_type)?;
10369                    }
10370                } else {
10371                    self.write_keyword("ALTER COLUMN");
10372                    self.write_space();
10373                    self.generate_identifier(name)?;
10374                    self.write_space();
10375                    self.generate_alter_column_action(action)?;
10376                }
10377            }
10378            AlterTableAction::RenameTable(new_name) => {
10379                // MySQL-like dialects (MySQL, Doris, StarRocks) use RENAME without TO
10380                let mysql_like = matches!(
10381                    self.config.dialect,
10382                    Some(DialectType::MySQL)
10383                        | Some(DialectType::Doris)
10384                        | Some(DialectType::StarRocks)
10385                        | Some(DialectType::SingleStore)
10386                );
10387                if mysql_like {
10388                    self.write_keyword("RENAME");
10389                } else {
10390                    self.write_keyword("RENAME TO");
10391                }
10392                self.write_space();
10393                // Doris, DuckDB, BigQuery, PostgreSQL strip schema/catalog from target table
10394                let rename_table_with_db = !matches!(
10395                    self.config.dialect,
10396                    Some(DialectType::Doris)
10397                        | Some(DialectType::DuckDB)
10398                        | Some(DialectType::BigQuery)
10399                        | Some(DialectType::PostgreSQL)
10400                );
10401                if !rename_table_with_db {
10402                    let mut stripped = new_name.clone();
10403                    stripped.schema = None;
10404                    stripped.catalog = None;
10405                    self.generate_table(&stripped)?;
10406                } else {
10407                    self.generate_table(new_name)?;
10408                }
10409            }
10410            AlterTableAction::AddConstraint(constraint) => {
10411                // For consecutive ADD CONSTRAINT actions (is_continuation=true), skip ADD keyword
10412                // to produce: ADD CONSTRAINT c1 ..., CONSTRAINT c2 ...
10413                if !is_continuation {
10414                    self.write_keyword("ADD");
10415                    self.write_space();
10416                }
10417                self.generate_table_constraint(constraint)?;
10418            }
10419            AlterTableAction::DropConstraint { name, if_exists } => {
10420                self.write_keyword("DROP CONSTRAINT");
10421                if *if_exists {
10422                    self.write_space();
10423                    self.write_keyword("IF EXISTS");
10424                }
10425                self.write_space();
10426                self.generate_identifier(name)?;
10427            }
10428            AlterTableAction::DropForeignKey { name } => {
10429                self.write_keyword("DROP FOREIGN KEY");
10430                self.write_space();
10431                self.generate_identifier(name)?;
10432            }
10433            AlterTableAction::DropPartition {
10434                partitions,
10435                if_exists,
10436            } => {
10437                self.write_keyword("DROP");
10438                if *if_exists {
10439                    self.write_space();
10440                    self.write_keyword("IF EXISTS");
10441                }
10442                for (i, partition) in partitions.iter().enumerate() {
10443                    if i > 0 {
10444                        self.write(",");
10445                    }
10446                    self.write_space();
10447                    self.write_keyword("PARTITION");
10448                    // Check for special ClickHouse partition formats
10449                    if partition.len() == 1 && partition[0].0.name == "__expr__" {
10450                        // ClickHouse: PARTITION <expression>
10451                        self.write_space();
10452                        self.generate_expression(&partition[0].1)?;
10453                    } else if partition.len() == 1 && partition[0].0.name == "ALL" {
10454                        // ClickHouse: PARTITION ALL
10455                        self.write_space();
10456                        self.write_keyword("ALL");
10457                    } else if partition.len() == 1 && partition[0].0.name == "ID" {
10458                        // ClickHouse: PARTITION ID 'string'
10459                        self.write_space();
10460                        self.write_keyword("ID");
10461                        self.write_space();
10462                        self.generate_expression(&partition[0].1)?;
10463                    } else {
10464                        // Standard SQL: PARTITION(key=value, ...)
10465                        self.write("(");
10466                        for (j, (key, value)) in partition.iter().enumerate() {
10467                            if j > 0 {
10468                                self.write(", ");
10469                            }
10470                            self.generate_identifier(key)?;
10471                            self.write(" = ");
10472                            self.generate_expression(value)?;
10473                        }
10474                        self.write(")");
10475                    }
10476                }
10477            }
10478            AlterTableAction::Delete { where_clause } => {
10479                self.write_keyword("DELETE");
10480                self.write_space();
10481                self.write_keyword("WHERE");
10482                self.write_space();
10483                self.generate_expression(where_clause)?;
10484            }
10485            AlterTableAction::SwapWith(target) => {
10486                self.write_keyword("SWAP WITH");
10487                self.write_space();
10488                self.generate_table(target)?;
10489            }
10490            AlterTableAction::SetProperty { properties } => {
10491                use crate::dialects::DialectType;
10492                self.write_keyword("SET");
10493                // Trino/Presto use SET PROPERTIES syntax with spaces around =
10494                let is_trino_presto = matches!(
10495                    self.config.dialect,
10496                    Some(DialectType::Trino) | Some(DialectType::Presto)
10497                );
10498                if is_trino_presto {
10499                    self.write_space();
10500                    self.write_keyword("PROPERTIES");
10501                }
10502                let eq = if is_trino_presto { " = " } else { "=" };
10503                for (i, (key, value)) in properties.iter().enumerate() {
10504                    if i > 0 {
10505                        self.write(",");
10506                    }
10507                    self.write_space();
10508                    // Handle quoted property names for Trino
10509                    if key.contains(' ') {
10510                        self.generate_string_literal(key)?;
10511                    } else {
10512                        self.write(key);
10513                    }
10514                    self.write(eq);
10515                    self.generate_expression(value)?;
10516                }
10517            }
10518            AlterTableAction::UnsetProperty { properties } => {
10519                self.write_keyword("UNSET");
10520                for (i, name) in properties.iter().enumerate() {
10521                    if i > 0 {
10522                        self.write(",");
10523                    }
10524                    self.write_space();
10525                    self.write(name);
10526                }
10527            }
10528            AlterTableAction::ClusterBy { expressions } => {
10529                self.write_keyword("CLUSTER BY");
10530                self.write(" (");
10531                for (i, expr) in expressions.iter().enumerate() {
10532                    if i > 0 {
10533                        self.write(", ");
10534                    }
10535                    self.generate_expression(expr)?;
10536                }
10537                self.write(")");
10538            }
10539            AlterTableAction::SetTag { expressions } => {
10540                self.write_keyword("SET TAG");
10541                for (i, (key, value)) in expressions.iter().enumerate() {
10542                    if i > 0 {
10543                        self.write(",");
10544                    }
10545                    self.write_space();
10546                    self.write(key);
10547                    self.write(" = ");
10548                    self.generate_expression(value)?;
10549                }
10550            }
10551            AlterTableAction::UnsetTag { names } => {
10552                self.write_keyword("UNSET TAG");
10553                for (i, name) in names.iter().enumerate() {
10554                    if i > 0 {
10555                        self.write(",");
10556                    }
10557                    self.write_space();
10558                    self.write(name);
10559                }
10560            }
10561            AlterTableAction::SetOptions { expressions } => {
10562                self.write_keyword("SET");
10563                self.write(" (");
10564                for (i, expr) in expressions.iter().enumerate() {
10565                    if i > 0 {
10566                        self.write(", ");
10567                    }
10568                    self.generate_expression(expr)?;
10569                }
10570                self.write(")");
10571            }
10572            AlterTableAction::AlterIndex { name, visible } => {
10573                self.write_keyword("ALTER INDEX");
10574                self.write_space();
10575                self.generate_identifier(name)?;
10576                self.write_space();
10577                if *visible {
10578                    self.write_keyword("VISIBLE");
10579                } else {
10580                    self.write_keyword("INVISIBLE");
10581                }
10582            }
10583            AlterTableAction::SetAttribute { attribute } => {
10584                self.write_keyword("SET");
10585                self.write_space();
10586                self.write_keyword(attribute);
10587            }
10588            AlterTableAction::SetStageFileFormat { options } => {
10589                self.write_keyword("SET");
10590                self.write_space();
10591                self.write_keyword("STAGE_FILE_FORMAT");
10592                self.write(" = (");
10593                if let Some(opts) = options {
10594                    self.generate_space_separated_properties(opts)?;
10595                }
10596                self.write(")");
10597            }
10598            AlterTableAction::SetStageCopyOptions { options } => {
10599                self.write_keyword("SET");
10600                self.write_space();
10601                self.write_keyword("STAGE_COPY_OPTIONS");
10602                self.write(" = (");
10603                if let Some(opts) = options {
10604                    self.generate_space_separated_properties(opts)?;
10605                }
10606                self.write(")");
10607            }
10608            AlterTableAction::AddColumns { columns, cascade } => {
10609                // Oracle uses ADD (...) without COLUMNS keyword
10610                // Hive/Spark uses ADD COLUMNS (...)
10611                let is_oracle = matches!(self.config.dialect, Some(DialectType::Oracle));
10612                if is_oracle {
10613                    self.write_keyword("ADD");
10614                } else {
10615                    self.write_keyword("ADD COLUMNS");
10616                }
10617                self.write(" (");
10618                for (i, col) in columns.iter().enumerate() {
10619                    if i > 0 {
10620                        self.write(", ");
10621                    }
10622                    self.generate_column_def(col)?;
10623                }
10624                self.write(")");
10625                if *cascade {
10626                    self.write_space();
10627                    self.write_keyword("CASCADE");
10628                }
10629            }
10630            AlterTableAction::ChangeColumn {
10631                old_name,
10632                new_name,
10633                data_type,
10634                comment,
10635                cascade,
10636            } => {
10637                use crate::dialects::DialectType;
10638                let is_spark = matches!(
10639                    self.config.dialect,
10640                    Some(DialectType::Spark) | Some(DialectType::Databricks)
10641                );
10642                let is_rename = old_name.name != new_name.name;
10643
10644                if is_spark {
10645                    if is_rename {
10646                        // Spark: RENAME COLUMN old TO new
10647                        self.write_keyword("RENAME COLUMN");
10648                        self.write_space();
10649                        self.generate_identifier(old_name)?;
10650                        self.write_space();
10651                        self.write_keyword("TO");
10652                        self.write_space();
10653                        self.generate_identifier(new_name)?;
10654                    } else if comment.is_some() {
10655                        // Spark: ALTER COLUMN old COMMENT 'comment'
10656                        self.write_keyword("ALTER COLUMN");
10657                        self.write_space();
10658                        self.generate_identifier(old_name)?;
10659                        self.write_space();
10660                        self.write_keyword("COMMENT");
10661                        self.write_space();
10662                        self.write("'");
10663                        self.write(comment.as_ref().unwrap());
10664                        self.write("'");
10665                    } else if data_type.is_some() {
10666                        // Spark: ALTER COLUMN old TYPE data_type
10667                        self.write_keyword("ALTER COLUMN");
10668                        self.write_space();
10669                        self.generate_identifier(old_name)?;
10670                        self.write_space();
10671                        self.write_keyword("TYPE");
10672                        self.write_space();
10673                        self.generate_data_type(data_type.as_ref().unwrap())?;
10674                    } else {
10675                        // Fallback to CHANGE COLUMN
10676                        self.write_keyword("CHANGE COLUMN");
10677                        self.write_space();
10678                        self.generate_identifier(old_name)?;
10679                        self.write_space();
10680                        self.generate_identifier(new_name)?;
10681                    }
10682                } else {
10683                    // Hive/MySQL/default: CHANGE [COLUMN] old new [type] [COMMENT '...'] [CASCADE]
10684                    if data_type.is_some() {
10685                        self.write_keyword("CHANGE COLUMN");
10686                    } else {
10687                        self.write_keyword("CHANGE");
10688                    }
10689                    self.write_space();
10690                    self.generate_identifier(old_name)?;
10691                    self.write_space();
10692                    self.generate_identifier(new_name)?;
10693                    if let Some(ref dt) = data_type {
10694                        self.write_space();
10695                        self.generate_data_type(dt)?;
10696                    }
10697                    if let Some(ref c) = comment {
10698                        self.write_space();
10699                        self.write_keyword("COMMENT");
10700                        self.write_space();
10701                        self.write("'");
10702                        self.write(c);
10703                        self.write("'");
10704                    }
10705                    if *cascade {
10706                        self.write_space();
10707                        self.write_keyword("CASCADE");
10708                    }
10709                }
10710            }
10711            AlterTableAction::AddPartition {
10712                partition,
10713                if_not_exists,
10714                location,
10715            } => {
10716                self.write_keyword("ADD");
10717                self.write_space();
10718                if *if_not_exists {
10719                    self.write_keyword("IF NOT EXISTS");
10720                    self.write_space();
10721                }
10722                self.generate_expression(partition)?;
10723                if let Some(ref loc) = location {
10724                    self.write_space();
10725                    self.write_keyword("LOCATION");
10726                    self.write_space();
10727                    self.generate_expression(loc)?;
10728                }
10729            }
10730            AlterTableAction::AlterSortKey {
10731                this,
10732                expressions,
10733                compound,
10734            } => {
10735                // Redshift: ALTER [COMPOUND] SORTKEY AUTO|NONE|(col1, col2)
10736                self.write_keyword("ALTER");
10737                if *compound {
10738                    self.write_space();
10739                    self.write_keyword("COMPOUND");
10740                }
10741                self.write_space();
10742                self.write_keyword("SORTKEY");
10743                self.write_space();
10744                if let Some(style) = this {
10745                    self.write_keyword(style);
10746                } else if !expressions.is_empty() {
10747                    self.write("(");
10748                    for (i, expr) in expressions.iter().enumerate() {
10749                        if i > 0 {
10750                            self.write(", ");
10751                        }
10752                        self.generate_expression(expr)?;
10753                    }
10754                    self.write(")");
10755                }
10756            }
10757            AlterTableAction::AlterDistStyle { style, distkey } => {
10758                // Redshift: ALTER DISTSTYLE ALL|EVEN|AUTO|KEY [DISTKEY col]
10759                self.write_keyword("ALTER");
10760                self.write_space();
10761                self.write_keyword("DISTSTYLE");
10762                self.write_space();
10763                self.write_keyword(style);
10764                if let Some(col) = distkey {
10765                    self.write_space();
10766                    self.write_keyword("DISTKEY");
10767                    self.write_space();
10768                    self.generate_identifier(col)?;
10769                }
10770            }
10771            AlterTableAction::SetTableProperties { properties } => {
10772                // Redshift: SET TABLE PROPERTIES ('a' = '5', 'b' = 'c')
10773                self.write_keyword("SET TABLE PROPERTIES");
10774                self.write(" (");
10775                for (i, (key, value)) in properties.iter().enumerate() {
10776                    if i > 0 {
10777                        self.write(", ");
10778                    }
10779                    self.generate_expression(key)?;
10780                    self.write(" = ");
10781                    self.generate_expression(value)?;
10782                }
10783                self.write(")");
10784            }
10785            AlterTableAction::SetLocation { location } => {
10786                // Redshift: SET LOCATION 's3://bucket/folder/'
10787                self.write_keyword("SET LOCATION");
10788                self.write_space();
10789                self.write("'");
10790                self.write(location);
10791                self.write("'");
10792            }
10793            AlterTableAction::SetFileFormat { format } => {
10794                // Redshift: SET FILE FORMAT AVRO
10795                self.write_keyword("SET FILE FORMAT");
10796                self.write_space();
10797                self.write_keyword(format);
10798            }
10799            AlterTableAction::ReplacePartition { partition, source } => {
10800                // ClickHouse: REPLACE PARTITION expr FROM source
10801                self.write_keyword("REPLACE PARTITION");
10802                self.write_space();
10803                self.generate_expression(partition)?;
10804                if let Some(src) = source {
10805                    self.write_space();
10806                    self.write_keyword("FROM");
10807                    self.write_space();
10808                    self.generate_expression(src)?;
10809                }
10810            }
10811            AlterTableAction::Raw { sql } => {
10812                self.write(sql);
10813            }
10814        }
10815        Ok(())
10816    }
10817
10818    fn generate_alter_column_action(&mut self, action: &AlterColumnAction) -> Result<()> {
10819        match action {
10820            AlterColumnAction::SetDataType {
10821                data_type,
10822                using,
10823                collate,
10824            } => {
10825                use crate::dialects::DialectType;
10826                // Dialect-specific type change syntax:
10827                // - TSQL/Fabric/Hive: no prefix (ALTER COLUMN col datatype)
10828                // - Redshift/Spark: TYPE (ALTER COLUMN col TYPE datatype)
10829                // - Default: SET DATA TYPE (ALTER COLUMN col SET DATA TYPE datatype)
10830                let is_no_prefix = matches!(
10831                    self.config.dialect,
10832                    Some(DialectType::TSQL) | Some(DialectType::Fabric) | Some(DialectType::Hive)
10833                );
10834                let is_type_only = matches!(
10835                    self.config.dialect,
10836                    Some(DialectType::Redshift)
10837                        | Some(DialectType::Spark)
10838                        | Some(DialectType::Databricks)
10839                );
10840                if is_type_only {
10841                    self.write_keyword("TYPE");
10842                    self.write_space();
10843                } else if !is_no_prefix {
10844                    self.write_keyword("SET DATA TYPE");
10845                    self.write_space();
10846                }
10847                self.generate_data_type(data_type)?;
10848                if let Some(ref collation) = collate {
10849                    self.write_space();
10850                    self.write_keyword("COLLATE");
10851                    self.write_space();
10852                    self.write(collation);
10853                }
10854                if let Some(ref using_expr) = using {
10855                    self.write_space();
10856                    self.write_keyword("USING");
10857                    self.write_space();
10858                    self.generate_expression(using_expr)?;
10859                }
10860            }
10861            AlterColumnAction::SetDefault(expr) => {
10862                self.write_keyword("SET DEFAULT");
10863                self.write_space();
10864                self.generate_expression(expr)?;
10865            }
10866            AlterColumnAction::DropDefault => {
10867                self.write_keyword("DROP DEFAULT");
10868            }
10869            AlterColumnAction::SetNotNull => {
10870                self.write_keyword("SET NOT NULL");
10871            }
10872            AlterColumnAction::DropNotNull => {
10873                self.write_keyword("DROP NOT NULL");
10874            }
10875            AlterColumnAction::Comment(comment) => {
10876                self.write_keyword("COMMENT");
10877                self.write_space();
10878                self.generate_string_literal(comment)?;
10879            }
10880            AlterColumnAction::SetVisible => {
10881                self.write_keyword("SET VISIBLE");
10882            }
10883            AlterColumnAction::SetInvisible => {
10884                self.write_keyword("SET INVISIBLE");
10885            }
10886        }
10887        Ok(())
10888    }
10889
10890    fn generate_create_index(&mut self, ci: &CreateIndex) -> Result<()> {
10891        self.write_keyword("CREATE");
10892
10893        if ci.unique {
10894            self.write_space();
10895            self.write_keyword("UNIQUE");
10896        }
10897
10898        // TSQL CLUSTERED/NONCLUSTERED modifier
10899        if let Some(ref clustered) = ci.clustered {
10900            self.write_space();
10901            self.write_keyword(clustered);
10902        }
10903
10904        self.write_space();
10905        self.write_keyword("INDEX");
10906
10907        // PostgreSQL CONCURRENTLY modifier
10908        if ci.concurrently {
10909            self.write_space();
10910            self.write_keyword("CONCURRENTLY");
10911        }
10912
10913        if ci.if_not_exists {
10914            self.write_space();
10915            self.write_keyword("IF NOT EXISTS");
10916        }
10917
10918        // Index name is optional in PostgreSQL when IF NOT EXISTS is specified
10919        if !ci.name.name.is_empty() {
10920            self.write_space();
10921            self.generate_identifier(&ci.name)?;
10922        }
10923        self.write_space();
10924        self.write_keyword("ON");
10925        // Hive uses ON TABLE
10926        if matches!(self.config.dialect, Some(DialectType::Hive)) {
10927            self.write_space();
10928            self.write_keyword("TABLE");
10929        }
10930        self.write_space();
10931        self.generate_table(&ci.table)?;
10932
10933        // Column list (optional for COLUMNSTORE indexes)
10934        // Standard SQL convention: ON t(a) without space before paren
10935        if !ci.columns.is_empty() || ci.using.is_some() {
10936            let space_before_paren = false;
10937
10938            if let Some(ref using) = ci.using {
10939                self.write_space();
10940                self.write_keyword("USING");
10941                self.write_space();
10942                self.write(using);
10943                if space_before_paren {
10944                    self.write(" (");
10945                } else {
10946                    self.write("(");
10947                }
10948            } else {
10949                if space_before_paren {
10950                    self.write(" (");
10951                } else {
10952                    self.write("(");
10953                }
10954            }
10955            for (i, col) in ci.columns.iter().enumerate() {
10956                if i > 0 {
10957                    self.write(", ");
10958                }
10959                self.generate_identifier(&col.column)?;
10960                if let Some(ref opclass) = col.opclass {
10961                    self.write_space();
10962                    self.write(opclass);
10963                }
10964                if col.desc {
10965                    self.write_space();
10966                    self.write_keyword("DESC");
10967                } else if col.asc {
10968                    self.write_space();
10969                    self.write_keyword("ASC");
10970                }
10971                if let Some(nulls_first) = col.nulls_first {
10972                    self.write_space();
10973                    self.write_keyword("NULLS");
10974                    self.write_space();
10975                    self.write_keyword(if nulls_first { "FIRST" } else { "LAST" });
10976                }
10977            }
10978            self.write(")");
10979        }
10980
10981        // PostgreSQL INCLUDE (col1, col2) clause
10982        if !ci.include_columns.is_empty() {
10983            self.write_space();
10984            self.write_keyword("INCLUDE");
10985            self.write(" (");
10986            for (i, col) in ci.include_columns.iter().enumerate() {
10987                if i > 0 {
10988                    self.write(", ");
10989                }
10990                self.generate_identifier(col)?;
10991            }
10992            self.write(")");
10993        }
10994
10995        // TSQL: WITH (option=value, ...) clause
10996        if !ci.with_options.is_empty() {
10997            self.write_space();
10998            self.write_keyword("WITH");
10999            self.write(" (");
11000            for (i, (key, value)) in ci.with_options.iter().enumerate() {
11001                if i > 0 {
11002                    self.write(", ");
11003                }
11004                self.write(key);
11005                self.write("=");
11006                self.write(value);
11007            }
11008            self.write(")");
11009        }
11010
11011        // PostgreSQL WHERE clause for partial indexes
11012        if let Some(ref where_clause) = ci.where_clause {
11013            self.write_space();
11014            self.write_keyword("WHERE");
11015            self.write_space();
11016            self.generate_expression(where_clause)?;
11017        }
11018
11019        // TSQL: ON filegroup or partition scheme clause
11020        if let Some(ref on_fg) = ci.on_filegroup {
11021            self.write_space();
11022            self.write_keyword("ON");
11023            self.write_space();
11024            self.write(on_fg);
11025        }
11026
11027        Ok(())
11028    }
11029
11030    fn generate_drop_index(&mut self, di: &DropIndex) -> Result<()> {
11031        self.write_keyword("DROP INDEX");
11032
11033        if di.concurrently {
11034            self.write_space();
11035            self.write_keyword("CONCURRENTLY");
11036        }
11037
11038        if di.if_exists {
11039            self.write_space();
11040            self.write_keyword("IF EXISTS");
11041        }
11042
11043        self.write_space();
11044        self.generate_identifier(&di.name)?;
11045
11046        if let Some(ref table) = di.table {
11047            self.write_space();
11048            self.write_keyword("ON");
11049            self.write_space();
11050            self.generate_table(table)?;
11051        }
11052
11053        Ok(())
11054    }
11055
11056    fn generate_create_view(&mut self, cv: &CreateView) -> Result<()> {
11057        self.write_keyword("CREATE");
11058
11059        // MySQL: ALGORITHM=...
11060        if let Some(ref algorithm) = cv.algorithm {
11061            self.write_space();
11062            self.write_keyword("ALGORITHM");
11063            self.write("=");
11064            self.write_keyword(algorithm);
11065        }
11066
11067        // MySQL: DEFINER=...
11068        if let Some(ref definer) = cv.definer {
11069            self.write_space();
11070            self.write_keyword("DEFINER");
11071            self.write("=");
11072            self.write(definer);
11073        }
11074
11075        // MySQL: SQL SECURITY DEFINER/INVOKER (before VIEW keyword, unless it appeared after view name)
11076        if cv.security_sql_style && !cv.security_after_name {
11077            if let Some(ref security) = cv.security {
11078                self.write_space();
11079                self.write_keyword("SQL SECURITY");
11080                self.write_space();
11081                match security {
11082                    FunctionSecurity::Definer => self.write_keyword("DEFINER"),
11083                    FunctionSecurity::Invoker => self.write_keyword("INVOKER"),
11084                    FunctionSecurity::None => self.write_keyword("NONE"),
11085                }
11086            }
11087        }
11088
11089        if cv.or_alter {
11090            self.write_space();
11091            self.write_keyword("OR ALTER");
11092        } else if cv.or_replace {
11093            self.write_space();
11094            self.write_keyword("OR REPLACE");
11095        }
11096
11097        if cv.temporary {
11098            self.write_space();
11099            self.write_keyword("TEMPORARY");
11100        }
11101
11102        if cv.materialized {
11103            self.write_space();
11104            self.write_keyword("MATERIALIZED");
11105        }
11106
11107        // Snowflake: SECURE VIEW
11108        if cv.secure {
11109            self.write_space();
11110            self.write_keyword("SECURE");
11111        }
11112
11113        self.write_space();
11114        self.write_keyword("VIEW");
11115
11116        if cv.if_not_exists {
11117            self.write_space();
11118            self.write_keyword("IF NOT EXISTS");
11119        }
11120
11121        self.write_space();
11122        self.generate_table(&cv.name)?;
11123
11124        // ClickHouse: ON CLUSTER clause
11125        if let Some(ref on_cluster) = cv.on_cluster {
11126            self.write_space();
11127            self.generate_on_cluster(on_cluster)?;
11128        }
11129
11130        // ClickHouse: TO destination_table
11131        if let Some(ref to_table) = cv.to_table {
11132            self.write_space();
11133            self.write_keyword("TO");
11134            self.write_space();
11135            self.generate_table(to_table)?;
11136        }
11137
11138        // For regular VIEW: columns come before COPY GRANTS
11139        // For MATERIALIZED VIEW: COPY GRANTS comes before columns
11140        if !cv.materialized {
11141            // Regular VIEW: columns first
11142            if !cv.columns.is_empty() {
11143                self.write(" (");
11144                for (i, col) in cv.columns.iter().enumerate() {
11145                    if i > 0 {
11146                        self.write(", ");
11147                    }
11148                    self.generate_identifier(&col.name)?;
11149                    // BigQuery: OPTIONS (key=value, ...) on view column
11150                    if !col.options.is_empty() {
11151                        self.write_space();
11152                        self.generate_options_clause(&col.options)?;
11153                    }
11154                    if let Some(ref comment) = col.comment {
11155                        self.write_space();
11156                        self.write_keyword("COMMENT");
11157                        self.write_space();
11158                        self.generate_string_literal(comment)?;
11159                    }
11160                }
11161                self.write(")");
11162            }
11163
11164            // Presto/Trino/StarRocks: SECURITY DEFINER/INVOKER/NONE (after columns)
11165            // Also handles SQL SECURITY after view name (security_after_name)
11166            if !cv.security_sql_style || cv.security_after_name {
11167                if let Some(ref security) = cv.security {
11168                    self.write_space();
11169                    if cv.security_sql_style {
11170                        self.write_keyword("SQL SECURITY");
11171                    } else {
11172                        self.write_keyword("SECURITY");
11173                    }
11174                    self.write_space();
11175                    match security {
11176                        FunctionSecurity::Definer => self.write_keyword("DEFINER"),
11177                        FunctionSecurity::Invoker => self.write_keyword("INVOKER"),
11178                        FunctionSecurity::None => self.write_keyword("NONE"),
11179                    }
11180                }
11181            }
11182
11183            // Snowflake: COPY GRANTS
11184            if cv.copy_grants {
11185                self.write_space();
11186                self.write_keyword("COPY GRANTS");
11187            }
11188        } else {
11189            // MATERIALIZED VIEW: COPY GRANTS first
11190            if cv.copy_grants {
11191                self.write_space();
11192                self.write_keyword("COPY GRANTS");
11193            }
11194
11195            // Doris: If we have a schema (typed columns), generate that instead
11196            if let Some(ref schema) = cv.schema {
11197                self.write(" (");
11198                for (i, expr) in schema.expressions.iter().enumerate() {
11199                    if i > 0 {
11200                        self.write(", ");
11201                    }
11202                    self.generate_expression(expr)?;
11203                }
11204                self.write(")");
11205            } else if !cv.columns.is_empty() {
11206                // Then columns (simple column names without types)
11207                self.write(" (");
11208                for (i, col) in cv.columns.iter().enumerate() {
11209                    if i > 0 {
11210                        self.write(", ");
11211                    }
11212                    self.generate_identifier(&col.name)?;
11213                    // BigQuery: OPTIONS (key=value, ...) on view column
11214                    if !col.options.is_empty() {
11215                        self.write_space();
11216                        self.generate_options_clause(&col.options)?;
11217                    }
11218                    if let Some(ref comment) = col.comment {
11219                        self.write_space();
11220                        self.write_keyword("COMMENT");
11221                        self.write_space();
11222                        self.generate_string_literal(comment)?;
11223                    }
11224                }
11225                self.write(")");
11226            }
11227
11228            // Doris: KEY (columns) for materialized views
11229            if let Some(ref unique_key) = cv.unique_key {
11230                self.write_space();
11231                self.write_keyword("KEY");
11232                self.write(" (");
11233                for (i, expr) in unique_key.expressions.iter().enumerate() {
11234                    if i > 0 {
11235                        self.write(", ");
11236                    }
11237                    self.generate_expression(expr)?;
11238                }
11239                self.write(")");
11240            }
11241        }
11242
11243        if let Some(ref row_access_policy) = cv.row_access_policy {
11244            self.write_space();
11245            self.write_keyword("WITH");
11246            self.write_space();
11247            self.write(row_access_policy);
11248        }
11249
11250        // Snowflake: COMMENT = 'text'
11251        if let Some(ref comment) = cv.comment {
11252            self.write_space();
11253            self.write_keyword("COMMENT");
11254            self.write("=");
11255            self.generate_string_literal(comment)?;
11256        }
11257
11258        // Snowflake: TAG (name='value', ...)
11259        if !cv.tags.is_empty() {
11260            self.write_space();
11261            self.write_keyword("TAG");
11262            self.write(" (");
11263            for (i, (name, value)) in cv.tags.iter().enumerate() {
11264                if i > 0 {
11265                    self.write(", ");
11266                }
11267                self.write(name);
11268                self.write("='");
11269                self.write(value);
11270                self.write("'");
11271            }
11272            self.write(")");
11273        }
11274
11275        // BigQuery: OPTIONS (key=value, ...)
11276        if !cv.options.is_empty() {
11277            self.write_space();
11278            self.generate_options_clause(&cv.options)?;
11279        }
11280
11281        // Doris: BUILD IMMEDIATE/DEFERRED for materialized views
11282        if let Some(ref build) = cv.build {
11283            self.write_space();
11284            self.write_keyword("BUILD");
11285            self.write_space();
11286            self.write_keyword(build);
11287        }
11288
11289        // Doris: REFRESH clause for materialized views
11290        if let Some(ref refresh) = cv.refresh {
11291            self.write_space();
11292            self.generate_refresh_trigger_property(refresh)?;
11293        }
11294
11295        // Redshift: AUTO REFRESH YES|NO for materialized views
11296        if let Some(auto_refresh) = cv.auto_refresh {
11297            self.write_space();
11298            self.write_keyword("AUTO REFRESH");
11299            self.write_space();
11300            if auto_refresh {
11301                self.write_keyword("YES");
11302            } else {
11303                self.write_keyword("NO");
11304            }
11305        }
11306
11307        // ClickHouse: Table properties (ENGINE, ORDER BY, SAMPLE, SETTINGS, TTL, etc.)
11308        for prop in &cv.table_properties {
11309            self.write_space();
11310            self.generate_expression(prop)?;
11311        }
11312
11313        // Only output AS clause if there's a real query (not just NULL placeholder)
11314        if !matches!(&cv.query, Expression::Null(_)) {
11315            self.write_space();
11316            self.write_keyword("AS");
11317            self.write_space();
11318
11319            // Teradata: LOCKING clause (between AS and query)
11320            if let Some(ref mode) = cv.locking_mode {
11321                self.write_keyword("LOCKING");
11322                self.write_space();
11323                self.write_keyword(mode);
11324                if let Some(ref access) = cv.locking_access {
11325                    self.write_space();
11326                    self.write_keyword("FOR");
11327                    self.write_space();
11328                    self.write_keyword(access);
11329                }
11330                self.write_space();
11331            }
11332
11333            if cv.query_parenthesized {
11334                self.write("(");
11335            }
11336            self.generate_expression(&cv.query)?;
11337            if cv.query_parenthesized {
11338                self.write(")");
11339            }
11340        }
11341
11342        // Redshift: WITH NO SCHEMA BINDING (after query)
11343        if cv.no_schema_binding {
11344            self.write_space();
11345            self.write_keyword("WITH NO SCHEMA BINDING");
11346        }
11347
11348        Ok(())
11349    }
11350
11351    fn generate_drop_view(&mut self, dv: &DropView) -> Result<()> {
11352        self.write_keyword("DROP");
11353
11354        if dv.materialized {
11355            self.write_space();
11356            self.write_keyword("MATERIALIZED");
11357        }
11358
11359        self.write_space();
11360        self.write_keyword("VIEW");
11361
11362        if dv.if_exists {
11363            self.write_space();
11364            self.write_keyword("IF EXISTS");
11365        }
11366
11367        self.write_space();
11368        self.generate_table(&dv.name)?;
11369
11370        Ok(())
11371    }
11372
11373    fn generate_truncate(&mut self, tr: &Truncate) -> Result<()> {
11374        match tr.target {
11375            TruncateTarget::Database => self.write_keyword("TRUNCATE DATABASE"),
11376            TruncateTarget::Table => self.write_keyword("TRUNCATE TABLE"),
11377        }
11378        if tr.if_exists {
11379            self.write_space();
11380            self.write_keyword("IF EXISTS");
11381        }
11382        self.write_space();
11383        self.generate_table(&tr.table)?;
11384
11385        // ClickHouse: ON CLUSTER clause
11386        if let Some(ref on_cluster) = tr.on_cluster {
11387            self.write_space();
11388            self.generate_on_cluster(on_cluster)?;
11389        }
11390
11391        // Check if first table has a * (multi-table with star)
11392        if !tr.extra_tables.is_empty() {
11393            // Check if the first entry matches the main table (star case)
11394            let skip_first = if let Some(first) = tr.extra_tables.first() {
11395                first.table.name == tr.table.name && first.star
11396            } else {
11397                false
11398            };
11399
11400            // PostgreSQL normalizes away the * suffix (it's the default behavior)
11401            let strip_star = matches!(
11402                self.config.dialect,
11403                Some(crate::dialects::DialectType::PostgreSQL)
11404                    | Some(crate::dialects::DialectType::Redshift)
11405            );
11406            if skip_first && !strip_star {
11407                self.write("*");
11408            }
11409
11410            // Generate additional tables
11411            for (i, entry) in tr.extra_tables.iter().enumerate() {
11412                if i == 0 && skip_first {
11413                    continue; // Already handled the star for first table
11414                }
11415                self.write(", ");
11416                self.generate_table(&entry.table)?;
11417                if entry.star && !strip_star {
11418                    self.write("*");
11419                }
11420            }
11421        }
11422
11423        // RESTART/CONTINUE IDENTITY
11424        if let Some(identity) = &tr.identity {
11425            self.write_space();
11426            match identity {
11427                TruncateIdentity::Restart => self.write_keyword("RESTART IDENTITY"),
11428                TruncateIdentity::Continue => self.write_keyword("CONTINUE IDENTITY"),
11429            }
11430        }
11431
11432        if tr.cascade {
11433            self.write_space();
11434            self.write_keyword("CASCADE");
11435        }
11436
11437        if tr.restrict {
11438            self.write_space();
11439            self.write_keyword("RESTRICT");
11440        }
11441
11442        // Output Hive PARTITION clause
11443        if let Some(ref partition) = tr.partition {
11444            self.write_space();
11445            self.generate_expression(partition)?;
11446        }
11447
11448        Ok(())
11449    }
11450
11451    fn generate_use(&mut self, u: &Use) -> Result<()> {
11452        // Teradata uses "DATABASE <name>" instead of "USE <name>"
11453        if matches!(self.config.dialect, Some(DialectType::Teradata)) {
11454            self.write_keyword("DATABASE");
11455            self.write_space();
11456            self.generate_identifier(&u.this)?;
11457            return Ok(());
11458        }
11459
11460        self.write_keyword("USE");
11461
11462        if let Some(kind) = &u.kind {
11463            self.write_space();
11464            match kind {
11465                UseKind::Database => self.write_keyword("DATABASE"),
11466                UseKind::Schema => self.write_keyword("SCHEMA"),
11467                UseKind::Role => self.write_keyword("ROLE"),
11468                UseKind::Warehouse => self.write_keyword("WAREHOUSE"),
11469                UseKind::Catalog => self.write_keyword("CATALOG"),
11470                UseKind::SecondaryRoles => self.write_keyword("SECONDARY ROLES"),
11471            }
11472        }
11473
11474        self.write_space();
11475        // For SECONDARY ROLES, write the value as-is (ALL, NONE, or role names)
11476        // without quoting, since these are keywords not identifiers
11477        if matches!(&u.kind, Some(UseKind::SecondaryRoles)) {
11478            self.write(&u.this.name);
11479        } else {
11480            self.generate_identifier(&u.this)?;
11481        }
11482        Ok(())
11483    }
11484
11485    fn generate_cache(&mut self, c: &Cache) -> Result<()> {
11486        self.write_keyword("CACHE");
11487        if c.lazy {
11488            self.write_space();
11489            self.write_keyword("LAZY");
11490        }
11491        self.write_space();
11492        self.write_keyword("TABLE");
11493        self.write_space();
11494        self.generate_identifier(&c.table)?;
11495
11496        // OPTIONS clause
11497        if !c.options.is_empty() {
11498            self.write_space();
11499            self.write_keyword("OPTIONS");
11500            self.write("(");
11501            for (i, (key, value)) in c.options.iter().enumerate() {
11502                if i > 0 {
11503                    self.write(", ");
11504                }
11505                self.generate_expression(key)?;
11506                self.write(" = ");
11507                self.generate_expression(value)?;
11508            }
11509            self.write(")");
11510        }
11511
11512        // AS query
11513        if let Some(query) = &c.query {
11514            self.write_space();
11515            self.write_keyword("AS");
11516            self.write_space();
11517            self.generate_expression(query)?;
11518        }
11519
11520        Ok(())
11521    }
11522
11523    fn generate_uncache(&mut self, u: &Uncache) -> Result<()> {
11524        self.write_keyword("UNCACHE TABLE");
11525        if u.if_exists {
11526            self.write_space();
11527            self.write_keyword("IF EXISTS");
11528        }
11529        self.write_space();
11530        self.generate_identifier(&u.table)?;
11531        Ok(())
11532    }
11533
11534    fn generate_load_data(&mut self, l: &LoadData) -> Result<()> {
11535        self.write_keyword("LOAD DATA");
11536        if l.local {
11537            self.write_space();
11538            self.write_keyword("LOCAL");
11539        }
11540        self.write_space();
11541        self.write_keyword("INPATH");
11542        self.write_space();
11543        self.write("'");
11544        self.write(&l.inpath);
11545        self.write("'");
11546
11547        if l.overwrite {
11548            self.write_space();
11549            self.write_keyword("OVERWRITE");
11550        }
11551
11552        self.write_space();
11553        self.write_keyword("INTO TABLE");
11554        self.write_space();
11555        self.generate_expression(&l.table)?;
11556
11557        // PARTITION clause
11558        if !l.partition.is_empty() {
11559            self.write_space();
11560            self.write_keyword("PARTITION");
11561            self.write("(");
11562            for (i, (col, val)) in l.partition.iter().enumerate() {
11563                if i > 0 {
11564                    self.write(", ");
11565                }
11566                self.generate_identifier(col)?;
11567                self.write(" = ");
11568                self.generate_expression(val)?;
11569            }
11570            self.write(")");
11571        }
11572
11573        // INPUTFORMAT clause
11574        if let Some(fmt) = &l.input_format {
11575            self.write_space();
11576            self.write_keyword("INPUTFORMAT");
11577            self.write_space();
11578            self.write("'");
11579            self.write(fmt);
11580            self.write("'");
11581        }
11582
11583        // SERDE clause
11584        if let Some(serde) = &l.serde {
11585            self.write_space();
11586            self.write_keyword("SERDE");
11587            self.write_space();
11588            self.write("'");
11589            self.write(serde);
11590            self.write("'");
11591        }
11592
11593        Ok(())
11594    }
11595
11596    fn generate_pragma(&mut self, p: &Pragma) -> Result<()> {
11597        self.write_keyword("PRAGMA");
11598        self.write_space();
11599
11600        // Schema prefix if present
11601        if let Some(schema) = &p.schema {
11602            self.generate_identifier(schema)?;
11603            self.write(".");
11604        }
11605
11606        // Pragma name
11607        self.generate_identifier(&p.name)?;
11608
11609        // Value assignment or function call
11610        if p.use_assignment_syntax {
11611            self.write(" = ");
11612            if let Some(value) = &p.value {
11613                self.generate_expression(value)?;
11614            } else if let Some(arg) = p.args.first() {
11615                self.generate_expression(arg)?;
11616            }
11617        } else if !p.args.is_empty() {
11618            self.write("(");
11619            for (i, arg) in p.args.iter().enumerate() {
11620                if i > 0 {
11621                    self.write(", ");
11622                }
11623                self.generate_expression(arg)?;
11624            }
11625            self.write(")");
11626        }
11627
11628        Ok(())
11629    }
11630
11631    fn generate_grant(&mut self, g: &Grant) -> Result<()> {
11632        self.write_keyword("GRANT");
11633        self.write_space();
11634
11635        // Privileges (with optional column lists)
11636        for (i, privilege) in g.privileges.iter().enumerate() {
11637            if i > 0 {
11638                self.write(", ");
11639            }
11640            self.write_keyword(&privilege.name);
11641            // Output column list if present: SELECT(col1, col2)
11642            if !privilege.columns.is_empty() {
11643                self.write("(");
11644                for (j, col) in privilege.columns.iter().enumerate() {
11645                    if j > 0 {
11646                        self.write(", ");
11647                    }
11648                    self.write(col);
11649                }
11650                self.write(")");
11651            }
11652        }
11653
11654        self.write_space();
11655        self.write_keyword("ON");
11656        self.write_space();
11657
11658        // Object kind (TABLE, SCHEMA, etc.)
11659        if let Some(kind) = &g.kind {
11660            self.write_keyword(kind);
11661            self.write_space();
11662        }
11663
11664        // Securable - normalize function/procedure names to uppercase for PostgreSQL family
11665        {
11666            use crate::dialects::DialectType;
11667            let should_upper = matches!(
11668                self.config.dialect,
11669                Some(DialectType::PostgreSQL)
11670                    | Some(DialectType::CockroachDB)
11671                    | Some(DialectType::Materialize)
11672                    | Some(DialectType::RisingWave)
11673            ) && (g.kind.as_deref() == Some("FUNCTION")
11674                || g.kind.as_deref() == Some("PROCEDURE"));
11675            if should_upper {
11676                use crate::expressions::Identifier;
11677                let upper_id = Identifier {
11678                    name: g.securable.name.to_ascii_uppercase(),
11679                    quoted: g.securable.quoted,
11680                    ..g.securable.clone()
11681                };
11682                self.generate_identifier(&upper_id)?;
11683            } else {
11684                self.generate_identifier(&g.securable)?;
11685            }
11686        }
11687
11688        // Function parameter types (if present)
11689        if !g.function_params.is_empty() {
11690            self.write("(");
11691            for (i, param) in g.function_params.iter().enumerate() {
11692                if i > 0 {
11693                    self.write(", ");
11694                }
11695                self.write(param);
11696            }
11697            self.write(")");
11698        }
11699
11700        self.write_space();
11701        self.write_keyword("TO");
11702        self.write_space();
11703
11704        // Principals
11705        for (i, principal) in g.principals.iter().enumerate() {
11706            if i > 0 {
11707                self.write(", ");
11708            }
11709            if principal.is_role {
11710                self.write_keyword("ROLE");
11711                self.write_space();
11712            } else if principal.is_group {
11713                self.write_keyword("GROUP");
11714                self.write_space();
11715            } else if principal.is_share {
11716                self.write_keyword("SHARE");
11717                self.write_space();
11718            }
11719            self.generate_identifier(&principal.name)?;
11720        }
11721
11722        // WITH GRANT OPTION
11723        if g.grant_option {
11724            self.write_space();
11725            self.write_keyword("WITH GRANT OPTION");
11726        }
11727
11728        // TSQL: AS principal
11729        if let Some(ref principal) = g.as_principal {
11730            self.write_space();
11731            self.write_keyword("AS");
11732            self.write_space();
11733            self.generate_identifier(principal)?;
11734        }
11735
11736        Ok(())
11737    }
11738
11739    fn generate_revoke(&mut self, r: &Revoke) -> Result<()> {
11740        self.write_keyword("REVOKE");
11741        self.write_space();
11742
11743        // GRANT OPTION FOR
11744        if r.grant_option {
11745            self.write_keyword("GRANT OPTION FOR");
11746            self.write_space();
11747        }
11748
11749        // Privileges (with optional column lists)
11750        for (i, privilege) in r.privileges.iter().enumerate() {
11751            if i > 0 {
11752                self.write(", ");
11753            }
11754            self.write_keyword(&privilege.name);
11755            // Output column list if present: SELECT(col1, col2)
11756            if !privilege.columns.is_empty() {
11757                self.write("(");
11758                for (j, col) in privilege.columns.iter().enumerate() {
11759                    if j > 0 {
11760                        self.write(", ");
11761                    }
11762                    self.write(col);
11763                }
11764                self.write(")");
11765            }
11766        }
11767
11768        self.write_space();
11769        self.write_keyword("ON");
11770        self.write_space();
11771
11772        // Object kind
11773        if let Some(kind) = &r.kind {
11774            self.write_keyword(kind);
11775            self.write_space();
11776        }
11777
11778        // Securable - normalize function/procedure names to uppercase for PostgreSQL family
11779        {
11780            use crate::dialects::DialectType;
11781            let should_upper = matches!(
11782                self.config.dialect,
11783                Some(DialectType::PostgreSQL)
11784                    | Some(DialectType::CockroachDB)
11785                    | Some(DialectType::Materialize)
11786                    | Some(DialectType::RisingWave)
11787            ) && (r.kind.as_deref() == Some("FUNCTION")
11788                || r.kind.as_deref() == Some("PROCEDURE"));
11789            if should_upper {
11790                use crate::expressions::Identifier;
11791                let upper_id = Identifier {
11792                    name: r.securable.name.to_ascii_uppercase(),
11793                    quoted: r.securable.quoted,
11794                    ..r.securable.clone()
11795                };
11796                self.generate_identifier(&upper_id)?;
11797            } else {
11798                self.generate_identifier(&r.securable)?;
11799            }
11800        }
11801
11802        // Function parameter types (if present)
11803        if !r.function_params.is_empty() {
11804            self.write("(");
11805            for (i, param) in r.function_params.iter().enumerate() {
11806                if i > 0 {
11807                    self.write(", ");
11808                }
11809                self.write(param);
11810            }
11811            self.write(")");
11812        }
11813
11814        self.write_space();
11815        self.write_keyword("FROM");
11816        self.write_space();
11817
11818        // Principals
11819        for (i, principal) in r.principals.iter().enumerate() {
11820            if i > 0 {
11821                self.write(", ");
11822            }
11823            if principal.is_role {
11824                self.write_keyword("ROLE");
11825                self.write_space();
11826            } else if principal.is_group {
11827                self.write_keyword("GROUP");
11828                self.write_space();
11829            } else if principal.is_share {
11830                self.write_keyword("SHARE");
11831                self.write_space();
11832            }
11833            self.generate_identifier(&principal.name)?;
11834        }
11835
11836        // CASCADE or RESTRICT
11837        if r.cascade {
11838            self.write_space();
11839            self.write_keyword("CASCADE");
11840        } else if r.restrict {
11841            self.write_space();
11842            self.write_keyword("RESTRICT");
11843        }
11844
11845        Ok(())
11846    }
11847
11848    fn generate_comment(&mut self, c: &Comment) -> Result<()> {
11849        self.write_keyword("COMMENT");
11850
11851        // IF EXISTS
11852        if c.exists {
11853            self.write_space();
11854            self.write_keyword("IF EXISTS");
11855        }
11856
11857        self.write_space();
11858        self.write_keyword("ON");
11859
11860        // MATERIALIZED
11861        if c.materialized {
11862            self.write_space();
11863            self.write_keyword("MATERIALIZED");
11864        }
11865
11866        self.write_space();
11867        self.write_keyword(&c.kind);
11868        self.write_space();
11869
11870        // Object name
11871        self.generate_expression(&c.this)?;
11872
11873        self.write_space();
11874        self.write_keyword("IS");
11875        self.write_space();
11876
11877        // Comment expression
11878        self.generate_expression(&c.expression)?;
11879
11880        Ok(())
11881    }
11882
11883    fn generate_set_statement(&mut self, s: &SetStatement) -> Result<()> {
11884        self.write_keyword("SET");
11885
11886        for (i, item) in s.items.iter().enumerate() {
11887            if i > 0 {
11888                self.write(",");
11889            }
11890            self.write_space();
11891
11892            // Kind modifier (GLOBAL, LOCAL, SESSION, PERSIST, PERSIST_ONLY, VARIABLE)
11893            let has_variable_kind = item.kind.as_deref() == Some("VARIABLE");
11894            if let Some(ref kind) = item.kind {
11895                // For VARIABLE kind, only output the keyword for dialects that require it
11896                // (Spark, Databricks, DuckDB) - matching Python sqlglot's
11897                // SET_ASSIGNMENT_REQUIRES_VARIABLE_KEYWORD flag
11898                if has_variable_kind {
11899                    if matches!(
11900                        self.config.dialect,
11901                        Some(DialectType::Spark | DialectType::Databricks | DialectType::DuckDB)
11902                    ) {
11903                        self.write_keyword("VARIABLE");
11904                        self.write_space();
11905                    }
11906                } else {
11907                    self.write_keyword(kind);
11908                    self.write_space();
11909                }
11910            }
11911
11912            // Check for special SET forms by name
11913            let name_str = match &item.name {
11914                Expression::Identifier(id) => Some(id.name.as_str()),
11915                _ => None,
11916            };
11917
11918            let is_transaction = name_str == Some("TRANSACTION");
11919            let is_character_set = name_str == Some("CHARACTER SET");
11920            let is_names = name_str == Some("NAMES");
11921            let is_collate = name_str == Some("COLLATE");
11922            let is_value_only =
11923                matches!(&item.value, Expression::Identifier(id) if id.name.is_empty());
11924
11925            if is_transaction {
11926                // Output: SET [GLOBAL|SESSION] TRANSACTION <characteristics>
11927                self.write_keyword("TRANSACTION");
11928                if let Expression::Identifier(id) = &item.value {
11929                    if !id.name.is_empty() {
11930                        self.write_space();
11931                        self.write(&id.name);
11932                    }
11933                }
11934            } else if is_character_set {
11935                // Output: SET CHARACTER SET <charset>
11936                self.write_keyword("CHARACTER SET");
11937                self.write_space();
11938                self.generate_set_value(&item.value)?;
11939            } else if is_names {
11940                // Output: SET NAMES <charset>
11941                self.write_keyword("NAMES");
11942                self.write_space();
11943                self.generate_set_value(&item.value)?;
11944            } else if is_collate {
11945                // Output: COLLATE <collation> (part of SET NAMES ... COLLATE ...)
11946                self.write_keyword("COLLATE");
11947                self.write_space();
11948                self.generate_set_value(&item.value)?;
11949            } else if has_variable_kind {
11950                // Output: SET [VARIABLE] <name> = <value>
11951                // VARIABLE keyword already written above if dialect requires it
11952                if let Some(ns) = name_str {
11953                    self.write(ns);
11954                } else {
11955                    self.generate_expression(&item.name)?;
11956                }
11957                self.write(" = ");
11958                self.generate_set_value(&item.value)?;
11959            } else if is_value_only {
11960                // SET <name> ON/OFF without = (TSQL: SET XACT_ABORT ON)
11961                self.generate_expression(&item.name)?;
11962            } else if item.no_equals && matches!(self.config.dialect, Some(DialectType::TSQL)) {
11963                // SET key value without = (TSQL style)
11964                self.generate_expression(&item.name)?;
11965                self.write_space();
11966                self.generate_set_value(&item.value)?;
11967            } else {
11968                // Standard: variable = value
11969                // SET item names should not be quoted (they are config parameter names, not column refs)
11970                match &item.name {
11971                    Expression::Identifier(id) => {
11972                        self.write(&id.name);
11973                    }
11974                    _ => {
11975                        self.generate_expression(&item.name)?;
11976                    }
11977                }
11978                self.write(" = ");
11979                self.generate_set_value(&item.value)?;
11980            }
11981        }
11982
11983        Ok(())
11984    }
11985
11986    /// Generate a SET statement value, writing keyword values (DEFAULT, ON, OFF)
11987    /// directly to avoid reserved keyword quoting.
11988    fn generate_set_value(&mut self, value: &Expression) -> Result<()> {
11989        if let Expression::Identifier(id) = value {
11990            match id.name.as_str() {
11991                "DEFAULT" | "ON" | "OFF" => {
11992                    self.write_keyword(&id.name);
11993                    return Ok(());
11994                }
11995                _ => {}
11996            }
11997        }
11998        self.generate_expression(value)
11999    }
12000
12001    // ==================== Phase 4: Additional DDL Generation ====================
12002
12003    fn generate_alter_view(&mut self, av: &AlterView) -> Result<()> {
12004        self.write_keyword("ALTER");
12005        // MySQL modifiers before VIEW
12006        if let Some(ref algorithm) = av.algorithm {
12007            self.write_space();
12008            self.write_keyword("ALGORITHM");
12009            self.write(" = ");
12010            self.write_keyword(algorithm);
12011        }
12012        if let Some(ref definer) = av.definer {
12013            self.write_space();
12014            self.write_keyword("DEFINER");
12015            self.write(" = ");
12016            self.write(definer);
12017        }
12018        if let Some(ref sql_security) = av.sql_security {
12019            self.write_space();
12020            self.write_keyword("SQL SECURITY");
12021            self.write(" = ");
12022            self.write_keyword(sql_security);
12023        }
12024        self.write_space();
12025        self.write_keyword("VIEW");
12026        self.write_space();
12027        self.generate_table(&av.name)?;
12028
12029        // Hive: Column aliases with optional COMMENT
12030        if !av.columns.is_empty() {
12031            self.write(" (");
12032            for (i, col) in av.columns.iter().enumerate() {
12033                if i > 0 {
12034                    self.write(", ");
12035                }
12036                self.generate_identifier(&col.name)?;
12037                if let Some(ref comment) = col.comment {
12038                    self.write_space();
12039                    self.write_keyword("COMMENT");
12040                    self.write(" ");
12041                    self.generate_string_literal(comment)?;
12042                }
12043            }
12044            self.write(")");
12045        }
12046
12047        // TSQL: WITH option before actions
12048        if let Some(ref opt) = av.with_option {
12049            self.write_space();
12050            self.write_keyword("WITH");
12051            self.write_space();
12052            self.write_keyword(opt);
12053        }
12054
12055        for action in &av.actions {
12056            self.write_space();
12057            match action {
12058                AlterViewAction::Rename(new_name) => {
12059                    self.write_keyword("RENAME TO");
12060                    self.write_space();
12061                    self.generate_table(new_name)?;
12062                }
12063                AlterViewAction::OwnerTo(owner) => {
12064                    self.write_keyword("OWNER TO");
12065                    self.write_space();
12066                    self.generate_identifier(owner)?;
12067                }
12068                AlterViewAction::SetSchema(schema) => {
12069                    self.write_keyword("SET SCHEMA");
12070                    self.write_space();
12071                    self.generate_identifier(schema)?;
12072                }
12073                AlterViewAction::SetAuthorization(auth) => {
12074                    self.write_keyword("SET AUTHORIZATION");
12075                    self.write_space();
12076                    self.write(auth);
12077                }
12078                AlterViewAction::AlterColumn { name, action } => {
12079                    self.write_keyword("ALTER COLUMN");
12080                    self.write_space();
12081                    self.generate_identifier(name)?;
12082                    self.write_space();
12083                    self.generate_alter_column_action(action)?;
12084                }
12085                AlterViewAction::AsSelect(query) => {
12086                    self.write_keyword("AS");
12087                    self.write_space();
12088                    self.generate_expression(query)?;
12089                }
12090                AlterViewAction::SetTblproperties(props) => {
12091                    self.write_keyword("SET TBLPROPERTIES");
12092                    self.write(" (");
12093                    for (i, (key, value)) in props.iter().enumerate() {
12094                        if i > 0 {
12095                            self.write(", ");
12096                        }
12097                        self.generate_string_literal(key)?;
12098                        self.write("=");
12099                        self.generate_string_literal(value)?;
12100                    }
12101                    self.write(")");
12102                }
12103                AlterViewAction::UnsetTblproperties(keys) => {
12104                    self.write_keyword("UNSET TBLPROPERTIES");
12105                    self.write(" (");
12106                    for (i, key) in keys.iter().enumerate() {
12107                        if i > 0 {
12108                            self.write(", ");
12109                        }
12110                        self.generate_string_literal(key)?;
12111                    }
12112                    self.write(")");
12113                }
12114            }
12115        }
12116
12117        Ok(())
12118    }
12119
12120    fn generate_alter_index(&mut self, ai: &AlterIndex) -> Result<()> {
12121        self.write_keyword("ALTER INDEX");
12122        self.write_space();
12123        self.generate_identifier(&ai.name)?;
12124
12125        if let Some(table) = &ai.table {
12126            self.write_space();
12127            self.write_keyword("ON");
12128            self.write_space();
12129            self.generate_table(table)?;
12130        }
12131
12132        for action in &ai.actions {
12133            self.write_space();
12134            match action {
12135                AlterIndexAction::Rename(new_name) => {
12136                    self.write_keyword("RENAME TO");
12137                    self.write_space();
12138                    self.generate_identifier(new_name)?;
12139                }
12140                AlterIndexAction::SetTablespace(tablespace) => {
12141                    self.write_keyword("SET TABLESPACE");
12142                    self.write_space();
12143                    self.generate_identifier(tablespace)?;
12144                }
12145                AlterIndexAction::Visible(visible) => {
12146                    if *visible {
12147                        self.write_keyword("VISIBLE");
12148                    } else {
12149                        self.write_keyword("INVISIBLE");
12150                    }
12151                }
12152            }
12153        }
12154
12155        Ok(())
12156    }
12157
12158    fn generate_create_schema(&mut self, cs: &CreateSchema) -> Result<()> {
12159        // Output leading comments
12160        for comment in &cs.leading_comments {
12161            self.write_formatted_comment(comment);
12162            self.write_space();
12163        }
12164
12165        // Athena: CREATE SCHEMA uses Hive engine (backticks)
12166        let saved_athena_hive_context = self.athena_hive_context;
12167        if matches!(
12168            self.config.dialect,
12169            Some(crate::dialects::DialectType::Athena)
12170        ) {
12171            self.athena_hive_context = true;
12172        }
12173
12174        self.write_keyword("CREATE SCHEMA");
12175
12176        if cs.if_not_exists {
12177            self.write_space();
12178            self.write_keyword("IF NOT EXISTS");
12179        }
12180
12181        self.write_space();
12182        for (i, part) in cs.name.iter().enumerate() {
12183            if i > 0 {
12184                self.write(".");
12185            }
12186            self.generate_identifier(part)?;
12187        }
12188
12189        if let Some(ref clone_parts) = cs.clone_from {
12190            self.write_keyword(" CLONE ");
12191            for (i, part) in clone_parts.iter().enumerate() {
12192                if i > 0 {
12193                    self.write(".");
12194                }
12195                self.generate_identifier(part)?;
12196            }
12197        }
12198
12199        if let Some(ref at_clause) = cs.at_clause {
12200            self.write_space();
12201            self.generate_expression(at_clause)?;
12202        }
12203
12204        if let Some(auth) = &cs.authorization {
12205            self.write_space();
12206            self.write_keyword("AUTHORIZATION");
12207            self.write_space();
12208            self.generate_identifier(auth)?;
12209        }
12210
12211        // Generate schema properties (e.g., DEFAULT COLLATE or WITH (props))
12212        // Separate WITH properties from other properties
12213        let with_properties: Vec<_> = cs
12214            .properties
12215            .iter()
12216            .filter(|p| matches!(p, Expression::Property(_)))
12217            .collect();
12218        let other_properties: Vec<_> = cs
12219            .properties
12220            .iter()
12221            .filter(|p| !matches!(p, Expression::Property(_)))
12222            .collect();
12223
12224        // Generate WITH (props) if we have Property expressions
12225        if !with_properties.is_empty() {
12226            self.write_space();
12227            self.write_keyword("WITH");
12228            self.write(" (");
12229            for (i, prop) in with_properties.iter().enumerate() {
12230                if i > 0 {
12231                    self.write(", ");
12232                }
12233                self.generate_expression(prop)?;
12234            }
12235            self.write(")");
12236        }
12237
12238        // Generate other properties (like DEFAULT COLLATE)
12239        for prop in other_properties {
12240            self.write_space();
12241            self.generate_expression(prop)?;
12242        }
12243
12244        // Restore Athena Hive context
12245        self.athena_hive_context = saved_athena_hive_context;
12246
12247        Ok(())
12248    }
12249
12250    fn generate_drop_schema(&mut self, ds: &DropSchema) -> Result<()> {
12251        self.write_keyword("DROP SCHEMA");
12252
12253        if ds.if_exists {
12254            self.write_space();
12255            self.write_keyword("IF EXISTS");
12256        }
12257
12258        self.write_space();
12259        self.generate_identifier(&ds.name)?;
12260
12261        if ds.cascade {
12262            self.write_space();
12263            self.write_keyword("CASCADE");
12264        }
12265
12266        Ok(())
12267    }
12268
12269    fn generate_drop_namespace(&mut self, dn: &DropNamespace) -> Result<()> {
12270        self.write_keyword("DROP NAMESPACE");
12271
12272        if dn.if_exists {
12273            self.write_space();
12274            self.write_keyword("IF EXISTS");
12275        }
12276
12277        self.write_space();
12278        self.generate_identifier(&dn.name)?;
12279
12280        if dn.cascade {
12281            self.write_space();
12282            self.write_keyword("CASCADE");
12283        }
12284
12285        Ok(())
12286    }
12287
12288    fn generate_create_database(&mut self, cd: &CreateDatabase) -> Result<()> {
12289        self.write_keyword("CREATE DATABASE");
12290
12291        if cd.if_not_exists {
12292            self.write_space();
12293            self.write_keyword("IF NOT EXISTS");
12294        }
12295
12296        self.write_space();
12297        self.generate_identifier(&cd.name)?;
12298
12299        if let Some(ref clone_src) = cd.clone_from {
12300            self.write_keyword(" CLONE ");
12301            self.generate_identifier(clone_src)?;
12302        }
12303
12304        // AT/BEFORE clause for time travel (Snowflake)
12305        if let Some(ref at_clause) = cd.at_clause {
12306            self.write_space();
12307            self.generate_expression(at_clause)?;
12308        }
12309
12310        for option in &cd.options {
12311            self.write_space();
12312            match option {
12313                DatabaseOption::CharacterSet(charset) => {
12314                    self.write_keyword("CHARACTER SET");
12315                    self.write(" = ");
12316                    self.write(&format!("'{}'", charset));
12317                }
12318                DatabaseOption::Collate(collate) => {
12319                    self.write_keyword("COLLATE");
12320                    self.write(" = ");
12321                    self.write(&format!("'{}'", collate));
12322                }
12323                DatabaseOption::Owner(owner) => {
12324                    self.write_keyword("OWNER");
12325                    self.write(" = ");
12326                    self.generate_identifier(owner)?;
12327                }
12328                DatabaseOption::Template(template) => {
12329                    self.write_keyword("TEMPLATE");
12330                    self.write(" = ");
12331                    self.generate_identifier(template)?;
12332                }
12333                DatabaseOption::Encoding(encoding) => {
12334                    self.write_keyword("ENCODING");
12335                    self.write(" = ");
12336                    self.write(&format!("'{}'", encoding));
12337                }
12338                DatabaseOption::Location(location) => {
12339                    self.write_keyword("LOCATION");
12340                    self.write(" = ");
12341                    self.write(&format!("'{}'", location));
12342                }
12343            }
12344        }
12345
12346        Ok(())
12347    }
12348
12349    fn generate_drop_database(&mut self, dd: &DropDatabase) -> Result<()> {
12350        self.write_keyword("DROP DATABASE");
12351
12352        if dd.if_exists {
12353            self.write_space();
12354            self.write_keyword("IF EXISTS");
12355        }
12356
12357        self.write_space();
12358        self.generate_identifier(&dd.name)?;
12359
12360        if dd.sync {
12361            self.write_space();
12362            self.write_keyword("SYNC");
12363        }
12364
12365        Ok(())
12366    }
12367
12368    fn generate_create_function(&mut self, cf: &CreateFunction) -> Result<()> {
12369        self.write_keyword("CREATE");
12370
12371        if cf.or_alter {
12372            self.write_space();
12373            self.write_keyword("OR ALTER");
12374        } else if cf.or_replace {
12375            self.write_space();
12376            self.write_keyword("OR REPLACE");
12377        }
12378
12379        if cf.temporary {
12380            self.write_space();
12381            self.write_keyword("TEMPORARY");
12382        }
12383
12384        self.write_space();
12385        if cf.is_table_function {
12386            self.write_keyword("TABLE FUNCTION");
12387        } else {
12388            self.write_keyword("FUNCTION");
12389        }
12390
12391        if cf.if_not_exists {
12392            self.write_space();
12393            self.write_keyword("IF NOT EXISTS");
12394        }
12395
12396        self.write_space();
12397        self.generate_table(&cf.name)?;
12398        if cf.has_parens {
12399            let func_multiline = self.config.pretty
12400                && matches!(
12401                    self.config.dialect,
12402                    Some(crate::dialects::DialectType::TSQL)
12403                        | Some(crate::dialects::DialectType::Fabric)
12404                )
12405                && !cf.parameters.is_empty();
12406            if func_multiline {
12407                self.write("(\n");
12408                self.indent_level += 2;
12409                self.write_indent();
12410                self.generate_function_parameters(&cf.parameters)?;
12411                self.write("\n");
12412                self.indent_level -= 2;
12413                self.write(")");
12414            } else {
12415                self.write("(");
12416                self.generate_function_parameters(&cf.parameters)?;
12417                self.write(")");
12418            }
12419        }
12420
12421        // Output RETURNS clause (always comes first after parameters)
12422        // BigQuery and TSQL use multiline formatting for CREATE FUNCTION structure
12423        let use_multiline = self.config.pretty
12424            && matches!(
12425                self.config.dialect,
12426                Some(crate::dialects::DialectType::BigQuery)
12427                    | Some(crate::dialects::DialectType::TSQL)
12428                    | Some(crate::dialects::DialectType::Fabric)
12429            );
12430
12431        if cf.language_first {
12432            // LANGUAGE first, then SQL data access, then RETURNS
12433            if let Some(lang) = &cf.language {
12434                if use_multiline {
12435                    self.write_newline();
12436                } else {
12437                    self.write_space();
12438                }
12439                self.write_keyword("LANGUAGE");
12440                self.write_space();
12441                self.write(lang);
12442            }
12443
12444            // SQL data access comes after LANGUAGE in this case
12445            if let Some(sql_data) = &cf.sql_data_access {
12446                self.write_space();
12447                match sql_data {
12448                    SqlDataAccess::NoSql => self.write_keyword("NO SQL"),
12449                    SqlDataAccess::ContainsSql => self.write_keyword("CONTAINS SQL"),
12450                    SqlDataAccess::ReadsSqlData => self.write_keyword("READS SQL DATA"),
12451                    SqlDataAccess::ModifiesSqlData => self.write_keyword("MODIFIES SQL DATA"),
12452                }
12453            }
12454
12455            if let Some(ref rtb) = cf.returns_table_body {
12456                if use_multiline {
12457                    self.write_newline();
12458                } else {
12459                    self.write_space();
12460                }
12461                self.write_keyword("RETURNS");
12462                self.write_space();
12463                self.write(rtb);
12464            } else if let Some(return_type) = &cf.return_type {
12465                if use_multiline {
12466                    self.write_newline();
12467                } else {
12468                    self.write_space();
12469                }
12470                self.write_keyword("RETURNS");
12471                self.write_space();
12472                self.generate_data_type(return_type)?;
12473            }
12474        } else {
12475            // RETURNS first (default)
12476            // DuckDB macros: skip RETURNS output (empty marker in returns_table_body means TABLE return)
12477            let is_duckdb = matches!(
12478                self.config.dialect,
12479                Some(crate::dialects::DialectType::DuckDB)
12480            );
12481            if let Some(ref rtb) = cf.returns_table_body {
12482                if !(is_duckdb && rtb.is_empty()) {
12483                    if use_multiline {
12484                        self.write_newline();
12485                    } else {
12486                        self.write_space();
12487                    }
12488                    self.write_keyword("RETURNS");
12489                    self.write_space();
12490                    self.write(rtb);
12491                }
12492            } else if let Some(return_type) = &cf.return_type {
12493                // DuckDB: skip all RETURNS (DuckDB macros don't use RETURNS clause)
12494                if !is_duckdb {
12495                    let is_table_return = matches!(return_type, crate::expressions::DataType::Custom { ref name } if name.eq_ignore_ascii_case("TABLE"));
12496                    if use_multiline {
12497                        self.write_newline();
12498                    } else {
12499                        self.write_space();
12500                    }
12501                    self.write_keyword("RETURNS");
12502                    self.write_space();
12503                    if is_table_return {
12504                        self.write_keyword("TABLE");
12505                    } else {
12506                        self.generate_data_type(return_type)?;
12507                    }
12508                }
12509            }
12510        }
12511
12512        // If we have property_order, use it to output properties in original order
12513        if !cf.property_order.is_empty() {
12514            // For BigQuery, OPTIONS must come before AS - reorder if needed
12515            let is_bigquery = matches!(
12516                self.config.dialect,
12517                Some(crate::dialects::DialectType::BigQuery)
12518            );
12519            let property_order = if is_bigquery {
12520                // Move Options before As if both are present
12521                let mut reordered = Vec::new();
12522                let mut has_as = false;
12523                let mut has_options = false;
12524                for prop in &cf.property_order {
12525                    match prop {
12526                        FunctionPropertyKind::As => has_as = true,
12527                        FunctionPropertyKind::Options => has_options = true,
12528                        _ => {}
12529                    }
12530                }
12531                if has_as && has_options {
12532                    // Output all props except As and Options, then Options, then As
12533                    for prop in &cf.property_order {
12534                        if *prop != FunctionPropertyKind::As
12535                            && *prop != FunctionPropertyKind::Options
12536                        {
12537                            reordered.push(*prop);
12538                        }
12539                    }
12540                    reordered.push(FunctionPropertyKind::Options);
12541                    reordered.push(FunctionPropertyKind::As);
12542                    reordered
12543                } else {
12544                    cf.property_order.clone()
12545                }
12546            } else {
12547                cf.property_order.clone()
12548            };
12549
12550            for prop in &property_order {
12551                match prop {
12552                    FunctionPropertyKind::Set => {
12553                        self.generate_function_set_options(cf)?;
12554                    }
12555                    FunctionPropertyKind::As => {
12556                        self.generate_function_body(cf)?;
12557                    }
12558                    FunctionPropertyKind::Using => {
12559                        self.generate_function_using_resources(cf)?;
12560                    }
12561                    FunctionPropertyKind::Language => {
12562                        if !cf.language_first {
12563                            // Only output here if not already output above
12564                            if let Some(lang) = &cf.language {
12565                                // Only BigQuery uses multiline formatting
12566                                let use_multiline = self.config.pretty
12567                                    && matches!(
12568                                        self.config.dialect,
12569                                        Some(crate::dialects::DialectType::BigQuery)
12570                                    );
12571                                if use_multiline {
12572                                    self.write_newline();
12573                                } else {
12574                                    self.write_space();
12575                                }
12576                                self.write_keyword("LANGUAGE");
12577                                self.write_space();
12578                                self.write(lang);
12579                            }
12580                        }
12581                    }
12582                    FunctionPropertyKind::Determinism => {
12583                        self.generate_function_determinism(cf)?;
12584                    }
12585                    FunctionPropertyKind::NullInput => {
12586                        self.generate_function_null_input(cf)?;
12587                    }
12588                    FunctionPropertyKind::Security => {
12589                        self.generate_function_security(cf)?;
12590                    }
12591                    FunctionPropertyKind::SqlDataAccess => {
12592                        if !cf.language_first {
12593                            // Only output here if not already output above
12594                            self.generate_function_sql_data_access(cf)?;
12595                        }
12596                    }
12597                    FunctionPropertyKind::Options => {
12598                        if !cf.options.is_empty() {
12599                            self.write_space();
12600                            self.generate_options_clause(&cf.options)?;
12601                        }
12602                    }
12603                    FunctionPropertyKind::Environment => {
12604                        if !cf.environment.is_empty() {
12605                            self.write_space();
12606                            self.generate_environment_clause(&cf.environment)?;
12607                        }
12608                    }
12609                    FunctionPropertyKind::Handler => {
12610                        if let Some(ref h) = cf.handler {
12611                            self.write_space();
12612                            self.write_keyword("HANDLER");
12613                            if cf.handler_uses_eq {
12614                                self.write(" = ");
12615                            } else {
12616                                self.write_space();
12617                            }
12618                            self.write("'");
12619                            self.write(h);
12620                            self.write("'");
12621                        }
12622                    }
12623                    FunctionPropertyKind::RuntimeVersion => {
12624                        if let Some(ref runtime_version) = cf.runtime_version {
12625                            self.write_space();
12626                            self.write_keyword("RUNTIME_VERSION");
12627                            self.write("='");
12628                            self.write(runtime_version);
12629                            self.write("'");
12630                        }
12631                    }
12632                    FunctionPropertyKind::Packages => {
12633                        if let Some(ref packages) = cf.packages {
12634                            self.write_space();
12635                            self.write_keyword("PACKAGES");
12636                            self.write("=(");
12637                            for (i, package) in packages.iter().enumerate() {
12638                                if i > 0 {
12639                                    self.write(", ");
12640                                }
12641                                self.write("'");
12642                                self.write(package);
12643                                self.write("'");
12644                            }
12645                            self.write(")");
12646                        }
12647                    }
12648                    FunctionPropertyKind::ParameterStyle => {
12649                        if let Some(ref ps) = cf.parameter_style {
12650                            self.write_space();
12651                            self.write_keyword("PARAMETER STYLE");
12652                            self.write_space();
12653                            self.write_keyword(ps);
12654                        }
12655                    }
12656                }
12657            }
12658
12659            // Output OPTIONS if not tracked in property_order (legacy)
12660            if !cf.options.is_empty() && !cf.property_order.contains(&FunctionPropertyKind::Options)
12661            {
12662                self.write_space();
12663                self.generate_options_clause(&cf.options)?;
12664            }
12665
12666            // Output ENVIRONMENT if not tracked in property_order (legacy)
12667            if !cf.environment.is_empty()
12668                && !cf
12669                    .property_order
12670                    .contains(&FunctionPropertyKind::Environment)
12671            {
12672                self.write_space();
12673                self.generate_environment_clause(&cf.environment)?;
12674            }
12675        } else {
12676            // Legacy behavior when property_order is empty
12677            // BigQuery: DETERMINISTIC/NOT DETERMINISTIC comes before LANGUAGE
12678            if matches!(
12679                self.config.dialect,
12680                Some(crate::dialects::DialectType::BigQuery)
12681            ) {
12682                self.generate_function_determinism(cf)?;
12683            }
12684
12685            // Only BigQuery uses multiline formatting for CREATE FUNCTION structure
12686            let use_multiline = self.config.pretty
12687                && matches!(
12688                    self.config.dialect,
12689                    Some(crate::dialects::DialectType::BigQuery)
12690                );
12691
12692            if !cf.language_first {
12693                if let Some(lang) = &cf.language {
12694                    if use_multiline {
12695                        self.write_newline();
12696                    } else {
12697                        self.write_space();
12698                    }
12699                    self.write_keyword("LANGUAGE");
12700                    self.write_space();
12701                    self.write(lang);
12702                }
12703
12704                // SQL data access characteristic comes after LANGUAGE
12705                self.generate_function_sql_data_access(cf)?;
12706            }
12707
12708            // For non-BigQuery dialects, output DETERMINISTIC/IMMUTABLE/VOLATILE here
12709            if !matches!(
12710                self.config.dialect,
12711                Some(crate::dialects::DialectType::BigQuery)
12712            ) {
12713                self.generate_function_determinism(cf)?;
12714            }
12715
12716            self.generate_function_null_input(cf)?;
12717            self.generate_function_security(cf)?;
12718            self.generate_function_set_options(cf)?;
12719
12720            // BigQuery: OPTIONS (key=value, ...) - comes before AS
12721            if !cf.options.is_empty() {
12722                self.write_space();
12723                self.generate_options_clause(&cf.options)?;
12724            }
12725
12726            // Databricks: ENVIRONMENT (dependencies = '...', ...) - comes before AS
12727            if !cf.environment.is_empty() {
12728                self.write_space();
12729                self.generate_environment_clause(&cf.environment)?;
12730            }
12731
12732            if let Some(ref h) = cf.handler {
12733                self.write_space();
12734                self.write_keyword("HANDLER");
12735                if cf.handler_uses_eq {
12736                    self.write(" = ");
12737                } else {
12738                    self.write_space();
12739                }
12740                self.write("'");
12741                self.write(h);
12742                self.write("'");
12743            }
12744
12745            if let Some(ref runtime_version) = cf.runtime_version {
12746                self.write_space();
12747                self.write_keyword("RUNTIME_VERSION");
12748                self.write("='");
12749                self.write(runtime_version);
12750                self.write("'");
12751            }
12752
12753            if let Some(ref packages) = cf.packages {
12754                self.write_space();
12755                self.write_keyword("PACKAGES");
12756                self.write("=(");
12757                for (i, package) in packages.iter().enumerate() {
12758                    if i > 0 {
12759                        self.write(", ");
12760                    }
12761                    self.write("'");
12762                    self.write(package);
12763                    self.write("'");
12764                }
12765                self.write(")");
12766            }
12767
12768            self.generate_function_body(cf)?;
12769            self.generate_function_using_resources(cf)?;
12770        }
12771
12772        Ok(())
12773    }
12774
12775    /// Generate SET options for CREATE FUNCTION
12776    fn generate_function_set_options(&mut self, cf: &CreateFunction) -> Result<()> {
12777        for opt in &cf.set_options {
12778            self.write_space();
12779            self.write_keyword("SET");
12780            self.write_space();
12781            self.write(&opt.name);
12782            match &opt.value {
12783                FunctionSetValue::Value { value, use_to } => {
12784                    if *use_to {
12785                        self.write(" TO ");
12786                    } else {
12787                        self.write(" = ");
12788                    }
12789                    self.write(value);
12790                }
12791                FunctionSetValue::FromCurrent => {
12792                    self.write_space();
12793                    self.write_keyword("FROM CURRENT");
12794                }
12795            }
12796        }
12797        Ok(())
12798    }
12799
12800    fn generate_function_using_resources(&mut self, cf: &CreateFunction) -> Result<()> {
12801        if cf.using_resources.is_empty() {
12802            return Ok(());
12803        }
12804
12805        self.write_space();
12806        self.write_keyword("USING");
12807        for resource in &cf.using_resources {
12808            self.write_space();
12809            self.write_keyword(&resource.kind);
12810            self.write_space();
12811            self.generate_string_literal(&resource.uri)?;
12812        }
12813        Ok(())
12814    }
12815
12816    /// Generate function body (AS clause)
12817    fn generate_function_body(&mut self, cf: &CreateFunction) -> Result<()> {
12818        if let Some(body) = &cf.body {
12819            // AS stays on same line as previous content (e.g., LANGUAGE js AS)
12820            self.write_space();
12821            // Only BigQuery uses multiline formatting for CREATE FUNCTION body
12822            let use_multiline = self.config.pretty
12823                && matches!(
12824                    self.config.dialect,
12825                    Some(crate::dialects::DialectType::BigQuery)
12826                );
12827            match body {
12828                FunctionBody::Block(block) => {
12829                    self.write_keyword("AS");
12830                    if matches!(
12831                        self.config.dialect,
12832                        Some(crate::dialects::DialectType::TSQL)
12833                    ) {
12834                        self.write(" BEGIN ");
12835                        self.write(block);
12836                        self.write(" END");
12837                    } else if matches!(
12838                        self.config.dialect,
12839                        Some(crate::dialects::DialectType::PostgreSQL)
12840                    ) {
12841                        self.write(" $$");
12842                        self.write(block);
12843                        self.write("$$");
12844                    } else {
12845                        // Escape content for single-quoted output
12846                        let escaped = self.escape_block_for_single_quote(block);
12847                        // In BigQuery pretty mode, body content goes on new line
12848                        if use_multiline {
12849                            self.write_newline();
12850                        } else {
12851                            self.write(" ");
12852                        }
12853                        self.write("'");
12854                        self.write(&escaped);
12855                        self.write("'");
12856                    }
12857                }
12858                FunctionBody::StringLiteral(s) => {
12859                    self.write_keyword("AS");
12860                    // In BigQuery pretty mode, body content goes on new line
12861                    if use_multiline {
12862                        self.write_newline();
12863                    } else {
12864                        self.write(" ");
12865                    }
12866                    self.write("'");
12867                    self.write(s);
12868                    self.write("'");
12869                }
12870                FunctionBody::Expression(expr) => {
12871                    self.write_keyword("AS");
12872                    self.write_space();
12873                    self.generate_expression(expr)?;
12874                }
12875                FunctionBody::External(name) => {
12876                    self.write_keyword("EXTERNAL NAME");
12877                    self.write(" '");
12878                    self.write(name);
12879                    self.write("'");
12880                }
12881                FunctionBody::Return(expr) => {
12882                    if matches!(
12883                        self.config.dialect,
12884                        Some(crate::dialects::DialectType::DuckDB)
12885                    ) {
12886                        // DuckDB macro syntax: AS [TABLE] expression (no RETURN keyword)
12887                        self.write_keyword("AS");
12888                        self.write_space();
12889                        // Check both returns_table_body marker and return_type = Custom "TABLE"
12890                        let is_table_return = cf.returns_table_body.is_some()
12891                            || matches!(&cf.return_type, Some(crate::expressions::DataType::Custom { ref name }) if name.eq_ignore_ascii_case("TABLE"));
12892                        if is_table_return {
12893                            self.write_keyword("TABLE");
12894                            self.write_space();
12895                        }
12896                        self.generate_expression(expr)?;
12897                    } else {
12898                        if self.config.create_function_return_as {
12899                            self.write_keyword("AS");
12900                            // TSQL pretty: newline between AS and RETURN
12901                            if self.config.pretty
12902                                && matches!(
12903                                    self.config.dialect,
12904                                    Some(crate::dialects::DialectType::TSQL)
12905                                        | Some(crate::dialects::DialectType::Fabric)
12906                                )
12907                            {
12908                                self.write_newline();
12909                            } else {
12910                                self.write_space();
12911                            }
12912                        }
12913                        self.write_keyword("RETURN");
12914                        self.write_space();
12915                        self.generate_expression(expr)?;
12916                    }
12917                }
12918                FunctionBody::Statements(stmts) => {
12919                    self.write_keyword("AS");
12920                    self.write(" BEGIN ");
12921                    for (i, stmt) in stmts.iter().enumerate() {
12922                        if i > 0 {
12923                            self.write(" ");
12924                        }
12925                        self.generate_expression(stmt)?;
12926                        self.write(";");
12927                    }
12928                    self.write(" END");
12929                }
12930                FunctionBody::RawBlock(text) => {
12931                    self.write_newline();
12932                    self.write(text);
12933                }
12934                FunctionBody::DollarQuoted { content, tag } => {
12935                    self.write_keyword("AS");
12936                    self.write(" ");
12937                    // Dialects that support dollar-quoted strings: PostgreSQL, Databricks, Redshift, DuckDB
12938                    let supports_dollar_quoting = matches!(
12939                        self.config.dialect,
12940                        Some(crate::dialects::DialectType::PostgreSQL)
12941                            | Some(crate::dialects::DialectType::Databricks)
12942                            | Some(crate::dialects::DialectType::Redshift)
12943                            | Some(crate::dialects::DialectType::DuckDB)
12944                    );
12945                    if supports_dollar_quoting {
12946                        // Output in dollar-quoted format
12947                        self.write("$");
12948                        if let Some(t) = tag {
12949                            self.write(t);
12950                        }
12951                        self.write("$");
12952                        self.write(content);
12953                        self.write("$");
12954                        if let Some(t) = tag {
12955                            self.write(t);
12956                        }
12957                        self.write("$");
12958                    } else {
12959                        // Convert to single-quoted string for other dialects
12960                        let escaped = self.escape_block_for_single_quote(content);
12961                        self.write("'");
12962                        self.write(&escaped);
12963                        self.write("'");
12964                    }
12965                }
12966            }
12967        }
12968        Ok(())
12969    }
12970
12971    /// Generate determinism clause (IMMUTABLE/VOLATILE/DETERMINISTIC)
12972    fn generate_function_determinism(&mut self, cf: &CreateFunction) -> Result<()> {
12973        if let Some(det) = cf.deterministic {
12974            self.write_space();
12975            if matches!(
12976                self.config.dialect,
12977                Some(crate::dialects::DialectType::BigQuery)
12978            ) {
12979                // BigQuery uses DETERMINISTIC/NOT DETERMINISTIC
12980                if det {
12981                    self.write_keyword("DETERMINISTIC");
12982                } else {
12983                    self.write_keyword("NOT DETERMINISTIC");
12984                }
12985            } else {
12986                // PostgreSQL and others use IMMUTABLE/VOLATILE
12987                if det {
12988                    self.write_keyword("IMMUTABLE");
12989                } else {
12990                    self.write_keyword("VOLATILE");
12991                }
12992            }
12993        }
12994        Ok(())
12995    }
12996
12997    /// Generate null input handling clause
12998    fn generate_function_null_input(&mut self, cf: &CreateFunction) -> Result<()> {
12999        if let Some(returns_null) = cf.returns_null_on_null_input {
13000            self.write_space();
13001            if returns_null {
13002                if cf.strict {
13003                    self.write_keyword("STRICT");
13004                } else {
13005                    self.write_keyword("RETURNS NULL ON NULL INPUT");
13006                }
13007            } else {
13008                self.write_keyword("CALLED ON NULL INPUT");
13009            }
13010        }
13011        Ok(())
13012    }
13013
13014    /// Generate security clause
13015    fn generate_function_security(&mut self, cf: &CreateFunction) -> Result<()> {
13016        if let Some(security) = &cf.security {
13017            self.write_space();
13018            // MySQL uses SQL SECURITY prefix
13019            if matches!(
13020                self.config.dialect,
13021                Some(crate::dialects::DialectType::MySQL)
13022            ) {
13023                self.write_keyword("SQL SECURITY");
13024            } else {
13025                self.write_keyword("SECURITY");
13026            }
13027            self.write_space();
13028            match security {
13029                FunctionSecurity::Definer => self.write_keyword("DEFINER"),
13030                FunctionSecurity::Invoker => self.write_keyword("INVOKER"),
13031                FunctionSecurity::None => self.write_keyword("NONE"),
13032            }
13033        }
13034        Ok(())
13035    }
13036
13037    /// Generate SQL data access clause
13038    fn generate_function_sql_data_access(&mut self, cf: &CreateFunction) -> Result<()> {
13039        if let Some(sql_data) = &cf.sql_data_access {
13040            self.write_space();
13041            match sql_data {
13042                SqlDataAccess::NoSql => self.write_keyword("NO SQL"),
13043                SqlDataAccess::ContainsSql => self.write_keyword("CONTAINS SQL"),
13044                SqlDataAccess::ReadsSqlData => self.write_keyword("READS SQL DATA"),
13045                SqlDataAccess::ModifiesSqlData => self.write_keyword("MODIFIES SQL DATA"),
13046            }
13047        }
13048        Ok(())
13049    }
13050
13051    fn generate_function_parameters(&mut self, params: &[FunctionParameter]) -> Result<()> {
13052        for (i, param) in params.iter().enumerate() {
13053            if i > 0 {
13054                self.write(", ");
13055            }
13056
13057            if let Some(mode) = &param.mode {
13058                if let Some(text) = &param.mode_text {
13059                    self.write(text);
13060                } else {
13061                    match mode {
13062                        ParameterMode::In => self.write_keyword("IN"),
13063                        ParameterMode::Out => self.write_keyword("OUT"),
13064                        ParameterMode::InOut => self.write_keyword("INOUT"),
13065                        ParameterMode::Variadic => self.write_keyword("VARIADIC"),
13066                    }
13067                }
13068                self.write_space();
13069            }
13070
13071            if let Some(name) = &param.name {
13072                self.generate_identifier(name)?;
13073                // Skip space and type for empty Custom types (e.g., DuckDB macros)
13074                let skip_type =
13075                    matches!(&param.data_type, DataType::Custom { name } if name.is_empty());
13076                if !skip_type {
13077                    self.write_space();
13078                    self.generate_data_type(&param.data_type)?;
13079                }
13080            } else {
13081                self.generate_data_type(&param.data_type)?;
13082            }
13083
13084            if let Some(default) = &param.default {
13085                if self.config.parameter_default_equals {
13086                    self.write(" = ");
13087                } else {
13088                    self.write(" DEFAULT ");
13089                }
13090                self.generate_expression(default)?;
13091            }
13092        }
13093
13094        Ok(())
13095    }
13096
13097    fn generate_drop_function(&mut self, df: &DropFunction) -> Result<()> {
13098        self.write_keyword("DROP FUNCTION");
13099
13100        if df.if_exists {
13101            self.write_space();
13102            self.write_keyword("IF EXISTS");
13103        }
13104
13105        self.write_space();
13106        self.generate_table(&df.name)?;
13107
13108        if let Some(params) = &df.parameters {
13109            self.write(" (");
13110            for (i, dt) in params.iter().enumerate() {
13111                if i > 0 {
13112                    self.write(", ");
13113                }
13114                self.generate_data_type(dt)?;
13115            }
13116            self.write(")");
13117        }
13118
13119        if df.cascade {
13120            self.write_space();
13121            self.write_keyword("CASCADE");
13122        }
13123
13124        Ok(())
13125    }
13126
13127    fn generate_create_procedure(&mut self, cp: &CreateProcedure) -> Result<()> {
13128        self.write_keyword("CREATE");
13129
13130        if cp.or_alter {
13131            self.write_space();
13132            self.write_keyword("OR ALTER");
13133        } else if cp.or_replace {
13134            self.write_space();
13135            self.write_keyword("OR REPLACE");
13136        }
13137
13138        self.write_space();
13139        if cp.use_proc_keyword {
13140            self.write_keyword("PROC");
13141        } else {
13142            self.write_keyword("PROCEDURE");
13143        }
13144
13145        if cp.if_not_exists {
13146            self.write_space();
13147            self.write_keyword("IF NOT EXISTS");
13148        }
13149
13150        self.write_space();
13151        self.generate_table(&cp.name)?;
13152        if cp.has_parens {
13153            self.write("(");
13154            self.generate_function_parameters(&cp.parameters)?;
13155            self.write(")");
13156        } else if !cp.parameters.is_empty() {
13157            // TSQL: unparenthesized parameters
13158            self.write_space();
13159            self.generate_function_parameters(&cp.parameters)?;
13160        }
13161
13162        // RETURNS clause (Snowflake)
13163        if let Some(return_type) = &cp.return_type {
13164            self.write_space();
13165            self.write_keyword("RETURNS");
13166            self.write_space();
13167            self.generate_data_type(return_type)?;
13168        }
13169
13170        // EXECUTE AS clause (Snowflake)
13171        if let Some(execute_as) = &cp.execute_as {
13172            self.write_space();
13173            self.write_keyword("EXECUTE AS");
13174            self.write_space();
13175            self.write_keyword(execute_as);
13176        }
13177
13178        if let Some(lang) = &cp.language {
13179            self.write_space();
13180            self.write_keyword("LANGUAGE");
13181            self.write_space();
13182            self.write(lang);
13183        }
13184
13185        if let Some(security) = &cp.security {
13186            self.write_space();
13187            self.write_keyword("SECURITY");
13188            self.write_space();
13189            match security {
13190                FunctionSecurity::Definer => self.write_keyword("DEFINER"),
13191                FunctionSecurity::Invoker => self.write_keyword("INVOKER"),
13192                FunctionSecurity::None => self.write_keyword("NONE"),
13193            }
13194        }
13195
13196        // TSQL WITH options (ENCRYPTION, RECOMPILE, etc.)
13197        if !cp.with_options.is_empty() {
13198            self.write_space();
13199            self.write_keyword("WITH");
13200            self.write_space();
13201            for (i, opt) in cp.with_options.iter().enumerate() {
13202                if i > 0 {
13203                    self.write(", ");
13204                }
13205                self.write(opt);
13206            }
13207        }
13208
13209        if let Some(body) = &cp.body {
13210            self.write_space();
13211            match body {
13212                FunctionBody::Block(block) => {
13213                    self.write_keyword("AS");
13214                    if matches!(
13215                        self.config.dialect,
13216                        Some(crate::dialects::DialectType::TSQL)
13217                    ) {
13218                        self.write(" BEGIN ");
13219                        self.write(block);
13220                        self.write(" END");
13221                    } else if matches!(
13222                        self.config.dialect,
13223                        Some(crate::dialects::DialectType::PostgreSQL)
13224                    ) {
13225                        self.write(" $$");
13226                        self.write(block);
13227                        self.write("$$");
13228                    } else {
13229                        // Escape content for single-quoted output
13230                        let escaped = self.escape_block_for_single_quote(block);
13231                        self.write(" '");
13232                        self.write(&escaped);
13233                        self.write("'");
13234                    }
13235                }
13236                FunctionBody::StringLiteral(s) => {
13237                    self.write_keyword("AS");
13238                    self.write(" '");
13239                    self.write(s);
13240                    self.write("'");
13241                }
13242                FunctionBody::Expression(expr) => {
13243                    self.write_keyword("AS");
13244                    self.write_space();
13245                    self.generate_expression(expr)?;
13246                }
13247                FunctionBody::External(name) => {
13248                    self.write_keyword("EXTERNAL NAME");
13249                    self.write(" '");
13250                    self.write(name);
13251                    self.write("'");
13252                }
13253                FunctionBody::Return(expr) => {
13254                    self.write_keyword("RETURN");
13255                    self.write_space();
13256                    self.generate_expression(expr)?;
13257                }
13258                FunctionBody::Statements(stmts) => {
13259                    self.write_keyword("AS");
13260                    self.write(" BEGIN ");
13261                    for (i, stmt) in stmts.iter().enumerate() {
13262                        if i > 0 {
13263                            self.write(" ");
13264                        }
13265                        self.generate_expression(stmt)?;
13266                        self.write(";");
13267                    }
13268                    self.write(" END");
13269                }
13270                FunctionBody::RawBlock(text) => {
13271                    self.write_newline();
13272                    self.write(text);
13273                }
13274                FunctionBody::DollarQuoted { content, tag } => {
13275                    self.write_keyword("AS");
13276                    self.write(" ");
13277                    // Dialects that support dollar-quoted strings: PostgreSQL, Databricks, Redshift, DuckDB
13278                    let supports_dollar_quoting = matches!(
13279                        self.config.dialect,
13280                        Some(crate::dialects::DialectType::PostgreSQL)
13281                            | Some(crate::dialects::DialectType::Databricks)
13282                            | Some(crate::dialects::DialectType::Redshift)
13283                            | Some(crate::dialects::DialectType::DuckDB)
13284                    );
13285                    if supports_dollar_quoting {
13286                        // Output in dollar-quoted format
13287                        self.write("$");
13288                        if let Some(t) = tag {
13289                            self.write(t);
13290                        }
13291                        self.write("$");
13292                        self.write(content);
13293                        self.write("$");
13294                        if let Some(t) = tag {
13295                            self.write(t);
13296                        }
13297                        self.write("$");
13298                    } else {
13299                        // Convert to single-quoted string for other dialects
13300                        let escaped = self.escape_block_for_single_quote(content);
13301                        self.write("'");
13302                        self.write(&escaped);
13303                        self.write("'");
13304                    }
13305                }
13306            }
13307        }
13308
13309        Ok(())
13310    }
13311
13312    fn generate_drop_procedure(&mut self, dp: &DropProcedure) -> Result<()> {
13313        self.write_keyword("DROP PROCEDURE");
13314
13315        if dp.if_exists {
13316            self.write_space();
13317            self.write_keyword("IF EXISTS");
13318        }
13319
13320        self.write_space();
13321        self.generate_table(&dp.name)?;
13322
13323        if let Some(params) = &dp.parameters {
13324            self.write(" (");
13325            for (i, dt) in params.iter().enumerate() {
13326                if i > 0 {
13327                    self.write(", ");
13328                }
13329                self.generate_data_type(dt)?;
13330            }
13331            self.write(")");
13332        }
13333
13334        if dp.cascade {
13335            self.write_space();
13336            self.write_keyword("CASCADE");
13337        }
13338
13339        Ok(())
13340    }
13341
13342    fn generate_create_sequence(&mut self, cs: &CreateSequence) -> Result<()> {
13343        self.write_keyword("CREATE");
13344
13345        if cs.or_replace {
13346            self.write_space();
13347            self.write_keyword("OR REPLACE");
13348        }
13349
13350        if cs.temporary {
13351            self.write_space();
13352            self.write_keyword("TEMPORARY");
13353        }
13354
13355        self.write_space();
13356        self.write_keyword("SEQUENCE");
13357
13358        if cs.if_not_exists {
13359            self.write_space();
13360            self.write_keyword("IF NOT EXISTS");
13361        }
13362
13363        self.write_space();
13364        self.generate_table(&cs.name)?;
13365
13366        // Output AS <type> if present
13367        if let Some(as_type) = &cs.as_type {
13368            self.write_space();
13369            self.write_keyword("AS");
13370            self.write_space();
13371            self.generate_data_type(as_type)?;
13372        }
13373
13374        // Output COMMENT first (Snowflake convention: COMMENT comes before other properties)
13375        if let Some(comment) = &cs.comment {
13376            self.write_space();
13377            self.write_keyword("COMMENT");
13378            self.write("=");
13379            self.generate_string_literal(comment)?;
13380        }
13381
13382        // If property_order is available, use it to preserve original order
13383        if !cs.property_order.is_empty() {
13384            for prop in &cs.property_order {
13385                match prop {
13386                    SeqPropKind::Start => {
13387                        if let Some(start) = cs.start {
13388                            self.write_space();
13389                            self.write_keyword("START WITH");
13390                            self.write(&format!(" {}", start));
13391                        }
13392                    }
13393                    SeqPropKind::Increment => {
13394                        if let Some(inc) = cs.increment {
13395                            self.write_space();
13396                            self.write_keyword("INCREMENT BY");
13397                            self.write(&format!(" {}", inc));
13398                        }
13399                    }
13400                    SeqPropKind::Minvalue => {
13401                        if let Some(min) = &cs.minvalue {
13402                            self.write_space();
13403                            match min {
13404                                SequenceBound::Value(v) => {
13405                                    self.write_keyword("MINVALUE");
13406                                    self.write(&format!(" {}", v));
13407                                }
13408                                SequenceBound::None => {
13409                                    self.write_keyword("NO MINVALUE");
13410                                }
13411                            }
13412                        }
13413                    }
13414                    SeqPropKind::Maxvalue => {
13415                        if let Some(max) = &cs.maxvalue {
13416                            self.write_space();
13417                            match max {
13418                                SequenceBound::Value(v) => {
13419                                    self.write_keyword("MAXVALUE");
13420                                    self.write(&format!(" {}", v));
13421                                }
13422                                SequenceBound::None => {
13423                                    self.write_keyword("NO MAXVALUE");
13424                                }
13425                            }
13426                        }
13427                    }
13428                    SeqPropKind::Cache => {
13429                        if let Some(cache) = cs.cache {
13430                            self.write_space();
13431                            self.write_keyword("CACHE");
13432                            self.write(&format!(" {}", cache));
13433                        }
13434                    }
13435                    SeqPropKind::NoCache => {
13436                        self.write_space();
13437                        self.write_keyword("NO CACHE");
13438                    }
13439                    SeqPropKind::NoCacheWord => {
13440                        self.write_space();
13441                        self.write_keyword("NOCACHE");
13442                    }
13443                    SeqPropKind::Cycle => {
13444                        self.write_space();
13445                        self.write_keyword("CYCLE");
13446                    }
13447                    SeqPropKind::NoCycle => {
13448                        self.write_space();
13449                        self.write_keyword("NO CYCLE");
13450                    }
13451                    SeqPropKind::NoCycleWord => {
13452                        self.write_space();
13453                        self.write_keyword("NOCYCLE");
13454                    }
13455                    SeqPropKind::OwnedBy => {
13456                        // Skip OWNED BY NONE (it's a no-op)
13457                        if !cs.owned_by_none {
13458                            if let Some(owned) = &cs.owned_by {
13459                                self.write_space();
13460                                self.write_keyword("OWNED BY");
13461                                self.write_space();
13462                                self.generate_table(owned)?;
13463                            }
13464                        }
13465                    }
13466                    SeqPropKind::Order => {
13467                        self.write_space();
13468                        self.write_keyword("ORDER");
13469                    }
13470                    SeqPropKind::NoOrder => {
13471                        self.write_space();
13472                        self.write_keyword("NOORDER");
13473                    }
13474                    SeqPropKind::Comment => {
13475                        // COMMENT is output above, before property_order iteration
13476                    }
13477                    SeqPropKind::Sharing => {
13478                        if let Some(val) = &cs.sharing {
13479                            self.write_space();
13480                            self.write(&format!("SHARING={}", val));
13481                        }
13482                    }
13483                    SeqPropKind::Keep => {
13484                        self.write_space();
13485                        self.write_keyword("KEEP");
13486                    }
13487                    SeqPropKind::NoKeep => {
13488                        self.write_space();
13489                        self.write_keyword("NOKEEP");
13490                    }
13491                    SeqPropKind::Scale => {
13492                        self.write_space();
13493                        self.write_keyword("SCALE");
13494                        if let Some(modifier) = &cs.scale_modifier {
13495                            if !modifier.is_empty() {
13496                                self.write_space();
13497                                self.write_keyword(modifier);
13498                            }
13499                        }
13500                    }
13501                    SeqPropKind::NoScale => {
13502                        self.write_space();
13503                        self.write_keyword("NOSCALE");
13504                    }
13505                    SeqPropKind::Shard => {
13506                        self.write_space();
13507                        self.write_keyword("SHARD");
13508                        if let Some(modifier) = &cs.shard_modifier {
13509                            if !modifier.is_empty() {
13510                                self.write_space();
13511                                self.write_keyword(modifier);
13512                            }
13513                        }
13514                    }
13515                    SeqPropKind::NoShard => {
13516                        self.write_space();
13517                        self.write_keyword("NOSHARD");
13518                    }
13519                    SeqPropKind::Session => {
13520                        self.write_space();
13521                        self.write_keyword("SESSION");
13522                    }
13523                    SeqPropKind::Global => {
13524                        self.write_space();
13525                        self.write_keyword("GLOBAL");
13526                    }
13527                    SeqPropKind::NoMinvalueWord => {
13528                        self.write_space();
13529                        self.write_keyword("NOMINVALUE");
13530                    }
13531                    SeqPropKind::NoMaxvalueWord => {
13532                        self.write_space();
13533                        self.write_keyword("NOMAXVALUE");
13534                    }
13535                }
13536            }
13537        } else {
13538            // Fallback: default order for backwards compatibility
13539            if let Some(inc) = cs.increment {
13540                self.write_space();
13541                self.write_keyword("INCREMENT BY");
13542                self.write(&format!(" {}", inc));
13543            }
13544
13545            if let Some(min) = &cs.minvalue {
13546                self.write_space();
13547                match min {
13548                    SequenceBound::Value(v) => {
13549                        self.write_keyword("MINVALUE");
13550                        self.write(&format!(" {}", v));
13551                    }
13552                    SequenceBound::None => {
13553                        self.write_keyword("NO MINVALUE");
13554                    }
13555                }
13556            }
13557
13558            if let Some(max) = &cs.maxvalue {
13559                self.write_space();
13560                match max {
13561                    SequenceBound::Value(v) => {
13562                        self.write_keyword("MAXVALUE");
13563                        self.write(&format!(" {}", v));
13564                    }
13565                    SequenceBound::None => {
13566                        self.write_keyword("NO MAXVALUE");
13567                    }
13568                }
13569            }
13570
13571            if let Some(start) = cs.start {
13572                self.write_space();
13573                self.write_keyword("START WITH");
13574                self.write(&format!(" {}", start));
13575            }
13576
13577            if let Some(cache) = cs.cache {
13578                self.write_space();
13579                self.write_keyword("CACHE");
13580                self.write(&format!(" {}", cache));
13581            }
13582
13583            if cs.cycle {
13584                self.write_space();
13585                self.write_keyword("CYCLE");
13586            }
13587
13588            if let Some(owned) = &cs.owned_by {
13589                self.write_space();
13590                self.write_keyword("OWNED BY");
13591                self.write_space();
13592                self.generate_table(owned)?;
13593            }
13594        }
13595
13596        Ok(())
13597    }
13598
13599    fn generate_drop_sequence(&mut self, ds: &DropSequence) -> Result<()> {
13600        self.write_keyword("DROP SEQUENCE");
13601
13602        if ds.if_exists {
13603            self.write_space();
13604            self.write_keyword("IF EXISTS");
13605        }
13606
13607        self.write_space();
13608        self.generate_table(&ds.name)?;
13609
13610        if ds.cascade {
13611            self.write_space();
13612            self.write_keyword("CASCADE");
13613        }
13614
13615        Ok(())
13616    }
13617
13618    fn generate_alter_sequence(&mut self, als: &AlterSequence) -> Result<()> {
13619        self.write_keyword("ALTER SEQUENCE");
13620
13621        if als.if_exists {
13622            self.write_space();
13623            self.write_keyword("IF EXISTS");
13624        }
13625
13626        self.write_space();
13627        self.generate_table(&als.name)?;
13628
13629        if let Some(inc) = als.increment {
13630            self.write_space();
13631            self.write_keyword("INCREMENT BY");
13632            self.write(&format!(" {}", inc));
13633        }
13634
13635        if let Some(min) = &als.minvalue {
13636            self.write_space();
13637            match min {
13638                SequenceBound::Value(v) => {
13639                    self.write_keyword("MINVALUE");
13640                    self.write(&format!(" {}", v));
13641                }
13642                SequenceBound::None => {
13643                    self.write_keyword("NO MINVALUE");
13644                }
13645            }
13646        }
13647
13648        if let Some(max) = &als.maxvalue {
13649            self.write_space();
13650            match max {
13651                SequenceBound::Value(v) => {
13652                    self.write_keyword("MAXVALUE");
13653                    self.write(&format!(" {}", v));
13654                }
13655                SequenceBound::None => {
13656                    self.write_keyword("NO MAXVALUE");
13657                }
13658            }
13659        }
13660
13661        if let Some(start) = als.start {
13662            self.write_space();
13663            self.write_keyword("START WITH");
13664            self.write(&format!(" {}", start));
13665        }
13666
13667        if let Some(restart) = &als.restart {
13668            self.write_space();
13669            self.write_keyword("RESTART");
13670            if let Some(val) = restart {
13671                self.write_keyword(" WITH");
13672                self.write(&format!(" {}", val));
13673            }
13674        }
13675
13676        if let Some(cache) = als.cache {
13677            self.write_space();
13678            self.write_keyword("CACHE");
13679            self.write(&format!(" {}", cache));
13680        }
13681
13682        if let Some(cycle) = als.cycle {
13683            self.write_space();
13684            if cycle {
13685                self.write_keyword("CYCLE");
13686            } else {
13687                self.write_keyword("NO CYCLE");
13688            }
13689        }
13690
13691        if let Some(owned) = &als.owned_by {
13692            self.write_space();
13693            self.write_keyword("OWNED BY");
13694            self.write_space();
13695            if let Some(table) = owned {
13696                self.generate_table(table)?;
13697            } else {
13698                self.write_keyword("NONE");
13699            }
13700        }
13701
13702        Ok(())
13703    }
13704
13705    fn generate_create_trigger(&mut self, ct: &CreateTrigger) -> Result<()> {
13706        self.write_keyword("CREATE");
13707
13708        if ct.or_alter {
13709            self.write_space();
13710            self.write_keyword("OR ALTER");
13711        } else if ct.or_replace {
13712            self.write_space();
13713            self.write_keyword("OR REPLACE");
13714        }
13715
13716        if ct.constraint {
13717            self.write_space();
13718            self.write_keyword("CONSTRAINT");
13719        }
13720
13721        self.write_space();
13722        self.write_keyword("TRIGGER");
13723        self.write_space();
13724        self.generate_identifier(&ct.name)?;
13725
13726        self.write_space();
13727        match ct.timing {
13728            TriggerTiming::Before => self.write_keyword("BEFORE"),
13729            TriggerTiming::After => self.write_keyword("AFTER"),
13730            TriggerTiming::InsteadOf => self.write_keyword("INSTEAD OF"),
13731        }
13732
13733        // Events
13734        for (i, event) in ct.events.iter().enumerate() {
13735            if i > 0 {
13736                self.write_keyword(" OR");
13737            }
13738            self.write_space();
13739            match event {
13740                TriggerEvent::Insert => self.write_keyword("INSERT"),
13741                TriggerEvent::Update(cols) => {
13742                    self.write_keyword("UPDATE");
13743                    if let Some(cols) = cols {
13744                        self.write_space();
13745                        self.write_keyword("OF");
13746                        for (j, col) in cols.iter().enumerate() {
13747                            if j > 0 {
13748                                self.write(",");
13749                            }
13750                            self.write_space();
13751                            self.generate_identifier(col)?;
13752                        }
13753                    }
13754                }
13755                TriggerEvent::Delete => self.write_keyword("DELETE"),
13756                TriggerEvent::Truncate => self.write_keyword("TRUNCATE"),
13757            }
13758        }
13759
13760        self.write_space();
13761        self.write_keyword("ON");
13762        self.write_space();
13763        self.generate_table(&ct.table)?;
13764
13765        // Referencing clause
13766        if let Some(ref_clause) = &ct.referencing {
13767            self.write_space();
13768            self.write_keyword("REFERENCING");
13769            if let Some(old_table) = &ref_clause.old_table {
13770                self.write_space();
13771                self.write_keyword("OLD TABLE AS");
13772                self.write_space();
13773                self.generate_identifier(old_table)?;
13774            }
13775            if let Some(new_table) = &ref_clause.new_table {
13776                self.write_space();
13777                self.write_keyword("NEW TABLE AS");
13778                self.write_space();
13779                self.generate_identifier(new_table)?;
13780            }
13781            if let Some(old_row) = &ref_clause.old_row {
13782                self.write_space();
13783                self.write_keyword("OLD ROW AS");
13784                self.write_space();
13785                self.generate_identifier(old_row)?;
13786            }
13787            if let Some(new_row) = &ref_clause.new_row {
13788                self.write_space();
13789                self.write_keyword("NEW ROW AS");
13790                self.write_space();
13791                self.generate_identifier(new_row)?;
13792            }
13793        }
13794
13795        // Deferrable options for constraint triggers (must come before FOR EACH)
13796        if let Some(deferrable) = ct.deferrable {
13797            self.write_space();
13798            if deferrable {
13799                self.write_keyword("DEFERRABLE");
13800            } else {
13801                self.write_keyword("NOT DEFERRABLE");
13802            }
13803        }
13804
13805        if let Some(initially) = ct.initially_deferred {
13806            self.write_space();
13807            self.write_keyword("INITIALLY");
13808            self.write_space();
13809            if initially {
13810                self.write_keyword("DEFERRED");
13811            } else {
13812                self.write_keyword("IMMEDIATE");
13813            }
13814        }
13815
13816        if let Some(for_each) = ct.for_each {
13817            self.write_space();
13818            self.write_keyword("FOR EACH");
13819            self.write_space();
13820            match for_each {
13821                TriggerForEach::Row => self.write_keyword("ROW"),
13822                TriggerForEach::Statement => self.write_keyword("STATEMENT"),
13823            }
13824        }
13825
13826        // When clause
13827        if let Some(when) = &ct.when {
13828            self.write_space();
13829            self.write_keyword("WHEN");
13830            if ct.when_paren {
13831                self.write(" (");
13832                self.generate_expression(when)?;
13833                self.write(")");
13834            } else {
13835                self.write_space();
13836                self.generate_expression(when)?;
13837            }
13838        }
13839
13840        // Body
13841        self.write_space();
13842        match &ct.body {
13843            TriggerBody::Execute { function, args } => {
13844                self.write_keyword("EXECUTE FUNCTION");
13845                self.write_space();
13846                self.generate_table(function)?;
13847                self.write("(");
13848                for (i, arg) in args.iter().enumerate() {
13849                    if i > 0 {
13850                        self.write(", ");
13851                    }
13852                    self.generate_expression(arg)?;
13853                }
13854                self.write(")");
13855            }
13856            TriggerBody::Block(block) => {
13857                self.write_keyword("BEGIN");
13858                self.write_space();
13859                self.write(block);
13860                self.write_space();
13861                self.write_keyword("END");
13862            }
13863        }
13864
13865        Ok(())
13866    }
13867
13868    fn generate_drop_trigger(&mut self, dt: &DropTrigger) -> Result<()> {
13869        self.write_keyword("DROP TRIGGER");
13870
13871        if dt.if_exists {
13872            self.write_space();
13873            self.write_keyword("IF EXISTS");
13874        }
13875
13876        self.write_space();
13877        self.generate_identifier(&dt.name)?;
13878
13879        if let Some(table) = &dt.table {
13880            self.write_space();
13881            self.write_keyword("ON");
13882            self.write_space();
13883            self.generate_table(table)?;
13884        }
13885
13886        if dt.cascade {
13887            self.write_space();
13888            self.write_keyword("CASCADE");
13889        }
13890
13891        Ok(())
13892    }
13893
13894    fn generate_create_type(&mut self, ct: &CreateType) -> Result<()> {
13895        self.write_keyword("CREATE TYPE");
13896
13897        if ct.if_not_exists {
13898            self.write_space();
13899            self.write_keyword("IF NOT EXISTS");
13900        }
13901
13902        self.write_space();
13903        self.generate_table(&ct.name)?;
13904
13905        self.write_space();
13906        self.write_keyword("AS");
13907        self.write_space();
13908
13909        match &ct.definition {
13910            TypeDefinition::Enum(values) => {
13911                self.write_keyword("ENUM");
13912                self.write(" (");
13913                for (i, val) in values.iter().enumerate() {
13914                    if i > 0 {
13915                        self.write(", ");
13916                    }
13917                    self.write(&format!("'{}'", val));
13918                }
13919                self.write(")");
13920            }
13921            TypeDefinition::Composite(attrs) => {
13922                self.write("(");
13923                for (i, attr) in attrs.iter().enumerate() {
13924                    if i > 0 {
13925                        self.write(", ");
13926                    }
13927                    self.generate_identifier(&attr.name)?;
13928                    self.write_space();
13929                    self.generate_data_type(&attr.data_type)?;
13930                    if let Some(collate) = &attr.collate {
13931                        self.write_space();
13932                        self.write_keyword("COLLATE");
13933                        self.write_space();
13934                        self.generate_identifier(collate)?;
13935                    }
13936                }
13937                self.write(")");
13938            }
13939            TypeDefinition::Range {
13940                subtype,
13941                subtype_diff,
13942                canonical,
13943            } => {
13944                self.write_keyword("RANGE");
13945                self.write(" (");
13946                self.write_keyword("SUBTYPE");
13947                self.write(" = ");
13948                self.generate_data_type(subtype)?;
13949                if let Some(diff) = subtype_diff {
13950                    self.write(", ");
13951                    self.write_keyword("SUBTYPE_DIFF");
13952                    self.write(" = ");
13953                    self.write(diff);
13954                }
13955                if let Some(canon) = canonical {
13956                    self.write(", ");
13957                    self.write_keyword("CANONICAL");
13958                    self.write(" = ");
13959                    self.write(canon);
13960                }
13961                self.write(")");
13962            }
13963            TypeDefinition::Base {
13964                input,
13965                output,
13966                internallength,
13967            } => {
13968                self.write("(");
13969                self.write_keyword("INPUT");
13970                self.write(" = ");
13971                self.write(input);
13972                self.write(", ");
13973                self.write_keyword("OUTPUT");
13974                self.write(" = ");
13975                self.write(output);
13976                if let Some(len) = internallength {
13977                    self.write(", ");
13978                    self.write_keyword("INTERNALLENGTH");
13979                    self.write(" = ");
13980                    self.write(&len.to_string());
13981                }
13982                self.write(")");
13983            }
13984            TypeDefinition::Domain {
13985                base_type,
13986                default,
13987                constraints,
13988            } => {
13989                self.generate_data_type(base_type)?;
13990                if let Some(def) = default {
13991                    self.write_space();
13992                    self.write_keyword("DEFAULT");
13993                    self.write_space();
13994                    self.generate_expression(def)?;
13995                }
13996                for constr in constraints {
13997                    self.write_space();
13998                    if let Some(name) = &constr.name {
13999                        self.write_keyword("CONSTRAINT");
14000                        self.write_space();
14001                        self.generate_identifier(name)?;
14002                        self.write_space();
14003                    }
14004                    self.write_keyword("CHECK");
14005                    self.write(" (");
14006                    self.generate_expression(&constr.check)?;
14007                    self.write(")");
14008                }
14009            }
14010        }
14011
14012        Ok(())
14013    }
14014
14015    fn generate_create_task(&mut self, task: &crate::expressions::CreateTask) -> Result<()> {
14016        self.write_keyword("CREATE");
14017        if task.or_replace {
14018            self.write_space();
14019            self.write_keyword("OR REPLACE");
14020        }
14021        self.write_space();
14022        self.write_keyword("TASK");
14023        if task.if_not_exists {
14024            self.write_space();
14025            self.write_keyword("IF NOT EXISTS");
14026        }
14027        self.write_space();
14028        self.write(&task.name);
14029        if !task.properties.is_empty() {
14030            // Properties already include leading whitespace from tokens_to_sql
14031            if !task.properties.starts_with('\n') && !task.properties.starts_with(' ') {
14032                self.write_space();
14033            }
14034            self.write(&task.properties);
14035        }
14036        self.write_space();
14037        self.write_keyword("AS");
14038        self.write_space();
14039        self.generate_expression(&task.body)?;
14040        Ok(())
14041    }
14042
14043    fn generate_drop_type(&mut self, dt: &DropType) -> Result<()> {
14044        self.write_keyword("DROP TYPE");
14045
14046        if dt.if_exists {
14047            self.write_space();
14048            self.write_keyword("IF EXISTS");
14049        }
14050
14051        self.write_space();
14052        self.generate_table(&dt.name)?;
14053
14054        if dt.cascade {
14055            self.write_space();
14056            self.write_keyword("CASCADE");
14057        }
14058
14059        Ok(())
14060    }
14061
14062    fn generate_describe(&mut self, d: &Describe) -> Result<()> {
14063        // Athena: DESCRIBE uses Hive engine (backticks)
14064        let saved_athena_hive_context = self.athena_hive_context;
14065        if matches!(
14066            self.config.dialect,
14067            Some(crate::dialects::DialectType::Athena)
14068        ) {
14069            self.athena_hive_context = true;
14070        }
14071
14072        // Output leading comments before DESCRIBE
14073        for comment in &d.leading_comments {
14074            self.write_formatted_comment(comment);
14075            self.write(" ");
14076        }
14077
14078        self.write_keyword("DESCRIBE");
14079
14080        if d.extended {
14081            self.write_space();
14082            self.write_keyword("EXTENDED");
14083        } else if d.formatted {
14084            self.write_space();
14085            self.write_keyword("FORMATTED");
14086        }
14087
14088        // Output style like ANALYZE, HISTORY
14089        if let Some(ref style) = d.style {
14090            self.write_space();
14091            self.write_keyword(style);
14092        }
14093
14094        // Handle object kind (TABLE, VIEW) based on dialect
14095        let should_output_kind = match self.config.dialect {
14096            // Spark doesn't use TABLE/VIEW after DESCRIBE
14097            Some(DialectType::Spark) | Some(DialectType::Databricks) | Some(DialectType::Hive) => {
14098                false
14099            }
14100            // Snowflake always includes TABLE
14101            Some(DialectType::Snowflake) => true,
14102            _ => d.kind.is_some(),
14103        };
14104        if should_output_kind {
14105            if let Some(ref kind) = d.kind {
14106                self.write_space();
14107                self.write_keyword(kind);
14108            } else if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
14109                self.write_space();
14110                self.write_keyword("TABLE");
14111            }
14112        }
14113
14114        self.write_space();
14115        self.generate_expression(&d.target)?;
14116
14117        // Output parenthesized parameter types for PROCEDURE/FUNCTION
14118        if !d.params.is_empty() {
14119            self.write("(");
14120            for (i, param) in d.params.iter().enumerate() {
14121                if i > 0 {
14122                    self.write(", ");
14123                }
14124                self.write(param);
14125            }
14126            self.write(")");
14127        }
14128
14129        // Output PARTITION clause if present (the Partition expression outputs its own PARTITION keyword)
14130        if let Some(ref partition) = d.partition {
14131            self.write_space();
14132            self.generate_expression(partition)?;
14133        }
14134
14135        // Databricks: AS JSON
14136        if d.as_json {
14137            self.write_space();
14138            self.write_keyword("AS JSON");
14139        }
14140
14141        // Output properties like type=stage
14142        for (name, value) in &d.properties {
14143            self.write_space();
14144            self.write(name);
14145            self.write("=");
14146            self.write(value);
14147        }
14148
14149        // Restore Athena Hive context
14150        self.athena_hive_context = saved_athena_hive_context;
14151
14152        Ok(())
14153    }
14154
14155    /// Generate SHOW statement (Snowflake, MySQL, etc.)
14156    /// SHOW [TERSE] <object_type> [HISTORY] [LIKE pattern] [IN <scope>] [STARTS WITH pattern] [LIMIT n] [FROM object]
14157    fn generate_show(&mut self, s: &Show) -> Result<()> {
14158        self.write_keyword("SHOW");
14159        self.write_space();
14160
14161        // TERSE keyword - but not for PRIMARY KEYS, UNIQUE KEYS, IMPORTED KEYS
14162        // where TERSE is syntactically valid but has no effect on output
14163        let show_terse = s.terse
14164            && !matches!(
14165                s.this.as_str(),
14166                "PRIMARY KEYS" | "UNIQUE KEYS" | "IMPORTED KEYS"
14167            );
14168        if show_terse {
14169            self.write_keyword("TERSE");
14170            self.write_space();
14171        }
14172
14173        // Object type (USERS, TABLES, DATABASES, etc.)
14174        self.write_keyword(&s.this);
14175
14176        // Target identifier (MySQL: engine name in SHOW ENGINE, preserved case)
14177        if let Some(ref target_expr) = s.target {
14178            self.write_space();
14179            self.generate_expression(target_expr)?;
14180        }
14181
14182        // HISTORY keyword
14183        if s.history {
14184            self.write_space();
14185            self.write_keyword("HISTORY");
14186        }
14187
14188        // FOR target (MySQL: SHOW GRANTS FOR foo, SHOW PROFILE ... FOR QUERY 5)
14189        if let Some(ref for_target) = s.for_target {
14190            self.write_space();
14191            self.write_keyword("FOR");
14192            self.write_space();
14193            self.generate_expression(for_target)?;
14194        }
14195
14196        // Determine ordering based on dialect:
14197        // - Snowflake: LIKE, IN, STARTS WITH, LIMIT, FROM
14198        // - MySQL: IN, FROM, LIKE (when FROM is present)
14199        use crate::dialects::DialectType;
14200        let is_snowflake = matches!(self.config.dialect, Some(DialectType::Snowflake));
14201
14202        if !is_snowflake && s.from.is_some() {
14203            // MySQL ordering: IN, FROM, LIKE
14204
14205            // IN scope_kind [scope]
14206            if let Some(ref scope_kind) = s.scope_kind {
14207                self.write_space();
14208                self.write_keyword("IN");
14209                self.write_space();
14210                self.write_keyword(scope_kind);
14211                if let Some(ref scope) = s.scope {
14212                    self.write_space();
14213                    self.generate_expression(scope)?;
14214                }
14215            } else if let Some(ref scope) = s.scope {
14216                self.write_space();
14217                self.write_keyword("IN");
14218                self.write_space();
14219                self.generate_expression(scope)?;
14220            }
14221
14222            // FROM clause
14223            if let Some(ref from) = s.from {
14224                self.write_space();
14225                self.write_keyword("FROM");
14226                self.write_space();
14227                self.generate_expression(from)?;
14228            }
14229
14230            // Second FROM clause (db name)
14231            if let Some(ref db) = s.db {
14232                self.write_space();
14233                self.write_keyword("FROM");
14234                self.write_space();
14235                self.generate_expression(db)?;
14236            }
14237
14238            // LIKE pattern
14239            if let Some(ref like) = s.like {
14240                self.write_space();
14241                self.write_keyword("LIKE");
14242                self.write_space();
14243                self.generate_expression(like)?;
14244            }
14245        } else {
14246            // Snowflake ordering: LIKE, IN, STARTS WITH, LIMIT, FROM
14247
14248            // LIKE pattern
14249            if let Some(ref like) = s.like {
14250                self.write_space();
14251                self.write_keyword("LIKE");
14252                self.write_space();
14253                self.generate_expression(like)?;
14254            }
14255
14256            // IN scope_kind [scope]
14257            if let Some(ref scope_kind) = s.scope_kind {
14258                self.write_space();
14259                self.write_keyword("IN");
14260                self.write_space();
14261                self.write_keyword(scope_kind);
14262                if let Some(ref scope) = s.scope {
14263                    self.write_space();
14264                    self.generate_expression(scope)?;
14265                }
14266            } else if let Some(ref scope) = s.scope {
14267                self.write_space();
14268                self.write_keyword("IN");
14269                self.write_space();
14270                self.generate_expression(scope)?;
14271            }
14272        }
14273
14274        // STARTS WITH pattern
14275        if let Some(ref starts_with) = s.starts_with {
14276            self.write_space();
14277            self.write_keyword("STARTS WITH");
14278            self.write_space();
14279            self.generate_expression(starts_with)?;
14280        }
14281
14282        // LIMIT clause
14283        if let Some(ref limit) = s.limit {
14284            self.write_space();
14285            self.generate_limit(limit)?;
14286        }
14287
14288        // FROM clause (for Snowflake, FROM comes after STARTS WITH and LIMIT)
14289        if is_snowflake {
14290            if let Some(ref from) = s.from {
14291                self.write_space();
14292                self.write_keyword("FROM");
14293                self.write_space();
14294                self.generate_expression(from)?;
14295            }
14296        }
14297
14298        // WHERE clause (MySQL: SHOW STATUS WHERE condition)
14299        if let Some(ref where_clause) = s.where_clause {
14300            self.write_space();
14301            self.write_keyword("WHERE");
14302            self.write_space();
14303            self.generate_expression(where_clause)?;
14304        }
14305
14306        // MUTEX/STATUS suffix (MySQL: SHOW ENGINE foo STATUS/MUTEX)
14307        if let Some(is_mutex) = s.mutex {
14308            self.write_space();
14309            if is_mutex {
14310                self.write_keyword("MUTEX");
14311            } else {
14312                self.write_keyword("STATUS");
14313            }
14314        }
14315
14316        // WITH PRIVILEGES clause (Snowflake: SHOW ... WITH PRIVILEGES USAGE, MODIFY)
14317        if !s.privileges.is_empty() {
14318            self.write_space();
14319            self.write_keyword("WITH PRIVILEGES");
14320            self.write_space();
14321            for (i, priv_name) in s.privileges.iter().enumerate() {
14322                if i > 0 {
14323                    self.write(", ");
14324                }
14325                self.write_keyword(priv_name);
14326            }
14327        }
14328
14329        Ok(())
14330    }
14331
14332    // ==================== End DDL Generation ====================
14333
14334    fn generate_literal(&mut self, lit: &Literal) -> Result<()> {
14335        use crate::dialects::DialectType;
14336        match lit {
14337            Literal::String(s) => {
14338                self.generate_string_literal(s)?;
14339            }
14340            Literal::Number(n) => {
14341                if matches!(self.config.dialect, Some(DialectType::MySQL))
14342                    && n.len() > 2
14343                    && (n.starts_with("0x") || n.starts_with("0X"))
14344                    && !n[2..].chars().all(|c| c.is_ascii_hexdigit())
14345                {
14346                    return self.generate_identifier(&Identifier {
14347                        name: n.clone(),
14348                        quoted: true,
14349                        trailing_comments: Vec::new(),
14350                        span: None,
14351                    });
14352                }
14353                // Strip underscore digit separators (e.g., 1_000_000 -> 1000000)
14354                // for dialects that don't support them (MySQL interprets as identifier).
14355                // ClickHouse, DuckDB, PostgreSQL, and Hive/Spark/Databricks support them.
14356                let n = if n.contains('_')
14357                    && !matches!(
14358                        self.config.dialect,
14359                        Some(DialectType::ClickHouse)
14360                            | Some(DialectType::DuckDB)
14361                            | Some(DialectType::PostgreSQL)
14362                            | Some(DialectType::Hive)
14363                            | Some(DialectType::Spark)
14364                            | Some(DialectType::Databricks)
14365                    ) {
14366                    std::borrow::Cow::Owned(n.replace('_', ""))
14367                } else {
14368                    std::borrow::Cow::Borrowed(n.as_str())
14369                };
14370                // Normalize numbers starting with decimal point to have leading zero
14371                // e.g., .25 -> 0.25 (matches sqlglot behavior)
14372                if n.starts_with('.') {
14373                    self.write("0");
14374                    self.write(&n);
14375                } else if n.starts_with("-.") {
14376                    // Handle negative numbers like -.25 -> -0.25
14377                    self.write("-0");
14378                    self.write(&n[1..]);
14379                } else {
14380                    self.write(&n);
14381                }
14382            }
14383            Literal::HexString(h) => {
14384                // Most dialects use lowercase x'...' for hex literals; Spark/Databricks/Teradata use uppercase X'...'
14385                match self.config.dialect {
14386                    Some(DialectType::Spark)
14387                    | Some(DialectType::Databricks)
14388                    | Some(DialectType::Teradata) => self.write("X'"),
14389                    _ => self.write("x'"),
14390                }
14391                self.write(h);
14392                self.write("'");
14393            }
14394            Literal::HexNumber(h) => {
14395                // Hex number (0xA) - integer in hex notation (from BigQuery)
14396                // For BigQuery, TSQL, Fabric output as 0xHEX (native hex notation)
14397                // For other dialects, convert to decimal integer
14398                match self.config.dialect {
14399                    Some(DialectType::BigQuery)
14400                    | Some(DialectType::TSQL)
14401                    | Some(DialectType::Fabric) => {
14402                        self.write("0x");
14403                        self.write(h);
14404                    }
14405                    _ => {
14406                        // Convert hex to decimal
14407                        if let Ok(val) = u64::from_str_radix(h, 16) {
14408                            self.write(&val.to_string());
14409                        } else {
14410                            // Fallback: keep as 0x notation
14411                            self.write("0x");
14412                            self.write(h);
14413                        }
14414                    }
14415                }
14416            }
14417            Literal::BitString(b) => {
14418                // Bit string B'0101...'
14419                self.write("B'");
14420                self.write(b);
14421                self.write("'");
14422            }
14423            Literal::ByteString(b) => {
14424                // Byte string b'...' (BigQuery style)
14425                self.write("b'");
14426                // Escape special characters for output
14427                self.write_escaped_byte_string(b);
14428                self.write("'");
14429            }
14430            Literal::NationalString(s) => {
14431                // N'string' is supported by TSQL, Oracle, MySQL, and generic SQL
14432                // Other dialects strip the N prefix and output as regular string
14433                let keep_n_prefix = matches!(
14434                    self.config.dialect,
14435                    Some(DialectType::TSQL)
14436                        | Some(DialectType::Oracle)
14437                        | Some(DialectType::MySQL)
14438                        | None
14439                );
14440                if keep_n_prefix {
14441                    self.write("N'");
14442                } else {
14443                    self.write("'");
14444                }
14445                self.write(s);
14446                self.write("'");
14447            }
14448            Literal::Date(d) => {
14449                self.generate_date_literal(d)?;
14450            }
14451            Literal::Time(t) => {
14452                self.generate_time_literal(t)?;
14453            }
14454            Literal::Timestamp(ts) => {
14455                self.generate_timestamp_literal(ts)?;
14456            }
14457            Literal::Datetime(dt) => {
14458                self.generate_datetime_literal(dt)?;
14459            }
14460            Literal::TripleQuotedString(s, _quote_char) => {
14461                // For BigQuery and other dialects that don't support triple-quote, normalize to regular strings
14462                if matches!(
14463                    self.config.dialect,
14464                    Some(crate::dialects::DialectType::BigQuery)
14465                        | Some(crate::dialects::DialectType::DuckDB)
14466                        | Some(crate::dialects::DialectType::Snowflake)
14467                        | Some(crate::dialects::DialectType::Spark)
14468                        | Some(crate::dialects::DialectType::Hive)
14469                        | Some(crate::dialects::DialectType::Presto)
14470                        | Some(crate::dialects::DialectType::Trino)
14471                        | Some(crate::dialects::DialectType::PostgreSQL)
14472                        | Some(crate::dialects::DialectType::MySQL)
14473                        | Some(crate::dialects::DialectType::Redshift)
14474                        | Some(crate::dialects::DialectType::TSQL)
14475                        | Some(crate::dialects::DialectType::Oracle)
14476                        | Some(crate::dialects::DialectType::ClickHouse)
14477                        | Some(crate::dialects::DialectType::Databricks)
14478                        | Some(crate::dialects::DialectType::SQLite)
14479                ) {
14480                    self.generate_string_literal(s)?;
14481                } else {
14482                    // Preserve triple-quoted string syntax for generic/unknown dialects
14483                    let quotes = format!("{0}{0}{0}", _quote_char);
14484                    self.write(&quotes);
14485                    self.write(s);
14486                    self.write(&quotes);
14487                }
14488            }
14489            Literal::EscapeString(s) => {
14490                // PostgreSQL escape string: e'...' or E'...'
14491                // Token text format is "e:content" or "E:content"
14492                // Normalize escape sequences: \' -> '' (standard SQL doubled quote)
14493                use crate::dialects::DialectType;
14494                let content = if let Some(c) = s.strip_prefix("e:") {
14495                    c
14496                } else if let Some(c) = s.strip_prefix("E:") {
14497                    c
14498                } else {
14499                    s.as_str()
14500                };
14501
14502                // MySQL: output the content without quotes or prefix
14503                if matches!(
14504                    self.config.dialect,
14505                    Some(DialectType::MySQL) | Some(DialectType::TiDB)
14506                ) {
14507                    self.write(content);
14508                } else {
14509                    // Some dialects use lowercase e' prefix
14510                    let prefix = if matches!(
14511                        self.config.dialect,
14512                        Some(DialectType::SingleStore)
14513                            | Some(DialectType::DuckDB)
14514                            | Some(DialectType::PostgreSQL)
14515                            | Some(DialectType::CockroachDB)
14516                            | Some(DialectType::Materialize)
14517                            | Some(DialectType::RisingWave)
14518                    ) {
14519                        "e'"
14520                    } else {
14521                        "E'"
14522                    };
14523
14524                    // Normalize \' to '' for output
14525                    let normalized = content.replace("\\'", "''");
14526                    self.write(prefix);
14527                    self.write(&normalized);
14528                    self.write("'");
14529                }
14530            }
14531            Literal::DollarString(s) => {
14532                // Convert dollar-quoted strings to single-quoted strings
14533                // (like Python sqlglot's rawstring_sql)
14534                use crate::dialects::DialectType;
14535                // Extract content from tag\x00content format
14536                let (_tag, content) = crate::tokens::parse_dollar_string_token(s);
14537                // Step 1: Escape backslashes if the dialect uses backslash as a string escape
14538                let escape_backslash = matches!(self.config.dialect, Some(DialectType::Snowflake));
14539                // Step 2: Determine quote escaping style
14540                // Snowflake: ' -> \' (backslash escape)
14541                // PostgreSQL, DuckDB, others: ' -> '' (doubled quote)
14542                let use_backslash_quote =
14543                    matches!(self.config.dialect, Some(DialectType::Snowflake));
14544
14545                let mut escaped = String::with_capacity(content.len() + 4);
14546                for ch in content.chars() {
14547                    if escape_backslash && ch == '\\' {
14548                        // Escape backslash first (before quote escaping)
14549                        escaped.push('\\');
14550                        escaped.push('\\');
14551                    } else if ch == '\'' {
14552                        if use_backslash_quote {
14553                            escaped.push('\\');
14554                            escaped.push('\'');
14555                        } else {
14556                            escaped.push('\'');
14557                            escaped.push('\'');
14558                        }
14559                    } else {
14560                        escaped.push(ch);
14561                    }
14562                }
14563                self.write("'");
14564                self.write(&escaped);
14565                self.write("'");
14566            }
14567            Literal::RawString(s) => {
14568                // Raw strings (r"..." or r'...') contain literal backslashes.
14569                // When converting to a regular string, this follows Python sqlglot's rawstring_sql:
14570                // 1. If \\ is in STRING_ESCAPES, double all backslashes
14571                // 2. Apply ESCAPED_SEQUENCES for special chars (but NOT for backslash itself)
14572                // 3. Escape quotes using STRING_ESCAPES[0] + quote_char
14573                use crate::dialects::DialectType;
14574
14575                // Dialects where \\ is in STRING_ESCAPES (backslashes need doubling)
14576                let escape_backslash = matches!(
14577                    self.config.dialect,
14578                    Some(DialectType::BigQuery)
14579                        | Some(DialectType::MySQL)
14580                        | Some(DialectType::SingleStore)
14581                        | Some(DialectType::TiDB)
14582                        | Some(DialectType::Hive)
14583                        | Some(DialectType::Spark)
14584                        | Some(DialectType::Databricks)
14585                        | Some(DialectType::Drill)
14586                        | Some(DialectType::Snowflake)
14587                        | Some(DialectType::Redshift)
14588                        | Some(DialectType::ClickHouse)
14589                );
14590
14591                // Dialects where backslash is the PRIMARY string escape (STRING_ESCAPES[0] = "\\")
14592                // These escape quotes as \' instead of ''
14593                let backslash_escapes_quote = matches!(
14594                    self.config.dialect,
14595                    Some(DialectType::BigQuery)
14596                        | Some(DialectType::Hive)
14597                        | Some(DialectType::Spark)
14598                        | Some(DialectType::Databricks)
14599                        | Some(DialectType::Drill)
14600                        | Some(DialectType::Snowflake)
14601                        | Some(DialectType::Redshift)
14602                );
14603
14604                // Whether this dialect supports escaped sequences (ESCAPED_SEQUENCES mapping)
14605                // This is True when \\ is in STRING_ESCAPES (same as escape_backslash)
14606                let supports_escape_sequences = escape_backslash;
14607
14608                let mut escaped = String::with_capacity(s.len() + 4);
14609                for ch in s.chars() {
14610                    if escape_backslash && ch == '\\' {
14611                        // Double the backslash for the target dialect
14612                        escaped.push('\\');
14613                        escaped.push('\\');
14614                    } else if ch == '\'' {
14615                        if backslash_escapes_quote {
14616                            // Use backslash to escape the quote: \'
14617                            escaped.push('\\');
14618                            escaped.push('\'');
14619                        } else {
14620                            // Use SQL standard quote doubling: ''
14621                            escaped.push('\'');
14622                            escaped.push('\'');
14623                        }
14624                    } else if supports_escape_sequences {
14625                        // Apply ESCAPED_SEQUENCES mapping for special chars
14626                        // (escape_backslash=False in rawstring_sql, so \\ is NOT escaped here)
14627                        match ch {
14628                            '\n' => {
14629                                escaped.push('\\');
14630                                escaped.push('n');
14631                            }
14632                            '\r' => {
14633                                escaped.push('\\');
14634                                escaped.push('r');
14635                            }
14636                            '\t' => {
14637                                escaped.push('\\');
14638                                escaped.push('t');
14639                            }
14640                            '\x07' => {
14641                                escaped.push('\\');
14642                                escaped.push('a');
14643                            }
14644                            '\x08' => {
14645                                escaped.push('\\');
14646                                escaped.push('b');
14647                            }
14648                            '\x0C' => {
14649                                escaped.push('\\');
14650                                escaped.push('f');
14651                            }
14652                            '\x0B' => {
14653                                escaped.push('\\');
14654                                escaped.push('v');
14655                            }
14656                            _ => escaped.push(ch),
14657                        }
14658                    } else {
14659                        escaped.push(ch);
14660                    }
14661                }
14662                self.write("'");
14663                self.write(&escaped);
14664                self.write("'");
14665            }
14666        }
14667        Ok(())
14668    }
14669
14670    /// Generate a DATE literal with dialect-specific formatting
14671    fn generate_date_literal(&mut self, d: &str) -> Result<()> {
14672        use crate::dialects::DialectType;
14673
14674        match self.config.dialect {
14675            // SQL Server / Fabric use CONVERT or CAST
14676            Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
14677                self.write("CAST('");
14678                self.write(d);
14679                self.write("' AS DATE)");
14680            }
14681            // BigQuery uses CAST syntax for type literals
14682            // DATE 'value' -> CAST('value' AS DATE)
14683            Some(DialectType::BigQuery) => {
14684                self.write("CAST('");
14685                self.write(d);
14686                self.write("' AS DATE)");
14687            }
14688            // Exasol uses CAST syntax for DATE literals
14689            // DATE 'value' -> CAST('value' AS DATE)
14690            Some(DialectType::Exasol) => {
14691                self.write("CAST('");
14692                self.write(d);
14693                self.write("' AS DATE)");
14694            }
14695            // Snowflake uses CAST syntax for DATE literals
14696            // DATE 'value' -> CAST('value' AS DATE)
14697            Some(DialectType::Snowflake) => {
14698                self.write("CAST('");
14699                self.write(d);
14700                self.write("' AS DATE)");
14701            }
14702            // PostgreSQL, MySQL, Redshift: DATE 'value' -> CAST('value' AS DATE)
14703            Some(DialectType::PostgreSQL)
14704            | Some(DialectType::MySQL)
14705            | Some(DialectType::SingleStore)
14706            | Some(DialectType::TiDB)
14707            | Some(DialectType::Redshift) => {
14708                self.write("CAST('");
14709                self.write(d);
14710                self.write("' AS DATE)");
14711            }
14712            // DuckDB, Presto, Trino, Spark: DATE 'value' -> CAST('value' AS DATE)
14713            Some(DialectType::DuckDB)
14714            | Some(DialectType::Presto)
14715            | Some(DialectType::Trino)
14716            | Some(DialectType::Athena)
14717            | Some(DialectType::Spark)
14718            | Some(DialectType::Databricks)
14719            | Some(DialectType::Hive) => {
14720                self.write("CAST('");
14721                self.write(d);
14722                self.write("' AS DATE)");
14723            }
14724            // Oracle: DATE 'value' -> TO_DATE('value', 'YYYY-MM-DD')
14725            Some(DialectType::Oracle) => {
14726                self.write("TO_DATE('");
14727                self.write(d);
14728                self.write("', 'YYYY-MM-DD')");
14729            }
14730            // Standard SQL: DATE '...'
14731            _ => {
14732                self.write_keyword("DATE");
14733                self.write(" '");
14734                self.write(d);
14735                self.write("'");
14736            }
14737        }
14738        Ok(())
14739    }
14740
14741    /// Generate a TIME literal with dialect-specific formatting
14742    fn generate_time_literal(&mut self, t: &str) -> Result<()> {
14743        use crate::dialects::DialectType;
14744
14745        match self.config.dialect {
14746            // SQL Server uses CONVERT or CAST
14747            Some(DialectType::TSQL) => {
14748                self.write("CAST('");
14749                self.write(t);
14750                self.write("' AS TIME)");
14751            }
14752            // Standard SQL: TIME '...'
14753            _ => {
14754                self.write_keyword("TIME");
14755                self.write(" '");
14756                self.write(t);
14757                self.write("'");
14758            }
14759        }
14760        Ok(())
14761    }
14762
14763    /// Generate a date expression for Dremio, converting DATE literals to CAST
14764    fn generate_dremio_date_expression(&mut self, expr: &Expression) -> Result<()> {
14765        use crate::expressions::Literal;
14766
14767        match expr {
14768            Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Date(_)) => {
14769                let Literal::Date(d) = lit.as_ref() else {
14770                    unreachable!()
14771                };
14772                // DATE 'value' -> CAST('value' AS DATE)
14773                self.write("CAST('");
14774                self.write(d);
14775                self.write("' AS DATE)");
14776            }
14777            _ => {
14778                // For all other expressions, generate normally
14779                self.generate_expression(expr)?;
14780            }
14781        }
14782        Ok(())
14783    }
14784
14785    /// Generate a TIMESTAMP literal with dialect-specific formatting
14786    fn generate_timestamp_literal(&mut self, ts: &str) -> Result<()> {
14787        use crate::dialects::DialectType;
14788
14789        match self.config.dialect {
14790            // SQL Server uses CONVERT or CAST
14791            Some(DialectType::TSQL) => {
14792                self.write("CAST('");
14793                self.write(ts);
14794                self.write("' AS DATETIME2)");
14795            }
14796            // BigQuery uses CAST syntax for type literals
14797            // TIMESTAMP 'value' -> CAST('value' AS TIMESTAMP)
14798            Some(DialectType::BigQuery) => {
14799                self.write("CAST('");
14800                self.write(ts);
14801                self.write("' AS TIMESTAMP)");
14802            }
14803            // Snowflake uses CAST syntax for TIMESTAMP literals
14804            // TIMESTAMP 'value' -> CAST('value' AS TIMESTAMP)
14805            Some(DialectType::Snowflake) => {
14806                self.write("CAST('");
14807                self.write(ts);
14808                self.write("' AS TIMESTAMP)");
14809            }
14810            // Dremio uses CAST syntax for TIMESTAMP literals
14811            // TIMESTAMP 'value' -> CAST('value' AS TIMESTAMP)
14812            Some(DialectType::Dremio) => {
14813                self.write("CAST('");
14814                self.write(ts);
14815                self.write("' AS TIMESTAMP)");
14816            }
14817            // Exasol uses CAST syntax for TIMESTAMP literals
14818            // TIMESTAMP 'value' -> CAST('value' AS TIMESTAMP)
14819            Some(DialectType::Exasol) => {
14820                self.write("CAST('");
14821                self.write(ts);
14822                self.write("' AS TIMESTAMP)");
14823            }
14824            // Oracle prefers TO_TIMESTAMP function call
14825            // TIMESTAMP 'value' -> TO_TIMESTAMP('value', 'YYYY-MM-DD HH24:MI:SS.FF6')
14826            Some(DialectType::Oracle) => {
14827                self.write("TO_TIMESTAMP('");
14828                self.write(ts);
14829                self.write("', 'YYYY-MM-DD HH24:MI:SS.FF6')");
14830            }
14831            // Presto/Trino: always use CAST for TIMESTAMP literals
14832            Some(DialectType::Presto) | Some(DialectType::Trino) => {
14833                if Self::timestamp_has_timezone(ts) {
14834                    self.write("CAST('");
14835                    self.write(ts);
14836                    self.write("' AS TIMESTAMP WITH TIME ZONE)");
14837                } else {
14838                    self.write("CAST('");
14839                    self.write(ts);
14840                    self.write("' AS TIMESTAMP)");
14841                }
14842            }
14843            // ClickHouse: CAST('...' AS Nullable(DateTime))
14844            Some(DialectType::ClickHouse) => {
14845                self.write("CAST('");
14846                self.write(ts);
14847                self.write("' AS Nullable(DateTime))");
14848            }
14849            // Spark: CAST('...' AS TIMESTAMP)
14850            Some(DialectType::Spark) => {
14851                self.write("CAST('");
14852                self.write(ts);
14853                self.write("' AS TIMESTAMP)");
14854            }
14855            // Redshift: CAST('...' AS TIMESTAMP) for regular timestamps,
14856            // but TIMESTAMP '...' for special values like 'epoch'
14857            Some(DialectType::Redshift) => {
14858                if ts == "epoch" {
14859                    self.write_keyword("TIMESTAMP");
14860                    self.write(" '");
14861                    self.write(ts);
14862                    self.write("'");
14863                } else {
14864                    self.write("CAST('");
14865                    self.write(ts);
14866                    self.write("' AS TIMESTAMP)");
14867                }
14868            }
14869            // PostgreSQL, Hive, DuckDB, etc.: CAST('...' AS TIMESTAMP)
14870            Some(DialectType::PostgreSQL)
14871            | Some(DialectType::Hive)
14872            | Some(DialectType::SQLite)
14873            | Some(DialectType::DuckDB)
14874            | Some(DialectType::Athena)
14875            | Some(DialectType::Drill)
14876            | Some(DialectType::Teradata) => {
14877                self.write("CAST('");
14878                self.write(ts);
14879                self.write("' AS TIMESTAMP)");
14880            }
14881            // MySQL/StarRocks: CAST('...' AS DATETIME)
14882            Some(DialectType::MySQL) | Some(DialectType::StarRocks) | Some(DialectType::Doris) => {
14883                self.write("CAST('");
14884                self.write(ts);
14885                self.write("' AS DATETIME)");
14886            }
14887            // Databricks: CAST('...' AS TIMESTAMP_NTZ)
14888            Some(DialectType::Databricks) => {
14889                self.write("CAST('");
14890                self.write(ts);
14891                self.write("' AS TIMESTAMP_NTZ)");
14892            }
14893            // Standard SQL: TIMESTAMP '...'
14894            _ => {
14895                self.write_keyword("TIMESTAMP");
14896                self.write(" '");
14897                self.write(ts);
14898                self.write("'");
14899            }
14900        }
14901        Ok(())
14902    }
14903
14904    /// Check if a timestamp string contains a timezone identifier
14905    /// This detects IANA timezone names like Europe/Prague, America/New_York, etc.
14906    fn timestamp_has_timezone(ts: &str) -> bool {
14907        // Check for common IANA timezone patterns: Continent/City format
14908        // Examples: Europe/Prague, America/New_York, Asia/Tokyo, etc.
14909        // Also handles: UTC, GMT, Etc/GMT+0, etc.
14910        let ts_lower = ts.to_ascii_lowercase();
14911
14912        // Check for Continent/City pattern (most common)
14913        let continent_prefixes = [
14914            "africa/",
14915            "america/",
14916            "antarctica/",
14917            "arctic/",
14918            "asia/",
14919            "atlantic/",
14920            "australia/",
14921            "europe/",
14922            "indian/",
14923            "pacific/",
14924            "etc/",
14925            "brazil/",
14926            "canada/",
14927            "chile/",
14928            "mexico/",
14929            "us/",
14930        ];
14931
14932        for prefix in &continent_prefixes {
14933            if ts_lower.contains(prefix) {
14934                return true;
14935            }
14936        }
14937
14938        // Check for standalone timezone abbreviations at the end
14939        // These typically appear after the time portion
14940        let tz_abbrevs = [
14941            " utc", " gmt", " cet", " cest", " eet", " eest", " wet", " west", " est", " edt",
14942            " cst", " cdt", " mst", " mdt", " pst", " pdt", " ist", " bst", " jst", " kst", " hkt",
14943            " sgt", " aest", " aedt", " acst", " acdt", " awst",
14944        ];
14945
14946        for abbrev in &tz_abbrevs {
14947            if ts_lower.ends_with(abbrev) {
14948                return true;
14949            }
14950        }
14951
14952        // Check for numeric timezone offsets: +N, -N, +NN:NN, -NN:NN
14953        // Examples: "2012-10-31 01:00 -2", "2012-10-31 01:00 +02:00"
14954        // Look for pattern: space followed by + or - and digits (optionally with :)
14955        let trimmed = ts.trim();
14956        if let Some(last_space) = trimmed.rfind(' ') {
14957            let suffix = &trimmed[last_space + 1..];
14958            if (suffix.starts_with('+') || suffix.starts_with('-')) && suffix.len() > 1 {
14959                // Check if rest is numeric (possibly with : for hh:mm format)
14960                let rest = &suffix[1..];
14961                if rest.chars().all(|c| c.is_ascii_digit() || c == ':') {
14962                    return true;
14963                }
14964            }
14965        }
14966
14967        false
14968    }
14969
14970    /// Generate a DATETIME literal with dialect-specific formatting
14971    fn generate_datetime_literal(&mut self, dt: &str) -> Result<()> {
14972        use crate::dialects::DialectType;
14973
14974        match self.config.dialect {
14975            // BigQuery uses CAST syntax for type literals
14976            // DATETIME 'value' -> CAST('value' AS DATETIME)
14977            Some(DialectType::BigQuery) => {
14978                self.write("CAST('");
14979                self.write(dt);
14980                self.write("' AS DATETIME)");
14981            }
14982            // DuckDB: DATETIME -> CAST('value' AS TIMESTAMP)
14983            Some(DialectType::DuckDB) => {
14984                self.write("CAST('");
14985                self.write(dt);
14986                self.write("' AS TIMESTAMP)");
14987            }
14988            // DATETIME is primarily a BigQuery type
14989            // Output as DATETIME '...' for dialects that support it
14990            _ => {
14991                self.write_keyword("DATETIME");
14992                self.write(" '");
14993                self.write(dt);
14994                self.write("'");
14995            }
14996        }
14997        Ok(())
14998    }
14999
15000    /// Generate a string literal with dialect-specific escaping
15001    fn generate_string_literal(&mut self, s: &str) -> Result<()> {
15002        use crate::dialects::DialectType;
15003
15004        match self.config.dialect {
15005            // MySQL/Hive: Uses SQL standard quote escaping ('') for quotes,
15006            // and backslash escaping for special characters like newlines
15007            // Hive STRING_ESCAPES = ["\\"] - uses backslash escapes
15008            Some(DialectType::Hive) | Some(DialectType::Spark) | Some(DialectType::Databricks) => {
15009                // Hive/Spark use backslash escaping for quotes (\') and special chars
15010                self.write("'");
15011                for c in s.chars() {
15012                    match c {
15013                        '\'' => self.write("\\'"),
15014                        '\\' => self.write("\\\\"),
15015                        '\n' => self.write("\\n"),
15016                        '\r' => self.write("\\r"),
15017                        '\t' => self.write("\\t"),
15018                        '\0' => self.write("\\0"),
15019                        _ => self.output.push(c),
15020                    }
15021                }
15022                self.write("'");
15023            }
15024            Some(DialectType::Drill) => {
15025                // Drill uses SQL-standard quote doubling ('') for quotes,
15026                // but backslash escaping for special characters
15027                self.write("'");
15028                for c in s.chars() {
15029                    match c {
15030                        '\'' => self.write("''"),
15031                        '\\' => self.write("\\\\"),
15032                        '\n' => self.write("\\n"),
15033                        '\r' => self.write("\\r"),
15034                        '\t' => self.write("\\t"),
15035                        '\0' => self.write("\\0"),
15036                        _ => self.output.push(c),
15037                    }
15038                }
15039                self.write("'");
15040            }
15041            Some(DialectType::MySQL) | Some(DialectType::SingleStore) | Some(DialectType::TiDB) => {
15042                self.write("'");
15043                for c in s.chars() {
15044                    match c {
15045                        // MySQL uses SQL standard quote doubling
15046                        '\'' => self.write("''"),
15047                        '\\' => self.write("\\\\"),
15048                        '\n' => self.write("\\n"),
15049                        '\r' => self.write("\\r"),
15050                        '\t' => self.write("\\t"),
15051                        // sqlglot writes a literal NUL for this case
15052                        '\0' => self.output.push('\0'),
15053                        _ => self.output.push(c),
15054                    }
15055                }
15056                self.write("'");
15057            }
15058            // BigQuery: Uses backslash escaping
15059            Some(DialectType::BigQuery) => {
15060                self.write("'");
15061                for c in s.chars() {
15062                    match c {
15063                        '\'' => self.write("\\'"),
15064                        '\\' => self.write("\\\\"),
15065                        '\n' => self.write("\\n"),
15066                        '\r' => self.write("\\r"),
15067                        '\t' => self.write("\\t"),
15068                        '\0' => self.write("\\0"),
15069                        '\x07' => self.write("\\a"),
15070                        '\x08' => self.write("\\b"),
15071                        '\x0C' => self.write("\\f"),
15072                        '\x0B' => self.write("\\v"),
15073                        _ => self.output.push(c),
15074                    }
15075                }
15076                self.write("'");
15077            }
15078            // Athena: Uses different escaping for DDL (Hive) vs DML (Trino)
15079            // In Hive context (DDL): backslash escaping for single quotes (\') and backslashes (\\)
15080            // In Trino context (DML): SQL-standard escaping ('') and literal backslashes
15081            Some(DialectType::Athena) => {
15082                if self.athena_hive_context {
15083                    // Hive-style: backslash escaping
15084                    self.write("'");
15085                    for c in s.chars() {
15086                        match c {
15087                            '\'' => self.write("\\'"),
15088                            '\\' => self.write("\\\\"),
15089                            '\n' => self.write("\\n"),
15090                            '\r' => self.write("\\r"),
15091                            '\t' => self.write("\\t"),
15092                            '\0' => self.write("\\0"),
15093                            _ => self.output.push(c),
15094                        }
15095                    }
15096                    self.write("'");
15097                } else {
15098                    // Trino-style: SQL-standard escaping, preserve backslashes
15099                    self.write("'");
15100                    for c in s.chars() {
15101                        match c {
15102                            '\'' => self.write("''"),
15103                            // Preserve backslashes literally (no re-escaping)
15104                            _ => self.output.push(c),
15105                        }
15106                    }
15107                    self.write("'");
15108                }
15109            }
15110            // Snowflake: Uses backslash escaping (STRING_ESCAPES = ["\\", "'"])
15111            // The tokenizer preserves backslash escape sequences literally (e.g., input '\\'
15112            // becomes string value '\\'), so we should NOT re-escape backslashes.
15113            // We only need to escape single quotes.
15114            Some(DialectType::Snowflake) => {
15115                self.write("'");
15116                for c in s.chars() {
15117                    match c {
15118                        '\'' => self.write("\\'"),
15119                        // Backslashes are already escaped in the tokenized string, don't re-escape
15120                        // Only escape special characters that might not have been escaped
15121                        '\n' => self.write("\\n"),
15122                        '\r' => self.write("\\r"),
15123                        '\t' => self.write("\\t"),
15124                        _ => self.output.push(c),
15125                    }
15126                }
15127                self.write("'");
15128            }
15129            // PostgreSQL: Output special characters as literal chars in strings (no E-string prefix)
15130            Some(DialectType::PostgreSQL) => {
15131                self.write("'");
15132                for c in s.chars() {
15133                    match c {
15134                        '\'' => self.write("''"),
15135                        _ => self.output.push(c),
15136                    }
15137                }
15138                self.write("'");
15139            }
15140            // Redshift: Uses backslash escaping for single quotes
15141            Some(DialectType::Redshift) => {
15142                self.write("'");
15143                for c in s.chars() {
15144                    match c {
15145                        '\'' => self.write("\\'"),
15146                        _ => self.output.push(c),
15147                    }
15148                }
15149                self.write("'");
15150            }
15151            // Oracle: Uses standard double single-quote escaping
15152            Some(DialectType::Oracle) => {
15153                self.write("'");
15154                for ch in s.chars() {
15155                    if ch == '\'' {
15156                        self.output.push_str("''");
15157                    } else {
15158                        self.output.push(ch);
15159                    }
15160                }
15161                self.write("'");
15162            }
15163            // ClickHouse: Uses SQL-standard quote doubling ('') for quotes,
15164            // backslash escaping for backslashes and special characters
15165            Some(DialectType::ClickHouse) => {
15166                self.write("'");
15167                for c in s.chars() {
15168                    match c {
15169                        '\'' => self.write("''"),
15170                        '\\' => self.write("\\\\"),
15171                        '\n' => self.write("\\n"),
15172                        '\r' => self.write("\\r"),
15173                        '\t' => self.write("\\t"),
15174                        '\0' => self.write("\\0"),
15175                        '\x07' => self.write("\\a"),
15176                        '\x08' => self.write("\\b"),
15177                        '\x0C' => self.write("\\f"),
15178                        '\x0B' => self.write("\\v"),
15179                        // Non-printable characters: emit as \xNN hex escapes
15180                        c if c.is_control() || (c as u32) < 0x20 => {
15181                            let byte = c as u32;
15182                            if byte < 256 {
15183                                self.write(&format!("\\x{:02X}", byte));
15184                            } else {
15185                                self.output.push(c);
15186                            }
15187                        }
15188                        _ => self.output.push(c),
15189                    }
15190                }
15191                self.write("'");
15192            }
15193            // Default: SQL standard double single quotes (works for most dialects)
15194            // PostgreSQL, Snowflake, DuckDB, TSQL, etc.
15195            _ => {
15196                self.write("'");
15197                for ch in s.chars() {
15198                    if ch == '\'' {
15199                        self.output.push_str("''");
15200                    } else {
15201                        self.output.push(ch);
15202                    }
15203                }
15204                self.write("'");
15205            }
15206        }
15207        Ok(())
15208    }
15209
15210    /// Write a byte string with proper escaping for BigQuery-style byte literals
15211    /// Escapes characters as \xNN hex escapes where needed
15212    fn write_escaped_byte_string(&mut self, s: &str) {
15213        for c in s.chars() {
15214            match c {
15215                // Escape single quotes
15216                '\'' => self.write("\\'"),
15217                // Escape backslashes
15218                '\\' => self.write("\\\\"),
15219                // Keep all printable characters (including non-ASCII) as-is
15220                _ if !c.is_control() => self.output.push(c),
15221                // Escape control characters as hex
15222                _ => {
15223                    let byte = c as u32;
15224                    if byte < 256 {
15225                        self.write(&format!("\\x{:02x}", byte));
15226                    } else {
15227                        // For unicode characters, write each UTF-8 byte
15228                        for b in c.to_string().as_bytes() {
15229                            self.write(&format!("\\x{:02x}", b));
15230                        }
15231                    }
15232                }
15233            }
15234        }
15235    }
15236
15237    fn generate_boolean(&mut self, b: &BooleanLiteral) -> Result<()> {
15238        use crate::dialects::DialectType;
15239
15240        // Different dialects have different boolean literal formats
15241        match self.config.dialect {
15242            // SQL Server typically uses 1/0 for boolean literals in many contexts
15243            // However, TRUE/FALSE also works in modern versions
15244            Some(DialectType::TSQL) => {
15245                self.write(if b.value { "1" } else { "0" });
15246            }
15247            // Oracle traditionally uses 1/0 (no native boolean until recent versions)
15248            Some(DialectType::Oracle) => {
15249                self.write(if b.value { "1" } else { "0" });
15250            }
15251            // MySQL accepts TRUE/FALSE as aliases for 1/0
15252            Some(DialectType::MySQL) => {
15253                self.write_keyword(if b.value { "TRUE" } else { "FALSE" });
15254            }
15255            // Most other dialects support TRUE/FALSE
15256            _ => {
15257                self.write_keyword(if b.value { "TRUE" } else { "FALSE" });
15258            }
15259        }
15260        Ok(())
15261    }
15262
15263    /// Generate an identifier that's used as an alias name
15264    /// This quotes reserved keywords in addition to already-quoted identifiers
15265    fn generate_alias_identifier(&mut self, id: &Identifier) -> Result<()> {
15266        let name = &id.name;
15267        let quote_style = &self.config.identifier_quote_style;
15268
15269        // For aliases, quote if:
15270        // 1. The identifier was explicitly quoted in the source
15271        // 2. The identifier is a reserved keyword for the current dialect
15272        let needs_quoting = id.quoted || self.is_reserved_keyword(name);
15273
15274        // Normalize identifier if configured
15275        let output_name = if self.config.normalize_identifiers && !id.quoted {
15276            name.to_ascii_lowercase()
15277        } else {
15278            name.to_string()
15279        };
15280
15281        if needs_quoting {
15282            // Escape any quote characters within the identifier
15283            let escaped_name = if quote_style.start == quote_style.end {
15284                output_name.replace(
15285                    quote_style.end,
15286                    &format!("{}{}", quote_style.end, quote_style.end),
15287                )
15288            } else {
15289                output_name.replace(
15290                    quote_style.end,
15291                    &format!("{}{}", quote_style.end, quote_style.end),
15292                )
15293            };
15294            self.write(&format!(
15295                "{}{}{}",
15296                quote_style.start, escaped_name, quote_style.end
15297            ));
15298        } else {
15299            self.write(&output_name);
15300        }
15301
15302        // Output trailing comments
15303        for comment in &id.trailing_comments {
15304            self.write(" ");
15305            self.write_formatted_comment(comment);
15306        }
15307        Ok(())
15308    }
15309
15310    fn generate_identifier(&mut self, id: &Identifier) -> Result<()> {
15311        use crate::dialects::DialectType;
15312
15313        let name = &id.name;
15314
15315        // For Athena, use backticks in Hive context, double quotes in Trino context
15316        let quote_style = if matches!(self.config.dialect, Some(DialectType::Athena))
15317            && self.athena_hive_context
15318        {
15319            &IdentifierQuoteStyle::BACKTICK
15320        } else {
15321            &self.config.identifier_quote_style
15322        };
15323
15324        // Quote if:
15325        // 1. The identifier was explicitly quoted in the source
15326        // 2. The identifier is a reserved keyword for the current dialect
15327        // 3. The config says to always quote identifiers (e.g., Athena/Presto)
15328        // This matches Python sqlglot's identifier_sql behavior
15329        // Also quote identifiers starting with digits if the target dialect doesn't support them
15330        let starts_with_digit = name.chars().next().map_or(false, |c| c.is_ascii_digit());
15331        let needs_digit_quoting = starts_with_digit
15332            && !self.config.identifiers_can_start_with_digit
15333            && self.config.dialect.is_some();
15334        let mysql_invalid_hex_identifier = matches!(self.config.dialect, Some(DialectType::MySQL))
15335            && name.len() > 2
15336            && (name.starts_with("0x") || name.starts_with("0X"))
15337            && !name[2..].chars().all(|c| c.is_ascii_hexdigit());
15338        let needs_quoting = id.quoted
15339            || self.is_reserved_keyword(name)
15340            || self.config.always_quote_identifiers
15341            || needs_digit_quoting
15342            || mysql_invalid_hex_identifier;
15343
15344        // Check for MySQL index column prefix length: name(16) or name(16) ASC/DESC
15345        // When quoted, we need to output `name`(16) not `name(16)`
15346        let (base_name, suffix) = if needs_quoting {
15347            // Try to extract prefix length from identifier: name(number) or name(number) ASC/DESC
15348            if let Some(paren_pos) = name.find('(') {
15349                let base = &name[..paren_pos];
15350                let rest = &name[paren_pos..];
15351                // Verify it looks like (digits) or (digits) ASC/DESC
15352                if rest.starts_with('(')
15353                    && (rest.ends_with(')') || rest.ends_with(") ASC") || rest.ends_with(") DESC"))
15354                {
15355                    // Check if content between parens is all digits
15356                    let close_paren = rest.find(')').unwrap_or(rest.len());
15357                    let inside = &rest[1..close_paren];
15358                    if inside.chars().all(|c| c.is_ascii_digit()) {
15359                        (base.to_string(), rest.to_string())
15360                    } else {
15361                        (name.to_string(), String::new())
15362                    }
15363                } else {
15364                    (name.to_string(), String::new())
15365                }
15366            } else if name.ends_with(" ASC") {
15367                let base = &name[..name.len() - 4];
15368                (base.to_string(), " ASC".to_string())
15369            } else if name.ends_with(" DESC") {
15370                let base = &name[..name.len() - 5];
15371                (base.to_string(), " DESC".to_string())
15372            } else {
15373                (name.to_string(), String::new())
15374            }
15375        } else {
15376            (name.to_string(), String::new())
15377        };
15378
15379        // Normalize identifier if configured, with special handling for Exasol
15380        // Exasol uses UPPERCASE normalization strategy, so reserved keywords that need quoting
15381        // should be uppercased when not already quoted (to match Python sqlglot behavior)
15382        let output_name = if self.config.normalize_identifiers && !id.quoted {
15383            base_name.to_ascii_lowercase()
15384        } else if matches!(self.config.dialect, Some(DialectType::Exasol))
15385            && !id.quoted
15386            && self.is_reserved_keyword(name)
15387        {
15388            // Exasol: uppercase reserved keywords when quoting them
15389            // This matches Python sqlglot's behavior with NORMALIZATION_STRATEGY = UPPERCASE
15390            base_name.to_ascii_uppercase()
15391        } else {
15392            base_name
15393        };
15394
15395        if needs_quoting {
15396            // Escape any quote characters within the identifier
15397            let escaped_name = if quote_style.start == quote_style.end {
15398                // Same start/end char (e.g., " or `) - double the quote char
15399                output_name.replace(
15400                    quote_style.end,
15401                    &format!("{}{}", quote_style.end, quote_style.end),
15402                )
15403            } else {
15404                // Different start/end (e.g., [ and ]) - escape only the end char
15405                output_name.replace(
15406                    quote_style.end,
15407                    &format!("{}{}", quote_style.end, quote_style.end),
15408                )
15409            };
15410            self.write(&format!(
15411                "{}{}{}{}",
15412                quote_style.start, escaped_name, quote_style.end, suffix
15413            ));
15414        } else {
15415            self.write(&output_name);
15416        }
15417
15418        // Output trailing comments
15419        for comment in &id.trailing_comments {
15420            self.write(" ");
15421            self.write_formatted_comment(comment);
15422        }
15423        Ok(())
15424    }
15425
15426    fn generate_column(&mut self, col: &Column) -> Result<()> {
15427        use crate::dialects::DialectType;
15428
15429        if let Some(table) = &col.table {
15430            // Exasol special case: LOCAL as column table prefix should NOT be quoted
15431            // LOCAL is a special keyword in Exasol for referencing aliases from the current scope
15432            // Only applies when: dialect is Exasol, name is "LOCAL" (case-insensitive), and not already quoted
15433            let is_exasol_local_prefix = matches!(self.config.dialect, Some(DialectType::Exasol))
15434                && !table.quoted
15435                && table.name.eq_ignore_ascii_case("LOCAL");
15436
15437            if is_exasol_local_prefix {
15438                // Write LOCAL unquoted (this is special Exasol syntax, not a table reference)
15439                self.write("LOCAL");
15440            } else {
15441                self.generate_identifier(table)?;
15442            }
15443            self.write(".");
15444        }
15445        self.generate_identifier(&col.name)?;
15446        // Oracle-style join marker (+)
15447        // Only output if dialect supports it (Oracle, Exasol)
15448        if col.join_mark && self.config.supports_column_join_marks {
15449            self.write(" (+)");
15450        }
15451        // Output trailing comments
15452        for comment in &col.trailing_comments {
15453            self.write_space();
15454            self.write_formatted_comment(comment);
15455        }
15456        Ok(())
15457    }
15458
15459    /// Generate a pseudocolumn (Oracle ROWNUM, ROWID, LEVEL, etc.)
15460    /// Pseudocolumns should NEVER be quoted, as quoting breaks them in Oracle
15461    fn generate_pseudocolumn(&mut self, pc: &Pseudocolumn) -> Result<()> {
15462        use crate::dialects::DialectType;
15463        use crate::expressions::PseudocolumnType;
15464
15465        // SYSDATE -> CURRENT_TIMESTAMP for non-Oracle/Redshift dialects
15466        if pc.kind == PseudocolumnType::Sysdate
15467            && !matches!(
15468                self.config.dialect,
15469                Some(DialectType::Oracle) | Some(DialectType::Redshift) | None
15470            )
15471        {
15472            self.write_keyword("CURRENT_TIMESTAMP");
15473            // Add () for dialects that expect it
15474            if matches!(
15475                self.config.dialect,
15476                Some(DialectType::MySQL)
15477                    | Some(DialectType::ClickHouse)
15478                    | Some(DialectType::Spark)
15479                    | Some(DialectType::Databricks)
15480                    | Some(DialectType::Hive)
15481            ) {
15482                self.write("()");
15483            }
15484        } else {
15485            self.write(pc.kind.as_str());
15486        }
15487        Ok(())
15488    }
15489
15490    /// Generate CONNECT BY clause (Oracle hierarchical queries)
15491    fn generate_connect(&mut self, connect: &Connect) -> Result<()> {
15492        use crate::dialects::DialectType;
15493
15494        // Generate native CONNECT BY for Oracle and Snowflake
15495        // For other dialects, add a comment noting manual conversion needed
15496        let supports_connect_by = matches!(
15497            self.config.dialect,
15498            Some(DialectType::Oracle) | Some(DialectType::Snowflake)
15499        );
15500
15501        if !supports_connect_by && self.config.dialect.is_some() {
15502            // Add comment for unsupported dialects
15503            if self.config.pretty {
15504                self.write_newline();
15505            } else {
15506                self.write_space();
15507            }
15508            self.write_unsupported_comment(
15509                "CONNECT BY requires manual conversion to recursive CTE",
15510            )?;
15511        }
15512
15513        // Generate START WITH if present (before CONNECT BY)
15514        if let Some(start) = &connect.start {
15515            if self.config.pretty {
15516                self.write_newline();
15517            } else {
15518                self.write_space();
15519            }
15520            self.write_keyword("START WITH");
15521            self.write_space();
15522            self.generate_expression(start)?;
15523        }
15524
15525        // Generate CONNECT BY
15526        if self.config.pretty {
15527            self.write_newline();
15528        } else {
15529            self.write_space();
15530        }
15531        self.write_keyword("CONNECT BY");
15532        if connect.nocycle {
15533            self.write_space();
15534            self.write_keyword("NOCYCLE");
15535        }
15536        self.write_space();
15537        self.generate_expression(&connect.connect)?;
15538
15539        Ok(())
15540    }
15541
15542    /// Generate Connect expression (for Expression::Connect variant)
15543    fn generate_connect_expr(&mut self, connect: &Connect) -> Result<()> {
15544        self.generate_connect(connect)
15545    }
15546
15547    /// Generate PRIOR expression
15548    fn generate_prior(&mut self, prior: &Prior) -> Result<()> {
15549        self.write_keyword("PRIOR");
15550        self.write_space();
15551        self.generate_expression(&prior.this)?;
15552        Ok(())
15553    }
15554
15555    /// Generate CONNECT_BY_ROOT function
15556    /// Syntax: CONNECT_BY_ROOT column (no parentheses)
15557    fn generate_connect_by_root(&mut self, cbr: &ConnectByRoot) -> Result<()> {
15558        self.write_keyword("CONNECT_BY_ROOT");
15559        self.write_space();
15560        self.generate_expression(&cbr.this)?;
15561        Ok(())
15562    }
15563
15564    /// Generate MATCH_RECOGNIZE clause
15565    fn generate_match_recognize(&mut self, mr: &MatchRecognize) -> Result<()> {
15566        use crate::dialects::DialectType;
15567
15568        // MATCH_RECOGNIZE is supported in Oracle, Snowflake, Presto, and Trino
15569        let supports_match_recognize = matches!(
15570            self.config.dialect,
15571            Some(DialectType::Oracle)
15572                | Some(DialectType::Snowflake)
15573                | Some(DialectType::Presto)
15574                | Some(DialectType::Trino)
15575        );
15576
15577        // Generate the source table first
15578        if let Some(source) = &mr.this {
15579            self.generate_expression(source)?;
15580        }
15581
15582        if !supports_match_recognize {
15583            self.write_unsupported_comment("MATCH_RECOGNIZE not supported in this dialect")?;
15584            return Ok(());
15585        }
15586
15587        // In pretty mode, MATCH_RECOGNIZE should be on a new line
15588        if self.config.pretty {
15589            self.write_newline();
15590        } else {
15591            self.write_space();
15592        }
15593
15594        self.write_keyword("MATCH_RECOGNIZE");
15595        self.write(" (");
15596
15597        if self.config.pretty {
15598            self.indent_level += 1;
15599        }
15600
15601        let mut needs_separator = false;
15602
15603        // PARTITION BY
15604        if let Some(partition_by) = &mr.partition_by {
15605            if !partition_by.is_empty() {
15606                if self.config.pretty {
15607                    self.write_newline();
15608                    self.write_indent();
15609                }
15610                self.write_keyword("PARTITION BY");
15611                self.write_space();
15612                for (i, expr) in partition_by.iter().enumerate() {
15613                    if i > 0 {
15614                        self.write(", ");
15615                    }
15616                    self.generate_expression(expr)?;
15617                }
15618                needs_separator = true;
15619            }
15620        }
15621
15622        // ORDER BY
15623        if let Some(order_by) = &mr.order_by {
15624            if !order_by.is_empty() {
15625                if needs_separator {
15626                    if self.config.pretty {
15627                        self.write_newline();
15628                        self.write_indent();
15629                    } else {
15630                        self.write_space();
15631                    }
15632                } else if self.config.pretty {
15633                    self.write_newline();
15634                    self.write_indent();
15635                }
15636                self.write_keyword("ORDER BY");
15637                // In pretty mode, put each ORDER BY column on a new indented line
15638                if self.config.pretty {
15639                    self.indent_level += 1;
15640                    for (i, ordered) in order_by.iter().enumerate() {
15641                        if i > 0 {
15642                            self.write(",");
15643                        }
15644                        self.write_newline();
15645                        self.write_indent();
15646                        self.generate_ordered(ordered)?;
15647                    }
15648                    self.indent_level -= 1;
15649                } else {
15650                    self.write_space();
15651                    for (i, ordered) in order_by.iter().enumerate() {
15652                        if i > 0 {
15653                            self.write(", ");
15654                        }
15655                        self.generate_ordered(ordered)?;
15656                    }
15657                }
15658                needs_separator = true;
15659            }
15660        }
15661
15662        // MEASURES
15663        if let Some(measures) = &mr.measures {
15664            if !measures.is_empty() {
15665                if needs_separator {
15666                    if self.config.pretty {
15667                        self.write_newline();
15668                        self.write_indent();
15669                    } else {
15670                        self.write_space();
15671                    }
15672                } else if self.config.pretty {
15673                    self.write_newline();
15674                    self.write_indent();
15675                }
15676                self.write_keyword("MEASURES");
15677                // In pretty mode, put each MEASURE on a new indented line
15678                if self.config.pretty {
15679                    self.indent_level += 1;
15680                    for (i, measure) in measures.iter().enumerate() {
15681                        if i > 0 {
15682                            self.write(",");
15683                        }
15684                        self.write_newline();
15685                        self.write_indent();
15686                        // Handle RUNNING/FINAL prefix
15687                        if let Some(semantics) = &measure.window_frame {
15688                            match semantics {
15689                                MatchRecognizeSemantics::Running => {
15690                                    self.write_keyword("RUNNING");
15691                                    self.write_space();
15692                                }
15693                                MatchRecognizeSemantics::Final => {
15694                                    self.write_keyword("FINAL");
15695                                    self.write_space();
15696                                }
15697                            }
15698                        }
15699                        self.generate_expression(&measure.this)?;
15700                    }
15701                    self.indent_level -= 1;
15702                } else {
15703                    self.write_space();
15704                    for (i, measure) in measures.iter().enumerate() {
15705                        if i > 0 {
15706                            self.write(", ");
15707                        }
15708                        // Handle RUNNING/FINAL prefix
15709                        if let Some(semantics) = &measure.window_frame {
15710                            match semantics {
15711                                MatchRecognizeSemantics::Running => {
15712                                    self.write_keyword("RUNNING");
15713                                    self.write_space();
15714                                }
15715                                MatchRecognizeSemantics::Final => {
15716                                    self.write_keyword("FINAL");
15717                                    self.write_space();
15718                                }
15719                            }
15720                        }
15721                        self.generate_expression(&measure.this)?;
15722                    }
15723                }
15724                needs_separator = true;
15725            }
15726        }
15727
15728        // Row semantics (ONE ROW PER MATCH, ALL ROWS PER MATCH, etc.)
15729        if let Some(rows) = &mr.rows {
15730            if needs_separator {
15731                if self.config.pretty {
15732                    self.write_newline();
15733                    self.write_indent();
15734                } else {
15735                    self.write_space();
15736                }
15737            } else if self.config.pretty {
15738                self.write_newline();
15739                self.write_indent();
15740            }
15741            match rows {
15742                MatchRecognizeRows::OneRowPerMatch => {
15743                    self.write_keyword("ONE ROW PER MATCH");
15744                }
15745                MatchRecognizeRows::AllRowsPerMatch => {
15746                    self.write_keyword("ALL ROWS PER MATCH");
15747                }
15748                MatchRecognizeRows::AllRowsPerMatchShowEmptyMatches => {
15749                    self.write_keyword("ALL ROWS PER MATCH SHOW EMPTY MATCHES");
15750                }
15751                MatchRecognizeRows::AllRowsPerMatchOmitEmptyMatches => {
15752                    self.write_keyword("ALL ROWS PER MATCH OMIT EMPTY MATCHES");
15753                }
15754                MatchRecognizeRows::AllRowsPerMatchWithUnmatchedRows => {
15755                    self.write_keyword("ALL ROWS PER MATCH WITH UNMATCHED ROWS");
15756                }
15757            }
15758            needs_separator = true;
15759        }
15760
15761        // AFTER MATCH SKIP
15762        if let Some(after) = &mr.after {
15763            if needs_separator {
15764                if self.config.pretty {
15765                    self.write_newline();
15766                    self.write_indent();
15767                } else {
15768                    self.write_space();
15769                }
15770            } else if self.config.pretty {
15771                self.write_newline();
15772                self.write_indent();
15773            }
15774            match after {
15775                MatchRecognizeAfter::PastLastRow => {
15776                    self.write_keyword("AFTER MATCH SKIP PAST LAST ROW");
15777                }
15778                MatchRecognizeAfter::ToNextRow => {
15779                    self.write_keyword("AFTER MATCH SKIP TO NEXT ROW");
15780                }
15781                MatchRecognizeAfter::ToFirst(ident) => {
15782                    self.write_keyword("AFTER MATCH SKIP TO FIRST");
15783                    self.write_space();
15784                    self.generate_identifier(ident)?;
15785                }
15786                MatchRecognizeAfter::ToLast(ident) => {
15787                    self.write_keyword("AFTER MATCH SKIP TO LAST");
15788                    self.write_space();
15789                    self.generate_identifier(ident)?;
15790                }
15791            }
15792            needs_separator = true;
15793        }
15794
15795        // PATTERN
15796        if let Some(pattern) = &mr.pattern {
15797            if needs_separator {
15798                if self.config.pretty {
15799                    self.write_newline();
15800                    self.write_indent();
15801                } else {
15802                    self.write_space();
15803                }
15804            } else if self.config.pretty {
15805                self.write_newline();
15806                self.write_indent();
15807            }
15808            self.write_keyword("PATTERN");
15809            self.write_space();
15810            self.write("(");
15811            self.write(pattern);
15812            self.write(")");
15813            needs_separator = true;
15814        }
15815
15816        // DEFINE
15817        if let Some(define) = &mr.define {
15818            if !define.is_empty() {
15819                if needs_separator {
15820                    if self.config.pretty {
15821                        self.write_newline();
15822                        self.write_indent();
15823                    } else {
15824                        self.write_space();
15825                    }
15826                } else if self.config.pretty {
15827                    self.write_newline();
15828                    self.write_indent();
15829                }
15830                self.write_keyword("DEFINE");
15831                // In pretty mode, put each DEFINE on a new indented line
15832                if self.config.pretty {
15833                    self.indent_level += 1;
15834                    for (i, (name, expr)) in define.iter().enumerate() {
15835                        if i > 0 {
15836                            self.write(",");
15837                        }
15838                        self.write_newline();
15839                        self.write_indent();
15840                        self.generate_identifier(name)?;
15841                        self.write(" AS ");
15842                        self.generate_expression(expr)?;
15843                    }
15844                    self.indent_level -= 1;
15845                } else {
15846                    self.write_space();
15847                    for (i, (name, expr)) in define.iter().enumerate() {
15848                        if i > 0 {
15849                            self.write(", ");
15850                        }
15851                        self.generate_identifier(name)?;
15852                        self.write(" AS ");
15853                        self.generate_expression(expr)?;
15854                    }
15855                }
15856            }
15857        }
15858
15859        if self.config.pretty {
15860            self.indent_level -= 1;
15861            self.write_newline();
15862        }
15863        self.write(")");
15864
15865        // Alias - only include AS if it was explicitly present in the input
15866        if let Some(alias) = &mr.alias {
15867            self.write(" ");
15868            if mr.alias_explicit_as {
15869                self.write_keyword("AS");
15870                self.write(" ");
15871            }
15872            self.generate_identifier(alias)?;
15873        }
15874
15875        Ok(())
15876    }
15877
15878    /// Generate a query hint /*+ ... */
15879    fn generate_hint(&mut self, hint: &Hint) -> Result<()> {
15880        use crate::dialects::DialectType;
15881
15882        // Output hints for dialects that support them, or when no dialect is specified (identity tests)
15883        let supports_hints = matches!(
15884            self.config.dialect,
15885            None |  // No dialect = preserve everything
15886            Some(DialectType::Oracle) | Some(DialectType::MySQL) |
15887            Some(DialectType::Spark) | Some(DialectType::Hive) |
15888            Some(DialectType::Databricks) | Some(DialectType::PostgreSQL)
15889        );
15890
15891        if !supports_hints || hint.expressions.is_empty() {
15892            return Ok(());
15893        }
15894
15895        // First, expand raw hint text into individual hint strings
15896        // This handles the case where the parser stored multiple hints as a single raw string
15897        let mut hint_strings: Vec<String> = Vec::new();
15898        for expr in &hint.expressions {
15899            match expr {
15900                HintExpression::Raw(text) => {
15901                    // Parse raw hint text into individual hint function calls
15902                    let parsed = self.parse_raw_hint_text(text);
15903                    hint_strings.extend(parsed);
15904                }
15905                _ => {
15906                    hint_strings.push(self.hint_expression_to_string(expr)?);
15907                }
15908            }
15909        }
15910
15911        // In pretty mode with multiple hints, always use multiline format
15912        // This matches Python sqlglot's behavior where expressions() with default dynamic=False
15913        // always joins with newlines in pretty mode
15914        let use_multiline = self.config.pretty && hint_strings.len() > 1;
15915
15916        if use_multiline {
15917            // Pretty print with each hint on its own line
15918            self.write(" /*+ ");
15919            for (i, hint_str) in hint_strings.iter().enumerate() {
15920                if i > 0 {
15921                    self.write_newline();
15922                    self.write("  "); // 2-space indent within hint block
15923                }
15924                self.write(hint_str);
15925            }
15926            self.write(" */");
15927        } else {
15928            // Single line format
15929            self.write(" /*+ ");
15930            let sep = match self.config.dialect {
15931                Some(DialectType::Spark) | Some(DialectType::Databricks) => ", ",
15932                _ => " ",
15933            };
15934            for (i, hint_str) in hint_strings.iter().enumerate() {
15935                if i > 0 {
15936                    self.write(sep);
15937                }
15938                self.write(hint_str);
15939            }
15940            self.write(" */");
15941        }
15942
15943        Ok(())
15944    }
15945
15946    /// Parse raw hint text into individual hint function calls
15947    /// e.g., "LEADING(a b) USE_NL(c)" -> ["LEADING(a b)", "USE_NL(c)"]
15948    /// If the hint contains unparseable content (like SQL keywords), return as single raw string
15949    fn parse_raw_hint_text(&self, text: &str) -> Vec<String> {
15950        let mut results = Vec::new();
15951        let mut chars = text.chars().peekable();
15952        let mut current = String::new();
15953        let mut paren_depth = 0;
15954        let mut has_unparseable_content = false;
15955        let mut position_after_last_function = 0;
15956        let mut char_position = 0;
15957
15958        while let Some(c) = chars.next() {
15959            char_position += c.len_utf8();
15960            match c {
15961                '(' => {
15962                    paren_depth += 1;
15963                    current.push(c);
15964                }
15965                ')' => {
15966                    paren_depth -= 1;
15967                    current.push(c);
15968                    // When we close the outer parenthesis, we've completed a hint function
15969                    if paren_depth == 0 {
15970                        let trimmed = current.trim().to_string();
15971                        if !trimmed.is_empty() {
15972                            // Format this hint for pretty printing if needed
15973                            let formatted = self.format_hint_function(&trimmed);
15974                            results.push(formatted);
15975                        }
15976                        current.clear();
15977                        position_after_last_function = char_position;
15978                    }
15979                }
15980                ' ' | '\t' | '\n' | ',' if paren_depth == 0 => {
15981                    // Space/comma/whitespace outside parentheses - skip
15982                }
15983                _ if paren_depth == 0 => {
15984                    // Character outside parentheses - accumulate for potential hint name
15985                    current.push(c);
15986                }
15987                _ => {
15988                    current.push(c);
15989                }
15990            }
15991        }
15992
15993        // Check if there's remaining text after the last function call
15994        let remaining_text = text[position_after_last_function..].trim();
15995        if !remaining_text.is_empty() {
15996            // Check if it looks like valid hint function names
15997            // Valid hint identifiers typically are uppercase alphanumeric with underscores
15998            // If we see multiple words without parens, it's likely unparseable
15999            let words: Vec<&str> = remaining_text.split_whitespace().collect();
16000            let looks_like_hint_functions = words.iter().all(|word| {
16001                // A valid hint name followed by opening paren, or a standalone uppercase identifier
16002                word.contains('(') || (word.chars().all(|c| c.is_ascii_uppercase() || c == '_'))
16003            });
16004
16005            if !looks_like_hint_functions && words.len() > 1 {
16006                has_unparseable_content = true;
16007            }
16008        }
16009
16010        // If we detected unparseable content (like SQL keywords), return the whole hint as-is
16011        if has_unparseable_content {
16012            return vec![text.trim().to_string()];
16013        }
16014
16015        // If we couldn't parse anything, return the original text as a single hint
16016        if results.is_empty() {
16017            results.push(text.trim().to_string());
16018        }
16019
16020        results
16021    }
16022
16023    /// Format a hint function for pretty printing
16024    /// e.g., "LEADING(aaa bbb ccc ddd)" -> multiline if args are too wide
16025    fn format_hint_function(&self, hint: &str) -> String {
16026        if !self.config.pretty {
16027            return hint.to_string();
16028        }
16029
16030        // Try to parse NAME(args) pattern
16031        if let Some(paren_pos) = hint.find('(') {
16032            if hint.ends_with(')') {
16033                let name = &hint[..paren_pos];
16034                let args_str = &hint[paren_pos + 1..hint.len() - 1];
16035
16036                // Parse arguments (space-separated for Oracle hints)
16037                let args: Vec<&str> = args_str.split_whitespace().collect();
16038
16039                // Calculate total width of arguments
16040                let total_args_width: usize =
16041                    args.iter().map(|s| s.len()).sum::<usize>() + args.len().saturating_sub(1); // spaces between args
16042
16043                // If too wide, format on multiple lines
16044                if total_args_width > self.config.max_text_width && !args.is_empty() {
16045                    let mut result = format!("{}(\n", name);
16046                    for arg in &args {
16047                        result.push_str("    "); // 4-space indent for args
16048                        result.push_str(arg);
16049                        result.push('\n');
16050                    }
16051                    result.push_str("  )"); // 2-space indent for closing paren
16052                    return result;
16053                }
16054            }
16055        }
16056
16057        hint.to_string()
16058    }
16059
16060    /// Convert a hint expression to a string, handling multiline formatting for long arguments
16061    fn hint_expression_to_string(&mut self, expr: &HintExpression) -> Result<String> {
16062        match expr {
16063            HintExpression::Function { name, args } => {
16064                // Generate each argument to a string
16065                let arg_strings: Vec<String> = args
16066                    .iter()
16067                    .map(|arg| {
16068                        let mut gen = Generator::with_arc_config(self.config.clone());
16069                        gen.generate_expression(arg)?;
16070                        Ok(gen.output)
16071                    })
16072                    .collect::<Result<Vec<_>>>()?;
16073
16074                // Oracle hints use space-separated arguments, not comma-separated
16075                let total_args_width: usize = arg_strings.iter().map(|s| s.len()).sum::<usize>()
16076                    + arg_strings.len().saturating_sub(1); // spaces between args
16077
16078                // Check if function args need multiline formatting
16079                // Use too_wide check for argument formatting
16080                let args_multiline =
16081                    self.config.pretty && total_args_width > self.config.max_text_width;
16082
16083                if args_multiline && !arg_strings.is_empty() {
16084                    // Multiline format for long argument lists
16085                    let mut result = format!("{}(\n", name);
16086                    for arg_str in &arg_strings {
16087                        result.push_str("    "); // 4-space indent for args
16088                        result.push_str(arg_str);
16089                        result.push('\n');
16090                    }
16091                    result.push_str("  )"); // 2-space indent for closing paren
16092                    Ok(result)
16093                } else {
16094                    // Single line format with space-separated args (Oracle style)
16095                    let args_str = arg_strings.join(" ");
16096                    Ok(format!("{}({})", name, args_str))
16097                }
16098            }
16099            HintExpression::Identifier(name) => Ok(name.clone()),
16100            HintExpression::Raw(text) => {
16101                // For pretty printing, try to format the raw text
16102                if self.config.pretty {
16103                    Ok(self.format_hint_function(text))
16104                } else {
16105                    Ok(text.clone())
16106                }
16107            }
16108        }
16109    }
16110
16111    fn generate_table(&mut self, table: &TableRef) -> Result<()> {
16112        // PostgreSQL ONLY modifier: prevents scanning child tables
16113        if table.only {
16114            self.write_keyword("ONLY");
16115            self.write_space();
16116        }
16117
16118        // Check for IDENTIFIER() (Snowflake) or OPENDATASOURCE(...).db.schema.table (TSQL)
16119        if let Some(ref identifier_func) = table.identifier_func {
16120            self.generate_expression(identifier_func)?;
16121            // If table name parts are present, emit .catalog.schema.name after the function
16122            if !table.name.name.is_empty() {
16123                if let Some(catalog) = &table.catalog {
16124                    self.write(".");
16125                    self.generate_identifier(catalog)?;
16126                }
16127                if let Some(schema) = &table.schema {
16128                    self.write(".");
16129                    self.generate_identifier(schema)?;
16130                }
16131                self.write(".");
16132                self.generate_identifier(&table.name)?;
16133            }
16134        } else {
16135            if let Some(catalog) = &table.catalog {
16136                self.generate_identifier(catalog)?;
16137                self.write(".");
16138            }
16139            if let Some(schema) = &table.schema {
16140                self.generate_identifier(schema)?;
16141                self.write(".");
16142            }
16143            self.generate_identifier(&table.name)?;
16144        }
16145
16146        // Output Snowflake CHANGES clause (before partition, includes its own AT/BEFORE/END)
16147        if let Some(changes) = &table.changes {
16148            self.write(" ");
16149            self.generate_changes(changes)?;
16150        }
16151
16152        // Output MySQL PARTITION clause: t1 PARTITION(p0, p1)
16153        if !table.partitions.is_empty() {
16154            self.write_space();
16155            self.write_keyword("PARTITION");
16156            self.write("(");
16157            for (i, partition) in table.partitions.iter().enumerate() {
16158                if i > 0 {
16159                    self.write(", ");
16160                }
16161                self.generate_identifier(partition)?;
16162            }
16163            self.write(")");
16164        }
16165
16166        // Output time travel clause: BEFORE (STATEMENT => ...) or AT (TIMESTAMP => ...)
16167        // Skip if CHANGES clause is present (CHANGES includes its own time travel)
16168        if table.changes.is_none() {
16169            if let Some(when) = &table.when {
16170                self.write_space();
16171                self.generate_historical_data(when)?;
16172            }
16173        }
16174
16175        // Output TSQL FOR SYSTEM_TIME temporal clause (before alias, except BigQuery)
16176        let system_time_post_alias = matches!(self.config.dialect, Some(DialectType::BigQuery));
16177        if !system_time_post_alias {
16178            if let Some(ref system_time) = table.system_time {
16179                self.write_space();
16180                self.write(system_time);
16181            }
16182        }
16183
16184        // Output Presto/Trino time travel: FOR VERSION AS OF / FOR TIMESTAMP AS OF
16185        if let Some(ref version) = table.version {
16186            self.write_space();
16187            self.generate_version(version)?;
16188        }
16189
16190        // When alias_post_tablesample is true, the order is: table TABLESAMPLE (...) alias
16191        // When alias_post_tablesample is false (default), the order is: table alias TABLESAMPLE (...)
16192        // Oracle, Hive, Spark use ALIAS_POST_TABLESAMPLE = true (alias comes after sample)
16193        let alias_post_tablesample = self.config.alias_post_tablesample;
16194
16195        if alias_post_tablesample {
16196            // TABLESAMPLE before alias (Oracle, Hive, Spark)
16197            self.generate_table_sample_clause(table)?;
16198        }
16199
16200        // Output table hints (TSQL: WITH (TABLOCK, INDEX(myindex), ...))
16201        // For SQLite, INDEXED BY hints come after the alias, so skip here
16202        let is_sqlite_hint = matches!(self.config.dialect, Some(DialectType::SQLite))
16203            && table.hints.iter().any(|h| {
16204                if let Expression::Identifier(id) = h {
16205                    id.name.starts_with("INDEXED BY") || id.name == "NOT INDEXED"
16206                } else {
16207                    false
16208                }
16209            });
16210        if !table.hints.is_empty() && !is_sqlite_hint {
16211            for hint in &table.hints {
16212                self.write_space();
16213                self.generate_expression(hint)?;
16214            }
16215        }
16216
16217        if let Some(alias) = &table.alias {
16218            self.write_space();
16219            // Output AS if it was explicitly present in the input, OR for certain dialects/cases
16220            // Generic mode and most dialects always use AS for table aliases
16221            let always_use_as = self.config.dialect.is_none()
16222                || matches!(
16223                    self.config.dialect,
16224                    Some(DialectType::Generic)
16225                        | Some(DialectType::PostgreSQL)
16226                        | Some(DialectType::Redshift)
16227                        | Some(DialectType::Snowflake)
16228                        | Some(DialectType::BigQuery)
16229                        | Some(DialectType::DuckDB)
16230                        | Some(DialectType::Presto)
16231                        | Some(DialectType::Trino)
16232                        | Some(DialectType::TSQL)
16233                        | Some(DialectType::Fabric)
16234                        | Some(DialectType::MySQL)
16235                        | Some(DialectType::Spark)
16236                        | Some(DialectType::Hive)
16237                        | Some(DialectType::SQLite)
16238                        | Some(DialectType::Drill)
16239                );
16240            let is_stage_ref = table.name.name.starts_with('@');
16241            // Oracle never uses AS for table aliases
16242            let suppress_as = matches!(self.config.dialect, Some(DialectType::Oracle));
16243            if !suppress_as && (table.alias_explicit_as || always_use_as || is_stage_ref) {
16244                self.write_keyword("AS");
16245                self.write_space();
16246            }
16247            self.generate_identifier(alias)?;
16248
16249            // Output column aliases if present: AS t(c1, c2)
16250            // Skip for dialects that don't support table alias columns (BigQuery, SQLite)
16251            if !table.column_aliases.is_empty() && self.config.supports_table_alias_columns {
16252                self.write("(");
16253                for (i, col_alias) in table.column_aliases.iter().enumerate() {
16254                    if i > 0 {
16255                        self.write(", ");
16256                    }
16257                    self.generate_identifier(col_alias)?;
16258                }
16259                self.write(")");
16260            }
16261        }
16262
16263        // BigQuery: FOR SYSTEM_TIME AS OF after alias
16264        if system_time_post_alias {
16265            if let Some(ref system_time) = table.system_time {
16266                self.write_space();
16267                self.write(system_time);
16268            }
16269        }
16270
16271        // For default behavior (alias_post_tablesample = false), output TABLESAMPLE after alias
16272        if !alias_post_tablesample {
16273            self.generate_table_sample_clause(table)?;
16274        }
16275
16276        // Output SQLite INDEXED BY / NOT INDEXED hints after alias
16277        if is_sqlite_hint {
16278            for hint in &table.hints {
16279                self.write_space();
16280                self.generate_expression(hint)?;
16281            }
16282        }
16283
16284        // ClickHouse FINAL modifier
16285        if table.final_ && matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
16286            self.write_space();
16287            self.write_keyword("FINAL");
16288        }
16289
16290        // Output trailing comments
16291        for comment in &table.trailing_comments {
16292            self.write_space();
16293            self.write_formatted_comment(comment);
16294        }
16295        // Note: leading_comments (from before table in FROM clause) are intentionally NOT
16296        // output here - they are output by the FROM/PIVOT generator after the full expression
16297
16298        Ok(())
16299    }
16300
16301    /// Helper to output TABLESAMPLE clause for a table reference
16302    fn generate_table_sample_clause(&mut self, table: &TableRef) -> Result<()> {
16303        if let Some(ref ts) = table.table_sample {
16304            self.write_space();
16305            if ts.is_using_sample {
16306                self.write_keyword("USING SAMPLE");
16307            } else {
16308                // Use the configured tablesample keyword (e.g., "TABLESAMPLE" or "SAMPLE")
16309                self.write_keyword(self.config.tablesample_keywords);
16310            }
16311            self.generate_sample_body(ts)?;
16312            // Seed for table-level sample - use dialect's configured keyword
16313            if let Some(ref seed) = ts.seed {
16314                self.write_space();
16315                self.write_keyword(self.config.tablesample_seed_keyword);
16316                self.write(" (");
16317                self.generate_expression(seed)?;
16318                self.write(")");
16319            }
16320        }
16321        Ok(())
16322    }
16323
16324    fn generate_stage_reference(&mut self, sr: &StageReference) -> Result<()> {
16325        // Output: '@stage_name/path' if quoted, or @stage_name/path otherwise
16326        // Optionally followed by (FILE_FORMAT => 'fmt', PATTERN => '*.csv')
16327
16328        if sr.quoted {
16329            self.write("'");
16330        }
16331
16332        self.write(&sr.name);
16333        if let Some(path) = &sr.path {
16334            self.write(path);
16335        }
16336
16337        if sr.quoted {
16338            self.write("'");
16339        }
16340
16341        // Output FILE_FORMAT and PATTERN if present
16342        let has_options = sr.file_format.is_some() || sr.pattern.is_some();
16343        if has_options {
16344            self.write(" (");
16345            let mut first = true;
16346
16347            if let Some(file_format) = &sr.file_format {
16348                if !first {
16349                    self.write(", ");
16350                }
16351                self.write_keyword("FILE_FORMAT");
16352                self.write(" => ");
16353                self.generate_expression(file_format)?;
16354                first = false;
16355            }
16356
16357            if let Some(pattern) = &sr.pattern {
16358                if !first {
16359                    self.write(", ");
16360                }
16361                self.write_keyword("PATTERN");
16362                self.write(" => '");
16363                self.write(pattern);
16364                self.write("'");
16365            }
16366
16367            self.write(")");
16368        }
16369        Ok(())
16370    }
16371
16372    fn generate_star(&mut self, star: &Star) -> Result<()> {
16373        use crate::dialects::DialectType;
16374
16375        if let Some(table) = &star.table {
16376            self.generate_identifier(table)?;
16377            self.write(".");
16378        }
16379        self.write("*");
16380
16381        // Generate EXCLUDE/EXCEPT clause based on dialect
16382        if let Some(except) = &star.except {
16383            if !except.is_empty() {
16384                self.write_space();
16385                // Use dialect-appropriate keyword
16386                match self.config.dialect {
16387                    Some(DialectType::BigQuery) => self.write_keyword("EXCEPT"),
16388                    Some(DialectType::DuckDB) | Some(DialectType::Snowflake) => {
16389                        self.write_keyword("EXCLUDE")
16390                    }
16391                    _ => self.write_keyword("EXCEPT"), // Default to EXCEPT
16392                }
16393                self.write(" (");
16394                for (i, col) in except.iter().enumerate() {
16395                    if i > 0 {
16396                        self.write(", ");
16397                    }
16398                    self.generate_identifier(col)?;
16399                }
16400                self.write(")");
16401            }
16402        }
16403
16404        // Generate REPLACE clause
16405        if let Some(replace) = &star.replace {
16406            if !replace.is_empty() {
16407                self.write_space();
16408                self.write_keyword("REPLACE");
16409                self.write(" (");
16410                for (i, alias) in replace.iter().enumerate() {
16411                    if i > 0 {
16412                        self.write(", ");
16413                    }
16414                    self.generate_expression(&alias.this)?;
16415                    self.write_space();
16416                    self.write_keyword("AS");
16417                    self.write_space();
16418                    self.generate_identifier(&alias.alias)?;
16419                }
16420                self.write(")");
16421            }
16422        }
16423
16424        // Generate RENAME clause (Snowflake specific)
16425        if let Some(rename) = &star.rename {
16426            if !rename.is_empty() {
16427                self.write_space();
16428                self.write_keyword("RENAME");
16429                self.write(" (");
16430                for (i, (old_name, new_name)) in rename.iter().enumerate() {
16431                    if i > 0 {
16432                        self.write(", ");
16433                    }
16434                    self.generate_identifier(old_name)?;
16435                    self.write_space();
16436                    self.write_keyword("AS");
16437                    self.write_space();
16438                    self.generate_identifier(new_name)?;
16439                }
16440                self.write(")");
16441            }
16442        }
16443
16444        // Output trailing comments
16445        for comment in &star.trailing_comments {
16446            self.write_space();
16447            self.write_formatted_comment(comment);
16448        }
16449
16450        Ok(())
16451    }
16452
16453    /// Generate Snowflake braced wildcard syntax: {*}, {tbl.*}, {* EXCLUDE (...)}, {* ILIKE '...'}
16454    fn generate_braced_wildcard(&mut self, expr: &Expression) -> Result<()> {
16455        self.write("{");
16456        match expr {
16457            Expression::Star(star) => {
16458                // Generate the star (table.* or just * with optional EXCLUDE)
16459                self.generate_star(star)?;
16460            }
16461            Expression::ILike(ilike) => {
16462                // {* ILIKE 'pattern'} syntax
16463                self.generate_expression(&ilike.left)?;
16464                self.write_space();
16465                self.write_keyword("ILIKE");
16466                self.write_space();
16467                self.generate_expression(&ilike.right)?;
16468            }
16469            _ => {
16470                self.generate_expression(expr)?;
16471            }
16472        }
16473        self.write("}");
16474        Ok(())
16475    }
16476
16477    fn generate_alias(&mut self, alias: &Alias) -> Result<()> {
16478        // Generate inner expression, but skip trailing comments if they're in pre_alias_comments
16479        // to avoid duplication (comments are captured as both Column.trailing_comments
16480        // and Alias.pre_alias_comments during parsing)
16481        match &alias.this {
16482            Expression::Column(col) => {
16483                // Generate column without trailing comments - they're in pre_alias_comments
16484                if let Some(table) = &col.table {
16485                    self.generate_identifier(table)?;
16486                    self.write(".");
16487                }
16488                self.generate_identifier(&col.name)?;
16489            }
16490            _ => {
16491                self.generate_expression(&alias.this)?;
16492            }
16493        }
16494
16495        // Handle pre-alias comments: when there are no trailing_comments, sqlglot
16496        // moves pre-alias comments to after the alias. When there are also trailing_comments,
16497        // keep pre-alias comments in their original position (between expression and AS).
16498        if !alias.pre_alias_comments.is_empty() && !alias.trailing_comments.is_empty() {
16499            for comment in &alias.pre_alias_comments {
16500                self.write_space();
16501                self.write_formatted_comment(comment);
16502            }
16503        }
16504
16505        use crate::dialects::DialectType;
16506
16507        // Determine if we should skip AS keyword for table-valued function aliases
16508        // Oracle and some other dialects don't use AS for table aliases
16509        // Note: We specifically use TableFromRows here, NOT Function, because Function
16510        // matches regular functions like MATCH_NUMBER() which should include the AS keyword.
16511        // TableFromRows represents TABLE(expr) constructs which are actual table-valued functions.
16512        let is_table_source = matches!(
16513            &alias.this,
16514            Expression::JSONTable(_)
16515                | Expression::XMLTable(_)
16516                | Expression::TableFromRows(_)
16517                | Expression::Unnest(_)
16518                | Expression::MatchRecognize(_)
16519                | Expression::Select(_)
16520                | Expression::Subquery(_)
16521                | Expression::Paren(_)
16522        );
16523        let dialect_skips_table_alias_as = matches!(self.config.dialect, Some(DialectType::Oracle));
16524        let skip_as = is_table_source && dialect_skips_table_alias_as;
16525
16526        self.write_space();
16527        if !skip_as {
16528            self.write_keyword("AS");
16529            self.write_space();
16530        }
16531
16532        // BigQuery doesn't support column aliases in table aliases: AS t(c1, c2)
16533        let skip_column_aliases = matches!(self.config.dialect, Some(DialectType::BigQuery));
16534
16535        // Check if we have column aliases only (no table alias name)
16536        if alias.alias.is_empty() && !alias.column_aliases.is_empty() && !skip_column_aliases {
16537            // Generate AS (col1, col2, ...)
16538            self.write("(");
16539            for (i, col_alias) in alias.column_aliases.iter().enumerate() {
16540                if i > 0 {
16541                    self.write(", ");
16542                }
16543                self.generate_alias_identifier(col_alias)?;
16544            }
16545            self.write(")");
16546        } else if !alias.column_aliases.is_empty() && !skip_column_aliases {
16547            // Generate AS alias(col1, col2, ...)
16548            self.generate_alias_identifier(&alias.alias)?;
16549            self.write("(");
16550            for (i, col_alias) in alias.column_aliases.iter().enumerate() {
16551                if i > 0 {
16552                    self.write(", ");
16553                }
16554                self.generate_alias_identifier(col_alias)?;
16555            }
16556            self.write(")");
16557        } else {
16558            // Simple alias (or BigQuery without column aliases)
16559            self.generate_alias_identifier(&alias.alias)?;
16560        }
16561
16562        // Output trailing comments (comments after the alias)
16563        for comment in &alias.trailing_comments {
16564            self.write_space();
16565            self.write_formatted_comment(comment);
16566        }
16567
16568        // Output pre-alias comments: when there are no trailing_comments, sqlglot
16569        // moves pre-alias comments to after the alias. When there are trailing_comments,
16570        // the pre-alias comments were already lost (consumed as column trailing comments
16571        // that were then used as pre_alias_comments). We always emit them after alias.
16572        if alias.trailing_comments.is_empty() {
16573            for comment in &alias.pre_alias_comments {
16574                self.write_space();
16575                self.write_formatted_comment(comment);
16576            }
16577        }
16578
16579        Ok(())
16580    }
16581
16582    fn generate_cast(&mut self, cast: &Cast) -> Result<()> {
16583        use crate::dialects::DialectType;
16584
16585        // SingleStore uses :> syntax
16586        if matches!(self.config.dialect, Some(DialectType::SingleStore)) {
16587            self.generate_expression(&cast.this)?;
16588            self.write(" :> ");
16589            self.generate_data_type(&cast.to)?;
16590            return Ok(());
16591        }
16592
16593        // Teradata: CAST(x AS FORMAT 'fmt') (no data type)
16594        if matches!(self.config.dialect, Some(DialectType::Teradata)) {
16595            let is_unknown_type = matches!(cast.to, DataType::Unknown)
16596                || matches!(cast.to, DataType::Custom { ref name } if name.is_empty());
16597            if is_unknown_type {
16598                if let Some(format) = &cast.format {
16599                    self.write_keyword("CAST");
16600                    self.write("(");
16601                    self.generate_expression(&cast.this)?;
16602                    self.write_space();
16603                    self.write_keyword("AS");
16604                    self.write_space();
16605                    self.write_keyword("FORMAT");
16606                    self.write_space();
16607                    self.generate_expression(format)?;
16608                    self.write(")");
16609                    return Ok(());
16610                }
16611            }
16612        }
16613
16614        // Oracle: CAST(x AS DATE/TIMESTAMP ..., 'format') -> TO_DATE/TO_TIMESTAMP(x, 'format')
16615        // This follows Python sqlglot's behavior of transforming CAST with format to native functions
16616        if matches!(self.config.dialect, Some(DialectType::Oracle)) {
16617            if let Some(format) = &cast.format {
16618                // Check if target type is DATE or TIMESTAMP
16619                let is_date = matches!(cast.to, DataType::Date);
16620                let is_timestamp = matches!(cast.to, DataType::Timestamp { .. });
16621
16622                if is_date || is_timestamp {
16623                    let func_name = if is_date { "TO_DATE" } else { "TO_TIMESTAMP" };
16624                    self.write_keyword(func_name);
16625                    self.write("(");
16626                    self.generate_expression(&cast.this)?;
16627                    self.write(", ");
16628
16629                    // Normalize format string for Oracle (HH -> HH12)
16630                    // Oracle HH is 12-hour format, same as HH12. For clarity, Python sqlglot uses HH12.
16631                    if let Expression::Literal(lit) = format.as_ref() {
16632                        if let Literal::String(fmt_str) = lit.as_ref() {
16633                            let normalized = self.normalize_oracle_format(fmt_str);
16634                            self.write("'");
16635                            self.write(&normalized);
16636                            self.write("'");
16637                        }
16638                    } else {
16639                        self.generate_expression(format)?;
16640                    }
16641
16642                    self.write(")");
16643                    return Ok(());
16644                }
16645            }
16646        }
16647
16648        // BigQuery: CAST(ARRAY[...] AS ARRAY<T>) -> ARRAY<T>[...]
16649        // This preserves sqlglot's typed inline array literal output.
16650        if matches!(self.config.dialect, Some(DialectType::BigQuery)) {
16651            if let Expression::Array(arr) = &cast.this {
16652                self.generate_data_type(&cast.to)?;
16653                // Output just the bracket content [values] without the ARRAY prefix
16654                self.write("[");
16655                for (i, expr) in arr.expressions.iter().enumerate() {
16656                    if i > 0 {
16657                        self.write(", ");
16658                    }
16659                    self.generate_expression(expr)?;
16660                }
16661                self.write("]");
16662                return Ok(());
16663            }
16664            if matches!(&cast.this, Expression::ArrayFunc(_)) {
16665                self.generate_data_type(&cast.to)?;
16666                self.generate_expression(&cast.this)?;
16667                return Ok(());
16668            }
16669        }
16670
16671        // DuckDB/Presto/Trino: When CAST(Struct([unnamed]) AS STRUCT(...)),
16672        // convert the inner Struct to ROW(values...) format
16673        if matches!(
16674            self.config.dialect,
16675            Some(DialectType::DuckDB) | Some(DialectType::Presto) | Some(DialectType::Trino)
16676        ) {
16677            if let Expression::Struct(ref s) = cast.this {
16678                let all_unnamed = s.fields.iter().all(|(name, _)| name.is_none());
16679                if all_unnamed && matches!(cast.to, DataType::Struct { .. }) {
16680                    self.write_keyword("CAST");
16681                    self.write("(");
16682                    self.generate_struct_as_row(s)?;
16683                    self.write_space();
16684                    self.write_keyword("AS");
16685                    self.write_space();
16686                    self.generate_data_type(&cast.to)?;
16687                    self.write(")");
16688                    return Ok(());
16689                }
16690            }
16691        }
16692
16693        // Determine if we should use :: syntax based on dialect
16694        // PostgreSQL prefers :: for identity, most others prefer CAST()
16695        let use_double_colon = cast.double_colon_syntax && self.dialect_prefers_double_colon();
16696
16697        if use_double_colon {
16698            // PostgreSQL :: syntax: expr::type
16699            self.generate_expression(&cast.this)?;
16700            self.write("::");
16701            self.generate_data_type(&cast.to)?;
16702        } else {
16703            // Standard CAST() syntax
16704            self.write_keyword("CAST");
16705            self.write("(");
16706            self.generate_expression(&cast.this)?;
16707            self.write_space();
16708            self.write_keyword("AS");
16709            self.write_space();
16710            // For MySQL/SingleStore/TiDB, map text/blob variant types to CHAR in CAST
16711            // This matches Python sqlglot's CAST_MAPPING behavior
16712            if matches!(
16713                self.config.dialect,
16714                Some(DialectType::MySQL) | Some(DialectType::SingleStore) | Some(DialectType::TiDB)
16715            ) {
16716                match &cast.to {
16717                    DataType::Custom { ref name } => {
16718                        if name.eq_ignore_ascii_case("LONGTEXT")
16719                            || name.eq_ignore_ascii_case("MEDIUMTEXT")
16720                            || name.eq_ignore_ascii_case("TINYTEXT")
16721                            || name.eq_ignore_ascii_case("LONGBLOB")
16722                            || name.eq_ignore_ascii_case("MEDIUMBLOB")
16723                            || name.eq_ignore_ascii_case("TINYBLOB")
16724                        {
16725                            self.write_keyword("CHAR");
16726                        } else {
16727                            self.generate_data_type(&cast.to)?;
16728                        }
16729                    }
16730                    DataType::VarChar { length, .. } => {
16731                        // MySQL CAST: VARCHAR -> CHAR
16732                        self.write_keyword("CHAR");
16733                        if let Some(n) = length {
16734                            self.write(&format!("({})", n));
16735                        }
16736                    }
16737                    DataType::Text => {
16738                        // MySQL CAST: TEXT -> CHAR
16739                        self.write_keyword("CHAR");
16740                    }
16741                    DataType::Timestamp {
16742                        precision,
16743                        timezone: false,
16744                    } => {
16745                        // MySQL CAST: TIMESTAMP -> DATETIME
16746                        self.write_keyword("DATETIME");
16747                        if let Some(p) = precision {
16748                            self.write(&format!("({})", p));
16749                        }
16750                    }
16751                    _ => {
16752                        self.generate_data_type(&cast.to)?;
16753                    }
16754                }
16755            } else if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
16756                // Snowflake CAST: STRING -> VARCHAR
16757                match &cast.to {
16758                    DataType::String { length } => {
16759                        self.write_keyword("VARCHAR");
16760                        if let Some(n) = length {
16761                            self.write(&format!("({})", n));
16762                        }
16763                    }
16764                    _ => {
16765                        self.generate_data_type(&cast.to)?;
16766                    }
16767                }
16768            } else {
16769                self.generate_data_type(&cast.to)?;
16770            }
16771
16772            // Output DEFAULT ... ON CONVERSION ERROR clause if present (Oracle)
16773            if let Some(default) = &cast.default {
16774                self.write_space();
16775                self.write_keyword("DEFAULT");
16776                self.write_space();
16777                self.generate_expression(default)?;
16778                self.write_space();
16779                self.write_keyword("ON");
16780                self.write_space();
16781                self.write_keyword("CONVERSION");
16782                self.write_space();
16783                self.write_keyword("ERROR");
16784            }
16785
16786            // Output FORMAT clause if present (BigQuery: CAST(x AS STRING FORMAT 'format'))
16787            // For Oracle with comma-separated format: CAST(x AS DATE DEFAULT NULL ON CONVERSION ERROR, 'format')
16788            if let Some(format) = &cast.format {
16789                // Check if Oracle dialect - use comma syntax
16790                if matches!(
16791                    self.config.dialect,
16792                    Some(crate::dialects::DialectType::Oracle)
16793                ) {
16794                    self.write(", ");
16795                } else {
16796                    self.write_space();
16797                    self.write_keyword("FORMAT");
16798                    self.write_space();
16799                }
16800                self.generate_expression(format)?;
16801            }
16802
16803            self.write(")");
16804            // Output trailing comments
16805            for comment in &cast.trailing_comments {
16806                self.write_space();
16807                self.write_formatted_comment(comment);
16808            }
16809        }
16810        Ok(())
16811    }
16812
16813    /// Generate a Struct as ROW(values...) format, recursively converting inner Struct to ROW too.
16814    /// Used for DuckDB/Presto/Trino CAST(Struct AS STRUCT(...)) context.
16815    fn generate_struct_as_row(&mut self, s: &crate::expressions::Struct) -> Result<()> {
16816        self.write_keyword("ROW");
16817        self.write("(");
16818        for (i, (_, expr)) in s.fields.iter().enumerate() {
16819            if i > 0 {
16820                self.write(", ");
16821            }
16822            // Recursively convert inner Struct to ROW format
16823            if let Expression::Struct(ref inner_s) = expr {
16824                self.generate_struct_as_row(inner_s)?;
16825            } else {
16826                self.generate_expression(expr)?;
16827            }
16828        }
16829        self.write(")");
16830        Ok(())
16831    }
16832
16833    /// Normalize Oracle date/time format strings
16834    /// HH -> HH12 (both are 12-hour format, but Python sqlglot prefers explicit HH12)
16835    fn normalize_oracle_format(&self, format: &str) -> String {
16836        // Replace standalone HH with HH12 (but not HH12 or HH24)
16837        // We need to be careful not to replace HH12 -> HH1212 or HH24 -> HH1224
16838        let mut result = String::new();
16839        let chars: Vec<char> = format.chars().collect();
16840        let mut i = 0;
16841
16842        while i < chars.len() {
16843            if i + 1 < chars.len() && chars[i] == 'H' && chars[i + 1] == 'H' {
16844                // Check what follows HH
16845                if i + 2 < chars.len() {
16846                    let next = chars[i + 2];
16847                    if next == '1' || next == '2' {
16848                        // This is HH12 or HH24, keep as is
16849                        result.push('H');
16850                        result.push('H');
16851                        i += 2;
16852                        continue;
16853                    }
16854                }
16855                // Standalone HH -> HH12
16856                result.push_str("HH12");
16857                i += 2;
16858            } else {
16859                result.push(chars[i]);
16860                i += 1;
16861            }
16862        }
16863
16864        result
16865    }
16866
16867    /// Check if the current dialect prefers :: cast syntax
16868    /// Note: Python sqlglot normalizes all :: to CAST() for output, even for PostgreSQL
16869    /// So we return false for all dialects to match Python sqlglot's behavior
16870    fn dialect_prefers_double_colon(&self) -> bool {
16871        // Python sqlglot normalizes :: syntax to CAST() for all dialects
16872        // Even PostgreSQL outputs CAST() not ::
16873        false
16874    }
16875
16876    /// Generate MOD function - uses % operator for Snowflake/MySQL/Presto/Trino, MOD() for others
16877    fn generate_mod_func(&mut self, f: &crate::expressions::BinaryFunc) -> Result<()> {
16878        use crate::dialects::DialectType;
16879
16880        // Snowflake, MySQL, Presto, Trino, PostgreSQL, and DuckDB prefer x % y instead of MOD(x, y)
16881        let use_percent_operator = matches!(
16882            self.config.dialect,
16883            Some(DialectType::Snowflake)
16884                | Some(DialectType::MySQL)
16885                | Some(DialectType::Presto)
16886                | Some(DialectType::Trino)
16887                | Some(DialectType::PostgreSQL)
16888                | Some(DialectType::DuckDB)
16889                | Some(DialectType::Hive)
16890                | Some(DialectType::Spark)
16891                | Some(DialectType::Databricks)
16892                | Some(DialectType::Athena)
16893        );
16894
16895        if use_percent_operator {
16896            // Wrap complex expressions in parens to preserve precedence
16897            // Since % has higher precedence than +/-, we need parens for Add/Sub on either side
16898            let needs_paren = |e: &Expression| matches!(e, Expression::Add(_) | Expression::Sub(_));
16899            if needs_paren(&f.this) {
16900                self.write("(");
16901                self.generate_expression(&f.this)?;
16902                self.write(")");
16903            } else {
16904                self.generate_expression(&f.this)?;
16905            }
16906            self.write(" % ");
16907            if needs_paren(&f.expression) {
16908                self.write("(");
16909                self.generate_expression(&f.expression)?;
16910                self.write(")");
16911            } else {
16912                self.generate_expression(&f.expression)?;
16913            }
16914            Ok(())
16915        } else {
16916            self.generate_binary_func("MOD", &f.this, &f.expression)
16917        }
16918    }
16919
16920    /// Generate IFNULL - uses COALESCE for Snowflake, IFNULL for others
16921    fn generate_ifnull(&mut self, f: &crate::expressions::BinaryFunc) -> Result<()> {
16922        use crate::dialects::DialectType;
16923
16924        // Snowflake normalizes IFNULL to COALESCE
16925        let func_name = match self.config.dialect {
16926            Some(DialectType::Snowflake) => "COALESCE",
16927            _ => "IFNULL",
16928        };
16929
16930        self.generate_binary_func(func_name, &f.this, &f.expression)
16931    }
16932
16933    /// Generate NVL - preserves original name if available, otherwise uses dialect-specific output
16934    fn generate_nvl(&mut self, f: &crate::expressions::BinaryFunc) -> Result<()> {
16935        // Use original function name if preserved (for identity tests)
16936        if let Some(ref original_name) = f.original_name {
16937            return self.generate_binary_func(original_name, &f.this, &f.expression);
16938        }
16939
16940        // Otherwise, use dialect-specific function names
16941        use crate::dialects::DialectType;
16942        let func_name = match self.config.dialect {
16943            Some(DialectType::Snowflake)
16944            | Some(DialectType::ClickHouse)
16945            | Some(DialectType::PostgreSQL)
16946            | Some(DialectType::Presto)
16947            | Some(DialectType::Trino)
16948            | Some(DialectType::Athena)
16949            | Some(DialectType::DuckDB)
16950            | Some(DialectType::BigQuery)
16951            | Some(DialectType::Spark)
16952            | Some(DialectType::Databricks)
16953            | Some(DialectType::Hive) => "COALESCE",
16954            Some(DialectType::MySQL)
16955            | Some(DialectType::Doris)
16956            | Some(DialectType::StarRocks)
16957            | Some(DialectType::SingleStore)
16958            | Some(DialectType::TiDB) => "IFNULL",
16959            _ => "NVL",
16960        };
16961
16962        self.generate_binary_func(func_name, &f.this, &f.expression)
16963    }
16964
16965    /// Generate STDDEV_SAMP - uses STDDEV for Snowflake, STDDEV_SAMP for others
16966    fn generate_stddev_samp(&mut self, f: &crate::expressions::AggFunc) -> Result<()> {
16967        use crate::dialects::DialectType;
16968
16969        // Snowflake normalizes STDDEV_SAMP to STDDEV
16970        let func_name = match self.config.dialect {
16971            Some(DialectType::Snowflake) => "STDDEV",
16972            _ => "STDDEV_SAMP",
16973        };
16974
16975        self.generate_agg_func(func_name, f)
16976    }
16977
16978    fn generate_collation(&mut self, coll: &CollationExpr) -> Result<()> {
16979        self.generate_expression(&coll.this)?;
16980        self.write_space();
16981        self.write_keyword("COLLATE");
16982        self.write_space();
16983        if coll.quoted {
16984            // Single-quoted string: COLLATE 'de_DE'
16985            self.write("'");
16986            self.write(&coll.collation);
16987            self.write("'");
16988        } else if coll.double_quoted {
16989            // Double-quoted identifier: COLLATE "de_DE"
16990            self.write("\"");
16991            self.write(&coll.collation);
16992            self.write("\"");
16993        } else {
16994            // Unquoted identifier: COLLATE de_DE
16995            self.write(&coll.collation);
16996        }
16997        Ok(())
16998    }
16999
17000    fn generate_case(&mut self, case: &Case) -> Result<()> {
17001        // In pretty mode, decide whether to expand based on total text width
17002        let multiline_case = if self.config.pretty {
17003            // Build the flat representation to check width
17004            let mut statements: Vec<String> = Vec::new();
17005            let operand_str = if let Some(operand) = &case.operand {
17006                let s = self.generate_to_string(operand)?;
17007                statements.push(format!("CASE {}", s));
17008                s
17009            } else {
17010                statements.push("CASE".to_string());
17011                String::new()
17012            };
17013            let _ = operand_str;
17014            for (condition, result) in &case.whens {
17015                statements.push(format!("WHEN {}", self.generate_to_string(condition)?));
17016                statements.push(format!("THEN {}", self.generate_to_string(result)?));
17017            }
17018            if let Some(else_) = &case.else_ {
17019                statements.push(format!("ELSE {}", self.generate_to_string(else_)?));
17020            }
17021            statements.push("END".to_string());
17022            self.too_wide(&statements)
17023        } else {
17024            false
17025        };
17026
17027        self.write_keyword("CASE");
17028        if let Some(operand) = &case.operand {
17029            self.write_space();
17030            self.generate_expression(operand)?;
17031        }
17032        if multiline_case {
17033            self.indent_level += 1;
17034        }
17035        for (condition, result) in &case.whens {
17036            if multiline_case {
17037                self.write_newline();
17038                self.write_indent();
17039            } else {
17040                self.write_space();
17041            }
17042            self.write_keyword("WHEN");
17043            self.write_space();
17044            self.generate_expression(condition)?;
17045            if multiline_case {
17046                self.write_newline();
17047                self.write_indent();
17048            } else {
17049                self.write_space();
17050            }
17051            self.write_keyword("THEN");
17052            self.write_space();
17053            self.generate_expression(result)?;
17054        }
17055        if let Some(else_) = &case.else_ {
17056            if multiline_case {
17057                self.write_newline();
17058                self.write_indent();
17059            } else {
17060                self.write_space();
17061            }
17062            self.write_keyword("ELSE");
17063            self.write_space();
17064            self.generate_expression(else_)?;
17065        }
17066        if multiline_case {
17067            self.indent_level -= 1;
17068            self.write_newline();
17069            self.write_indent();
17070        } else {
17071            self.write_space();
17072        }
17073        self.write_keyword("END");
17074        // Emit any comments that were attached to the CASE keyword
17075        for comment in &case.comments {
17076            self.write(" ");
17077            self.write_formatted_comment(comment);
17078        }
17079        Ok(())
17080    }
17081
17082    fn generate_function(&mut self, func: &Function) -> Result<()> {
17083        // Normalize function name based on dialect settings
17084        let normalized_name = self.normalize_func_name(&func.name);
17085
17086        // DuckDB: ARRAY_CONSTRUCT_COMPACT(a, b, c) -> LIST_FILTER([a, b, c], _u -> NOT _u IS NULL)
17087        if matches!(self.config.dialect, Some(DialectType::DuckDB))
17088            && func.name.eq_ignore_ascii_case("ARRAY_CONSTRUCT_COMPACT")
17089        {
17090            self.write("LIST_FILTER(");
17091            self.write("[");
17092            for (i, arg) in func.args.iter().enumerate() {
17093                if i > 0 {
17094                    self.write(", ");
17095                }
17096                self.generate_expression(arg)?;
17097            }
17098            self.write("], _u -> NOT _u IS NULL)");
17099            return Ok(());
17100        }
17101
17102        // Snowflake fixtures expect TO_VARIANT applied to arrays to keep ARRAY_CONSTRUCT(...)
17103        // rather than bracket-array syntax.
17104        if matches!(self.config.dialect, Some(DialectType::Snowflake))
17105            && func.name.eq_ignore_ascii_case("TO_VARIANT")
17106            && func.args.len() == 1
17107        {
17108            let array_expressions = match &func.args[0] {
17109                Expression::ArrayFunc(arr) => Some(&arr.expressions),
17110                Expression::Array(arr) => Some(&arr.expressions),
17111                _ => None,
17112            };
17113            if let Some(expressions) = array_expressions {
17114                self.write_keyword("TO_VARIANT");
17115                self.write("(");
17116                self.write_keyword("ARRAY_CONSTRUCT");
17117                self.write("(");
17118                for (i, arg) in expressions.iter().enumerate() {
17119                    if i > 0 {
17120                        self.write(", ");
17121                    }
17122                    self.generate_expression(arg)?;
17123                }
17124                self.write(")");
17125                self.write(")");
17126                return Ok(());
17127            }
17128        }
17129
17130        // STRUCT function: BigQuery STRUCT('Alice' AS name, 85 AS score) -> dialect-specific
17131        if func.name.eq_ignore_ascii_case("STRUCT")
17132            && !matches!(
17133                self.config.dialect,
17134                Some(DialectType::BigQuery)
17135                    | Some(DialectType::Spark)
17136                    | Some(DialectType::Databricks)
17137                    | Some(DialectType::Hive)
17138                    | None
17139            )
17140        {
17141            return self.generate_struct_function_cross_dialect(func);
17142        }
17143
17144        // SingleStore: __SS_JSON_PATH_QMARK__(expr, key) -> expr::?key
17145        // This is an internal marker function for ::? JSON path syntax
17146        if func.name.eq_ignore_ascii_case("__SS_JSON_PATH_QMARK__") && func.args.len() == 2 {
17147            self.generate_expression(&func.args[0])?;
17148            self.write("::?");
17149            // Extract the key from the string literal
17150            if let Expression::Literal(lit) = &func.args[1] {
17151                if let crate::expressions::Literal::String(key) = lit.as_ref() {
17152                    self.write(key);
17153                }
17154            } else {
17155                self.generate_expression(&func.args[1])?;
17156            }
17157            return Ok(());
17158        }
17159
17160        // PostgreSQL: __PG_BITWISE_XOR__(a, b) -> a # b
17161        if func.name.eq_ignore_ascii_case("__PG_BITWISE_XOR__") && func.args.len() == 2 {
17162            self.generate_expression(&func.args[0])?;
17163            self.write(" # ");
17164            self.generate_expression(&func.args[1])?;
17165            return Ok(());
17166        }
17167
17168        // Spark/Hive family: unwrap TRY(expr) since these dialects don't emit TRY as a scalar wrapper.
17169        if matches!(
17170            self.config.dialect,
17171            Some(DialectType::Spark | DialectType::Databricks | DialectType::Hive)
17172        ) && func.name.eq_ignore_ascii_case("TRY")
17173            && func.args.len() == 1
17174        {
17175            self.generate_expression(&func.args[0])?;
17176            return Ok(());
17177        }
17178
17179        // ClickHouse normalization: toStartOfDay(x) -> dateTrunc('DAY', x)
17180        if self.config.dialect == Some(DialectType::ClickHouse)
17181            && func.name.eq_ignore_ascii_case("TOSTARTOFDAY")
17182            && func.args.len() == 1
17183        {
17184            self.write("dateTrunc('DAY', ");
17185            self.generate_expression(&func.args[0])?;
17186            self.write(")");
17187            return Ok(());
17188        }
17189
17190        // Redshift: CONCAT(a, b, ...) -> a || b || ...
17191        if self.config.dialect == Some(DialectType::Redshift)
17192            && func.name.eq_ignore_ascii_case("CONCAT")
17193            && func.args.len() >= 2
17194        {
17195            for (i, arg) in func.args.iter().enumerate() {
17196                if i > 0 {
17197                    self.write(" || ");
17198                }
17199                self.generate_expression(arg)?;
17200            }
17201            return Ok(());
17202        }
17203
17204        // Redshift: CONCAT_WS(delim, a, b, c) -> a || delim || b || delim || c
17205        if self.config.dialect == Some(DialectType::Redshift)
17206            && func.name.eq_ignore_ascii_case("CONCAT_WS")
17207            && func.args.len() >= 2
17208        {
17209            let sep = &func.args[0];
17210            for (i, arg) in func.args.iter().skip(1).enumerate() {
17211                if i > 0 {
17212                    self.write(" || ");
17213                    self.generate_expression(sep)?;
17214                    self.write(" || ");
17215                }
17216                self.generate_expression(arg)?;
17217            }
17218            return Ok(());
17219        }
17220
17221        // Redshift: DATEDIFF/DATE_DIFF(unit, start, end) -> DATEDIFF(UNIT, start, end)
17222        // Unit should be unquoted uppercase identifier
17223        if self.config.dialect == Some(DialectType::Redshift)
17224            && (func.name.eq_ignore_ascii_case("DATEDIFF")
17225                || func.name.eq_ignore_ascii_case("DATE_DIFF"))
17226            && func.args.len() == 3
17227        {
17228            self.write_keyword("DATEDIFF");
17229            self.write("(");
17230            // First arg is unit - normalize to unquoted uppercase
17231            self.write_redshift_date_part(&func.args[0]);
17232            self.write(", ");
17233            self.generate_expression(&func.args[1])?;
17234            self.write(", ");
17235            self.generate_expression(&func.args[2])?;
17236            self.write(")");
17237            return Ok(());
17238        }
17239
17240        // Redshift: DATEADD/DATE_ADD(unit, interval, date) -> DATEADD(UNIT, interval, date)
17241        // Unit should be unquoted uppercase identifier
17242        if self.config.dialect == Some(DialectType::Redshift)
17243            && (func.name.eq_ignore_ascii_case("DATEADD")
17244                || func.name.eq_ignore_ascii_case("DATE_ADD"))
17245            && func.args.len() == 3
17246        {
17247            self.write_keyword("DATEADD");
17248            self.write("(");
17249            // First arg is unit - normalize to unquoted uppercase
17250            self.write_redshift_date_part(&func.args[0]);
17251            self.write(", ");
17252            self.generate_expression(&func.args[1])?;
17253            self.write(", ");
17254            self.generate_expression(&func.args[2])?;
17255            self.write(")");
17256            return Ok(());
17257        }
17258
17259        // UUID_STRING(args) from Snowflake -> dialect-specific UUID function (dropping args)
17260        if func.name.eq_ignore_ascii_case("UUID_STRING")
17261            && !matches!(self.config.dialect, Some(DialectType::Snowflake) | None)
17262        {
17263            let func_name = match self.config.dialect {
17264                Some(DialectType::PostgreSQL) | Some(DialectType::Redshift) => "GEN_RANDOM_UUID",
17265                Some(DialectType::BigQuery) => "GENERATE_UUID",
17266                _ => "UUID",
17267            };
17268            self.write_keyword(func_name);
17269            self.write("()");
17270            return Ok(());
17271        }
17272
17273        // Snowflake: GENERATOR(val) -> GENERATOR(ROWCOUNT => val)
17274        // GENERATOR(val1, val2) -> GENERATOR(ROWCOUNT => val1, TIMELIMIT => val2)
17275        // Positional args are mapped to named parameters.
17276        if matches!(self.config.dialect, Some(DialectType::Snowflake))
17277            && func.name.eq_ignore_ascii_case("GENERATOR")
17278        {
17279            let has_positional_args =
17280                !func.args.is_empty() && !matches!(&func.args[0], Expression::NamedArgument(_));
17281            if has_positional_args {
17282                let param_names = ["ROWCOUNT", "TIMELIMIT"];
17283                self.write_keyword("GENERATOR");
17284                self.write("(");
17285                for (i, arg) in func.args.iter().enumerate() {
17286                    if i > 0 {
17287                        self.write(", ");
17288                    }
17289                    if i < param_names.len() {
17290                        self.write_keyword(param_names[i]);
17291                        self.write(" => ");
17292                        self.generate_expression(arg)?;
17293                    } else {
17294                        self.generate_expression(arg)?;
17295                    }
17296                }
17297                self.write(")");
17298                return Ok(());
17299            }
17300        }
17301
17302        // Redshift: DATE_TRUNC('unit', date) -> DATE_TRUNC('UNIT', date)
17303        // Unit should be quoted uppercase string
17304        if self.config.dialect == Some(DialectType::Redshift)
17305            && func.name.eq_ignore_ascii_case("DATE_TRUNC")
17306            && func.args.len() == 2
17307        {
17308            self.write_keyword("DATE_TRUNC");
17309            self.write("(");
17310            // First arg is unit - normalize to quoted uppercase
17311            self.write_redshift_date_part_quoted(&func.args[0]);
17312            self.write(", ");
17313            self.generate_expression(&func.args[1])?;
17314            self.write(")");
17315            return Ok(());
17316        }
17317
17318        // TSQL/Fabric: DATE_PART -> DATEPART (no underscore)
17319        if matches!(
17320            self.config.dialect,
17321            Some(DialectType::TSQL) | Some(DialectType::Fabric)
17322        ) && (func.name.eq_ignore_ascii_case("DATE_PART")
17323            || func.name.eq_ignore_ascii_case("DATEPART"))
17324            && func.args.len() == 2
17325        {
17326            self.write_keyword("DATEPART");
17327            self.write("(");
17328            self.generate_expression(&func.args[0])?;
17329            self.write(", ");
17330            self.generate_expression(&func.args[1])?;
17331            self.write(")");
17332            return Ok(());
17333        }
17334
17335        // PostgreSQL/Redshift: DATE_PART(part, value) -> EXTRACT(part FROM value)
17336        if matches!(
17337            self.config.dialect,
17338            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
17339        ) && (func.name.eq_ignore_ascii_case("DATE_PART")
17340            || func.name.eq_ignore_ascii_case("DATEPART"))
17341            && func.args.len() == 2
17342        {
17343            self.write_keyword("EXTRACT");
17344            self.write("(");
17345            // Extract the datetime field - if it's a string literal, strip quotes to make it a keyword
17346            match &func.args[0] {
17347                Expression::Literal(lit)
17348                    if matches!(lit.as_ref(), crate::expressions::Literal::String(_)) =>
17349                {
17350                    let crate::expressions::Literal::String(s) = lit.as_ref() else {
17351                        unreachable!()
17352                    };
17353                    self.write(&s.to_ascii_lowercase());
17354                }
17355                _ => self.generate_expression(&func.args[0])?,
17356            }
17357            self.write_space();
17358            self.write_keyword("FROM");
17359            self.write_space();
17360            self.generate_expression(&func.args[1])?;
17361            self.write(")");
17362            return Ok(());
17363        }
17364
17365        // PostgreSQL: DATE_ADD(date, INTERVAL '...') / DATE_SUB(...) -> infix interval arithmetic.
17366        if self.config.dialect == Some(DialectType::PostgreSQL)
17367            && matches!(
17368                func.name.to_ascii_uppercase().as_str(),
17369                "DATE_ADD" | "DATE_SUB"
17370            )
17371            && func.args.len() == 2
17372            && matches!(func.args[1], Expression::Interval(_))
17373        {
17374            self.generate_expression(&func.args[0])?;
17375            self.write_space();
17376            if func.name.eq_ignore_ascii_case("DATE_SUB") {
17377                self.write("-");
17378            } else {
17379                self.write("+");
17380            }
17381            self.write_space();
17382            self.generate_expression(&func.args[1])?;
17383            return Ok(());
17384        }
17385
17386        // Dremio: DATE_PART(part, value) -> EXTRACT(part FROM value)
17387        // Also DATE literals in Dremio should be CAST(...AS DATE)
17388        if self.config.dialect == Some(DialectType::Dremio)
17389            && (func.name.eq_ignore_ascii_case("DATE_PART")
17390                || func.name.eq_ignore_ascii_case("DATEPART"))
17391            && func.args.len() == 2
17392        {
17393            self.write_keyword("EXTRACT");
17394            self.write("(");
17395            self.generate_expression(&func.args[0])?;
17396            self.write_space();
17397            self.write_keyword("FROM");
17398            self.write_space();
17399            // For Dremio, DATE literals should become CAST('value' AS DATE)
17400            self.generate_dremio_date_expression(&func.args[1])?;
17401            self.write(")");
17402            return Ok(());
17403        }
17404
17405        // Dremio: CURRENT_DATE_UTC() -> CURRENT_DATE_UTC (no parentheses)
17406        if self.config.dialect == Some(DialectType::Dremio)
17407            && func.name.eq_ignore_ascii_case("CURRENT_DATE_UTC")
17408            && func.args.is_empty()
17409        {
17410            self.write_keyword("CURRENT_DATE_UTC");
17411            return Ok(());
17412        }
17413
17414        // Dremio: DATETYPE(year, month, day) transformation
17415        // - If all args are integer literals: DATE('YYYY-MM-DD')
17416        // - If args are expressions: CAST(CONCAT(x, '-', y, '-', z) AS DATE)
17417        if self.config.dialect == Some(DialectType::Dremio)
17418            && func.name.eq_ignore_ascii_case("DATETYPE")
17419            && func.args.len() == 3
17420        {
17421            // Helper function to extract integer from number literal
17422            fn get_int_literal(expr: &Expression) -> Option<i64> {
17423                if let Expression::Literal(lit) = expr {
17424                    if let crate::expressions::Literal::Number(s) = lit.as_ref() {
17425                        s.parse::<i64>().ok()
17426                    } else {
17427                        None
17428                    }
17429                } else {
17430                    None
17431                }
17432            }
17433
17434            // Check if all arguments are integer literals
17435            if let (Some(year), Some(month), Some(day)) = (
17436                get_int_literal(&func.args[0]),
17437                get_int_literal(&func.args[1]),
17438                get_int_literal(&func.args[2]),
17439            ) {
17440                // All are integer literals: DATE('YYYY-MM-DD')
17441                self.write_keyword("DATE");
17442                self.write(&format!("('{:04}-{:02}-{:02}')", year, month, day));
17443                return Ok(());
17444            }
17445
17446            // For expressions: CAST(CONCAT(x, '-', y, '-', z) AS DATE)
17447            self.write_keyword("CAST");
17448            self.write("(");
17449            self.write_keyword("CONCAT");
17450            self.write("(");
17451            self.generate_expression(&func.args[0])?;
17452            self.write(", '-', ");
17453            self.generate_expression(&func.args[1])?;
17454            self.write(", '-', ");
17455            self.generate_expression(&func.args[2])?;
17456            self.write(")");
17457            self.write_space();
17458            self.write_keyword("AS");
17459            self.write_space();
17460            self.write_keyword("DATE");
17461            self.write(")");
17462            return Ok(());
17463        }
17464
17465        // Presto/Trino: DATE_ADD('unit', interval, date) - wrap interval in CAST(...AS BIGINT)
17466        // when it's not an integer literal
17467        let is_presto_like = matches!(
17468            self.config.dialect,
17469            Some(DialectType::Presto) | Some(DialectType::Trino)
17470        );
17471        if is_presto_like && func.name.eq_ignore_ascii_case("DATE_ADD") && func.args.len() == 3 {
17472            self.write_keyword("DATE_ADD");
17473            self.write("(");
17474            // First arg: unit (pass through as-is, e.g., 'DAY')
17475            self.generate_expression(&func.args[0])?;
17476            self.write(", ");
17477            // Second arg: interval - wrap in CAST(...AS BIGINT) if it doesn't return integer type
17478            let interval = &func.args[1];
17479            let needs_cast = !self.returns_integer_type(interval);
17480            if needs_cast {
17481                self.write_keyword("CAST");
17482                self.write("(");
17483            }
17484            self.generate_expression(interval)?;
17485            if needs_cast {
17486                self.write_space();
17487                self.write_keyword("AS");
17488                self.write_space();
17489                self.write_keyword("BIGINT");
17490                self.write(")");
17491            }
17492            self.write(", ");
17493            // Third arg: date
17494            self.generate_expression(&func.args[2])?;
17495            self.write(")");
17496            return Ok(());
17497        }
17498
17499        // Use bracket syntax if the function was parsed with brackets (e.g., MAP[keys, values])
17500        let use_brackets = func.use_bracket_syntax;
17501
17502        // Special case: functions WITH ORDINALITY need special output order
17503        // Input: FUNC(args) WITH ORDINALITY
17504        // Stored as: name="FUNC WITH ORDINALITY", args=[...]
17505        // Output must be: FUNC(args) WITH ORDINALITY
17506        let has_ordinality = func.name.len() >= 16
17507            && func.name[func.name.len() - 16..].eq_ignore_ascii_case(" WITH ORDINALITY");
17508        let output_name = if has_ordinality {
17509            let base_name = &func.name[..func.name.len() - " WITH ORDINALITY".len()];
17510            self.normalize_func_name(base_name)
17511        } else {
17512            normalized_name.clone()
17513        };
17514
17515        // For qualified names (schema.function or object.method), preserve original case
17516        // because they can be case-sensitive (e.g., TSQL XML methods like .nodes(), .value())
17517        if func.name.contains('.') && !has_ordinality {
17518            // Don't normalize qualified functions - preserve original case
17519            // If the function was quoted (e.g., BigQuery `p.d.UdF`), wrap it in backticks
17520            if func.quoted {
17521                self.write("`");
17522                self.write(&func.name);
17523                self.write("`");
17524            } else {
17525                self.write(&func.name);
17526            }
17527        } else {
17528            self.write(&output_name);
17529        }
17530
17531        // If no_parens is true and there are no args, output just the function name
17532        // Unless the target dialect requires parens for this function
17533        let force_parens = func.no_parens && func.args.is_empty() && !func.distinct && {
17534            let needs_parens = if func.name.eq_ignore_ascii_case("CURRENT_USER")
17535                || func.name.eq_ignore_ascii_case("SESSION_USER")
17536                || func.name.eq_ignore_ascii_case("SYSTEM_USER")
17537            {
17538                matches!(
17539                    self.config.dialect,
17540                    Some(DialectType::Snowflake)
17541                        | Some(DialectType::Spark)
17542                        | Some(DialectType::Databricks)
17543                        | Some(DialectType::Hive)
17544                )
17545            } else {
17546                false
17547            };
17548            !needs_parens
17549        };
17550        if force_parens {
17551            // Output trailing comments
17552            for comment in &func.trailing_comments {
17553                self.write_space();
17554                self.write_formatted_comment(comment);
17555            }
17556            return Ok(());
17557        }
17558
17559        // CUBE, ROLLUP, GROUPING SETS need a space before the parenthesis
17560        if func.name.eq_ignore_ascii_case("CUBE")
17561            || func.name.eq_ignore_ascii_case("ROLLUP")
17562            || func.name.eq_ignore_ascii_case("GROUPING SETS")
17563        {
17564            self.write(" (");
17565        } else if use_brackets {
17566            self.write("[");
17567        } else {
17568            self.write("(");
17569        }
17570        if func.distinct {
17571            self.write_keyword("DISTINCT");
17572            self.write_space();
17573        }
17574
17575        // Check if arguments should be split onto multiple lines (pretty + too wide)
17576        let compact_pretty_func = matches!(self.config.dialect, Some(DialectType::Snowflake))
17577            && (func.name.eq_ignore_ascii_case("TABLE")
17578                || func.name.eq_ignore_ascii_case("FLATTEN"));
17579        // GROUPING SETS, CUBE, ROLLUP always expand in pretty mode
17580        let is_grouping_func = func.name.eq_ignore_ascii_case("GROUPING SETS")
17581            || func.name.eq_ignore_ascii_case("CUBE")
17582            || func.name.eq_ignore_ascii_case("ROLLUP");
17583        let should_split = if self.config.pretty && !func.args.is_empty() && !compact_pretty_func {
17584            if is_grouping_func {
17585                true
17586            } else {
17587                // Pre-render arguments to check total width
17588                let mut expr_strings: Vec<String> = Vec::with_capacity(func.args.len());
17589                for arg in &func.args {
17590                    let mut temp_gen = Generator::with_arc_config(self.config.clone());
17591                    Arc::make_mut(&mut temp_gen.config).pretty = false; // Don't recurse into pretty
17592                    temp_gen.generate_expression(arg)?;
17593                    expr_strings.push(temp_gen.output);
17594                }
17595                self.too_wide(&expr_strings)
17596            }
17597        } else {
17598            false
17599        };
17600
17601        if should_split {
17602            // Split onto multiple lines
17603            self.write_newline();
17604            self.indent_level += 1;
17605            for (i, arg) in func.args.iter().enumerate() {
17606                self.write_indent();
17607                self.generate_expression(arg)?;
17608                if i + 1 < func.args.len() {
17609                    self.write(",");
17610                }
17611                self.write_newline();
17612            }
17613            self.indent_level -= 1;
17614            self.write_indent();
17615        } else {
17616            // All on one line
17617            for (i, arg) in func.args.iter().enumerate() {
17618                if i > 0 {
17619                    self.write(", ");
17620                }
17621                self.generate_expression(arg)?;
17622            }
17623        }
17624
17625        if use_brackets {
17626            self.write("]");
17627        } else {
17628            self.write(")");
17629        }
17630        // Append WITH ORDINALITY after closing paren for table-valued functions
17631        if has_ordinality {
17632            self.write_space();
17633            self.write_keyword("WITH ORDINALITY");
17634        }
17635        // Output trailing comments
17636        for comment in &func.trailing_comments {
17637            self.write_space();
17638            self.write_formatted_comment(comment);
17639        }
17640        Ok(())
17641    }
17642
17643    fn generate_function_emits(&mut self, fe: &FunctionEmits) -> Result<()> {
17644        self.generate_expression(&fe.this)?;
17645        self.write_keyword(" EMITS ");
17646        self.generate_expression(&fe.emits)?;
17647        Ok(())
17648    }
17649
17650    fn generate_aggregate_function(&mut self, func: &AggregateFunction) -> Result<()> {
17651        // Normalize function name based on dialect settings
17652        let mut normalized_name = self.normalize_func_name(&func.name);
17653
17654        // Dialect-specific name mappings for aggregate functions
17655        if func.name.eq_ignore_ascii_case("MAX_BY") || func.name.eq_ignore_ascii_case("MIN_BY") {
17656            let is_max = func.name.eq_ignore_ascii_case("MAX_BY");
17657            match self.config.dialect {
17658                Some(DialectType::ClickHouse) => {
17659                    normalized_name = if is_max {
17660                        Cow::Borrowed("argMax")
17661                    } else {
17662                        Cow::Borrowed("argMin")
17663                    };
17664                }
17665                Some(DialectType::DuckDB) => {
17666                    normalized_name = if is_max {
17667                        Cow::Borrowed("ARG_MAX")
17668                    } else {
17669                        Cow::Borrowed("ARG_MIN")
17670                    };
17671                }
17672                _ => {}
17673            }
17674        }
17675        self.write(normalized_name.as_ref());
17676        self.write("(");
17677        if func.distinct {
17678            self.write_keyword("DISTINCT");
17679            self.write_space();
17680        }
17681
17682        // Check if we need to transform multi-arg COUNT DISTINCT
17683        // When dialect doesn't support multi_arg_distinct, transform:
17684        // COUNT(DISTINCT a, b) -> COUNT(DISTINCT CASE WHEN a IS NULL THEN NULL WHEN b IS NULL THEN NULL ELSE (a, b) END)
17685        let is_count = normalized_name.eq_ignore_ascii_case("COUNT");
17686        let needs_multi_arg_transform =
17687            func.distinct && is_count && func.args.len() > 1 && !self.config.multi_arg_distinct;
17688
17689        if needs_multi_arg_transform {
17690            // Generate: CASE WHEN a IS NULL THEN NULL WHEN b IS NULL THEN NULL ELSE (a, b) END
17691            self.write_keyword("CASE");
17692            for arg in &func.args {
17693                self.write_space();
17694                self.write_keyword("WHEN");
17695                self.write_space();
17696                self.generate_expression(arg)?;
17697                self.write_space();
17698                self.write_keyword("IS NULL THEN NULL");
17699            }
17700            self.write_space();
17701            self.write_keyword("ELSE");
17702            self.write(" (");
17703            for (i, arg) in func.args.iter().enumerate() {
17704                if i > 0 {
17705                    self.write(", ");
17706                }
17707                self.generate_expression(arg)?;
17708            }
17709            self.write(")");
17710            self.write_space();
17711            self.write_keyword("END");
17712        } else {
17713            for (i, arg) in func.args.iter().enumerate() {
17714                if i > 0 {
17715                    self.write(", ");
17716                }
17717                self.generate_expression(arg)?;
17718            }
17719        }
17720
17721        // IGNORE NULLS / RESPECT NULLS inside parens (for BigQuery style or when config says in_func)
17722        if self.config.ignore_nulls_in_func
17723            && !matches!(self.config.dialect, Some(DialectType::DuckDB))
17724        {
17725            if let Some(ignore) = func.ignore_nulls {
17726                self.write_space();
17727                if ignore {
17728                    self.write_keyword("IGNORE NULLS");
17729                } else {
17730                    self.write_keyword("RESPECT NULLS");
17731                }
17732            }
17733        }
17734
17735        // ORDER BY inside aggregate
17736        if !func.order_by.is_empty() {
17737            self.write_space();
17738            self.write_keyword("ORDER BY");
17739            self.write_space();
17740            for (i, ord) in func.order_by.iter().enumerate() {
17741                if i > 0 {
17742                    self.write(", ");
17743                }
17744                self.generate_ordered(ord)?;
17745            }
17746        }
17747
17748        // LIMIT inside aggregate
17749        if let Some(limit) = &func.limit {
17750            self.write_space();
17751            self.write_keyword("LIMIT");
17752            self.write_space();
17753            // Check if this is a Tuple representing LIMIT offset, count
17754            if let Expression::Tuple(t) = limit.as_ref() {
17755                if t.expressions.len() == 2 {
17756                    self.generate_expression(&t.expressions[0])?;
17757                    self.write(", ");
17758                    self.generate_expression(&t.expressions[1])?;
17759                } else {
17760                    self.generate_expression(limit)?;
17761                }
17762            } else {
17763                self.generate_expression(limit)?;
17764            }
17765        }
17766
17767        self.write(")");
17768
17769        // IGNORE NULLS / RESPECT NULLS outside parens (standard style)
17770        if !self.config.ignore_nulls_in_func
17771            && !matches!(self.config.dialect, Some(DialectType::DuckDB))
17772        {
17773            if let Some(ignore) = func.ignore_nulls {
17774                self.write_space();
17775                if ignore {
17776                    self.write_keyword("IGNORE NULLS");
17777                } else {
17778                    self.write_keyword("RESPECT NULLS");
17779                }
17780            }
17781        }
17782
17783        if let Some(filter) = &func.filter {
17784            self.write_space();
17785            self.write_keyword("FILTER");
17786            self.write("(");
17787            self.write_keyword("WHERE");
17788            self.write_space();
17789            self.generate_expression(filter)?;
17790            self.write(")");
17791        }
17792
17793        Ok(())
17794    }
17795
17796    fn generate_window_function(&mut self, wf: &WindowFunction) -> Result<()> {
17797        self.generate_expression(&wf.this)?;
17798
17799        // Generate KEEP clause if present (Oracle KEEP (DENSE_RANK FIRST|LAST ORDER BY ...))
17800        if let Some(keep) = &wf.keep {
17801            self.write_space();
17802            self.write_keyword("KEEP");
17803            self.write(" (");
17804            self.write_keyword("DENSE_RANK");
17805            self.write_space();
17806            if keep.first {
17807                self.write_keyword("FIRST");
17808            } else {
17809                self.write_keyword("LAST");
17810            }
17811            self.write_space();
17812            self.write_keyword("ORDER BY");
17813            self.write_space();
17814            for (i, ord) in keep.order_by.iter().enumerate() {
17815                if i > 0 {
17816                    self.write(", ");
17817                }
17818                self.generate_ordered(ord)?;
17819            }
17820            self.write(")");
17821        }
17822
17823        // Check if there's any OVER clause content
17824        let has_over = !wf.over.partition_by.is_empty()
17825            || !wf.over.order_by.is_empty()
17826            || wf.over.frame.is_some()
17827            || wf.over.window_name.is_some();
17828
17829        // Only output OVER if there's actual window specification (not just KEEP alone)
17830        if has_over {
17831            self.write_space();
17832            self.write_keyword("OVER");
17833
17834            // Check if this is just a bare named window reference (no parens needed)
17835            let has_specs = !wf.over.partition_by.is_empty()
17836                || !wf.over.order_by.is_empty()
17837                || wf.over.frame.is_some();
17838
17839            if wf.over.window_name.is_some() && !has_specs {
17840                // OVER window_name (without parentheses)
17841                self.write_space();
17842                self.write(&wf.over.window_name.as_ref().unwrap().name);
17843            } else {
17844                // OVER (...) or OVER (window_name ...)
17845                self.write(" (");
17846                self.generate_over(&wf.over)?;
17847                self.write(")");
17848            }
17849        } else if wf.keep.is_none() {
17850            // No KEEP and no OVER content, but still a WindowFunction - output empty OVER ()
17851            self.write_space();
17852            self.write_keyword("OVER");
17853            self.write(" ()");
17854        }
17855
17856        Ok(())
17857    }
17858
17859    /// Generate WITHIN GROUP clause (for ordered-set aggregate functions)
17860    fn generate_within_group(&mut self, wg: &WithinGroup) -> Result<()> {
17861        self.generate_expression(&wg.this)?;
17862        self.write_space();
17863        self.write_keyword("WITHIN GROUP");
17864        self.write(" (");
17865        self.write_keyword("ORDER BY");
17866        self.write_space();
17867        for (i, ord) in wg.order_by.iter().enumerate() {
17868            if i > 0 {
17869                self.write(", ");
17870            }
17871            self.generate_ordered(ord)?;
17872        }
17873        self.write(")");
17874        Ok(())
17875    }
17876
17877    /// Generate the contents of an OVER clause (without parentheses)
17878    fn generate_over(&mut self, over: &Over) -> Result<()> {
17879        let mut has_content = false;
17880
17881        // Named window reference
17882        if let Some(name) = &over.window_name {
17883            self.write(&name.name);
17884            has_content = true;
17885        }
17886
17887        // PARTITION BY
17888        if !over.partition_by.is_empty() {
17889            if has_content {
17890                self.write_space();
17891            }
17892            self.write_keyword("PARTITION BY");
17893            self.write_space();
17894            for (i, expr) in over.partition_by.iter().enumerate() {
17895                if i > 0 {
17896                    self.write(", ");
17897                }
17898                self.generate_expression(expr)?;
17899            }
17900            has_content = true;
17901        }
17902
17903        // ORDER BY
17904        if !over.order_by.is_empty() {
17905            if has_content {
17906                self.write_space();
17907            }
17908            self.write_keyword("ORDER BY");
17909            self.write_space();
17910            for (i, ordered) in over.order_by.iter().enumerate() {
17911                if i > 0 {
17912                    self.write(", ");
17913                }
17914                self.generate_ordered(ordered)?;
17915            }
17916            has_content = true;
17917        }
17918
17919        // Window frame
17920        if let Some(frame) = &over.frame {
17921            if has_content {
17922                self.write_space();
17923            }
17924            self.generate_window_frame(frame)?;
17925        }
17926
17927        Ok(())
17928    }
17929
17930    fn generate_window_frame(&mut self, frame: &WindowFrame) -> Result<()> {
17931        // Exasol uses lowercase for frame kind (rows/range/groups)
17932        let lowercase_frame = self.config.lowercase_window_frame_keywords;
17933
17934        // Use preserved kind_text if available (for case preservation), unless lowercase override is active
17935        if !lowercase_frame {
17936            if let Some(kind_text) = &frame.kind_text {
17937                self.write(kind_text);
17938            } else {
17939                match frame.kind {
17940                    WindowFrameKind::Rows => self.write_keyword("ROWS"),
17941                    WindowFrameKind::Range => self.write_keyword("RANGE"),
17942                    WindowFrameKind::Groups => self.write_keyword("GROUPS"),
17943                }
17944            }
17945        } else {
17946            match frame.kind {
17947                WindowFrameKind::Rows => self.write("rows"),
17948                WindowFrameKind::Range => self.write("range"),
17949                WindowFrameKind::Groups => self.write("groups"),
17950            }
17951        }
17952
17953        // Use BETWEEN format only when there's an explicit end bound,
17954        // or when normalize_window_frame_between is enabled and the start is a directional bound
17955        self.write_space();
17956        let should_normalize = self.config.normalize_window_frame_between
17957            && frame.end.is_none()
17958            && matches!(
17959                frame.start,
17960                WindowFrameBound::Preceding(_)
17961                    | WindowFrameBound::Following(_)
17962                    | WindowFrameBound::UnboundedPreceding
17963                    | WindowFrameBound::UnboundedFollowing
17964            );
17965
17966        if let Some(end) = &frame.end {
17967            // BETWEEN format: RANGE BETWEEN start AND end
17968            self.write_keyword("BETWEEN");
17969            self.write_space();
17970            self.generate_window_frame_bound(&frame.start, frame.start_side_text.as_deref())?;
17971            self.write_space();
17972            self.write_keyword("AND");
17973            self.write_space();
17974            self.generate_window_frame_bound(end, frame.end_side_text.as_deref())?;
17975        } else if should_normalize {
17976            // Normalize single-bound to BETWEEN form: ROWS 1 PRECEDING → ROWS BETWEEN 1 PRECEDING AND CURRENT ROW
17977            self.write_keyword("BETWEEN");
17978            self.write_space();
17979            self.generate_window_frame_bound(&frame.start, frame.start_side_text.as_deref())?;
17980            self.write_space();
17981            self.write_keyword("AND");
17982            self.write_space();
17983            self.write_keyword("CURRENT ROW");
17984        } else {
17985            // Single bound format: RANGE CURRENT ROW
17986            self.generate_window_frame_bound(&frame.start, frame.start_side_text.as_deref())?;
17987        }
17988
17989        // EXCLUDE clause
17990        if let Some(exclude) = &frame.exclude {
17991            self.write_space();
17992            self.write_keyword("EXCLUDE");
17993            self.write_space();
17994            match exclude {
17995                WindowFrameExclude::CurrentRow => self.write_keyword("CURRENT ROW"),
17996                WindowFrameExclude::Group => self.write_keyword("GROUP"),
17997                WindowFrameExclude::Ties => self.write_keyword("TIES"),
17998                WindowFrameExclude::NoOthers => self.write_keyword("NO OTHERS"),
17999            }
18000        }
18001
18002        Ok(())
18003    }
18004
18005    fn generate_window_frame_bound(
18006        &mut self,
18007        bound: &WindowFrameBound,
18008        side_text: Option<&str>,
18009    ) -> Result<()> {
18010        // Exasol uses lowercase for preceding/following
18011        let lowercase_frame = self.config.lowercase_window_frame_keywords;
18012
18013        match bound {
18014            WindowFrameBound::CurrentRow => {
18015                self.write_keyword("CURRENT ROW");
18016            }
18017            WindowFrameBound::UnboundedPreceding => {
18018                self.write_keyword("UNBOUNDED");
18019                self.write_space();
18020                if lowercase_frame {
18021                    self.write("preceding");
18022                } else if let Some(text) = side_text {
18023                    self.write(text);
18024                } else {
18025                    self.write_keyword("PRECEDING");
18026                }
18027            }
18028            WindowFrameBound::UnboundedFollowing => {
18029                self.write_keyword("UNBOUNDED");
18030                self.write_space();
18031                if lowercase_frame {
18032                    self.write("following");
18033                } else if let Some(text) = side_text {
18034                    self.write(text);
18035                } else {
18036                    self.write_keyword("FOLLOWING");
18037                }
18038            }
18039            WindowFrameBound::Preceding(expr) => {
18040                self.generate_expression(expr)?;
18041                self.write_space();
18042                if lowercase_frame {
18043                    self.write("preceding");
18044                } else if let Some(text) = side_text {
18045                    self.write(text);
18046                } else {
18047                    self.write_keyword("PRECEDING");
18048                }
18049            }
18050            WindowFrameBound::Following(expr) => {
18051                self.generate_expression(expr)?;
18052                self.write_space();
18053                if lowercase_frame {
18054                    self.write("following");
18055                } else if let Some(text) = side_text {
18056                    self.write(text);
18057                } else {
18058                    self.write_keyword("FOLLOWING");
18059                }
18060            }
18061            WindowFrameBound::BarePreceding => {
18062                if lowercase_frame {
18063                    self.write("preceding");
18064                } else if let Some(text) = side_text {
18065                    self.write(text);
18066                } else {
18067                    self.write_keyword("PRECEDING");
18068                }
18069            }
18070            WindowFrameBound::BareFollowing => {
18071                if lowercase_frame {
18072                    self.write("following");
18073                } else if let Some(text) = side_text {
18074                    self.write(text);
18075                } else {
18076                    self.write_keyword("FOLLOWING");
18077                }
18078            }
18079            WindowFrameBound::Value(expr) => {
18080                // Bare numeric bound without PRECEDING/FOLLOWING
18081                self.generate_expression(expr)?;
18082            }
18083        }
18084        Ok(())
18085    }
18086
18087    fn generate_interval(&mut self, interval: &Interval) -> Result<()> {
18088        // For Oracle with ExprSpan: only output INTERVAL if `this` is a literal
18089        // (e.g., `(expr) DAY(9) TO SECOND(3)` should NOT have INTERVAL prefix)
18090        let skip_interval_keyword = matches!(self.config.dialect, Some(DialectType::Oracle))
18091            && matches!(&interval.unit, Some(IntervalUnitSpec::ExprSpan(_)))
18092            && !matches!(&interval.this, Some(Expression::Literal(_)));
18093
18094        // SINGLE_STRING_INTERVAL: combine value and unit into a single quoted string
18095        // e.g., INTERVAL '1' DAY -> INTERVAL '1 DAY'
18096        if self.config.single_string_interval {
18097            if let (
18098                Some(Expression::Literal(lit)),
18099                Some(IntervalUnitSpec::Simple {
18100                    ref unit,
18101                    ref use_plural,
18102                }),
18103            ) = (&interval.this, &interval.unit)
18104            {
18105                if let Literal::String(ref val) = lit.as_ref() {
18106                    self.write_keyword("INTERVAL");
18107                    self.write_space();
18108                    let effective_plural = *use_plural && self.config.interval_allows_plural_form;
18109                    let unit_str = self.interval_unit_str(unit, effective_plural);
18110                    self.write("'");
18111                    self.write(val);
18112                    self.write(" ");
18113                    self.write(&unit_str);
18114                    self.write("'");
18115                    return Ok(());
18116                }
18117            }
18118        }
18119
18120        if !skip_interval_keyword {
18121            self.write_keyword("INTERVAL");
18122        }
18123
18124        // Generate value if present
18125        if let Some(ref value) = interval.this {
18126            if !skip_interval_keyword {
18127                self.write_space();
18128            }
18129            // If the value is a complex expression (not a literal/column/function call)
18130            // and there's a unit, wrap it in parentheses
18131            // e.g., INTERVAL (2 * 2) MONTH, INTERVAL (DAYOFMONTH(dt) - 1) DAY
18132            let needs_parens = interval.unit.is_some()
18133                && matches!(
18134                    value,
18135                    Expression::Add(_)
18136                        | Expression::Sub(_)
18137                        | Expression::Mul(_)
18138                        | Expression::Div(_)
18139                        | Expression::Mod(_)
18140                        | Expression::BitwiseAnd(_)
18141                        | Expression::BitwiseOr(_)
18142                        | Expression::BitwiseXor(_)
18143                );
18144            if needs_parens {
18145                self.write("(");
18146            }
18147            self.generate_expression(value)?;
18148            if needs_parens {
18149                self.write(")");
18150            }
18151        }
18152
18153        // Generate unit if present
18154        if let Some(ref unit_spec) = interval.unit {
18155            self.write_space();
18156            self.write_interval_unit_spec(unit_spec)?;
18157        }
18158
18159        Ok(())
18160    }
18161
18162    /// Return the string representation of an interval unit
18163    fn interval_unit_str(&self, unit: &IntervalUnit, use_plural: bool) -> &'static str {
18164        match (unit, use_plural) {
18165            (IntervalUnit::Year, false) => "YEAR",
18166            (IntervalUnit::Year, true) => "YEARS",
18167            (IntervalUnit::Quarter, false) => "QUARTER",
18168            (IntervalUnit::Quarter, true) => "QUARTERS",
18169            (IntervalUnit::Month, false) => "MONTH",
18170            (IntervalUnit::Month, true) => "MONTHS",
18171            (IntervalUnit::Week, false) => "WEEK",
18172            (IntervalUnit::Week, true) => "WEEKS",
18173            (IntervalUnit::Day, false) => "DAY",
18174            (IntervalUnit::Day, true) => "DAYS",
18175            (IntervalUnit::Hour, false) => "HOUR",
18176            (IntervalUnit::Hour, true) => "HOURS",
18177            (IntervalUnit::Minute, false) => "MINUTE",
18178            (IntervalUnit::Minute, true) => "MINUTES",
18179            (IntervalUnit::Second, false) => "SECOND",
18180            (IntervalUnit::Second, true) => "SECONDS",
18181            (IntervalUnit::Millisecond, false) => "MILLISECOND",
18182            (IntervalUnit::Millisecond, true) => "MILLISECONDS",
18183            (IntervalUnit::Microsecond, false) => "MICROSECOND",
18184            (IntervalUnit::Microsecond, true) => "MICROSECONDS",
18185            (IntervalUnit::Nanosecond, false) => "NANOSECOND",
18186            (IntervalUnit::Nanosecond, true) => "NANOSECONDS",
18187        }
18188    }
18189
18190    fn write_interval_unit_spec(&mut self, unit_spec: &IntervalUnitSpec) -> Result<()> {
18191        match unit_spec {
18192            IntervalUnitSpec::Simple { unit, use_plural } => {
18193                // If dialect doesn't allow plural forms, force singular
18194                let effective_plural = *use_plural && self.config.interval_allows_plural_form;
18195                self.write_simple_interval_unit(unit, effective_plural);
18196            }
18197            IntervalUnitSpec::Span(span) => {
18198                self.write_simple_interval_unit(&span.this, false);
18199                self.write_space();
18200                self.write_keyword("TO");
18201                self.write_space();
18202                self.write_simple_interval_unit(&span.expression, false);
18203            }
18204            IntervalUnitSpec::ExprSpan(span) => {
18205                // Expression-based interval span (e.g., DAY(9) TO SECOND(3))
18206                self.generate_expression(&span.this)?;
18207                self.write_space();
18208                self.write_keyword("TO");
18209                self.write_space();
18210                self.generate_expression(&span.expression)?;
18211            }
18212            IntervalUnitSpec::Expr(expr) => {
18213                self.generate_expression(expr)?;
18214            }
18215        }
18216        Ok(())
18217    }
18218
18219    fn write_simple_interval_unit(&mut self, unit: &IntervalUnit, use_plural: bool) {
18220        // Output interval unit, respecting plural preference
18221        match (unit, use_plural) {
18222            (IntervalUnit::Year, false) => self.write_keyword("YEAR"),
18223            (IntervalUnit::Year, true) => self.write_keyword("YEARS"),
18224            (IntervalUnit::Quarter, false) => self.write_keyword("QUARTER"),
18225            (IntervalUnit::Quarter, true) => self.write_keyword("QUARTERS"),
18226            (IntervalUnit::Month, false) => self.write_keyword("MONTH"),
18227            (IntervalUnit::Month, true) => self.write_keyword("MONTHS"),
18228            (IntervalUnit::Week, false) => self.write_keyword("WEEK"),
18229            (IntervalUnit::Week, true) => self.write_keyword("WEEKS"),
18230            (IntervalUnit::Day, false) => self.write_keyword("DAY"),
18231            (IntervalUnit::Day, true) => self.write_keyword("DAYS"),
18232            (IntervalUnit::Hour, false) => self.write_keyword("HOUR"),
18233            (IntervalUnit::Hour, true) => self.write_keyword("HOURS"),
18234            (IntervalUnit::Minute, false) => self.write_keyword("MINUTE"),
18235            (IntervalUnit::Minute, true) => self.write_keyword("MINUTES"),
18236            (IntervalUnit::Second, false) => self.write_keyword("SECOND"),
18237            (IntervalUnit::Second, true) => self.write_keyword("SECONDS"),
18238            (IntervalUnit::Millisecond, false) => self.write_keyword("MILLISECOND"),
18239            (IntervalUnit::Millisecond, true) => self.write_keyword("MILLISECONDS"),
18240            (IntervalUnit::Microsecond, false) => self.write_keyword("MICROSECOND"),
18241            (IntervalUnit::Microsecond, true) => self.write_keyword("MICROSECONDS"),
18242            (IntervalUnit::Nanosecond, false) => self.write_keyword("NANOSECOND"),
18243            (IntervalUnit::Nanosecond, true) => self.write_keyword("NANOSECONDS"),
18244        }
18245    }
18246
18247    /// Normalize a date part expression to unquoted uppercase for Redshift DATEDIFF/DATEADD
18248    /// Converts: 'day', 'days', day, days, DAY -> DAY (unquoted)
18249    fn write_redshift_date_part(&mut self, expr: &Expression) {
18250        let part_str = self.extract_date_part_string(expr);
18251        if let Some(part) = part_str {
18252            let normalized = self.normalize_date_part(&part);
18253            self.write_keyword(&normalized);
18254        } else {
18255            // If we can't extract a date part string, fall back to generating the expression
18256            let _ = self.generate_expression(expr);
18257        }
18258    }
18259
18260    /// Normalize a date part expression to quoted uppercase for Redshift DATE_TRUNC
18261    /// Converts: 'day', day, DAY -> 'DAY' (quoted)
18262    fn write_redshift_date_part_quoted(&mut self, expr: &Expression) {
18263        let part_str = self.extract_date_part_string(expr);
18264        if let Some(part) = part_str {
18265            let normalized = self.normalize_date_part(&part);
18266            self.write("'");
18267            self.write(&normalized);
18268            self.write("'");
18269        } else {
18270            // If we can't extract a date part string, fall back to generating the expression
18271            let _ = self.generate_expression(expr);
18272        }
18273    }
18274
18275    /// Extract date part string from expression (handles string literals and identifiers)
18276    fn extract_date_part_string(&self, expr: &Expression) -> Option<String> {
18277        match expr {
18278            Expression::Literal(lit)
18279                if matches!(lit.as_ref(), crate::expressions::Literal::String(_)) =>
18280            {
18281                let crate::expressions::Literal::String(s) = lit.as_ref() else {
18282                    unreachable!()
18283                };
18284                Some(s.clone())
18285            }
18286            Expression::Identifier(id) => Some(id.name.clone()),
18287            Expression::Var(v) => Some(v.this.clone()),
18288            Expression::Column(col) if col.table.is_none() => {
18289                // Simple column reference without table prefix, treat as identifier
18290                Some(col.name.name.clone())
18291            }
18292            _ => None,
18293        }
18294    }
18295
18296    /// Normalize date part to uppercase singular form
18297    /// days -> DAY, months -> MONTH, etc.
18298    fn normalize_date_part(&self, part: &str) -> String {
18299        let mut buf = [0u8; 64];
18300        let lower: &str = if part.len() <= 64 {
18301            for (i, b) in part.bytes().enumerate() {
18302                buf[i] = b.to_ascii_lowercase();
18303            }
18304            std::str::from_utf8(&buf[..part.len()]).unwrap_or(part)
18305        } else {
18306            return part.to_ascii_uppercase();
18307        };
18308        match lower {
18309            "day" | "days" | "d" => "DAY".to_string(),
18310            "month" | "months" | "mon" | "mm" => "MONTH".to_string(),
18311            "year" | "years" | "y" | "yy" | "yyyy" => "YEAR".to_string(),
18312            "week" | "weeks" | "w" | "wk" => "WEEK".to_string(),
18313            "hour" | "hours" | "h" | "hh" => "HOUR".to_string(),
18314            "minute" | "minutes" | "m" | "mi" | "n" => "MINUTE".to_string(),
18315            "second" | "seconds" | "s" | "ss" => "SECOND".to_string(),
18316            "millisecond" | "milliseconds" | "ms" => "MILLISECOND".to_string(),
18317            "microsecond" | "microseconds" | "us" => "MICROSECOND".to_string(),
18318            "quarter" | "quarters" | "q" | "qq" => "QUARTER".to_string(),
18319            _ => part.to_ascii_uppercase(),
18320        }
18321    }
18322
18323    fn write_datetime_field(&mut self, field: &DateTimeField) {
18324        match field {
18325            DateTimeField::Year => self.write_keyword("YEAR"),
18326            DateTimeField::Month => self.write_keyword("MONTH"),
18327            DateTimeField::Day => self.write_keyword("DAY"),
18328            DateTimeField::Hour => self.write_keyword("HOUR"),
18329            DateTimeField::Minute => self.write_keyword("MINUTE"),
18330            DateTimeField::Second => self.write_keyword("SECOND"),
18331            DateTimeField::Millisecond => self.write_keyword("MILLISECOND"),
18332            DateTimeField::Microsecond => self.write_keyword("MICROSECOND"),
18333            DateTimeField::DayOfWeek => {
18334                let name = match self.config.dialect {
18335                    Some(DialectType::DuckDB) | Some(DialectType::Snowflake) => "DAYOFWEEK",
18336                    Some(DialectType::TSQL) | Some(DialectType::Fabric) => "WEEKDAY",
18337                    _ => "DOW",
18338                };
18339                self.write_keyword(name);
18340            }
18341            DateTimeField::DayOfYear => {
18342                let name = match self.config.dialect {
18343                    Some(DialectType::DuckDB) | Some(DialectType::Snowflake) => "DAYOFYEAR",
18344                    _ => "DOY",
18345                };
18346                self.write_keyword(name);
18347            }
18348            DateTimeField::Week => self.write_keyword("WEEK"),
18349            DateTimeField::WeekWithModifier(modifier) => {
18350                self.write_keyword("WEEK");
18351                self.write("(");
18352                self.write(modifier);
18353                self.write(")");
18354            }
18355            DateTimeField::Quarter => self.write_keyword("QUARTER"),
18356            DateTimeField::Epoch => self.write_keyword("EPOCH"),
18357            DateTimeField::Timezone => self.write_keyword("TIMEZONE"),
18358            DateTimeField::TimezoneHour => self.write_keyword("TIMEZONE_HOUR"),
18359            DateTimeField::TimezoneMinute => self.write_keyword("TIMEZONE_MINUTE"),
18360            DateTimeField::Date => self.write_keyword("DATE"),
18361            DateTimeField::Time => self.write_keyword("TIME"),
18362            DateTimeField::Custom(name) => self.write(name),
18363        }
18364    }
18365
18366    /// Write datetime field in lowercase (for Spark/Hive/Databricks)
18367    fn write_datetime_field_lower(&mut self, field: &DateTimeField) {
18368        match field {
18369            DateTimeField::Year => self.write("year"),
18370            DateTimeField::Month => self.write("month"),
18371            DateTimeField::Day => self.write("day"),
18372            DateTimeField::Hour => self.write("hour"),
18373            DateTimeField::Minute => self.write("minute"),
18374            DateTimeField::Second => self.write("second"),
18375            DateTimeField::Millisecond => self.write("millisecond"),
18376            DateTimeField::Microsecond => self.write("microsecond"),
18377            DateTimeField::DayOfWeek => self.write("dow"),
18378            DateTimeField::DayOfYear => self.write("doy"),
18379            DateTimeField::Week => self.write("week"),
18380            DateTimeField::WeekWithModifier(modifier) => {
18381                self.write("week(");
18382                self.write(modifier);
18383                self.write(")");
18384            }
18385            DateTimeField::Quarter => self.write("quarter"),
18386            DateTimeField::Epoch => self.write("epoch"),
18387            DateTimeField::Timezone => self.write("timezone"),
18388            DateTimeField::TimezoneHour => self.write("timezone_hour"),
18389            DateTimeField::TimezoneMinute => self.write("timezone_minute"),
18390            DateTimeField::Date => self.write("date"),
18391            DateTimeField::Time => self.write("time"),
18392            DateTimeField::Custom(name) => self.write(name),
18393        }
18394    }
18395
18396    // Helper function generators
18397
18398    fn generate_simple_func(&mut self, name: &str, arg: &Expression) -> Result<()> {
18399        self.write_keyword(name);
18400        self.write("(");
18401        self.generate_expression(arg)?;
18402        self.write(")");
18403        Ok(())
18404    }
18405
18406    /// Generate a unary function, using the original name if available for round-trip preservation
18407    fn generate_unary_func(
18408        &mut self,
18409        default_name: &str,
18410        f: &crate::expressions::UnaryFunc,
18411    ) -> Result<()> {
18412        let name = f.original_name.as_deref().unwrap_or(default_name);
18413        self.write_keyword(name);
18414        self.write("(");
18415        self.generate_expression(&f.this)?;
18416        self.write(")");
18417        Ok(())
18418    }
18419
18420    /// Generate SQRT/CBRT - always use function form (matches Python SQLGlot normalization)
18421    fn generate_sqrt_cbrt(
18422        &mut self,
18423        f: &crate::expressions::UnaryFunc,
18424        func_name: &str,
18425        _op: &str,
18426    ) -> Result<()> {
18427        // Python SQLGlot normalizes |/ and ||/ to SQRT() and CBRT()
18428        // Always use function syntax for consistency
18429        self.write_keyword(func_name);
18430        self.write("(");
18431        self.generate_expression(&f.this)?;
18432        self.write(")");
18433        Ok(())
18434    }
18435
18436    fn generate_binary_func(
18437        &mut self,
18438        name: &str,
18439        arg1: &Expression,
18440        arg2: &Expression,
18441    ) -> Result<()> {
18442        self.write_keyword(name);
18443        self.write("(");
18444        self.generate_expression(arg1)?;
18445        self.write(", ");
18446        self.generate_expression(arg2)?;
18447        self.write(")");
18448        Ok(())
18449    }
18450
18451    /// Generate CHAR/CHR function with optional USING charset
18452    /// e.g., CHAR(77, 77.3, '77.3' USING utf8mb4)
18453    /// e.g., CHR(187 USING NCHAR_CS) -- Oracle
18454    fn generate_char_func(&mut self, f: &crate::expressions::CharFunc) -> Result<()> {
18455        // Use stored name if available, otherwise default to CHAR
18456        let func_name = f.name.as_deref().unwrap_or("CHAR");
18457        self.write_keyword(func_name);
18458        self.write("(");
18459        for (i, arg) in f.args.iter().enumerate() {
18460            if i > 0 {
18461                self.write(", ");
18462            }
18463            self.generate_expression(arg)?;
18464        }
18465        if let Some(ref charset) = f.charset {
18466            self.write(" ");
18467            self.write_keyword("USING");
18468            self.write(" ");
18469            self.write(charset);
18470        }
18471        self.write(")");
18472        Ok(())
18473    }
18474
18475    fn generate_power(&mut self, f: &BinaryFunc) -> Result<()> {
18476        use crate::dialects::DialectType;
18477
18478        match self.config.dialect {
18479            Some(DialectType::Teradata) => {
18480                // Teradata uses ** operator for exponentiation
18481                self.generate_expression(&f.this)?;
18482                self.write(" ** ");
18483                self.generate_expression(&f.expression)?;
18484                Ok(())
18485            }
18486            _ => {
18487                // Other dialects use POWER function
18488                self.generate_binary_func("POWER", &f.this, &f.expression)
18489            }
18490        }
18491    }
18492
18493    fn generate_vararg_func(&mut self, name: &str, args: &[Expression]) -> Result<()> {
18494        self.write_func_name(name);
18495        self.write("(");
18496        for (i, arg) in args.iter().enumerate() {
18497            if i > 0 {
18498                self.write(", ");
18499            }
18500            self.generate_expression(arg)?;
18501        }
18502        self.write(")");
18503        Ok(())
18504    }
18505
18506    // String function generators
18507
18508    fn generate_concat_ws(&mut self, f: &ConcatWs) -> Result<()> {
18509        self.write_keyword("CONCAT_WS");
18510        self.write("(");
18511        self.generate_expression(&f.separator)?;
18512        for expr in &f.expressions {
18513            self.write(", ");
18514            self.generate_expression(expr)?;
18515        }
18516        self.write(")");
18517        Ok(())
18518    }
18519
18520    fn collect_concat_operands<'a>(expr: &'a Expression, out: &mut Vec<&'a Expression>) {
18521        if let Expression::Concat(op) = expr {
18522            Self::collect_concat_operands(&op.left, out);
18523            Self::collect_concat_operands(&op.right, out);
18524        } else {
18525            out.push(expr);
18526        }
18527    }
18528
18529    fn generate_mysql_concat_from_concat(&mut self, op: &BinaryOp) -> Result<()> {
18530        let mut operands = Vec::new();
18531        Self::collect_concat_operands(&op.left, &mut operands);
18532        Self::collect_concat_operands(&op.right, &mut operands);
18533
18534        self.write_keyword("CONCAT");
18535        self.write("(");
18536        for (i, operand) in operands.iter().enumerate() {
18537            if i > 0 {
18538                self.write(", ");
18539            }
18540            self.generate_expression(operand)?;
18541        }
18542        self.write(")");
18543        Ok(())
18544    }
18545
18546    fn collect_dpipe_operands<'a>(expr: &'a Expression, out: &mut Vec<&'a Expression>) {
18547        if let Expression::DPipe(dpipe) = expr {
18548            Self::collect_dpipe_operands(&dpipe.this, out);
18549            Self::collect_dpipe_operands(&dpipe.expression, out);
18550        } else {
18551            out.push(expr);
18552        }
18553    }
18554
18555    fn generate_mysql_concat_from_dpipe(&mut self, e: &DPipe) -> Result<()> {
18556        let mut operands = Vec::new();
18557        Self::collect_dpipe_operands(&e.this, &mut operands);
18558        Self::collect_dpipe_operands(&e.expression, &mut operands);
18559
18560        self.write_keyword("CONCAT");
18561        self.write("(");
18562        for (i, operand) in operands.iter().enumerate() {
18563            if i > 0 {
18564                self.write(", ");
18565            }
18566            self.generate_expression(operand)?;
18567        }
18568        self.write(")");
18569        Ok(())
18570    }
18571
18572    fn generate_substring(&mut self, f: &SubstringFunc) -> Result<()> {
18573        // Oracle uses SUBSTR; most others use SUBSTRING
18574        let is_oracle = matches!(self.config.dialect, Some(DialectType::Oracle));
18575        if is_oracle {
18576            self.write_keyword("SUBSTR");
18577        } else {
18578            self.write_keyword("SUBSTRING");
18579        }
18580        self.write("(");
18581        self.generate_expression(&f.this)?;
18582        // PostgreSQL always uses FROM/FOR syntax
18583        let force_from_for = matches!(self.config.dialect, Some(DialectType::PostgreSQL));
18584        // Spark/Hive/TSQL/Fabric use comma syntax, not FROM/FOR syntax
18585        let use_comma_syntax = matches!(
18586            self.config.dialect,
18587            Some(DialectType::Spark)
18588                | Some(DialectType::Hive)
18589                | Some(DialectType::Databricks)
18590                | Some(DialectType::TSQL)
18591                | Some(DialectType::Fabric)
18592        );
18593        if (f.from_for_syntax || force_from_for) && !use_comma_syntax {
18594            // SQL standard syntax: SUBSTRING(str FROM pos FOR len)
18595            self.write_space();
18596            self.write_keyword("FROM");
18597            self.write_space();
18598            self.generate_expression(&f.start)?;
18599            if let Some(length) = &f.length {
18600                self.write_space();
18601                self.write_keyword("FOR");
18602                self.write_space();
18603                self.generate_expression(length)?;
18604            }
18605        } else {
18606            // Comma-separated syntax: SUBSTRING(str, pos, len) or SUBSTR(str, pos, len)
18607            self.write(", ");
18608            self.generate_expression(&f.start)?;
18609            if let Some(length) = &f.length {
18610                self.write(", ");
18611                self.generate_expression(length)?;
18612            }
18613        }
18614        self.write(")");
18615        Ok(())
18616    }
18617
18618    fn generate_overlay(&mut self, f: &OverlayFunc) -> Result<()> {
18619        self.write_keyword("OVERLAY");
18620        self.write("(");
18621        self.generate_expression(&f.this)?;
18622        self.write_space();
18623        self.write_keyword("PLACING");
18624        self.write_space();
18625        self.generate_expression(&f.replacement)?;
18626        self.write_space();
18627        self.write_keyword("FROM");
18628        self.write_space();
18629        self.generate_expression(&f.from)?;
18630        if let Some(length) = &f.length {
18631            self.write_space();
18632            self.write_keyword("FOR");
18633            self.write_space();
18634            self.generate_expression(length)?;
18635        }
18636        self.write(")");
18637        Ok(())
18638    }
18639
18640    fn generate_trim(&mut self, f: &TrimFunc) -> Result<()> {
18641        // Special case: TRIM(LEADING str) -> LTRIM(str), TRIM(TRAILING str) -> RTRIM(str)
18642        // when no characters are specified (PostgreSQL style)
18643        if f.position_explicit && f.characters.is_none() {
18644            match f.position {
18645                TrimPosition::Leading => {
18646                    self.write_keyword("LTRIM");
18647                    self.write("(");
18648                    self.generate_expression(&f.this)?;
18649                    self.write(")");
18650                    return Ok(());
18651                }
18652                TrimPosition::Trailing => {
18653                    self.write_keyword("RTRIM");
18654                    self.write("(");
18655                    self.generate_expression(&f.this)?;
18656                    self.write(")");
18657                    return Ok(());
18658                }
18659                TrimPosition::Both => {
18660                    // TRIM(BOTH str) -> BTRIM(str) in PostgreSQL, but TRIM(str) is more standard
18661                    // Fall through to standard TRIM handling
18662                }
18663            }
18664        }
18665
18666        self.write_keyword("TRIM");
18667        self.write("(");
18668        // When BOTH is specified without trim characters, simplify to just TRIM(str)
18669        // Force standard syntax for dialects that require it (Hive, Spark, Databricks, ClickHouse)
18670        let force_standard = f.characters.is_some()
18671            && !f.sql_standard_syntax
18672            && matches!(
18673                self.config.dialect,
18674                Some(DialectType::Hive)
18675                    | Some(DialectType::Spark)
18676                    | Some(DialectType::Databricks)
18677                    | Some(DialectType::ClickHouse)
18678            );
18679        let use_standard = (f.sql_standard_syntax || force_standard)
18680            && !(f.position_explicit
18681                && f.characters.is_none()
18682                && matches!(f.position, TrimPosition::Both));
18683        if use_standard {
18684            // SQL standard syntax: TRIM(BOTH chars FROM str)
18685            // Only output position if it was explicitly specified
18686            if f.position_explicit {
18687                match f.position {
18688                    TrimPosition::Both => self.write_keyword("BOTH"),
18689                    TrimPosition::Leading => self.write_keyword("LEADING"),
18690                    TrimPosition::Trailing => self.write_keyword("TRAILING"),
18691                }
18692                self.write_space();
18693            }
18694            if let Some(chars) = &f.characters {
18695                self.generate_expression(chars)?;
18696                self.write_space();
18697            }
18698            self.write_keyword("FROM");
18699            self.write_space();
18700            self.generate_expression(&f.this)?;
18701        } else {
18702            // Simple function syntax: TRIM(str) or TRIM(str, chars)
18703            self.generate_expression(&f.this)?;
18704            if let Some(chars) = &f.characters {
18705                self.write(", ");
18706                self.generate_expression(chars)?;
18707            }
18708        }
18709        self.write(")");
18710        Ok(())
18711    }
18712
18713    fn generate_replace(&mut self, f: &ReplaceFunc) -> Result<()> {
18714        self.write_keyword("REPLACE");
18715        self.write("(");
18716        self.generate_expression(&f.this)?;
18717        self.write(", ");
18718        self.generate_expression(&f.old)?;
18719        self.write(", ");
18720        self.generate_expression(&f.new)?;
18721        self.write(")");
18722        Ok(())
18723    }
18724
18725    fn generate_left_right(&mut self, name: &str, f: &LeftRightFunc) -> Result<()> {
18726        self.write_keyword(name);
18727        self.write("(");
18728        self.generate_expression(&f.this)?;
18729        self.write(", ");
18730        self.generate_expression(&f.length)?;
18731        self.write(")");
18732        Ok(())
18733    }
18734
18735    fn generate_repeat(&mut self, f: &RepeatFunc) -> Result<()> {
18736        self.write_keyword("REPEAT");
18737        self.write("(");
18738        self.generate_expression(&f.this)?;
18739        self.write(", ");
18740        self.generate_expression(&f.times)?;
18741        self.write(")");
18742        Ok(())
18743    }
18744
18745    fn generate_pad(&mut self, name: &str, f: &PadFunc) -> Result<()> {
18746        self.write_keyword(name);
18747        self.write("(");
18748        self.generate_expression(&f.this)?;
18749        self.write(", ");
18750        self.generate_expression(&f.length)?;
18751        if let Some(fill) = &f.fill {
18752            self.write(", ");
18753            self.generate_expression(fill)?;
18754        }
18755        self.write(")");
18756        Ok(())
18757    }
18758
18759    fn generate_split(&mut self, f: &SplitFunc) -> Result<()> {
18760        self.write_keyword("SPLIT");
18761        self.write("(");
18762        self.generate_expression(&f.this)?;
18763        self.write(", ");
18764        self.generate_expression(&f.delimiter)?;
18765        self.write(")");
18766        Ok(())
18767    }
18768
18769    fn generate_regexp_like(&mut self, f: &RegexpFunc) -> Result<()> {
18770        use crate::dialects::DialectType;
18771        // PostgreSQL uses ~ operator for regex matching
18772        if matches!(self.config.dialect, Some(DialectType::PostgreSQL)) && f.flags.is_none() {
18773            self.generate_expression(&f.this)?;
18774            self.write(" ~ ");
18775            self.generate_expression(&f.pattern)?;
18776        } else if matches!(self.config.dialect, Some(DialectType::Exasol)) && f.flags.is_none() {
18777            // Exasol uses REGEXP_LIKE as infix binary operator
18778            self.generate_expression(&f.this)?;
18779            self.write_keyword(" REGEXP_LIKE ");
18780            self.generate_expression(&f.pattern)?;
18781        } else if matches!(
18782            self.config.dialect,
18783            Some(DialectType::SingleStore)
18784                | Some(DialectType::Spark)
18785                | Some(DialectType::Hive)
18786                | Some(DialectType::Databricks)
18787        ) && f.flags.is_none()
18788        {
18789            // SingleStore/Spark/Hive/Databricks use RLIKE infix operator
18790            self.generate_expression(&f.this)?;
18791            self.write_keyword(" RLIKE ");
18792            self.generate_expression(&f.pattern)?;
18793        } else if matches!(self.config.dialect, Some(DialectType::StarRocks)) {
18794            // StarRocks uses REGEXP function syntax
18795            self.write_keyword("REGEXP");
18796            self.write("(");
18797            self.generate_expression(&f.this)?;
18798            self.write(", ");
18799            self.generate_expression(&f.pattern)?;
18800            if let Some(flags) = &f.flags {
18801                self.write(", ");
18802                self.generate_expression(flags)?;
18803            }
18804            self.write(")");
18805        } else {
18806            self.write_keyword("REGEXP_LIKE");
18807            self.write("(");
18808            self.generate_expression(&f.this)?;
18809            self.write(", ");
18810            self.generate_expression(&f.pattern)?;
18811            if let Some(flags) = &f.flags {
18812                self.write(", ");
18813                self.generate_expression(flags)?;
18814            }
18815            self.write(")");
18816        }
18817        Ok(())
18818    }
18819
18820    fn generate_regexp_replace(&mut self, f: &RegexpReplaceFunc) -> Result<()> {
18821        self.write_keyword("REGEXP_REPLACE");
18822        self.write("(");
18823        self.generate_expression(&f.this)?;
18824        self.write(", ");
18825        self.generate_expression(&f.pattern)?;
18826        self.write(", ");
18827        self.generate_expression(&f.replacement)?;
18828        if let Some(flags) = &f.flags {
18829            self.write(", ");
18830            self.generate_expression(flags)?;
18831        }
18832        self.write(")");
18833        Ok(())
18834    }
18835
18836    fn generate_regexp_extract(&mut self, f: &RegexpExtractFunc) -> Result<()> {
18837        self.write_keyword("REGEXP_EXTRACT");
18838        self.write("(");
18839        self.generate_expression(&f.this)?;
18840        self.write(", ");
18841        self.generate_expression(&f.pattern)?;
18842        if let Some(group) = &f.group {
18843            self.write(", ");
18844            self.generate_expression(group)?;
18845        }
18846        self.write(")");
18847        Ok(())
18848    }
18849
18850    // Math function generators
18851
18852    fn generate_round(&mut self, f: &RoundFunc) -> Result<()> {
18853        self.write_keyword("ROUND");
18854        self.write("(");
18855        self.generate_expression(&f.this)?;
18856        if let Some(decimals) = &f.decimals {
18857            self.write(", ");
18858            self.generate_expression(decimals)?;
18859        }
18860        self.write(")");
18861        Ok(())
18862    }
18863
18864    fn generate_floor(&mut self, f: &FloorFunc) -> Result<()> {
18865        self.write_keyword("FLOOR");
18866        self.write("(");
18867        self.generate_expression(&f.this)?;
18868        // Handle Druid-style FLOOR(time TO unit) syntax
18869        if let Some(to) = &f.to {
18870            self.write(" ");
18871            self.write_keyword("TO");
18872            self.write(" ");
18873            self.generate_expression(to)?;
18874        } else if let Some(scale) = &f.scale {
18875            self.write(", ");
18876            self.generate_expression(scale)?;
18877        }
18878        self.write(")");
18879        Ok(())
18880    }
18881
18882    fn generate_ceil(&mut self, f: &CeilFunc) -> Result<()> {
18883        self.write_keyword("CEIL");
18884        self.write("(");
18885        self.generate_expression(&f.this)?;
18886        // Handle Druid-style CEIL(time TO unit) syntax
18887        if let Some(to) = &f.to {
18888            self.write(" ");
18889            self.write_keyword("TO");
18890            self.write(" ");
18891            self.generate_expression(to)?;
18892        } else if let Some(decimals) = &f.decimals {
18893            self.write(", ");
18894            self.generate_expression(decimals)?;
18895        }
18896        self.write(")");
18897        Ok(())
18898    }
18899
18900    fn generate_log(&mut self, f: &LogFunc) -> Result<()> {
18901        use crate::expressions::Literal;
18902
18903        if let Some(base) = &f.base {
18904            // Check for LOG_BASE_FIRST = None dialects (Presto, Trino, ClickHouse, Athena)
18905            // These dialects use LOG2()/LOG10() instead of LOG(base, value)
18906            if self.is_log_base_none() {
18907                if matches!(base, Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(s) if s == "2"))
18908                {
18909                    self.write_func_name("LOG2");
18910                    self.write("(");
18911                    self.generate_expression(&f.this)?;
18912                    self.write(")");
18913                    return Ok(());
18914                } else if matches!(base, Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(s) if s == "10"))
18915                {
18916                    self.write_func_name("LOG10");
18917                    self.write("(");
18918                    self.generate_expression(&f.this)?;
18919                    self.write(")");
18920                    return Ok(());
18921                }
18922                // Other bases: fall through to LOG(base, value) — best effort
18923            }
18924
18925            self.write_func_name("LOG");
18926            self.write("(");
18927            if self.is_log_value_first() {
18928                // BigQuery, TSQL, Tableau, Fabric: LOG(value, base)
18929                self.generate_expression(&f.this)?;
18930                self.write(", ");
18931                self.generate_expression(base)?;
18932            } else {
18933                // Default (PostgreSQL, etc.): LOG(base, value)
18934                self.generate_expression(base)?;
18935                self.write(", ");
18936                self.generate_expression(&f.this)?;
18937            }
18938            self.write(")");
18939        } else {
18940            // Single arg: LOG(x) — unspecified base (log base 10 in default dialect)
18941            self.write_func_name("LOG");
18942            self.write("(");
18943            self.generate_expression(&f.this)?;
18944            self.write(")");
18945        }
18946        Ok(())
18947    }
18948
18949    /// Whether the target dialect uses LOG(value, base) order (value first).
18950    /// BigQuery, TSQL, Tableau, Fabric use LOG(value, base).
18951    fn is_log_value_first(&self) -> bool {
18952        use crate::dialects::DialectType;
18953        matches!(
18954            self.config.dialect,
18955            Some(DialectType::BigQuery)
18956                | Some(DialectType::TSQL)
18957                | Some(DialectType::Tableau)
18958                | Some(DialectType::Fabric)
18959        )
18960    }
18961
18962    /// Whether the target dialect has LOG_BASE_FIRST = None (uses LOG2/LOG10 instead).
18963    /// Presto, Trino, ClickHouse, Athena.
18964    fn is_log_base_none(&self) -> bool {
18965        use crate::dialects::DialectType;
18966        matches!(
18967            self.config.dialect,
18968            Some(DialectType::Presto)
18969                | Some(DialectType::Trino)
18970                | Some(DialectType::ClickHouse)
18971                | Some(DialectType::Athena)
18972        )
18973    }
18974
18975    // Date/time function generators
18976
18977    fn generate_current_time(&mut self, f: &CurrentTime) -> Result<()> {
18978        self.write_keyword("CURRENT_TIME");
18979        if let Some(precision) = f.precision {
18980            self.write(&format!("({})", precision));
18981        } else if matches!(
18982            self.config.dialect,
18983            Some(crate::dialects::DialectType::MySQL)
18984                | Some(crate::dialects::DialectType::SingleStore)
18985                | Some(crate::dialects::DialectType::TiDB)
18986        ) {
18987            self.write("()");
18988        }
18989        Ok(())
18990    }
18991
18992    fn generate_current_timestamp(&mut self, f: &CurrentTimestamp) -> Result<()> {
18993        use crate::dialects::DialectType;
18994
18995        // Oracle/Redshift SYSDATE handling
18996        if f.sysdate {
18997            match self.config.dialect {
18998                Some(DialectType::Oracle) | Some(DialectType::Redshift) => {
18999                    self.write_keyword("SYSDATE");
19000                    return Ok(());
19001                }
19002                Some(DialectType::Snowflake) => {
19003                    // Snowflake uses SYSDATE() function
19004                    self.write_keyword("SYSDATE");
19005                    self.write("()");
19006                    return Ok(());
19007                }
19008                _ => {
19009                    // Other dialects use CURRENT_TIMESTAMP for SYSDATE
19010                }
19011            }
19012        }
19013
19014        self.write_keyword("CURRENT_TIMESTAMP");
19015        // MySQL, Spark, Hive always use CURRENT_TIMESTAMP() with parentheses
19016        if let Some(precision) = f.precision {
19017            self.write(&format!("({})", precision));
19018        } else if matches!(
19019            self.config.dialect,
19020            Some(crate::dialects::DialectType::MySQL)
19021                | Some(crate::dialects::DialectType::SingleStore)
19022                | Some(crate::dialects::DialectType::TiDB)
19023                | Some(crate::dialects::DialectType::Spark)
19024                | Some(crate::dialects::DialectType::Hive)
19025                | Some(crate::dialects::DialectType::Databricks)
19026                | Some(crate::dialects::DialectType::ClickHouse)
19027                | Some(crate::dialects::DialectType::BigQuery)
19028                | Some(crate::dialects::DialectType::Snowflake)
19029                | Some(crate::dialects::DialectType::Exasol)
19030        ) {
19031            self.write("()");
19032        }
19033        Ok(())
19034    }
19035
19036    fn generate_at_time_zone(&mut self, f: &AtTimeZone) -> Result<()> {
19037        // Exasol uses CONVERT_TZ(timestamp, 'UTC', zone) instead of AT TIME ZONE
19038        if self.config.dialect == Some(DialectType::Exasol) {
19039            self.write_keyword("CONVERT_TZ");
19040            self.write("(");
19041            self.generate_expression(&f.this)?;
19042            self.write(", 'UTC', ");
19043            self.generate_expression(&f.zone)?;
19044            self.write(")");
19045            return Ok(());
19046        }
19047
19048        self.generate_expression(&f.this)?;
19049        self.write_space();
19050        self.write_keyword("AT TIME ZONE");
19051        self.write_space();
19052        self.generate_expression(&f.zone)?;
19053        Ok(())
19054    }
19055
19056    fn generate_date_add(&mut self, f: &DateAddFunc, name: &str) -> Result<()> {
19057        use crate::dialects::DialectType;
19058
19059        // Presto/Trino use DATE_ADD('unit', interval, date) format
19060        // with the interval cast to BIGINT when needed
19061        let is_presto_like = matches!(
19062            self.config.dialect,
19063            Some(DialectType::Presto) | Some(DialectType::Trino)
19064        );
19065
19066        if is_presto_like {
19067            self.write_keyword(name);
19068            self.write("(");
19069            // Unit as string literal
19070            self.write("'");
19071            self.write_simple_interval_unit(&f.unit, false);
19072            self.write("'");
19073            self.write(", ");
19074            // Interval - wrap in CAST(...AS BIGINT) if it doesn't return integer type
19075            let needs_cast = !self.returns_integer_type(&f.interval);
19076            if needs_cast {
19077                self.write_keyword("CAST");
19078                self.write("(");
19079            }
19080            self.generate_expression(&f.interval)?;
19081            if needs_cast {
19082                self.write_space();
19083                self.write_keyword("AS");
19084                self.write_space();
19085                self.write_keyword("BIGINT");
19086                self.write(")");
19087            }
19088            self.write(", ");
19089            self.generate_expression(&f.this)?;
19090            self.write(")");
19091        } else if matches!(self.config.dialect, Some(DialectType::PostgreSQL)) {
19092            self.generate_expression(&f.this)?;
19093            self.write_space();
19094            if name.eq_ignore_ascii_case("DATE_SUB") {
19095                self.write("-");
19096            } else {
19097                self.write("+");
19098            }
19099            self.write_space();
19100            self.write_keyword("INTERVAL");
19101            self.write_space();
19102            self.write("'");
19103            let mut interval_gen = Generator::with_arc_config(self.config.clone());
19104            let interval_sql = interval_gen.generate(&f.interval)?;
19105            self.write(&interval_sql);
19106            self.write(" ");
19107            self.write_simple_interval_unit(&f.unit, false);
19108            self.write("'");
19109        } else {
19110            self.write_keyword(name);
19111            self.write("(");
19112            self.generate_expression(&f.this)?;
19113            self.write(", ");
19114            self.write_keyword("INTERVAL");
19115            self.write_space();
19116            self.generate_expression(&f.interval)?;
19117            self.write_space();
19118            self.write_simple_interval_unit(&f.unit, false); // Use singular form for DATEADD
19119            self.write(")");
19120        }
19121        Ok(())
19122    }
19123
19124    /// Check if an expression returns an integer type (doesn't need cast to BIGINT in Presto DATE_ADD)
19125    /// This is a heuristic to avoid full type inference
19126    fn returns_integer_type(&self, expr: &Expression) -> bool {
19127        use crate::expressions::{DataType, Literal};
19128        match expr {
19129            // Integer literals (no decimal point)
19130            Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(_)) => {
19131                let Literal::Number(n) = lit.as_ref() else {
19132                    unreachable!()
19133                };
19134                !n.contains('.')
19135            }
19136
19137            // FLOOR(x) returns integer if x is integer
19138            Expression::Floor(f) => self.returns_integer_type(&f.this),
19139
19140            // ROUND(x) returns integer if x is integer
19141            Expression::Round(f) => {
19142                // Only if no decimals arg or it's returning an integer
19143                f.decimals.is_none() && self.returns_integer_type(&f.this)
19144            }
19145
19146            // SIGN returns integer if input is integer
19147            Expression::Sign(f) => self.returns_integer_type(&f.this),
19148
19149            // ABS returns the same type as input
19150            Expression::Abs(f) => self.returns_integer_type(&f.this),
19151
19152            // Arithmetic operations on integers return integers
19153            Expression::Mul(op) => {
19154                self.returns_integer_type(&op.left) && self.returns_integer_type(&op.right)
19155            }
19156            Expression::Add(op) => {
19157                self.returns_integer_type(&op.left) && self.returns_integer_type(&op.right)
19158            }
19159            Expression::Sub(op) => {
19160                self.returns_integer_type(&op.left) && self.returns_integer_type(&op.right)
19161            }
19162            Expression::Mod(op) => self.returns_integer_type(&op.left),
19163
19164            // CAST(x AS BIGINT/INT/INTEGER/SMALLINT/TINYINT) returns integer
19165            Expression::Cast(c) => matches!(
19166                &c.to,
19167                DataType::BigInt { .. }
19168                    | DataType::Int { .. }
19169                    | DataType::SmallInt { .. }
19170                    | DataType::TinyInt { .. }
19171            ),
19172
19173            // Negation: -x returns integer if x is integer
19174            Expression::Neg(op) => self.returns_integer_type(&op.this),
19175
19176            // Parenthesized expression
19177            Expression::Paren(p) => self.returns_integer_type(&p.this),
19178
19179            // Column references and most expressions are assumed to need casting
19180            // since we don't have full type information
19181            _ => false,
19182        }
19183    }
19184
19185    fn generate_datediff(&mut self, f: &DateDiffFunc) -> Result<()> {
19186        self.write_keyword("DATEDIFF");
19187        self.write("(");
19188        if let Some(unit) = &f.unit {
19189            self.write_simple_interval_unit(unit, false); // Use singular form for DATEDIFF
19190            self.write(", ");
19191        }
19192        self.generate_expression(&f.this)?;
19193        self.write(", ");
19194        self.generate_expression(&f.expression)?;
19195        self.write(")");
19196        Ok(())
19197    }
19198
19199    fn generate_date_trunc(&mut self, f: &DateTruncFunc) -> Result<()> {
19200        self.write_keyword("DATE_TRUNC");
19201        self.write("('");
19202        self.write_datetime_field(&f.unit);
19203        self.write("', ");
19204        self.generate_expression(&f.this)?;
19205        self.write(")");
19206        Ok(())
19207    }
19208
19209    fn generate_last_day(&mut self, f: &LastDayFunc) -> Result<()> {
19210        use crate::dialects::DialectType;
19211        use crate::expressions::DateTimeField;
19212
19213        self.write_keyword("LAST_DAY");
19214        self.write("(");
19215        self.generate_expression(&f.this)?;
19216        if let Some(unit) = &f.unit {
19217            self.write(", ");
19218            // BigQuery: strip week-start modifier from WEEK(SUNDAY), WEEK(MONDAY), etc.
19219            // WEEK(SUNDAY) -> WEEK
19220            if matches!(self.config.dialect, Some(DialectType::BigQuery)) {
19221                if let DateTimeField::WeekWithModifier(_) = unit {
19222                    self.write_keyword("WEEK");
19223                } else {
19224                    self.write_datetime_field(unit);
19225                }
19226            } else {
19227                self.write_datetime_field(unit);
19228            }
19229        }
19230        self.write(")");
19231        Ok(())
19232    }
19233
19234    fn generate_extract(&mut self, f: &ExtractFunc) -> Result<()> {
19235        // TSQL/Fabric use DATEPART(part, expr) instead of EXTRACT(part FROM expr)
19236        if matches!(
19237            self.config.dialect,
19238            Some(DialectType::TSQL) | Some(DialectType::Fabric)
19239        ) {
19240            self.write_keyword("DATEPART");
19241            self.write("(");
19242            self.write_datetime_field(&f.field);
19243            self.write(", ");
19244            self.generate_expression(&f.this)?;
19245            self.write(")");
19246            return Ok(());
19247        }
19248        self.write_keyword("EXTRACT");
19249        self.write("(");
19250        // Hive/Spark use lowercase datetime fields in EXTRACT
19251        if matches!(
19252            self.config.dialect,
19253            Some(DialectType::Hive) | Some(DialectType::Spark) | Some(DialectType::Databricks)
19254        ) {
19255            self.write_datetime_field_lower(&f.field);
19256        } else {
19257            self.write_datetime_field(&f.field);
19258        }
19259        self.write_space();
19260        self.write_keyword("FROM");
19261        self.write_space();
19262        self.generate_expression(&f.this)?;
19263        self.write(")");
19264        Ok(())
19265    }
19266
19267    fn generate_to_date(&mut self, f: &ToDateFunc) -> Result<()> {
19268        self.write_keyword("TO_DATE");
19269        self.write("(");
19270        self.generate_expression(&f.this)?;
19271        if let Some(format) = &f.format {
19272            self.write(", ");
19273            self.generate_expression(format)?;
19274        }
19275        self.write(")");
19276        Ok(())
19277    }
19278
19279    fn generate_to_timestamp(&mut self, f: &ToTimestampFunc) -> Result<()> {
19280        self.write_keyword("TO_TIMESTAMP");
19281        self.write("(");
19282        self.generate_expression(&f.this)?;
19283        if let Some(format) = &f.format {
19284            self.write(", ");
19285            self.generate_expression(format)?;
19286        }
19287        self.write(")");
19288        Ok(())
19289    }
19290
19291    // Control flow function generators
19292
19293    fn generate_if_func(&mut self, f: &IfFunc) -> Result<()> {
19294        use crate::dialects::DialectType;
19295
19296        // Generic mode: normalize IF to CASE WHEN
19297        if self.config.dialect.is_none() || self.config.dialect == Some(DialectType::Generic) {
19298            self.write_keyword("CASE WHEN");
19299            self.write_space();
19300            self.generate_expression(&f.condition)?;
19301            self.write_space();
19302            self.write_keyword("THEN");
19303            self.write_space();
19304            self.generate_expression(&f.true_value)?;
19305            if let Some(false_val) = &f.false_value {
19306                self.write_space();
19307                self.write_keyword("ELSE");
19308                self.write_space();
19309                self.generate_expression(false_val)?;
19310            }
19311            self.write_space();
19312            self.write_keyword("END");
19313            return Ok(());
19314        }
19315
19316        // Exasol uses IF condition THEN true_value ELSE false_value ENDIF syntax
19317        if self.config.dialect == Some(DialectType::Exasol) {
19318            self.write_keyword("IF");
19319            self.write_space();
19320            self.generate_expression(&f.condition)?;
19321            self.write_space();
19322            self.write_keyword("THEN");
19323            self.write_space();
19324            self.generate_expression(&f.true_value)?;
19325            if let Some(false_val) = &f.false_value {
19326                self.write_space();
19327                self.write_keyword("ELSE");
19328                self.write_space();
19329                self.generate_expression(false_val)?;
19330            }
19331            self.write_space();
19332            self.write_keyword("ENDIF");
19333            return Ok(());
19334        }
19335
19336        // Choose function name based on target dialect
19337        let func_name = match self.config.dialect {
19338            Some(DialectType::Snowflake) => "IFF",
19339            Some(DialectType::SQLite) | Some(DialectType::TSQL) => "IIF",
19340            Some(DialectType::Drill) => "`IF`",
19341            _ => "IF",
19342        };
19343        self.write(func_name);
19344        self.write("(");
19345        self.generate_expression(&f.condition)?;
19346        self.write(", ");
19347        self.generate_expression(&f.true_value)?;
19348        if let Some(false_val) = &f.false_value {
19349            self.write(", ");
19350            self.generate_expression(false_val)?;
19351        }
19352        self.write(")");
19353        Ok(())
19354    }
19355
19356    fn generate_nvl2(&mut self, f: &Nvl2Func) -> Result<()> {
19357        self.write_keyword("NVL2");
19358        self.write("(");
19359        self.generate_expression(&f.this)?;
19360        self.write(", ");
19361        self.generate_expression(&f.true_value)?;
19362        self.write(", ");
19363        self.generate_expression(&f.false_value)?;
19364        self.write(")");
19365        Ok(())
19366    }
19367
19368    // Typed aggregate function generators
19369
19370    fn generate_count(&mut self, f: &CountFunc) -> Result<()> {
19371        // Use normalize_functions for COUNT to respect ClickHouse case preservation
19372        let count_name = match self.config.normalize_functions {
19373            NormalizeFunctions::Upper => "COUNT".to_string(),
19374            NormalizeFunctions::Lower => "count".to_string(),
19375            NormalizeFunctions::None => f
19376                .original_name
19377                .clone()
19378                .unwrap_or_else(|| "COUNT".to_string()),
19379        };
19380        self.write(&count_name);
19381        self.write("(");
19382        if f.distinct {
19383            self.write_keyword("DISTINCT");
19384            self.write_space();
19385        }
19386        if f.star {
19387            self.write("*");
19388        } else if let Some(ref expr) = f.this {
19389            // For COUNT(DISTINCT a, b), unwrap the Tuple to avoid extra parentheses
19390            if let Expression::Tuple(tuple) = expr {
19391                // Check if we need to transform multi-arg COUNT DISTINCT
19392                // When dialect doesn't support multi_arg_distinct, transform:
19393                // COUNT(DISTINCT a, b) -> COUNT(DISTINCT CASE WHEN a IS NULL THEN NULL WHEN b IS NULL THEN NULL ELSE (a, b) END)
19394                let needs_transform =
19395                    f.distinct && tuple.expressions.len() > 1 && !self.config.multi_arg_distinct;
19396
19397                if needs_transform {
19398                    // Generate: CASE WHEN a IS NULL THEN NULL WHEN b IS NULL THEN NULL ELSE (a, b) END
19399                    self.write_keyword("CASE");
19400                    for e in &tuple.expressions {
19401                        self.write_space();
19402                        self.write_keyword("WHEN");
19403                        self.write_space();
19404                        self.generate_expression(e)?;
19405                        self.write_space();
19406                        self.write_keyword("IS NULL THEN NULL");
19407                    }
19408                    self.write_space();
19409                    self.write_keyword("ELSE");
19410                    self.write(" (");
19411                    for (i, e) in tuple.expressions.iter().enumerate() {
19412                        if i > 0 {
19413                            self.write(", ");
19414                        }
19415                        self.generate_expression(e)?;
19416                    }
19417                    self.write(")");
19418                    self.write_space();
19419                    self.write_keyword("END");
19420                } else {
19421                    for (i, e) in tuple.expressions.iter().enumerate() {
19422                        if i > 0 {
19423                            self.write(", ");
19424                        }
19425                        self.generate_expression(e)?;
19426                    }
19427                }
19428            } else {
19429                self.generate_expression(expr)?;
19430            }
19431        }
19432        // RESPECT NULLS / IGNORE NULLS
19433        if let Some(ignore) = f.ignore_nulls {
19434            self.write_space();
19435            if ignore {
19436                self.write_keyword("IGNORE NULLS");
19437            } else {
19438                self.write_keyword("RESPECT NULLS");
19439            }
19440        }
19441        self.write(")");
19442        if let Some(ref filter) = f.filter {
19443            self.write_space();
19444            self.write_keyword("FILTER");
19445            self.write("(");
19446            self.write_keyword("WHERE");
19447            self.write_space();
19448            self.generate_expression(filter)?;
19449            self.write(")");
19450        }
19451        Ok(())
19452    }
19453
19454    fn generate_agg_func(&mut self, name: &str, f: &AggFunc) -> Result<()> {
19455        // Apply function name normalization based on config
19456        let func_name: Cow<'_, str> = match self.config.normalize_functions {
19457            NormalizeFunctions::Upper => Cow::Owned(name.to_ascii_uppercase()),
19458            NormalizeFunctions::Lower => Cow::Owned(name.to_ascii_lowercase()),
19459            NormalizeFunctions::None => {
19460                // Use the original function name from parsing if available,
19461                // otherwise fall back to lowercase of the hardcoded constant
19462                if let Some(ref original) = f.name {
19463                    Cow::Owned(original.clone())
19464                } else {
19465                    Cow::Owned(name.to_ascii_lowercase())
19466                }
19467            }
19468        };
19469        self.write(func_name.as_ref());
19470        self.write("(");
19471        if f.distinct {
19472            self.write_keyword("DISTINCT");
19473            self.write_space();
19474        }
19475        // Skip generating the expression if it's a NULL placeholder for zero-arg aggregates like MODE()
19476        if !matches!(f.this, Expression::Null(_)) {
19477            self.generate_expression(&f.this)?;
19478        }
19479        // Generate IGNORE NULLS / RESPECT NULLS inside parens if config says so (BigQuery style)
19480        // DuckDB doesn't support IGNORE NULLS / RESPECT NULLS in aggregate functions - skip it
19481        if self.config.ignore_nulls_in_func
19482            && !matches!(self.config.dialect, Some(DialectType::DuckDB))
19483        {
19484            match f.ignore_nulls {
19485                Some(true) => {
19486                    self.write_space();
19487                    self.write_keyword("IGNORE NULLS");
19488                }
19489                Some(false) => {
19490                    self.write_space();
19491                    self.write_keyword("RESPECT NULLS");
19492                }
19493                None => {}
19494            }
19495        }
19496        // Generate HAVING MAX/MIN if present (BigQuery syntax)
19497        // e.g., ANY_VALUE(fruit HAVING MAX sold)
19498        if let Some((ref expr, is_max)) = f.having_max {
19499            self.write_space();
19500            self.write_keyword("HAVING");
19501            self.write_space();
19502            if is_max {
19503                self.write_keyword("MAX");
19504            } else {
19505                self.write_keyword("MIN");
19506            }
19507            self.write_space();
19508            self.generate_expression(expr)?;
19509        }
19510        // Generate ORDER BY if present (for aggregates like ARRAY_AGG(x ORDER BY y))
19511        if !f.order_by.is_empty() {
19512            self.write_space();
19513            self.write_keyword("ORDER BY");
19514            self.write_space();
19515            for (i, ord) in f.order_by.iter().enumerate() {
19516                if i > 0 {
19517                    self.write(", ");
19518                }
19519                self.generate_ordered(ord)?;
19520            }
19521        }
19522        // Generate LIMIT if present (for aggregates like ARRAY_AGG(x ORDER BY y LIMIT 2))
19523        if let Some(ref limit) = f.limit {
19524            self.write_space();
19525            self.write_keyword("LIMIT");
19526            self.write_space();
19527            // Check if this is a Tuple representing LIMIT offset, count
19528            if let Expression::Tuple(t) = limit.as_ref() {
19529                if t.expressions.len() == 2 {
19530                    self.generate_expression(&t.expressions[0])?;
19531                    self.write(", ");
19532                    self.generate_expression(&t.expressions[1])?;
19533                } else {
19534                    self.generate_expression(limit)?;
19535                }
19536            } else {
19537                self.generate_expression(limit)?;
19538            }
19539        }
19540        self.write(")");
19541        // Generate IGNORE NULLS / RESPECT NULLS outside parens if config says so (standard style)
19542        // DuckDB doesn't support IGNORE NULLS / RESPECT NULLS in aggregate functions - skip it
19543        if !self.config.ignore_nulls_in_func
19544            && !matches!(self.config.dialect, Some(DialectType::DuckDB))
19545        {
19546            match f.ignore_nulls {
19547                Some(true) => {
19548                    self.write_space();
19549                    self.write_keyword("IGNORE NULLS");
19550                }
19551                Some(false) => {
19552                    self.write_space();
19553                    self.write_keyword("RESPECT NULLS");
19554                }
19555                None => {}
19556            }
19557        }
19558        if let Some(ref filter) = f.filter {
19559            self.write_space();
19560            self.write_keyword("FILTER");
19561            self.write("(");
19562            self.write_keyword("WHERE");
19563            self.write_space();
19564            self.generate_expression(filter)?;
19565            self.write(")");
19566        }
19567        Ok(())
19568    }
19569
19570    /// Generate FIRST/LAST aggregate functions with Hive/Spark2-style boolean argument
19571    /// for IGNORE NULLS. In Hive/Spark2, `FIRST(col) IGNORE NULLS` is written as `FIRST(col, TRUE)`.
19572    fn generate_agg_func_with_ignore_nulls_bool(&mut self, name: &str, f: &AggFunc) -> Result<()> {
19573        // For Hive/Spark2 dialects, convert IGNORE NULLS to boolean TRUE argument
19574        if matches!(self.config.dialect, Some(DialectType::Hive)) && f.ignore_nulls == Some(true) {
19575            // Create a modified copy without ignore_nulls, add TRUE as part of the output
19576            let func_name: Cow<'_, str> = match self.config.normalize_functions {
19577                NormalizeFunctions::Upper => Cow::Owned(name.to_ascii_uppercase()),
19578                NormalizeFunctions::Lower => Cow::Owned(name.to_ascii_lowercase()),
19579                NormalizeFunctions::None => {
19580                    if let Some(ref original) = f.name {
19581                        Cow::Owned(original.clone())
19582                    } else {
19583                        Cow::Owned(name.to_ascii_lowercase())
19584                    }
19585                }
19586            };
19587            self.write(func_name.as_ref());
19588            self.write("(");
19589            if f.distinct {
19590                self.write_keyword("DISTINCT");
19591                self.write_space();
19592            }
19593            if !matches!(f.this, Expression::Null(_)) {
19594                self.generate_expression(&f.this)?;
19595            }
19596            self.write(", ");
19597            self.write_keyword("TRUE");
19598            self.write(")");
19599            return Ok(());
19600        }
19601        self.generate_agg_func(name, f)
19602    }
19603
19604    fn generate_group_concat(&mut self, f: &GroupConcatFunc) -> Result<()> {
19605        self.write_keyword("GROUP_CONCAT");
19606        self.write("(");
19607        if f.distinct {
19608            self.write_keyword("DISTINCT");
19609            self.write_space();
19610        }
19611        self.generate_expression(&f.this)?;
19612        if let Some(ref order_by) = f.order_by {
19613            self.write_space();
19614            self.write_keyword("ORDER BY");
19615            self.write_space();
19616            for (i, ord) in order_by.iter().enumerate() {
19617                if i > 0 {
19618                    self.write(", ");
19619                }
19620                self.generate_ordered(ord)?;
19621            }
19622        }
19623        if let Some(ref sep) = f.separator {
19624            // SQLite uses GROUP_CONCAT(x, sep) syntax (comma-separated)
19625            // MySQL and others use GROUP_CONCAT(x SEPARATOR sep) syntax
19626            if matches!(
19627                self.config.dialect,
19628                Some(crate::dialects::DialectType::SQLite)
19629            ) {
19630                self.write(", ");
19631                self.generate_expression(sep)?;
19632            } else {
19633                self.write_space();
19634                self.write_keyword("SEPARATOR");
19635                self.write_space();
19636                self.generate_expression(sep)?;
19637            }
19638        }
19639        if let Some(ref limit) = f.limit {
19640            self.write_space();
19641            self.write_keyword("LIMIT");
19642            self.write_space();
19643            self.generate_expression(limit)?;
19644        }
19645        self.write(")");
19646        if let Some(ref filter) = f.filter {
19647            self.write_space();
19648            self.write_keyword("FILTER");
19649            self.write("(");
19650            self.write_keyword("WHERE");
19651            self.write_space();
19652            self.generate_expression(filter)?;
19653            self.write(")");
19654        }
19655        Ok(())
19656    }
19657
19658    fn generate_string_agg(&mut self, f: &StringAggFunc) -> Result<()> {
19659        let is_tsql = matches!(
19660            self.config.dialect,
19661            Some(crate::dialects::DialectType::TSQL)
19662        );
19663        self.write_keyword("STRING_AGG");
19664        self.write("(");
19665        if f.distinct {
19666            self.write_keyword("DISTINCT");
19667            self.write_space();
19668        }
19669        self.generate_expression(&f.this)?;
19670        if let Some(ref separator) = f.separator {
19671            self.write(", ");
19672            self.generate_expression(separator)?;
19673        }
19674        // For TSQL, ORDER BY goes in WITHIN GROUP clause after the closing paren
19675        if !is_tsql {
19676            if let Some(ref order_by) = f.order_by {
19677                self.write_space();
19678                self.write_keyword("ORDER BY");
19679                self.write_space();
19680                for (i, ord) in order_by.iter().enumerate() {
19681                    if i > 0 {
19682                        self.write(", ");
19683                    }
19684                    self.generate_ordered(ord)?;
19685                }
19686            }
19687        }
19688        if let Some(ref limit) = f.limit {
19689            self.write_space();
19690            self.write_keyword("LIMIT");
19691            self.write_space();
19692            self.generate_expression(limit)?;
19693        }
19694        self.write(")");
19695        // TSQL uses WITHIN GROUP (ORDER BY ...) after the function call
19696        if is_tsql {
19697            if let Some(ref order_by) = f.order_by {
19698                self.write_space();
19699                self.write_keyword("WITHIN GROUP");
19700                self.write(" (");
19701                self.write_keyword("ORDER BY");
19702                self.write_space();
19703                for (i, ord) in order_by.iter().enumerate() {
19704                    if i > 0 {
19705                        self.write(", ");
19706                    }
19707                    self.generate_ordered(ord)?;
19708                }
19709                self.write(")");
19710            }
19711        }
19712        if let Some(ref filter) = f.filter {
19713            self.write_space();
19714            self.write_keyword("FILTER");
19715            self.write("(");
19716            self.write_keyword("WHERE");
19717            self.write_space();
19718            self.generate_expression(filter)?;
19719            self.write(")");
19720        }
19721        Ok(())
19722    }
19723
19724    fn generate_listagg(&mut self, f: &ListAggFunc) -> Result<()> {
19725        use crate::dialects::DialectType;
19726        self.write_keyword("LISTAGG");
19727        self.write("(");
19728        if f.distinct {
19729            self.write_keyword("DISTINCT");
19730            self.write_space();
19731        }
19732        self.generate_expression(&f.this)?;
19733        if let Some(ref sep) = f.separator {
19734            self.write(", ");
19735            self.generate_expression(sep)?;
19736        } else if matches!(
19737            self.config.dialect,
19738            Some(DialectType::Trino) | Some(DialectType::Presto)
19739        ) {
19740            // Trino/Presto require explicit separator; default to ','
19741            self.write(", ','");
19742        }
19743        if let Some(ref overflow) = f.on_overflow {
19744            self.write_space();
19745            self.write_keyword("ON OVERFLOW");
19746            self.write_space();
19747            match overflow {
19748                ListAggOverflow::Error => self.write_keyword("ERROR"),
19749                ListAggOverflow::Truncate { filler, with_count } => {
19750                    self.write_keyword("TRUNCATE");
19751                    if let Some(ref fill) = filler {
19752                        self.write_space();
19753                        self.generate_expression(fill)?;
19754                    }
19755                    if *with_count {
19756                        self.write_space();
19757                        self.write_keyword("WITH COUNT");
19758                    } else {
19759                        self.write_space();
19760                        self.write_keyword("WITHOUT COUNT");
19761                    }
19762                }
19763            }
19764        }
19765        self.write(")");
19766        if let Some(ref order_by) = f.order_by {
19767            self.write_space();
19768            self.write_keyword("WITHIN GROUP");
19769            self.write(" (");
19770            self.write_keyword("ORDER BY");
19771            self.write_space();
19772            for (i, ord) in order_by.iter().enumerate() {
19773                if i > 0 {
19774                    self.write(", ");
19775                }
19776                self.generate_ordered(ord)?;
19777            }
19778            self.write(")");
19779        }
19780        if let Some(ref filter) = f.filter {
19781            self.write_space();
19782            self.write_keyword("FILTER");
19783            self.write("(");
19784            self.write_keyword("WHERE");
19785            self.write_space();
19786            self.generate_expression(filter)?;
19787            self.write(")");
19788        }
19789        Ok(())
19790    }
19791
19792    fn generate_sum_if(&mut self, f: &SumIfFunc) -> Result<()> {
19793        self.write_keyword("SUM_IF");
19794        self.write("(");
19795        self.generate_expression(&f.this)?;
19796        self.write(", ");
19797        self.generate_expression(&f.condition)?;
19798        self.write(")");
19799        if let Some(ref filter) = f.filter {
19800            self.write_space();
19801            self.write_keyword("FILTER");
19802            self.write("(");
19803            self.write_keyword("WHERE");
19804            self.write_space();
19805            self.generate_expression(filter)?;
19806            self.write(")");
19807        }
19808        Ok(())
19809    }
19810
19811    fn generate_approx_percentile(&mut self, f: &ApproxPercentileFunc) -> Result<()> {
19812        self.write_keyword("APPROX_PERCENTILE");
19813        self.write("(");
19814        self.generate_expression(&f.this)?;
19815        self.write(", ");
19816        self.generate_expression(&f.percentile)?;
19817        if let Some(ref acc) = f.accuracy {
19818            self.write(", ");
19819            self.generate_expression(acc)?;
19820        }
19821        self.write(")");
19822        if let Some(ref filter) = f.filter {
19823            self.write_space();
19824            self.write_keyword("FILTER");
19825            self.write("(");
19826            self.write_keyword("WHERE");
19827            self.write_space();
19828            self.generate_expression(filter)?;
19829            self.write(")");
19830        }
19831        Ok(())
19832    }
19833
19834    fn generate_percentile(&mut self, name: &str, f: &PercentileFunc) -> Result<()> {
19835        self.write_keyword(name);
19836        self.write("(");
19837        self.generate_expression(&f.percentile)?;
19838        self.write(")");
19839        if let Some(ref order_by) = f.order_by {
19840            self.write_space();
19841            self.write_keyword("WITHIN GROUP");
19842            self.write(" (");
19843            self.write_keyword("ORDER BY");
19844            self.write_space();
19845            self.generate_expression(&f.this)?;
19846            for ord in order_by.iter() {
19847                if ord.desc {
19848                    self.write_space();
19849                    self.write_keyword("DESC");
19850                }
19851            }
19852            self.write(")");
19853        }
19854        if let Some(ref filter) = f.filter {
19855            self.write_space();
19856            self.write_keyword("FILTER");
19857            self.write("(");
19858            self.write_keyword("WHERE");
19859            self.write_space();
19860            self.generate_expression(filter)?;
19861            self.write(")");
19862        }
19863        Ok(())
19864    }
19865
19866    // Window function generators
19867
19868    fn generate_ntile(&mut self, f: &NTileFunc) -> Result<()> {
19869        self.write_keyword("NTILE");
19870        self.write("(");
19871        if let Some(num_buckets) = &f.num_buckets {
19872            self.generate_expression(num_buckets)?;
19873        }
19874        if let Some(order_by) = &f.order_by {
19875            self.write_keyword(" ORDER BY ");
19876            for (i, ob) in order_by.iter().enumerate() {
19877                if i > 0 {
19878                    self.write(", ");
19879                }
19880                self.generate_ordered(ob)?;
19881            }
19882        }
19883        self.write(")");
19884        Ok(())
19885    }
19886
19887    fn generate_lead_lag(&mut self, name: &str, f: &LeadLagFunc) -> Result<()> {
19888        self.write_keyword(name);
19889        self.write("(");
19890        self.generate_expression(&f.this)?;
19891        if let Some(ref offset) = f.offset {
19892            self.write(", ");
19893            self.generate_expression(offset)?;
19894            if let Some(ref default) = f.default {
19895                self.write(", ");
19896                self.generate_expression(default)?;
19897            }
19898        }
19899        // IGNORE NULLS / RESPECT NULLS inside parens for dialects like BigQuery
19900        if self.config.ignore_nulls_in_func {
19901            match f.ignore_nulls {
19902                Some(true) => {
19903                    self.write_space();
19904                    self.write_keyword("IGNORE NULLS");
19905                }
19906                Some(false) => {
19907                    self.write_space();
19908                    self.write_keyword("RESPECT NULLS");
19909                }
19910                None => {}
19911            }
19912        }
19913        self.write(")");
19914        // IGNORE NULLS / RESPECT NULLS outside parens for other dialects
19915        if !self.config.ignore_nulls_in_func {
19916            match f.ignore_nulls {
19917                Some(true) => {
19918                    self.write_space();
19919                    self.write_keyword("IGNORE NULLS");
19920                }
19921                Some(false) => {
19922                    self.write_space();
19923                    self.write_keyword("RESPECT NULLS");
19924                }
19925                None => {}
19926            }
19927        }
19928        Ok(())
19929    }
19930
19931    fn generate_value_func(&mut self, name: &str, f: &ValueFunc) -> Result<()> {
19932        self.write_keyword(name);
19933        self.write("(");
19934        self.generate_expression(&f.this)?;
19935        // ORDER BY inside parens (e.g., DuckDB: LAST_VALUE(x ORDER BY x))
19936        if !f.order_by.is_empty() {
19937            self.write_space();
19938            self.write_keyword("ORDER BY");
19939            self.write_space();
19940            for (i, ordered) in f.order_by.iter().enumerate() {
19941                if i > 0 {
19942                    self.write(", ");
19943                }
19944                self.generate_ordered(ordered)?;
19945            }
19946        }
19947        // IGNORE NULLS / RESPECT NULLS inside parens for dialects like BigQuery, DuckDB
19948        if self.config.ignore_nulls_in_func {
19949            match f.ignore_nulls {
19950                Some(true) => {
19951                    self.write_space();
19952                    self.write_keyword("IGNORE NULLS");
19953                }
19954                Some(false) => {
19955                    self.write_space();
19956                    self.write_keyword("RESPECT NULLS");
19957                }
19958                None => {}
19959            }
19960        }
19961        self.write(")");
19962        // IGNORE NULLS / RESPECT NULLS outside parens for other dialects
19963        if !self.config.ignore_nulls_in_func {
19964            match f.ignore_nulls {
19965                Some(true) => {
19966                    self.write_space();
19967                    self.write_keyword("IGNORE NULLS");
19968                }
19969                Some(false) => {
19970                    self.write_space();
19971                    self.write_keyword("RESPECT NULLS");
19972                }
19973                None => {}
19974            }
19975        }
19976        Ok(())
19977    }
19978
19979    /// Generate FIRST_VALUE/LAST_VALUE with Hive/Spark2-style boolean argument for IGNORE NULLS.
19980    /// In Hive/Spark2, `FIRST_VALUE(col) IGNORE NULLS` is written as `FIRST_VALUE(col, TRUE)`.
19981    fn generate_value_func_with_ignore_nulls_bool(
19982        &mut self,
19983        name: &str,
19984        f: &ValueFunc,
19985    ) -> Result<()> {
19986        if matches!(self.config.dialect, Some(DialectType::Hive)) && f.ignore_nulls == Some(true) {
19987            self.write_keyword(name);
19988            self.write("(");
19989            self.generate_expression(&f.this)?;
19990            self.write(", ");
19991            self.write_keyword("TRUE");
19992            self.write(")");
19993            return Ok(());
19994        }
19995        self.generate_value_func(name, f)
19996    }
19997
19998    fn generate_nth_value(&mut self, f: &NthValueFunc) -> Result<()> {
19999        self.write_keyword("NTH_VALUE");
20000        self.write("(");
20001        self.generate_expression(&f.this)?;
20002        self.write(", ");
20003        self.generate_expression(&f.offset)?;
20004        // IGNORE NULLS / RESPECT NULLS inside parens for dialects like BigQuery, DuckDB
20005        if self.config.ignore_nulls_in_func {
20006            match f.ignore_nulls {
20007                Some(true) => {
20008                    self.write_space();
20009                    self.write_keyword("IGNORE NULLS");
20010                }
20011                Some(false) => {
20012                    self.write_space();
20013                    self.write_keyword("RESPECT NULLS");
20014                }
20015                None => {}
20016            }
20017        }
20018        self.write(")");
20019        // FROM FIRST / FROM LAST (Snowflake-specific, before IGNORE/RESPECT NULLS)
20020        if matches!(
20021            self.config.dialect,
20022            Some(crate::dialects::DialectType::Snowflake)
20023        ) {
20024            match f.from_first {
20025                Some(true) => {
20026                    self.write_space();
20027                    self.write_keyword("FROM FIRST");
20028                }
20029                Some(false) => {
20030                    self.write_space();
20031                    self.write_keyword("FROM LAST");
20032                }
20033                None => {}
20034            }
20035        }
20036        // IGNORE NULLS / RESPECT NULLS outside parens for other dialects
20037        if !self.config.ignore_nulls_in_func {
20038            match f.ignore_nulls {
20039                Some(true) => {
20040                    self.write_space();
20041                    self.write_keyword("IGNORE NULLS");
20042                }
20043                Some(false) => {
20044                    self.write_space();
20045                    self.write_keyword("RESPECT NULLS");
20046                }
20047                None => {}
20048            }
20049        }
20050        Ok(())
20051    }
20052
20053    // Additional string function generators
20054
20055    fn generate_position(&mut self, f: &PositionFunc) -> Result<()> {
20056        // Standard syntax: POSITION(substr IN str)
20057        // ClickHouse prefers comma syntax with reversed arg order: POSITION(str, substr[, start])
20058        if matches!(
20059            self.config.dialect,
20060            Some(crate::dialects::DialectType::ClickHouse)
20061        ) {
20062            self.write_keyword("POSITION");
20063            self.write("(");
20064            self.generate_expression(&f.string)?;
20065            self.write(", ");
20066            self.generate_expression(&f.substring)?;
20067            if let Some(ref start) = f.start {
20068                self.write(", ");
20069                self.generate_expression(start)?;
20070            }
20071            self.write(")");
20072            return Ok(());
20073        }
20074
20075        self.write_keyword("POSITION");
20076        self.write("(");
20077        self.generate_expression(&f.substring)?;
20078        self.write_space();
20079        self.write_keyword("IN");
20080        self.write_space();
20081        self.generate_expression(&f.string)?;
20082        if let Some(ref start) = f.start {
20083            self.write(", ");
20084            self.generate_expression(start)?;
20085        }
20086        self.write(")");
20087        Ok(())
20088    }
20089
20090    // Additional math function generators
20091
20092    fn generate_rand(&mut self, f: &Rand) -> Result<()> {
20093        // Teradata RANDOM(lower, upper)
20094        if f.lower.is_some() || f.upper.is_some() {
20095            self.write_keyword("RANDOM");
20096            self.write("(");
20097            if let Some(ref lower) = f.lower {
20098                self.generate_expression(lower)?;
20099            }
20100            if let Some(ref upper) = f.upper {
20101                self.write(", ");
20102                self.generate_expression(upper)?;
20103            }
20104            self.write(")");
20105            return Ok(());
20106        }
20107        // Snowflake uses RANDOM instead of RAND, DuckDB uses RANDOM without seed
20108        let func_name = match self.config.dialect {
20109            Some(crate::dialects::DialectType::Snowflake)
20110            | Some(crate::dialects::DialectType::DuckDB) => "RANDOM",
20111            _ => "RAND",
20112        };
20113        self.write_keyword(func_name);
20114        self.write("(");
20115        // DuckDB doesn't support seeded RANDOM, so skip the seed
20116        if !matches!(
20117            self.config.dialect,
20118            Some(crate::dialects::DialectType::DuckDB)
20119        ) {
20120            if let Some(ref seed) = f.seed {
20121                self.generate_expression(seed)?;
20122            }
20123        }
20124        self.write(")");
20125        Ok(())
20126    }
20127
20128    fn generate_truncate_func(&mut self, f: &TruncateFunc) -> Result<()> {
20129        self.write_keyword("TRUNCATE");
20130        self.write("(");
20131        self.generate_expression(&f.this)?;
20132        if let Some(ref decimals) = f.decimals {
20133            self.write(", ");
20134            self.generate_expression(decimals)?;
20135        }
20136        self.write(")");
20137        Ok(())
20138    }
20139
20140    // Control flow generators
20141
20142    fn generate_decode(&mut self, f: &DecodeFunc) -> Result<()> {
20143        self.write_keyword("DECODE");
20144        self.write("(");
20145        self.generate_expression(&f.this)?;
20146        for (search, result) in &f.search_results {
20147            self.write(", ");
20148            self.generate_expression(search)?;
20149            self.write(", ");
20150            self.generate_expression(result)?;
20151        }
20152        if let Some(ref default) = f.default {
20153            self.write(", ");
20154            self.generate_expression(default)?;
20155        }
20156        self.write(")");
20157        Ok(())
20158    }
20159
20160    // Date/time function generators
20161
20162    fn generate_date_format(&mut self, name: &str, f: &DateFormatFunc) -> Result<()> {
20163        self.write_keyword(name);
20164        self.write("(");
20165        self.generate_expression(&f.this)?;
20166        self.write(", ");
20167        self.generate_expression(&f.format)?;
20168        self.write(")");
20169        Ok(())
20170    }
20171
20172    fn generate_from_unixtime(&mut self, f: &FromUnixtimeFunc) -> Result<()> {
20173        self.write_keyword("FROM_UNIXTIME");
20174        self.write("(");
20175        self.generate_expression(&f.this)?;
20176        if let Some(ref format) = f.format {
20177            self.write(", ");
20178            self.generate_expression(format)?;
20179        }
20180        self.write(")");
20181        Ok(())
20182    }
20183
20184    fn generate_unix_timestamp(&mut self, f: &UnixTimestampFunc) -> Result<()> {
20185        self.write_keyword("UNIX_TIMESTAMP");
20186        self.write("(");
20187        if let Some(ref expr) = f.this {
20188            self.generate_expression(expr)?;
20189            if let Some(ref format) = f.format {
20190                self.write(", ");
20191                self.generate_expression(format)?;
20192            }
20193        } else if matches!(
20194            self.config.dialect,
20195            Some(DialectType::Spark) | Some(DialectType::Hive) | Some(DialectType::Databricks)
20196        ) {
20197            // Spark/Hive: UNIX_TIMESTAMP() -> UNIX_TIMESTAMP(CURRENT_TIMESTAMP())
20198            self.write_keyword("CURRENT_TIMESTAMP");
20199            self.write("()");
20200        }
20201        self.write(")");
20202        Ok(())
20203    }
20204
20205    fn generate_make_date(&mut self, f: &MakeDateFunc) -> Result<()> {
20206        self.write_keyword("MAKE_DATE");
20207        self.write("(");
20208        self.generate_expression(&f.year)?;
20209        self.write(", ");
20210        self.generate_expression(&f.month)?;
20211        self.write(", ");
20212        self.generate_expression(&f.day)?;
20213        self.write(")");
20214        Ok(())
20215    }
20216
20217    fn generate_make_timestamp(&mut self, f: &MakeTimestampFunc) -> Result<()> {
20218        self.write_keyword("MAKE_TIMESTAMP");
20219        self.write("(");
20220        self.generate_expression(&f.year)?;
20221        self.write(", ");
20222        self.generate_expression(&f.month)?;
20223        self.write(", ");
20224        self.generate_expression(&f.day)?;
20225        self.write(", ");
20226        self.generate_expression(&f.hour)?;
20227        self.write(", ");
20228        self.generate_expression(&f.minute)?;
20229        self.write(", ");
20230        self.generate_expression(&f.second)?;
20231        if let Some(ref tz) = f.timezone {
20232            self.write(", ");
20233            self.generate_expression(tz)?;
20234        }
20235        self.write(")");
20236        Ok(())
20237    }
20238
20239    /// Extract field names from a struct expression (either Struct or Function named STRUCT with Alias args)
20240    fn extract_struct_field_names(expr: &Expression) -> Option<Vec<String>> {
20241        match expr {
20242            Expression::Struct(s) => {
20243                if s.fields.iter().all(|(name, _)| name.is_some()) {
20244                    Some(
20245                        s.fields
20246                            .iter()
20247                            .map(|(name, _)| name.as_deref().unwrap_or("").to_string())
20248                            .collect(),
20249                    )
20250                } else {
20251                    None
20252                }
20253            }
20254            Expression::Function(f) if f.name.eq_ignore_ascii_case("STRUCT") => {
20255                // Check if all args are Alias (named fields)
20256                if f.args.iter().all(|a| matches!(a, Expression::Alias(_))) {
20257                    Some(
20258                        f.args
20259                            .iter()
20260                            .filter_map(|a| {
20261                                if let Expression::Alias(alias) = a {
20262                                    Some(alias.alias.name.clone())
20263                                } else {
20264                                    None
20265                                }
20266                            })
20267                            .collect(),
20268                    )
20269                } else {
20270                    None
20271                }
20272            }
20273            _ => None,
20274        }
20275    }
20276
20277    /// Check if a struct expression has any unnamed fields
20278    fn struct_has_unnamed_fields(expr: &Expression) -> bool {
20279        match expr {
20280            Expression::Struct(s) => s.fields.iter().any(|(name, _)| name.is_none()),
20281            Expression::Function(f) if f.name.eq_ignore_ascii_case("STRUCT") => {
20282                f.args.iter().any(|a| !matches!(a, Expression::Alias(_)))
20283            }
20284            _ => false,
20285        }
20286    }
20287
20288    /// Get the field count of a struct expression
20289    fn struct_field_count(expr: &Expression) -> usize {
20290        match expr {
20291            Expression::Struct(s) => s.fields.len(),
20292            Expression::Function(f) if f.name.eq_ignore_ascii_case("STRUCT") => f.args.len(),
20293            _ => 0,
20294        }
20295    }
20296
20297    /// Apply field names to an unnamed struct expression, producing a new expression with names
20298    fn apply_struct_field_names(expr: &Expression, field_names: &[String]) -> Expression {
20299        match expr {
20300            Expression::Struct(s) => {
20301                let mut new_fields = Vec::with_capacity(s.fields.len());
20302                for (i, (name, value)) in s.fields.iter().enumerate() {
20303                    if name.is_none() && i < field_names.len() {
20304                        new_fields.push((Some(field_names[i].clone()), value.clone()));
20305                    } else {
20306                        new_fields.push((name.clone(), value.clone()));
20307                    }
20308                }
20309                Expression::Struct(Box::new(crate::expressions::Struct { fields: new_fields }))
20310            }
20311            Expression::Function(f) if f.name.eq_ignore_ascii_case("STRUCT") => {
20312                let mut new_args = Vec::with_capacity(f.args.len());
20313                for (i, arg) in f.args.iter().enumerate() {
20314                    if !matches!(arg, Expression::Alias(_)) && i < field_names.len() {
20315                        // Wrap the value in an Alias with the inherited name
20316                        new_args.push(Expression::Alias(Box::new(crate::expressions::Alias {
20317                            this: arg.clone(),
20318                            alias: crate::expressions::Identifier::new(field_names[i].clone()),
20319                            column_aliases: Vec::new(),
20320                            pre_alias_comments: Vec::new(),
20321                            trailing_comments: Vec::new(),
20322                            inferred_type: None,
20323                        })));
20324                    } else {
20325                        new_args.push(arg.clone());
20326                    }
20327                }
20328                Expression::Function(Box::new(crate::expressions::Function {
20329                    name: f.name.clone(),
20330                    args: new_args,
20331                    distinct: f.distinct,
20332                    trailing_comments: f.trailing_comments.clone(),
20333                    use_bracket_syntax: f.use_bracket_syntax,
20334                    no_parens: f.no_parens,
20335                    quoted: f.quoted,
20336                    span: None,
20337                    inferred_type: None,
20338                }))
20339            }
20340            _ => expr.clone(),
20341        }
20342    }
20343
20344    /// Propagate struct field names from the first struct in an array to subsequent unnamed structs.
20345    /// This implements BigQuery's implicit field name inheritance for struct arrays.
20346    /// Handles both Expression::Struct and Expression::Function named "STRUCT".
20347    fn inherit_struct_field_names(expressions: &[Expression]) -> Vec<Expression> {
20348        let first = match expressions.first() {
20349            Some(e) => e,
20350            None => return expressions.to_vec(),
20351        };
20352
20353        let field_names = match Self::extract_struct_field_names(first) {
20354            Some(names) if !names.is_empty() => names,
20355            _ => return expressions.to_vec(),
20356        };
20357
20358        let mut result = Vec::with_capacity(expressions.len());
20359        for (idx, expr) in expressions.iter().enumerate() {
20360            if idx == 0 {
20361                result.push(expr.clone());
20362                continue;
20363            }
20364            // Check if this is a struct with unnamed fields that needs name propagation
20365            if Self::struct_field_count(expr) == field_names.len()
20366                && Self::struct_has_unnamed_fields(expr)
20367            {
20368                result.push(Self::apply_struct_field_names(expr, &field_names));
20369            } else {
20370                result.push(expr.clone());
20371            }
20372        }
20373        result
20374    }
20375
20376    // Array function generators
20377
20378    fn generate_array_constructor(&mut self, f: &ArrayConstructor) -> Result<()> {
20379        // Apply struct name inheritance for target dialects that need it
20380        // (DuckDB, Spark, Databricks, Hive, Snowflake, Presto, Trino)
20381        let needs_inheritance = matches!(
20382            self.config.dialect,
20383            Some(DialectType::DuckDB)
20384                | Some(DialectType::Spark)
20385                | Some(DialectType::Databricks)
20386                | Some(DialectType::Hive)
20387                | Some(DialectType::Snowflake)
20388                | Some(DialectType::Presto)
20389                | Some(DialectType::Trino)
20390        );
20391        let propagated: Vec<Expression>;
20392        let expressions = if needs_inheritance && f.expressions.len() > 1 {
20393            propagated = Self::inherit_struct_field_names(&f.expressions);
20394            &propagated
20395        } else {
20396            &f.expressions
20397        };
20398
20399        // Check if elements should be split onto multiple lines (pretty + too wide)
20400        let should_split = if self.config.pretty && !expressions.is_empty() {
20401            let mut expr_strings: Vec<String> = Vec::with_capacity(expressions.len());
20402            for expr in expressions {
20403                let mut temp_gen = Generator::with_arc_config(self.config.clone());
20404                Arc::make_mut(&mut temp_gen.config).pretty = false;
20405                temp_gen.generate_expression(expr)?;
20406                expr_strings.push(temp_gen.output);
20407            }
20408            self.too_wide(&expr_strings)
20409        } else {
20410            false
20411        };
20412
20413        if f.bracket_notation {
20414            // For Spark/Databricks, use ARRAY(...) with parens
20415            // For Presto/Trino/PostgreSQL, use ARRAY[...] with keyword prefix
20416            // For others (DuckDB, Snowflake), use bare [...]
20417            let (open, close) = match self.config.dialect {
20418                None
20419                | Some(DialectType::Generic)
20420                | Some(DialectType::Spark)
20421                | Some(DialectType::Databricks)
20422                | Some(DialectType::Hive) => {
20423                    self.write_keyword("ARRAY");
20424                    ("(", ")")
20425                }
20426                Some(DialectType::Presto)
20427                | Some(DialectType::Trino)
20428                | Some(DialectType::PostgreSQL)
20429                | Some(DialectType::Redshift)
20430                | Some(DialectType::Materialize)
20431                | Some(DialectType::RisingWave)
20432                | Some(DialectType::CockroachDB) => {
20433                    self.write_keyword("ARRAY");
20434                    ("[", "]")
20435                }
20436                _ => ("[", "]"),
20437            };
20438            self.write(open);
20439            if should_split {
20440                self.write_newline();
20441                self.indent_level += 1;
20442                for (i, expr) in expressions.iter().enumerate() {
20443                    self.write_indent();
20444                    self.generate_expression(expr)?;
20445                    if i + 1 < expressions.len() {
20446                        self.write(",");
20447                    }
20448                    self.write_newline();
20449                }
20450                self.indent_level -= 1;
20451                self.write_indent();
20452            } else {
20453                for (i, expr) in expressions.iter().enumerate() {
20454                    if i > 0 {
20455                        self.write(", ");
20456                    }
20457                    self.generate_expression(expr)?;
20458                }
20459            }
20460            self.write(close);
20461        } else {
20462            // Use LIST keyword if that was the original syntax (DuckDB)
20463            if f.use_list_keyword {
20464                self.write_keyword("LIST");
20465            } else {
20466                self.write_keyword("ARRAY");
20467            }
20468            // For Spark/Hive, always use ARRAY(...) with parens
20469            // Also use parens for BigQuery when the array contains a subquery (ARRAY(SELECT ...))
20470            let has_subquery = expressions
20471                .iter()
20472                .any(|e| matches!(e, Expression::Select(_)));
20473            let (open, close) = if matches!(
20474                self.config.dialect,
20475                Some(DialectType::Spark) | Some(DialectType::Databricks) | Some(DialectType::Hive)
20476            ) || (matches!(self.config.dialect, Some(DialectType::BigQuery))
20477                && has_subquery)
20478            {
20479                ("(", ")")
20480            } else {
20481                ("[", "]")
20482            };
20483            self.write(open);
20484            if should_split {
20485                self.write_newline();
20486                self.indent_level += 1;
20487                for (i, expr) in expressions.iter().enumerate() {
20488                    self.write_indent();
20489                    self.generate_expression(expr)?;
20490                    if i + 1 < expressions.len() {
20491                        self.write(",");
20492                    }
20493                    self.write_newline();
20494                }
20495                self.indent_level -= 1;
20496                self.write_indent();
20497            } else {
20498                for (i, expr) in expressions.iter().enumerate() {
20499                    if i > 0 {
20500                        self.write(", ");
20501                    }
20502                    self.generate_expression(expr)?;
20503                }
20504            }
20505            self.write(close);
20506        }
20507        Ok(())
20508    }
20509
20510    fn generate_array_sort(&mut self, f: &ArraySortFunc) -> Result<()> {
20511        self.write_keyword("ARRAY_SORT");
20512        self.write("(");
20513        self.generate_expression(&f.this)?;
20514        if let Some(ref comp) = f.comparator {
20515            self.write(", ");
20516            self.generate_expression(comp)?;
20517        }
20518        self.write(")");
20519        Ok(())
20520    }
20521
20522    fn generate_array_join(&mut self, name: &str, f: &ArrayJoinFunc) -> Result<()> {
20523        self.write_keyword(name);
20524        self.write("(");
20525        self.generate_expression(&f.this)?;
20526        self.write(", ");
20527        self.generate_expression(&f.separator)?;
20528        if let Some(ref null_rep) = f.null_replacement {
20529            self.write(", ");
20530            self.generate_expression(null_rep)?;
20531        }
20532        self.write(")");
20533        Ok(())
20534    }
20535
20536    fn generate_unnest(&mut self, f: &UnnestFunc) -> Result<()> {
20537        self.write_keyword("UNNEST");
20538        self.write("(");
20539        self.generate_expression(&f.this)?;
20540        for extra in &f.expressions {
20541            self.write(", ");
20542            self.generate_expression(extra)?;
20543        }
20544        self.write(")");
20545        if f.with_ordinality {
20546            self.write_space();
20547            if self.config.unnest_with_ordinality {
20548                // Presto/Trino: UNNEST(arr) WITH ORDINALITY [AS alias]
20549                self.write_keyword("WITH ORDINALITY");
20550            } else if f.offset_alias.is_some() {
20551                // BigQuery: UNNEST(arr) [AS col] WITH OFFSET AS pos
20552                // Alias (if any) comes BEFORE WITH OFFSET
20553                if let Some(ref alias) = f.alias {
20554                    self.write_keyword("AS");
20555                    self.write_space();
20556                    self.generate_identifier(alias)?;
20557                    self.write_space();
20558                }
20559                self.write_keyword("WITH OFFSET");
20560                if let Some(ref offset_alias) = f.offset_alias {
20561                    self.write_space();
20562                    self.write_keyword("AS");
20563                    self.write_space();
20564                    self.generate_identifier(offset_alias)?;
20565                }
20566            } else {
20567                // WITH OFFSET (BigQuery identity) - add default "AS offset" if no explicit alias
20568                self.write_keyword("WITH OFFSET");
20569                if f.alias.is_none() {
20570                    self.write(" AS offset");
20571                }
20572            }
20573        }
20574        if let Some(ref alias) = f.alias {
20575            // Add alias for: non-WITH-OFFSET cases, Presto/Trino WITH ORDINALITY, or BigQuery WITH OFFSET + alias (no offset_alias)
20576            let should_add_alias = if !f.with_ordinality {
20577                true
20578            } else if self.config.unnest_with_ordinality {
20579                // Presto/Trino: alias comes after WITH ORDINALITY
20580                true
20581            } else if f.offset_alias.is_some() {
20582                // BigQuery expansion: alias already handled above
20583                false
20584            } else {
20585                // BigQuery WITH OFFSET + alias but no offset_alias: alias comes after
20586                true
20587            };
20588            if should_add_alias {
20589                self.write_space();
20590                self.write_keyword("AS");
20591                self.write_space();
20592                self.generate_identifier(alias)?;
20593            }
20594        }
20595        Ok(())
20596    }
20597
20598    fn generate_array_filter(&mut self, f: &ArrayFilterFunc) -> Result<()> {
20599        self.write_keyword("FILTER");
20600        self.write("(");
20601        self.generate_expression(&f.this)?;
20602        self.write(", ");
20603        self.generate_expression(&f.filter)?;
20604        self.write(")");
20605        Ok(())
20606    }
20607
20608    fn generate_array_transform(&mut self, f: &ArrayTransformFunc) -> Result<()> {
20609        self.write_keyword("TRANSFORM");
20610        self.write("(");
20611        self.generate_expression(&f.this)?;
20612        self.write(", ");
20613        self.generate_expression(&f.transform)?;
20614        self.write(")");
20615        Ok(())
20616    }
20617
20618    fn generate_sequence(&mut self, name: &str, f: &SequenceFunc) -> Result<()> {
20619        self.write_keyword(name);
20620        self.write("(");
20621        self.generate_expression(&f.start)?;
20622        self.write(", ");
20623        self.generate_expression(&f.stop)?;
20624        if let Some(ref step) = f.step {
20625            self.write(", ");
20626            self.generate_expression(step)?;
20627        }
20628        self.write(")");
20629        Ok(())
20630    }
20631
20632    // Struct function generators
20633
20634    fn generate_struct_constructor(&mut self, f: &StructConstructor) -> Result<()> {
20635        self.write_keyword("STRUCT");
20636        self.write("(");
20637        for (i, (name, expr)) in f.fields.iter().enumerate() {
20638            if i > 0 {
20639                self.write(", ");
20640            }
20641            if let Some(ref id) = name {
20642                self.generate_identifier(id)?;
20643                self.write(" ");
20644                self.write_keyword("AS");
20645                self.write(" ");
20646            }
20647            self.generate_expression(expr)?;
20648        }
20649        self.write(")");
20650        Ok(())
20651    }
20652
20653    /// Convert BigQuery STRUCT function (parsed as Function with Alias args) to target dialect
20654    fn generate_struct_function_cross_dialect(&mut self, func: &Function) -> Result<()> {
20655        // Extract named/unnamed fields from function args
20656        // Args are either Alias(this=value, alias=name) for named or plain expressions for unnamed
20657        let mut names: Vec<Option<String>> = Vec::new();
20658        let mut values: Vec<&Expression> = Vec::new();
20659        let mut all_named = true;
20660
20661        for arg in &func.args {
20662            match arg {
20663                Expression::Alias(a) => {
20664                    names.push(Some(a.alias.name.clone()));
20665                    values.push(&a.this);
20666                }
20667                _ => {
20668                    names.push(None);
20669                    values.push(arg);
20670                    all_named = false;
20671                }
20672            }
20673        }
20674
20675        if matches!(self.config.dialect, Some(DialectType::DuckDB)) {
20676            // DuckDB: {'name': value, ...} for named, {'_0': value, ...} for unnamed
20677            self.write("{");
20678            for (i, (name, value)) in names.iter().zip(values.iter()).enumerate() {
20679                if i > 0 {
20680                    self.write(", ");
20681                }
20682                if let Some(n) = name {
20683                    self.write("'");
20684                    self.write(n);
20685                    self.write("'");
20686                } else {
20687                    self.write("'_");
20688                    self.write(&i.to_string());
20689                    self.write("'");
20690                }
20691                self.write(": ");
20692                self.generate_expression(value)?;
20693            }
20694            self.write("}");
20695            return Ok(());
20696        }
20697
20698        if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
20699            // Snowflake: OBJECT_CONSTRUCT('name', value, ...)
20700            self.write_keyword("OBJECT_CONSTRUCT");
20701            self.write("(");
20702            for (i, (name, value)) in names.iter().zip(values.iter()).enumerate() {
20703                if i > 0 {
20704                    self.write(", ");
20705                }
20706                if let Some(n) = name {
20707                    self.write("'");
20708                    self.write(n);
20709                    self.write("'");
20710                } else {
20711                    self.write("'_");
20712                    self.write(&i.to_string());
20713                    self.write("'");
20714                }
20715                self.write(", ");
20716                self.generate_expression(value)?;
20717            }
20718            self.write(")");
20719            return Ok(());
20720        }
20721
20722        if matches!(
20723            self.config.dialect,
20724            Some(DialectType::Presto) | Some(DialectType::Trino)
20725        ) {
20726            if all_named && !names.is_empty() {
20727                // Presto/Trino: CAST(ROW(values...) AS ROW(name TYPE, ...))
20728                // Need to infer types from values
20729                self.write_keyword("CAST");
20730                self.write("(");
20731                self.write_keyword("ROW");
20732                self.write("(");
20733                for (i, value) in values.iter().enumerate() {
20734                    if i > 0 {
20735                        self.write(", ");
20736                    }
20737                    self.generate_expression(value)?;
20738                }
20739                self.write(")");
20740                self.write(" ");
20741                self.write_keyword("AS");
20742                self.write(" ");
20743                self.write_keyword("ROW");
20744                self.write("(");
20745                for (i, (name, value)) in names.iter().zip(values.iter()).enumerate() {
20746                    if i > 0 {
20747                        self.write(", ");
20748                    }
20749                    if let Some(n) = name {
20750                        self.write(n);
20751                    }
20752                    self.write(" ");
20753                    let type_str = Self::infer_sql_type_for_presto(value);
20754                    self.write_keyword(&type_str);
20755                }
20756                self.write(")");
20757                self.write(")");
20758            } else {
20759                // Unnamed: ROW(values...)
20760                self.write_keyword("ROW");
20761                self.write("(");
20762                for (i, value) in values.iter().enumerate() {
20763                    if i > 0 {
20764                        self.write(", ");
20765                    }
20766                    self.generate_expression(value)?;
20767                }
20768                self.write(")");
20769            }
20770            return Ok(());
20771        }
20772
20773        // Default: ROW(values...) for other dialects
20774        self.write_keyword("ROW");
20775        self.write("(");
20776        for (i, value) in values.iter().enumerate() {
20777            if i > 0 {
20778                self.write(", ");
20779            }
20780            self.generate_expression(value)?;
20781        }
20782        self.write(")");
20783        Ok(())
20784    }
20785
20786    /// Infer SQL type name for a Presto/Trino ROW CAST from a literal expression
20787    fn infer_sql_type_for_presto(expr: &Expression) -> String {
20788        match expr {
20789            Expression::Literal(lit)
20790                if matches!(lit.as_ref(), crate::expressions::Literal::String(_)) =>
20791            {
20792                "VARCHAR".to_string()
20793            }
20794            Expression::Literal(lit)
20795                if matches!(lit.as_ref(), crate::expressions::Literal::Number(_)) =>
20796            {
20797                let crate::expressions::Literal::Number(n) = lit.as_ref() else {
20798                    unreachable!()
20799                };
20800                if n.contains('.') {
20801                    "DOUBLE".to_string()
20802                } else {
20803                    "INTEGER".to_string()
20804                }
20805            }
20806            Expression::Boolean(_) => "BOOLEAN".to_string(),
20807            Expression::Literal(lit)
20808                if matches!(lit.as_ref(), crate::expressions::Literal::Date(_)) =>
20809            {
20810                "DATE".to_string()
20811            }
20812            Expression::Literal(lit)
20813                if matches!(lit.as_ref(), crate::expressions::Literal::Timestamp(_)) =>
20814            {
20815                "TIMESTAMP".to_string()
20816            }
20817            Expression::Literal(lit)
20818                if matches!(lit.as_ref(), crate::expressions::Literal::Datetime(_)) =>
20819            {
20820                "TIMESTAMP".to_string()
20821            }
20822            Expression::Array(_) | Expression::ArrayFunc(_) => {
20823                // Try to infer element type from first element
20824                "ARRAY(VARCHAR)".to_string()
20825            }
20826            // For nested structs - generate a nested ROW type by inspecting fields
20827            Expression::Struct(_) | Expression::StructFunc(_) => "ROW".to_string(),
20828            Expression::Function(f) => {
20829                if f.name.eq_ignore_ascii_case("STRUCT") {
20830                    "ROW".to_string()
20831                } else if f.name.eq_ignore_ascii_case("CURRENT_DATE") {
20832                    "DATE".to_string()
20833                } else if f.name.eq_ignore_ascii_case("CURRENT_TIMESTAMP")
20834                    || f.name.eq_ignore_ascii_case("NOW")
20835                {
20836                    "TIMESTAMP".to_string()
20837                } else {
20838                    "VARCHAR".to_string()
20839                }
20840            }
20841            _ => "VARCHAR".to_string(),
20842        }
20843    }
20844
20845    fn generate_struct_extract(&mut self, f: &StructExtractFunc) -> Result<()> {
20846        // DuckDB uses STRUCT_EXTRACT function syntax
20847        if matches!(self.config.dialect, Some(DialectType::DuckDB)) {
20848            self.write_keyword("STRUCT_EXTRACT");
20849            self.write("(");
20850            self.generate_expression(&f.this)?;
20851            self.write(", ");
20852            // Output field name as string literal
20853            self.write("'");
20854            self.write(&f.field.name);
20855            self.write("'");
20856            self.write(")");
20857            return Ok(());
20858        }
20859        self.generate_expression(&f.this)?;
20860        self.write(".");
20861        self.generate_identifier(&f.field)
20862    }
20863
20864    fn generate_named_struct(&mut self, f: &NamedStructFunc) -> Result<()> {
20865        self.write_keyword("NAMED_STRUCT");
20866        self.write("(");
20867        for (i, (name, value)) in f.pairs.iter().enumerate() {
20868            if i > 0 {
20869                self.write(", ");
20870            }
20871            self.generate_expression(name)?;
20872            self.write(", ");
20873            self.generate_expression(value)?;
20874        }
20875        self.write(")");
20876        Ok(())
20877    }
20878
20879    // Map function generators
20880
20881    fn generate_map_constructor(&mut self, f: &MapConstructor) -> Result<()> {
20882        if f.curly_brace_syntax {
20883            // Curly brace syntax: MAP {'a': 1, 'b': 2} or just {'a': 1, 'b': 2}
20884            if f.with_map_keyword {
20885                self.write_keyword("MAP");
20886                self.write(" ");
20887            }
20888            self.write("{");
20889            for (i, (key, val)) in f.keys.iter().zip(f.values.iter()).enumerate() {
20890                if i > 0 {
20891                    self.write(", ");
20892                }
20893                self.generate_expression(key)?;
20894                self.write(": ");
20895                self.generate_expression(val)?;
20896            }
20897            self.write("}");
20898        } else {
20899            // MAP function syntax: MAP(ARRAY[keys], ARRAY[values])
20900            self.write_keyword("MAP");
20901            self.write("(");
20902            self.write_keyword("ARRAY");
20903            self.write("[");
20904            for (i, key) in f.keys.iter().enumerate() {
20905                if i > 0 {
20906                    self.write(", ");
20907                }
20908                self.generate_expression(key)?;
20909            }
20910            self.write("], ");
20911            self.write_keyword("ARRAY");
20912            self.write("[");
20913            for (i, val) in f.values.iter().enumerate() {
20914                if i > 0 {
20915                    self.write(", ");
20916                }
20917                self.generate_expression(val)?;
20918            }
20919            self.write("])");
20920        }
20921        Ok(())
20922    }
20923
20924    fn generate_transform_func(&mut self, name: &str, f: &TransformFunc) -> Result<()> {
20925        self.write_keyword(name);
20926        self.write("(");
20927        self.generate_expression(&f.this)?;
20928        self.write(", ");
20929        self.generate_expression(&f.transform)?;
20930        self.write(")");
20931        Ok(())
20932    }
20933
20934    // JSON function generators
20935
20936    fn generate_json_extract(&mut self, name: &str, f: &JsonExtractFunc) -> Result<()> {
20937        use crate::dialects::DialectType;
20938
20939        // Check if we should use arrow syntax (-> or ->>)
20940        let use_arrow = f.arrow_syntax && self.dialect_supports_json_arrow();
20941
20942        if use_arrow {
20943            // Output arrow syntax: expr -> path or expr ->> path
20944            self.generate_expression(&f.this)?;
20945            if name == "JSON_EXTRACT_SCALAR" || name == "JSON_EXTRACT_PATH_TEXT" {
20946                self.write(" ->> ");
20947            } else {
20948                self.write(" -> ");
20949            }
20950            self.generate_expression(&f.path)?;
20951            return Ok(());
20952        }
20953
20954        // PostgreSQL uses #>> operator for JSONB path text extraction (only when hash_arrow_syntax is true)
20955        if f.hash_arrow_syntax
20956            && matches!(
20957                self.config.dialect,
20958                Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
20959            )
20960        {
20961            self.generate_expression(&f.this)?;
20962            self.write(" #>> ");
20963            self.generate_expression(&f.path)?;
20964            return Ok(());
20965        }
20966
20967        // For PostgreSQL/Redshift, use JSON_EXTRACT_PATH / JSON_EXTRACT_PATH_TEXT for extraction without arrow syntax
20968        // Redshift maps everything to JSON_EXTRACT_PATH_TEXT since it doesn't have JSON_EXTRACT_PATH
20969        let func_name = if matches!(self.config.dialect, Some(DialectType::Redshift)) {
20970            match name {
20971                "JSON_EXTRACT_SCALAR"
20972                | "JSON_EXTRACT_PATH_TEXT"
20973                | "JSON_EXTRACT"
20974                | "JSON_EXTRACT_PATH" => "JSON_EXTRACT_PATH_TEXT",
20975                _ => name,
20976            }
20977        } else if matches!(self.config.dialect, Some(DialectType::PostgreSQL)) {
20978            match name {
20979                "JSON_EXTRACT_SCALAR" | "JSON_EXTRACT_PATH_TEXT" => "JSON_EXTRACT_PATH_TEXT",
20980                "JSON_EXTRACT" | "JSON_EXTRACT_PATH" => "JSON_EXTRACT_PATH",
20981                _ => name,
20982            }
20983        } else {
20984            name
20985        };
20986
20987        self.write_keyword(func_name);
20988        self.write("(");
20989        // For Redshift, strip CAST(... AS JSON) wrapper from the expression
20990        if matches!(self.config.dialect, Some(DialectType::Redshift)) {
20991            if let Expression::Cast(ref cast) = f.this {
20992                if matches!(cast.to, crate::expressions::DataType::Json) {
20993                    self.generate_expression(&cast.this)?;
20994                } else {
20995                    self.generate_expression(&f.this)?;
20996                }
20997            } else {
20998                self.generate_expression(&f.this)?;
20999            }
21000        } else {
21001            self.generate_expression(&f.this)?;
21002        }
21003        // For PostgreSQL/Redshift JSON_EXTRACT_PATH/JSON_EXTRACT_PATH_TEXT,
21004        // decompose JSON path into separate string arguments
21005        if matches!(
21006            self.config.dialect,
21007            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
21008        ) && (func_name == "JSON_EXTRACT_PATH" || func_name == "JSON_EXTRACT_PATH_TEXT")
21009        {
21010            if let Expression::Literal(ref lit) = f.path {
21011                if let Literal::String(ref s) = lit.as_ref() {
21012                    let parts = Self::decompose_json_path(s);
21013                    for part in &parts {
21014                        self.write(", '");
21015                        self.write(part);
21016                        self.write("'");
21017                    }
21018                }
21019            } else {
21020                self.write(", ");
21021                self.generate_expression(&f.path)?;
21022            }
21023        } else {
21024            self.write(", ");
21025            self.generate_expression(&f.path)?;
21026        }
21027
21028        // Output JSON_QUERY/JSON_VALUE options (Trino/Presto style)
21029        // These go BEFORE the closing parenthesis
21030        if let Some(ref wrapper) = f.wrapper_option {
21031            self.write_space();
21032            self.write_keyword(wrapper);
21033        }
21034        if let Some(ref quotes) = f.quotes_option {
21035            self.write_space();
21036            self.write_keyword(quotes);
21037            if f.on_scalar_string {
21038                self.write_space();
21039                self.write_keyword("ON SCALAR STRING");
21040            }
21041        }
21042        if let Some(ref on_err) = f.on_error {
21043            self.write_space();
21044            self.write_keyword(on_err);
21045        }
21046        if let Some(ref ret_type) = f.returning {
21047            self.write_space();
21048            self.write_keyword("RETURNING");
21049            self.write_space();
21050            self.generate_data_type(ret_type)?;
21051        }
21052
21053        self.write(")");
21054        Ok(())
21055    }
21056
21057    /// Check if the current dialect supports JSON arrow operators (-> and ->>)
21058    fn dialect_supports_json_arrow(&self) -> bool {
21059        use crate::dialects::DialectType;
21060        match self.config.dialect {
21061            // PostgreSQL, MySQL, DuckDB support -> and ->> operators
21062            Some(DialectType::PostgreSQL) => true,
21063            Some(DialectType::MySQL) => true,
21064            Some(DialectType::DuckDB) => true,
21065            Some(DialectType::CockroachDB) => true,
21066            Some(DialectType::StarRocks) => true,
21067            Some(DialectType::SQLite) => true,
21068            // Other dialects use function syntax
21069            _ => false,
21070        }
21071    }
21072
21073    fn generate_json_path(&mut self, name: &str, f: &JsonPathFunc) -> Result<()> {
21074        use crate::dialects::DialectType;
21075
21076        // PostgreSQL uses #> operator for JSONB path extraction
21077        if matches!(
21078            self.config.dialect,
21079            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
21080        ) && name == "JSON_EXTRACT_PATH"
21081        {
21082            self.generate_expression(&f.this)?;
21083            self.write(" #> ");
21084            if f.paths.len() == 1 {
21085                self.generate_expression(&f.paths[0])?;
21086            } else {
21087                // Multiple paths: ARRAY[path1, path2, ...]
21088                self.write_keyword("ARRAY");
21089                self.write("[");
21090                for (i, path) in f.paths.iter().enumerate() {
21091                    if i > 0 {
21092                        self.write(", ");
21093                    }
21094                    self.generate_expression(path)?;
21095                }
21096                self.write("]");
21097            }
21098            return Ok(());
21099        }
21100
21101        self.write_keyword(name);
21102        self.write("(");
21103        self.generate_expression(&f.this)?;
21104        for path in &f.paths {
21105            self.write(", ");
21106            self.generate_expression(path)?;
21107        }
21108        self.write(")");
21109        Ok(())
21110    }
21111
21112    fn generate_json_object(&mut self, f: &JsonObjectFunc) -> Result<()> {
21113        use crate::dialects::DialectType;
21114
21115        self.write_keyword("JSON_OBJECT");
21116        self.write("(");
21117        if f.star {
21118            self.write("*");
21119        } else {
21120            // BigQuery, MySQL, and SQLite use comma syntax: JSON_OBJECT('key', value)
21121            // Standard SQL uses colon syntax: JSON_OBJECT('key': value)
21122            // Also respect the json_key_value_pair_sep config
21123            let use_comma_syntax = self.config.json_key_value_pair_sep == ","
21124                || matches!(
21125                    self.config.dialect,
21126                    Some(DialectType::BigQuery)
21127                        | Some(DialectType::MySQL)
21128                        | Some(DialectType::SQLite)
21129                );
21130
21131            for (i, (key, value)) in f.pairs.iter().enumerate() {
21132                if i > 0 {
21133                    self.write(", ");
21134                }
21135                self.generate_expression(key)?;
21136                if use_comma_syntax {
21137                    self.write(", ");
21138                } else {
21139                    self.write(": ");
21140                }
21141                self.generate_expression(value)?;
21142            }
21143        }
21144        if let Some(null_handling) = f.null_handling {
21145            self.write_space();
21146            match null_handling {
21147                JsonNullHandling::NullOnNull => self.write_keyword("NULL ON NULL"),
21148                JsonNullHandling::AbsentOnNull => self.write_keyword("ABSENT ON NULL"),
21149            }
21150        }
21151        if f.with_unique_keys {
21152            self.write_space();
21153            self.write_keyword("WITH UNIQUE KEYS");
21154        }
21155        if let Some(ref ret_type) = f.returning_type {
21156            self.write_space();
21157            self.write_keyword("RETURNING");
21158            self.write_space();
21159            self.generate_data_type(ret_type)?;
21160            if f.format_json {
21161                self.write_space();
21162                self.write_keyword("FORMAT JSON");
21163            }
21164            if let Some(ref enc) = f.encoding {
21165                self.write_space();
21166                self.write_keyword("ENCODING");
21167                self.write_space();
21168                self.write(enc);
21169            }
21170        }
21171        self.write(")");
21172        Ok(())
21173    }
21174
21175    fn generate_json_modify(&mut self, name: &str, f: &JsonModifyFunc) -> Result<()> {
21176        self.write_keyword(name);
21177        self.write("(");
21178        self.generate_expression(&f.this)?;
21179        for (path, value) in &f.path_values {
21180            self.write(", ");
21181            self.generate_expression(path)?;
21182            self.write(", ");
21183            self.generate_expression(value)?;
21184        }
21185        self.write(")");
21186        Ok(())
21187    }
21188
21189    fn generate_json_array_agg(&mut self, f: &JsonArrayAggFunc) -> Result<()> {
21190        self.write_keyword("JSON_ARRAYAGG");
21191        self.write("(");
21192        self.generate_expression(&f.this)?;
21193        if let Some(ref order_by) = f.order_by {
21194            self.write_space();
21195            self.write_keyword("ORDER BY");
21196            self.write_space();
21197            for (i, ord) in order_by.iter().enumerate() {
21198                if i > 0 {
21199                    self.write(", ");
21200                }
21201                self.generate_ordered(ord)?;
21202            }
21203        }
21204        if let Some(null_handling) = f.null_handling {
21205            self.write_space();
21206            match null_handling {
21207                JsonNullHandling::NullOnNull => self.write_keyword("NULL ON NULL"),
21208                JsonNullHandling::AbsentOnNull => self.write_keyword("ABSENT ON NULL"),
21209            }
21210        }
21211        self.write(")");
21212        if let Some(ref filter) = f.filter {
21213            self.write_space();
21214            self.write_keyword("FILTER");
21215            self.write("(");
21216            self.write_keyword("WHERE");
21217            self.write_space();
21218            self.generate_expression(filter)?;
21219            self.write(")");
21220        }
21221        Ok(())
21222    }
21223
21224    fn generate_json_object_agg(&mut self, f: &JsonObjectAggFunc) -> Result<()> {
21225        self.write_keyword("JSON_OBJECTAGG");
21226        self.write("(");
21227        self.generate_expression(&f.key)?;
21228        self.write(": ");
21229        self.generate_expression(&f.value)?;
21230        if let Some(null_handling) = f.null_handling {
21231            self.write_space();
21232            match null_handling {
21233                JsonNullHandling::NullOnNull => self.write_keyword("NULL ON NULL"),
21234                JsonNullHandling::AbsentOnNull => self.write_keyword("ABSENT ON NULL"),
21235            }
21236        }
21237        self.write(")");
21238        if let Some(ref filter) = f.filter {
21239            self.write_space();
21240            self.write_keyword("FILTER");
21241            self.write("(");
21242            self.write_keyword("WHERE");
21243            self.write_space();
21244            self.generate_expression(filter)?;
21245            self.write(")");
21246        }
21247        Ok(())
21248    }
21249
21250    // Type casting/conversion generators
21251
21252    fn generate_convert(&mut self, f: &ConvertFunc) -> Result<()> {
21253        use crate::dialects::DialectType;
21254
21255        // Redshift: CONVERT(type, expr) -> CAST(expr AS type)
21256        if self.config.dialect == Some(DialectType::Redshift) {
21257            self.write_keyword("CAST");
21258            self.write("(");
21259            self.generate_expression(&f.this)?;
21260            self.write_space();
21261            self.write_keyword("AS");
21262            self.write_space();
21263            self.generate_data_type(&f.to)?;
21264            self.write(")");
21265            return Ok(());
21266        }
21267
21268        self.write_keyword("CONVERT");
21269        self.write("(");
21270        self.generate_data_type(&f.to)?;
21271        self.write(", ");
21272        self.generate_expression(&f.this)?;
21273        if let Some(ref style) = f.style {
21274            self.write(", ");
21275            self.generate_expression(style)?;
21276        }
21277        self.write(")");
21278        Ok(())
21279    }
21280
21281    // Additional expression generators
21282
21283    fn generate_lambda(&mut self, f: &LambdaExpr) -> Result<()> {
21284        if f.colon {
21285            // DuckDB syntax: LAMBDA x : expr
21286            self.write_keyword("LAMBDA");
21287            self.write_space();
21288            for (i, param) in f.parameters.iter().enumerate() {
21289                if i > 0 {
21290                    self.write(", ");
21291                }
21292                self.generate_identifier(param)?;
21293            }
21294            self.write(" : ");
21295        } else {
21296            // Standard syntax: x -> expr or (x, y) -> expr
21297            if f.parameters.len() == 1 {
21298                self.generate_identifier(&f.parameters[0])?;
21299            } else {
21300                self.write("(");
21301                for (i, param) in f.parameters.iter().enumerate() {
21302                    if i > 0 {
21303                        self.write(", ");
21304                    }
21305                    self.generate_identifier(param)?;
21306                }
21307                self.write(")");
21308            }
21309            self.write(" -> ");
21310        }
21311        self.generate_expression(&f.body)
21312    }
21313
21314    fn generate_named_argument(&mut self, f: &NamedArgument) -> Result<()> {
21315        self.generate_identifier(&f.name)?;
21316        match f.separator {
21317            NamedArgSeparator::DArrow => self.write(" => "),
21318            NamedArgSeparator::ColonEq => self.write(" := "),
21319            NamedArgSeparator::Eq => self.write(" = "),
21320        }
21321        self.generate_expression(&f.value)
21322    }
21323
21324    fn generate_table_argument(&mut self, f: &TableArgument) -> Result<()> {
21325        self.write_keyword(&f.prefix);
21326        self.write(" ");
21327        self.generate_expression(&f.this)
21328    }
21329
21330    fn generate_parameter(&mut self, f: &Parameter) -> Result<()> {
21331        match f.style {
21332            ParameterStyle::Question => self.write("?"),
21333            ParameterStyle::Dollar => {
21334                self.write("$");
21335                if let Some(idx) = f.index {
21336                    self.write(&idx.to_string());
21337                } else if let Some(ref name) = f.name {
21338                    // Session variable like $x or $query_id
21339                    self.write(name);
21340                }
21341            }
21342            ParameterStyle::DollarBrace => {
21343                // Template variable like ${x} or ${hiveconf:name} (Databricks, Hive)
21344                self.write("${");
21345                if let Some(ref name) = f.name {
21346                    self.write(name);
21347                }
21348                if let Some(ref expr) = f.expression {
21349                    self.write(":");
21350                    self.write(expr);
21351                }
21352                self.write("}");
21353            }
21354            ParameterStyle::Colon => {
21355                self.write(":");
21356                if let Some(idx) = f.index {
21357                    self.write(&idx.to_string());
21358                } else if let Some(ref name) = f.name {
21359                    self.write(name);
21360                }
21361            }
21362            ParameterStyle::At => {
21363                self.write("@");
21364                if let Some(ref name) = f.name {
21365                    if f.string_quoted {
21366                        self.write("'");
21367                        self.write(name);
21368                        self.write("'");
21369                    } else if f.quoted {
21370                        self.write("\"");
21371                        self.write(name);
21372                        self.write("\"");
21373                    } else {
21374                        self.write(name);
21375                    }
21376                }
21377            }
21378            ParameterStyle::DoubleAt => {
21379                self.write("@@");
21380                if let Some(ref name) = f.name {
21381                    self.write(name);
21382                }
21383            }
21384            ParameterStyle::DoubleDollar => {
21385                self.write("$$");
21386                if let Some(ref name) = f.name {
21387                    self.write(name);
21388                }
21389            }
21390            ParameterStyle::Percent => {
21391                if let Some(ref name) = f.name {
21392                    // %(name)s format
21393                    self.write("%(");
21394                    self.write(name);
21395                    self.write(")s");
21396                } else {
21397                    // %s format
21398                    self.write("%s");
21399                }
21400            }
21401            ParameterStyle::Brace => {
21402                // Spark/Databricks widget template variable: {name}
21403                // ClickHouse query parameter may include kind: {name: Type}
21404                self.write("{");
21405                if let Some(ref name) = f.name {
21406                    self.write(name);
21407                }
21408                if let Some(ref expr) = f.expression {
21409                    self.write(": ");
21410                    self.write(expr);
21411                }
21412                self.write("}");
21413            }
21414        }
21415        Ok(())
21416    }
21417
21418    fn generate_placeholder(&mut self, f: &Placeholder) -> Result<()> {
21419        self.write("?");
21420        if let Some(idx) = f.index {
21421            self.write(&idx.to_string());
21422        }
21423        Ok(())
21424    }
21425
21426    fn generate_sql_comment(&mut self, f: &SqlComment) -> Result<()> {
21427        if f.is_block {
21428            self.write("/*");
21429            self.write(&f.text);
21430            self.write("*/");
21431        } else {
21432            self.write("--");
21433            self.write(&f.text);
21434        }
21435        Ok(())
21436    }
21437
21438    // Additional predicate generators
21439
21440    fn generate_similar_to(&mut self, f: &SimilarToExpr) -> Result<()> {
21441        self.generate_expression(&f.this)?;
21442        if f.not {
21443            self.write_space();
21444            self.write_keyword("NOT");
21445        }
21446        self.write_space();
21447        self.write_keyword("SIMILAR TO");
21448        self.write_space();
21449        self.generate_expression(&f.pattern)?;
21450        if let Some(ref escape) = f.escape {
21451            self.write_space();
21452            self.write_keyword("ESCAPE");
21453            self.write_space();
21454            self.generate_expression(escape)?;
21455        }
21456        Ok(())
21457    }
21458
21459    fn generate_quantified(&mut self, name: &str, f: &QuantifiedExpr) -> Result<()> {
21460        self.generate_expression(&f.this)?;
21461        self.write_space();
21462        // Output comparison operator if present
21463        if let Some(op) = &f.op {
21464            match op {
21465                QuantifiedOp::Eq => self.write("="),
21466                QuantifiedOp::Neq => self.write("<>"),
21467                QuantifiedOp::Lt => self.write("<"),
21468                QuantifiedOp::Lte => self.write("<="),
21469                QuantifiedOp::Gt => self.write(">"),
21470                QuantifiedOp::Gte => self.write(">="),
21471            }
21472            self.write_space();
21473        }
21474        self.write_keyword(name);
21475
21476        // If the child is a Subquery, it provides its own parens — output with space
21477        if matches!(&f.subquery, Expression::Subquery(_)) {
21478            self.write_space();
21479            self.generate_expression(&f.subquery)?;
21480        } else {
21481            self.write("(");
21482
21483            let is_statement = matches!(
21484                &f.subquery,
21485                Expression::Select(_)
21486                    | Expression::Union(_)
21487                    | Expression::Intersect(_)
21488                    | Expression::Except(_)
21489            );
21490
21491            if self.config.pretty && is_statement {
21492                self.write_newline();
21493                self.indent_level += 1;
21494                self.write_indent();
21495            }
21496            self.generate_expression(&f.subquery)?;
21497            if self.config.pretty && is_statement {
21498                self.write_newline();
21499                self.indent_level -= 1;
21500                self.write_indent();
21501            }
21502            self.write(")");
21503        }
21504        Ok(())
21505    }
21506
21507    fn generate_overlaps(&mut self, f: &OverlapsExpr) -> Result<()> {
21508        // Check if this is a simple binary form (this OVERLAPS expression)
21509        if let (Some(this), Some(expr)) = (&f.this, &f.expression) {
21510            self.generate_expression(this)?;
21511            self.write_space();
21512            self.write_keyword("OVERLAPS");
21513            self.write_space();
21514            self.generate_expression(expr)?;
21515        } else if let (Some(ls), Some(le), Some(rs), Some(re)) =
21516            (&f.left_start, &f.left_end, &f.right_start, &f.right_end)
21517        {
21518            // Full ANSI form: (a, b) OVERLAPS (c, d)
21519            self.write("(");
21520            self.generate_expression(ls)?;
21521            self.write(", ");
21522            self.generate_expression(le)?;
21523            self.write(")");
21524            self.write_space();
21525            self.write_keyword("OVERLAPS");
21526            self.write_space();
21527            self.write("(");
21528            self.generate_expression(rs)?;
21529            self.write(", ");
21530            self.generate_expression(re)?;
21531            self.write(")");
21532        }
21533        Ok(())
21534    }
21535
21536    // Type conversion generators
21537
21538    fn generate_try_cast(&mut self, cast: &Cast) -> Result<()> {
21539        use crate::dialects::DialectType;
21540
21541        // SingleStore uses !:> syntax for try cast
21542        if matches!(self.config.dialect, Some(DialectType::SingleStore)) {
21543            self.generate_expression(&cast.this)?;
21544            self.write(" !:> ");
21545            self.generate_data_type(&cast.to)?;
21546            return Ok(());
21547        }
21548
21549        // Teradata uses TRYCAST (no underscore)
21550        if matches!(self.config.dialect, Some(DialectType::Teradata)) {
21551            self.write_keyword("TRYCAST");
21552            self.write("(");
21553            self.generate_expression(&cast.this)?;
21554            self.write_space();
21555            self.write_keyword("AS");
21556            self.write_space();
21557            self.generate_data_type(&cast.to)?;
21558            self.write(")");
21559            return Ok(());
21560        }
21561
21562        // Dialects without TRY_CAST: generate as regular CAST
21563        let keyword = if matches!(
21564            self.config.dialect,
21565            Some(DialectType::Hive)
21566                | Some(DialectType::MySQL)
21567                | Some(DialectType::SQLite)
21568                | Some(DialectType::Oracle)
21569                | Some(DialectType::ClickHouse)
21570                | Some(DialectType::Redshift)
21571                | Some(DialectType::PostgreSQL)
21572                | Some(DialectType::StarRocks)
21573                | Some(DialectType::Doris)
21574        ) {
21575            "CAST"
21576        } else {
21577            "TRY_CAST"
21578        };
21579
21580        self.write_keyword(keyword);
21581        self.write("(");
21582        self.generate_expression(&cast.this)?;
21583        self.write_space();
21584        self.write_keyword("AS");
21585        self.write_space();
21586        self.generate_data_type(&cast.to)?;
21587
21588        // Output FORMAT clause if present
21589        if let Some(format) = &cast.format {
21590            self.write_space();
21591            self.write_keyword("FORMAT");
21592            self.write_space();
21593            self.generate_expression(format)?;
21594        }
21595
21596        self.write(")");
21597        Ok(())
21598    }
21599
21600    fn generate_safe_cast(&mut self, cast: &Cast) -> Result<()> {
21601        self.write_keyword("SAFE_CAST");
21602        self.write("(");
21603        self.generate_expression(&cast.this)?;
21604        self.write_space();
21605        self.write_keyword("AS");
21606        self.write_space();
21607        self.generate_data_type(&cast.to)?;
21608
21609        // Output FORMAT clause if present
21610        if let Some(format) = &cast.format {
21611            self.write_space();
21612            self.write_keyword("FORMAT");
21613            self.write_space();
21614            self.generate_expression(format)?;
21615        }
21616
21617        self.write(")");
21618        Ok(())
21619    }
21620
21621    // Array/struct/map access generators
21622
21623    fn generate_subscript(&mut self, s: &Subscript) -> Result<()> {
21624        // Wrap the base expression in parentheses when it uses arrow syntax (->)
21625        // which has lower precedence than bracket subscript ([]).
21626        // E.g., (t.v -> '$.a')[s.x] instead of t.v -> '$.a'[s.x]
21627        let needs_parens = matches!(&s.this, Expression::JsonExtract(ref f) if f.arrow_syntax);
21628        if needs_parens {
21629            self.write("(");
21630        }
21631        self.generate_expression(&s.this)?;
21632        if needs_parens {
21633            self.write(")");
21634        }
21635        self.write("[");
21636        self.generate_expression(&s.index)?;
21637        self.write("]");
21638        Ok(())
21639    }
21640
21641    fn generate_dot_access(&mut self, d: &DotAccess) -> Result<()> {
21642        self.generate_expression(&d.this)?;
21643        // Snowflake uses : (colon) for first-level struct/object field access on CAST/column expressions
21644        // e.g., CAST(col AS OBJECT(fld1 OBJECT(fld2 INT))):fld1.fld2
21645        let use_colon = matches!(self.config.dialect, Some(DialectType::Snowflake))
21646            && matches!(
21647                &d.this,
21648                Expression::Cast(_) | Expression::SafeCast(_) | Expression::TryCast(_)
21649            );
21650        if use_colon {
21651            self.write(":");
21652        } else {
21653            self.write(".");
21654        }
21655        self.generate_identifier(&d.field)
21656    }
21657
21658    fn generate_method_call(&mut self, m: &MethodCall) -> Result<()> {
21659        self.generate_expression(&m.this)?;
21660        self.write(".");
21661        // Method names after a dot should not be quoted based on reserved keywords
21662        // Only quote if explicitly marked as quoted in the AST
21663        if m.method.quoted {
21664            let q = self.config.identifier_quote;
21665            self.write(&format!("{}{}{}", q, m.method.name, q));
21666        } else {
21667            self.write(&m.method.name);
21668        }
21669        self.write("(");
21670        for (i, arg) in m.args.iter().enumerate() {
21671            if i > 0 {
21672                self.write(", ");
21673            }
21674            self.generate_expression(arg)?;
21675        }
21676        self.write(")");
21677        Ok(())
21678    }
21679
21680    fn generate_array_slice(&mut self, s: &ArraySlice) -> Result<()> {
21681        // Check if we need to wrap the inner expression in parentheses
21682        // JSON arrow expressions have lower precedence than array subscript
21683        let needs_parens = matches!(
21684            &s.this,
21685            Expression::JsonExtract(f) if f.arrow_syntax
21686        ) || matches!(
21687            &s.this,
21688            Expression::JsonExtractScalar(f) if f.arrow_syntax
21689        );
21690
21691        if needs_parens {
21692            self.write("(");
21693        }
21694        self.generate_expression(&s.this)?;
21695        if needs_parens {
21696            self.write(")");
21697        }
21698        self.write("[");
21699        if let Some(start) = &s.start {
21700            self.generate_expression(start)?;
21701        }
21702        self.write(":");
21703        if let Some(end) = &s.end {
21704            self.generate_expression(end)?;
21705        }
21706        self.write("]");
21707        Ok(())
21708    }
21709
21710    fn generate_binary_op(&mut self, op: &BinaryOp, operator: &str) -> Result<()> {
21711        // Generate left expression, but skip trailing comments if they're already in left_comments
21712        // to avoid duplication (comments are captured as both expr.trailing_comments
21713        // and BinaryOp.left_comments during parsing)
21714        match &op.left {
21715            Expression::Column(col) => {
21716                // Generate column with trailing comments but skip them if they're
21717                // already captured in BinaryOp.left_comments to avoid duplication
21718                if let Some(table) = &col.table {
21719                    self.generate_identifier(table)?;
21720                    self.write(".");
21721                }
21722                self.generate_identifier(&col.name)?;
21723                // Oracle-style join marker (+)
21724                if col.join_mark && self.config.supports_column_join_marks {
21725                    self.write(" (+)");
21726                }
21727                // Output column trailing comments if they're not already in left_comments
21728                if op.left_comments.is_empty() {
21729                    for comment in &col.trailing_comments {
21730                        self.write_space();
21731                        self.write_formatted_comment(comment);
21732                    }
21733                }
21734            }
21735            Expression::Add(inner_op)
21736            | Expression::Sub(inner_op)
21737            | Expression::Mul(inner_op)
21738            | Expression::Div(inner_op)
21739            | Expression::Concat(inner_op) => {
21740                // Generate binary op without its trailing comments
21741                self.generate_binary_op_no_trailing(inner_op, match &op.left {
21742                    Expression::Add(_) => "+",
21743                    Expression::Sub(_) => "-",
21744                    Expression::Mul(_) => "*",
21745                    Expression::Div(_) => "/",
21746                    Expression::Concat(_) => "||",
21747                    _ => unreachable!("op.left variant already matched by outer arm as Add/Sub/Mul/Div/Concat"),
21748                })?;
21749            }
21750            _ => {
21751                self.generate_expression(&op.left)?;
21752            }
21753        }
21754        // Output comments after left operand
21755        for comment in &op.left_comments {
21756            self.write_space();
21757            self.write_formatted_comment(comment);
21758        }
21759        if self.config.pretty
21760            && matches!(self.config.dialect, Some(DialectType::Snowflake))
21761            && (operator == "AND" || operator == "OR")
21762        {
21763            self.write_newline();
21764            self.write_indent();
21765            self.write_keyword(operator);
21766        } else {
21767            self.write_space();
21768            if operator.chars().all(|c| c.is_alphabetic()) {
21769                self.write_keyword(operator);
21770            } else {
21771                self.write(operator);
21772            }
21773        }
21774        // Output comments after operator (before right operand)
21775        for comment in &op.operator_comments {
21776            self.write_space();
21777            self.write_formatted_comment(comment);
21778        }
21779        self.write_space();
21780        self.generate_expression(&op.right)?;
21781        // Output trailing comments after right operand
21782        for comment in &op.trailing_comments {
21783            self.write_space();
21784            self.write_formatted_comment(comment);
21785        }
21786        Ok(())
21787    }
21788
21789    fn generate_connector_op(&mut self, op: &BinaryOp, connector: ConnectorOperator) -> Result<()> {
21790        let keyword = connector.keyword();
21791        let Some(terms) = self.flatten_connector_terms(op, connector) else {
21792            return self.generate_binary_op(op, keyword);
21793        };
21794
21795        self.generate_expression(terms[0])?;
21796        for term in terms.iter().skip(1) {
21797            if self.config.pretty && matches!(self.config.dialect, Some(DialectType::Snowflake)) {
21798                self.write_newline();
21799                self.write_indent();
21800                self.write_keyword(keyword);
21801            } else {
21802                self.write_space();
21803                self.write_keyword(keyword);
21804            }
21805            self.write_space();
21806            self.generate_expression(term)?;
21807        }
21808
21809        Ok(())
21810    }
21811
21812    fn flatten_connector_terms<'a>(
21813        &self,
21814        root: &'a BinaryOp,
21815        connector: ConnectorOperator,
21816    ) -> Option<Vec<&'a Expression>> {
21817        if !root.left_comments.is_empty()
21818            || !root.operator_comments.is_empty()
21819            || !root.trailing_comments.is_empty()
21820        {
21821            return None;
21822        }
21823
21824        let mut terms = Vec::new();
21825        let mut stack: Vec<&Expression> = vec![&root.right, &root.left];
21826
21827        while let Some(expr) = stack.pop() {
21828            match (connector, expr) {
21829                (ConnectorOperator::And, Expression::And(inner))
21830                    if inner.left_comments.is_empty()
21831                        && inner.operator_comments.is_empty()
21832                        && inner.trailing_comments.is_empty() =>
21833                {
21834                    stack.push(&inner.right);
21835                    stack.push(&inner.left);
21836                }
21837                (ConnectorOperator::Or, Expression::Or(inner))
21838                    if inner.left_comments.is_empty()
21839                        && inner.operator_comments.is_empty()
21840                        && inner.trailing_comments.is_empty() =>
21841                {
21842                    stack.push(&inner.right);
21843                    stack.push(&inner.left);
21844                }
21845                _ => terms.push(expr),
21846            }
21847        }
21848
21849        if terms.len() > 1 {
21850            Some(terms)
21851        } else {
21852            None
21853        }
21854    }
21855
21856    /// Generate LIKE/ILIKE operation with optional ESCAPE clause
21857    fn generate_like_op(&mut self, op: &LikeOp, operator: &str) -> Result<()> {
21858        self.generate_expression(&op.left)?;
21859        self.write_space();
21860        // Drill backtick-quotes ILIKE
21861        if operator == "ILIKE" && matches!(self.config.dialect, Some(DialectType::Drill)) {
21862            self.write("`ILIKE`");
21863        } else {
21864            self.write_keyword(operator);
21865        }
21866        if let Some(quantifier) = &op.quantifier {
21867            self.write_space();
21868            self.write_keyword(quantifier);
21869            // Match Python sqlglot behavior:
21870            // ANY + Paren (single value): no space → ILIKE ANY('%a%')
21871            // ANY + Tuple (multiple values): space → LIKE ANY ('a', 'b')
21872            // ALL + anything: always space → LIKE ALL ('%a%'), LIKE ALL ('a', 'b')
21873            let is_any =
21874                quantifier.eq_ignore_ascii_case("ANY") || quantifier.eq_ignore_ascii_case("SOME");
21875            if !(is_any && matches!(&op.right, Expression::Paren(_))) {
21876                self.write_space();
21877            }
21878        } else {
21879            self.write_space();
21880        }
21881        self.generate_expression(&op.right)?;
21882        if let Some(escape) = &op.escape {
21883            self.write_space();
21884            self.write_keyword("ESCAPE");
21885            self.write_space();
21886            self.generate_expression(escape)?;
21887        }
21888        Ok(())
21889    }
21890
21891    /// Generate null-safe equality
21892    /// MySQL uses <=>, other dialects use IS NOT DISTINCT FROM
21893    fn generate_null_safe_eq(&mut self, op: &BinaryOp) -> Result<()> {
21894        use crate::dialects::DialectType;
21895        self.generate_expression(&op.left)?;
21896        self.write_space();
21897        if matches!(self.config.dialect, Some(DialectType::MySQL)) {
21898            self.write("<=>");
21899        } else {
21900            self.write_keyword("IS NOT DISTINCT FROM");
21901        }
21902        self.write_space();
21903        self.generate_expression(&op.right)?;
21904        Ok(())
21905    }
21906
21907    /// Generate IS DISTINCT FROM (null-safe inequality)
21908    fn generate_null_safe_neq(&mut self, op: &BinaryOp) -> Result<()> {
21909        self.generate_expression(&op.left)?;
21910        self.write_space();
21911        self.write_keyword("IS DISTINCT FROM");
21912        self.write_space();
21913        self.generate_expression(&op.right)?;
21914        Ok(())
21915    }
21916
21917    /// Generate binary op without trailing comments (used when nested inside another binary op)
21918    fn generate_binary_op_no_trailing(&mut self, op: &BinaryOp, operator: &str) -> Result<()> {
21919        // Generate left expression, but skip trailing comments
21920        match &op.left {
21921            Expression::Column(col) => {
21922                if let Some(table) = &col.table {
21923                    self.generate_identifier(table)?;
21924                    self.write(".");
21925                }
21926                self.generate_identifier(&col.name)?;
21927                // Oracle-style join marker (+)
21928                if col.join_mark && self.config.supports_column_join_marks {
21929                    self.write(" (+)");
21930                }
21931            }
21932            Expression::Add(inner_op)
21933            | Expression::Sub(inner_op)
21934            | Expression::Mul(inner_op)
21935            | Expression::Div(inner_op)
21936            | Expression::Concat(inner_op) => {
21937                self.generate_binary_op_no_trailing(inner_op, match &op.left {
21938                    Expression::Add(_) => "+",
21939                    Expression::Sub(_) => "-",
21940                    Expression::Mul(_) => "*",
21941                    Expression::Div(_) => "/",
21942                    Expression::Concat(_) => "||",
21943                    _ => unreachable!("op.left variant already matched by outer arm as Add/Sub/Mul/Div/Concat"),
21944                })?;
21945            }
21946            _ => {
21947                self.generate_expression(&op.left)?;
21948            }
21949        }
21950        // Output left_comments
21951        for comment in &op.left_comments {
21952            self.write_space();
21953            self.write_formatted_comment(comment);
21954        }
21955        self.write_space();
21956        if operator.chars().all(|c| c.is_alphabetic()) {
21957            self.write_keyword(operator);
21958        } else {
21959            self.write(operator);
21960        }
21961        // Output operator_comments
21962        for comment in &op.operator_comments {
21963            self.write_space();
21964            self.write_formatted_comment(comment);
21965        }
21966        self.write_space();
21967        // Generate right expression, but skip trailing comments if it's a Column
21968        // (the parent's left_comments will output them)
21969        match &op.right {
21970            Expression::Column(col) => {
21971                if let Some(table) = &col.table {
21972                    self.generate_identifier(table)?;
21973                    self.write(".");
21974                }
21975                self.generate_identifier(&col.name)?;
21976                // Oracle-style join marker (+)
21977                if col.join_mark && self.config.supports_column_join_marks {
21978                    self.write(" (+)");
21979                }
21980            }
21981            _ => {
21982                self.generate_expression(&op.right)?;
21983            }
21984        }
21985        // Skip trailing_comments - parent will handle them via its left_comments
21986        Ok(())
21987    }
21988
21989    fn generate_unary_op(&mut self, op: &UnaryOp, operator: &str) -> Result<()> {
21990        if operator.chars().all(|c| c.is_alphabetic()) {
21991            self.write_keyword(operator);
21992            self.write_space();
21993        } else {
21994            self.write(operator);
21995            // Add space between consecutive unary operators (e.g., "- -5" not "--5")
21996            if matches!(&op.this, Expression::Neg(_) | Expression::BitwiseNot(_)) {
21997                self.write_space();
21998            }
21999        }
22000        self.generate_expression(&op.this)
22001    }
22002
22003    fn generate_in(&mut self, in_expr: &In) -> Result<()> {
22004        // Generic mode supports two styles for negated IN:
22005        // - Prefix: NOT a IN (...)
22006        // - Infix:  a NOT IN (...)
22007        let is_generic =
22008            self.config.dialect.is_none() || self.config.dialect == Some(DialectType::Generic);
22009        let use_prefix_not =
22010            in_expr.not && is_generic && self.config.not_in_style == NotInStyle::Prefix;
22011        if use_prefix_not {
22012            self.write_keyword("NOT");
22013            self.write_space();
22014        }
22015        self.generate_expression(&in_expr.this)?;
22016        if in_expr.global {
22017            self.write_space();
22018            self.write_keyword("GLOBAL");
22019        }
22020        if in_expr.not && !use_prefix_not {
22021            self.write_space();
22022            self.write_keyword("NOT");
22023        }
22024        self.write_space();
22025        self.write_keyword("IN");
22026
22027        // BigQuery: IN UNNEST(expr)
22028        if let Some(unnest_expr) = &in_expr.unnest {
22029            self.write_space();
22030            self.write_keyword("UNNEST");
22031            self.write("(");
22032            self.generate_expression(unnest_expr)?;
22033            self.write(")");
22034            return Ok(());
22035        }
22036
22037        if let Some(query) = &in_expr.query {
22038            // Check if this is a bare identifier (PIVOT FOR foo IN y_enum)
22039            // vs a subquery (col IN (SELECT ...))
22040            let is_bare = in_expr.expressions.is_empty()
22041                && !matches!(
22042                    query,
22043                    Expression::Select(_)
22044                        | Expression::Union(_)
22045                        | Expression::Intersect(_)
22046                        | Expression::Except(_)
22047                        | Expression::Subquery(_)
22048                );
22049            if is_bare {
22050                // Bare identifier: no parentheses
22051                self.write_space();
22052                self.generate_expression(query)?;
22053            } else {
22054                // Subquery: with parentheses
22055                self.write(" (");
22056                let is_statement = matches!(
22057                    query,
22058                    Expression::Select(_)
22059                        | Expression::Union(_)
22060                        | Expression::Intersect(_)
22061                        | Expression::Except(_)
22062                        | Expression::Subquery(_)
22063                );
22064                if self.config.pretty && is_statement {
22065                    self.write_newline();
22066                    self.indent_level += 1;
22067                    self.write_indent();
22068                }
22069                self.generate_expression(query)?;
22070                if self.config.pretty && is_statement {
22071                    self.write_newline();
22072                    self.indent_level -= 1;
22073                    self.write_indent();
22074                }
22075                self.write(")");
22076            }
22077        } else {
22078            // DuckDB: IN without parentheses for single expression that is NOT a literal
22079            // (array/list membership like 'red' IN tbl.flags)
22080            // ClickHouse: IN without parentheses for single non-array expressions
22081            let is_duckdb = matches!(
22082                self.config.dialect,
22083                Some(crate::dialects::DialectType::DuckDB)
22084            );
22085            let is_clickhouse = matches!(
22086                self.config.dialect,
22087                Some(crate::dialects::DialectType::ClickHouse)
22088            );
22089            let single_expr = in_expr.expressions.len() == 1;
22090            if is_clickhouse && single_expr {
22091                if let Expression::Array(arr) = &in_expr.expressions[0] {
22092                    // ClickHouse: x IN [1, 2] -> x IN (1, 2)
22093                    self.write(" (");
22094                    for (i, expr) in arr.expressions.iter().enumerate() {
22095                        if i > 0 {
22096                            self.write(", ");
22097                        }
22098                        self.generate_expression(expr)?;
22099                    }
22100                    self.write(")");
22101                } else {
22102                    self.write_space();
22103                    self.generate_expression(&in_expr.expressions[0])?;
22104                }
22105            } else {
22106                let is_bare_ref = single_expr
22107                    && matches!(
22108                        &in_expr.expressions[0],
22109                        Expression::Column(_) | Expression::Identifier(_) | Expression::Dot(_)
22110                    );
22111                if (is_duckdb && is_bare_ref) || (in_expr.is_field && single_expr) {
22112                    // Bare field reference (no parens in source): IN identifier
22113                    // Also DuckDB: IN without parentheses for array/list membership
22114                    self.write_space();
22115                    self.generate_expression(&in_expr.expressions[0])?;
22116                } else {
22117                    // Standard IN (list)
22118                    self.write(" (");
22119                    for (i, expr) in in_expr.expressions.iter().enumerate() {
22120                        if i > 0 {
22121                            self.write(", ");
22122                        }
22123                        self.generate_expression(expr)?;
22124                    }
22125                    self.write(")");
22126                }
22127            }
22128        }
22129
22130        Ok(())
22131    }
22132
22133    fn generate_between(&mut self, between: &Between) -> Result<()> {
22134        // Generic mode: normalize NOT BETWEEN to prefix form: NOT a BETWEEN b AND c
22135        let use_prefix_not = between.not
22136            && (self.config.dialect.is_none() || self.config.dialect == Some(DialectType::Generic));
22137        if use_prefix_not {
22138            self.write_keyword("NOT");
22139            self.write_space();
22140        }
22141        self.generate_expression(&between.this)?;
22142        if between.not && !use_prefix_not {
22143            self.write_space();
22144            self.write_keyword("NOT");
22145        }
22146        self.write_space();
22147        self.write_keyword("BETWEEN");
22148        // Emit SYMMETRIC/ASYMMETRIC if present
22149        if let Some(sym) = between.symmetric {
22150            if sym {
22151                self.write(" SYMMETRIC");
22152            } else {
22153                self.write(" ASYMMETRIC");
22154            }
22155        }
22156        self.write_space();
22157        self.generate_expression(&between.low)?;
22158        self.write_space();
22159        self.write_keyword("AND");
22160        self.write_space();
22161        self.generate_expression(&between.high)
22162    }
22163
22164    fn generate_is_null(&mut self, is_null: &IsNull) -> Result<()> {
22165        // Generic mode: normalize IS NOT NULL to prefix form: NOT x IS NULL
22166        let use_prefix_not = is_null.not
22167            && (self.config.dialect.is_none()
22168                || self.config.dialect == Some(DialectType::Generic)
22169                || is_null.postfix_form);
22170        if use_prefix_not {
22171            // NOT x IS NULL (generic normalization and NOTNULL postfix form)
22172            self.write_keyword("NOT");
22173            self.write_space();
22174            self.generate_expression(&is_null.this)?;
22175            self.write_space();
22176            self.write_keyword("IS");
22177            self.write_space();
22178            self.write_keyword("NULL");
22179        } else {
22180            self.generate_expression(&is_null.this)?;
22181            self.write_space();
22182            self.write_keyword("IS");
22183            if is_null.not {
22184                self.write_space();
22185                self.write_keyword("NOT");
22186            }
22187            self.write_space();
22188            self.write_keyword("NULL");
22189        }
22190        Ok(())
22191    }
22192
22193    fn generate_is_true(&mut self, is_true: &IsTrueFalse) -> Result<()> {
22194        self.generate_expression(&is_true.this)?;
22195        self.write_space();
22196        self.write_keyword("IS");
22197        if is_true.not {
22198            self.write_space();
22199            self.write_keyword("NOT");
22200        }
22201        self.write_space();
22202        self.write_keyword("TRUE");
22203        Ok(())
22204    }
22205
22206    fn generate_is_false(&mut self, is_false: &IsTrueFalse) -> Result<()> {
22207        self.generate_expression(&is_false.this)?;
22208        self.write_space();
22209        self.write_keyword("IS");
22210        if is_false.not {
22211            self.write_space();
22212            self.write_keyword("NOT");
22213        }
22214        self.write_space();
22215        self.write_keyword("FALSE");
22216        Ok(())
22217    }
22218
22219    fn generate_is_json(&mut self, is_json: &IsJson) -> Result<()> {
22220        self.generate_expression(&is_json.this)?;
22221        self.write_space();
22222        self.write_keyword("IS");
22223        if is_json.negated {
22224            self.write_space();
22225            self.write_keyword("NOT");
22226        }
22227        self.write_space();
22228        self.write_keyword("JSON");
22229
22230        // Output JSON type if specified (VALUE, SCALAR, OBJECT, ARRAY)
22231        if let Some(ref json_type) = is_json.json_type {
22232            self.write_space();
22233            self.write_keyword(json_type);
22234        }
22235
22236        // Output key uniqueness constraint if specified
22237        match &is_json.unique_keys {
22238            Some(JsonUniqueKeys::With) => {
22239                self.write_space();
22240                self.write_keyword("WITH UNIQUE KEYS");
22241            }
22242            Some(JsonUniqueKeys::Without) => {
22243                self.write_space();
22244                self.write_keyword("WITHOUT UNIQUE KEYS");
22245            }
22246            Some(JsonUniqueKeys::Shorthand) => {
22247                self.write_space();
22248                self.write_keyword("UNIQUE KEYS");
22249            }
22250            None => {}
22251        }
22252
22253        Ok(())
22254    }
22255
22256    fn generate_is(&mut self, is_expr: &BinaryOp) -> Result<()> {
22257        self.generate_expression(&is_expr.left)?;
22258        self.write_space();
22259        self.write_keyword("IS");
22260        self.write_space();
22261        self.generate_expression(&is_expr.right)
22262    }
22263
22264    fn generate_exists(&mut self, exists: &Exists) -> Result<()> {
22265        if exists.not {
22266            self.write_keyword("NOT");
22267            self.write_space();
22268        }
22269        self.write_keyword("EXISTS");
22270        self.write("(");
22271        let is_statement = matches!(
22272            &exists.this,
22273            Expression::Select(_)
22274                | Expression::Union(_)
22275                | Expression::Intersect(_)
22276                | Expression::Except(_)
22277        );
22278        if self.config.pretty && is_statement {
22279            self.write_newline();
22280            self.indent_level += 1;
22281            self.write_indent();
22282            self.generate_expression(&exists.this)?;
22283            self.write_newline();
22284            self.indent_level -= 1;
22285            self.write_indent();
22286            self.write(")");
22287        } else {
22288            self.generate_expression(&exists.this)?;
22289            self.write(")");
22290        }
22291        Ok(())
22292    }
22293
22294    fn generate_member_of(&mut self, op: &BinaryOp) -> Result<()> {
22295        self.generate_expression(&op.left)?;
22296        self.write_space();
22297        self.write_keyword("MEMBER OF");
22298        self.write("(");
22299        self.generate_expression(&op.right)?;
22300        self.write(")");
22301        Ok(())
22302    }
22303
22304    fn generate_subquery(&mut self, subquery: &Subquery) -> Result<()> {
22305        if subquery.lateral {
22306            self.write_keyword("LATERAL");
22307            self.write_space();
22308        }
22309
22310        // If the inner expression is a Paren wrapping a statement, don't add extra parentheses
22311        // This handles cases like ((SELECT 1)) LIMIT 1 where we wrap Paren in Subquery
22312        // to carry the LIMIT modifier without adding more parens
22313        let skip_outer_parens = if let Expression::Paren(ref p) = &subquery.this {
22314            matches!(
22315                &p.this,
22316                Expression::Select(_)
22317                    | Expression::Union(_)
22318                    | Expression::Intersect(_)
22319                    | Expression::Except(_)
22320                    | Expression::Subquery(_)
22321            )
22322        } else {
22323            false
22324        };
22325
22326        // Check if inner expression is a statement for pretty formatting
22327        let is_statement = matches!(
22328            &subquery.this,
22329            Expression::Select(_)
22330                | Expression::Union(_)
22331                | Expression::Intersect(_)
22332                | Expression::Except(_)
22333                | Expression::Merge(_)
22334        );
22335
22336        if !skip_outer_parens {
22337            self.write("(");
22338            if self.config.pretty && is_statement {
22339                self.write_newline();
22340                self.indent_level += 1;
22341                self.write_indent();
22342            }
22343        }
22344        self.generate_expression(&subquery.this)?;
22345
22346        // Generate ORDER BY, LIMIT, OFFSET based on modifiers_inside flag
22347        if subquery.modifiers_inside {
22348            // Generate modifiers INSIDE the parentheses: (SELECT ... LIMIT 1)
22349            if let Some(order_by) = &subquery.order_by {
22350                self.write_space();
22351                self.write_keyword("ORDER BY");
22352                self.write_space();
22353                for (i, ord) in order_by.expressions.iter().enumerate() {
22354                    if i > 0 {
22355                        self.write(", ");
22356                    }
22357                    self.generate_ordered(ord)?;
22358                }
22359            }
22360
22361            if let Some(limit) = &subquery.limit {
22362                self.write_space();
22363                self.write_keyword("LIMIT");
22364                self.write_space();
22365                self.generate_expression(&limit.this)?;
22366                if limit.percent {
22367                    self.write_space();
22368                    self.write_keyword("PERCENT");
22369                }
22370            }
22371
22372            if let Some(offset) = &subquery.offset {
22373                self.write_space();
22374                self.write_keyword("OFFSET");
22375                self.write_space();
22376                self.generate_expression(&offset.this)?;
22377            }
22378        }
22379
22380        if !skip_outer_parens {
22381            if self.config.pretty && is_statement {
22382                self.write_newline();
22383                self.indent_level -= 1;
22384                self.write_indent();
22385            }
22386            self.write(")");
22387        }
22388
22389        // Generate modifiers OUTSIDE the parentheses: (SELECT ...) LIMIT 1
22390        if !subquery.modifiers_inside {
22391            if let Some(order_by) = &subquery.order_by {
22392                self.write_space();
22393                self.write_keyword("ORDER BY");
22394                self.write_space();
22395                for (i, ord) in order_by.expressions.iter().enumerate() {
22396                    if i > 0 {
22397                        self.write(", ");
22398                    }
22399                    self.generate_ordered(ord)?;
22400                }
22401            }
22402
22403            if let Some(limit) = &subquery.limit {
22404                self.write_space();
22405                self.write_keyword("LIMIT");
22406                self.write_space();
22407                self.generate_expression(&limit.this)?;
22408                if limit.percent {
22409                    self.write_space();
22410                    self.write_keyword("PERCENT");
22411                }
22412            }
22413
22414            if let Some(offset) = &subquery.offset {
22415                self.write_space();
22416                self.write_keyword("OFFSET");
22417                self.write_space();
22418                self.generate_expression(&offset.this)?;
22419            }
22420
22421            // Generate DISTRIBUTE BY (Hive/Spark)
22422            if let Some(distribute_by) = &subquery.distribute_by {
22423                self.write_space();
22424                self.write_keyword("DISTRIBUTE BY");
22425                self.write_space();
22426                for (i, expr) in distribute_by.expressions.iter().enumerate() {
22427                    if i > 0 {
22428                        self.write(", ");
22429                    }
22430                    self.generate_expression(expr)?;
22431                }
22432            }
22433
22434            // Generate SORT BY (Hive/Spark)
22435            if let Some(sort_by) = &subquery.sort_by {
22436                self.write_space();
22437                self.write_keyword("SORT BY");
22438                self.write_space();
22439                for (i, ord) in sort_by.expressions.iter().enumerate() {
22440                    if i > 0 {
22441                        self.write(", ");
22442                    }
22443                    self.generate_ordered(ord)?;
22444                }
22445            }
22446
22447            // Generate CLUSTER BY (Hive/Spark)
22448            if let Some(cluster_by) = &subquery.cluster_by {
22449                self.write_space();
22450                self.write_keyword("CLUSTER BY");
22451                self.write_space();
22452                for (i, ord) in cluster_by.expressions.iter().enumerate() {
22453                    if i > 0 {
22454                        self.write(", ");
22455                    }
22456                    self.generate_ordered(ord)?;
22457                }
22458            }
22459        }
22460
22461        if let Some(alias) = &subquery.alias {
22462            self.write_space();
22463            // Oracle doesn't use AS for subquery aliases
22464            let skip_as = matches!(
22465                self.config.dialect,
22466                Some(crate::dialects::DialectType::Oracle)
22467            );
22468            if !skip_as {
22469                self.write_keyword("AS");
22470                self.write_space();
22471            }
22472            self.generate_identifier(alias)?;
22473            if !subquery.column_aliases.is_empty() {
22474                self.write("(");
22475                for (i, col) in subquery.column_aliases.iter().enumerate() {
22476                    if i > 0 {
22477                        self.write(", ");
22478                    }
22479                    self.generate_identifier(col)?;
22480                }
22481                self.write(")");
22482            }
22483        }
22484        // Output trailing comments
22485        for comment in &subquery.trailing_comments {
22486            self.write(" ");
22487            self.write_formatted_comment(comment);
22488        }
22489        Ok(())
22490    }
22491
22492    fn generate_pivot(&mut self, pivot: &Pivot) -> Result<()> {
22493        // Generate WITH clause if present
22494        if let Some(ref with) = pivot.with {
22495            self.generate_with(with)?;
22496            self.write_space();
22497        }
22498
22499        let direction = if pivot.unpivot { "UNPIVOT" } else { "PIVOT" };
22500
22501        // Check for Redshift UNPIVOT in FROM clause:
22502        // UNPIVOT expr [AS val AT attr]
22503        // This is when unpivot=true, expressions is empty, fields is empty, and this is not Null
22504        let is_redshift_unpivot = pivot.unpivot
22505            && pivot.expressions.is_empty()
22506            && pivot.fields.is_empty()
22507            && pivot.using.is_empty()
22508            && pivot.into.is_none()
22509            && !matches!(&pivot.this, Expression::Null(_));
22510
22511        if is_redshift_unpivot {
22512            // Redshift UNPIVOT: UNPIVOT expr [AS alias]
22513            self.write_keyword("UNPIVOT");
22514            self.write_space();
22515            self.generate_expression(&pivot.this)?;
22516            // Alias - for Redshift it can be "val AT attr" format
22517            if let Some(alias) = &pivot.alias {
22518                self.write_space();
22519                self.write_keyword("AS");
22520                self.write_space();
22521                // The alias might contain " AT " for the attr part
22522                self.write(&alias.name);
22523            }
22524            return Ok(());
22525        }
22526
22527        // Check if this is a DuckDB simplified pivot (has `using` or `into`, or no `fields`)
22528        let is_simplified = !pivot.using.is_empty()
22529            || pivot.into.is_some()
22530            || (pivot.fields.is_empty()
22531                && !pivot.expressions.is_empty()
22532                && !matches!(&pivot.this, Expression::Null(_)));
22533
22534        if is_simplified {
22535            // DuckDB simplified syntax:
22536            //   PIVOT table ON cols [IN (...)] USING agg [AS alias], ... [GROUP BY ...]
22537            //   UNPIVOT table ON cols INTO NAME col VALUE col
22538            self.write_keyword(direction);
22539            self.write_space();
22540            self.generate_expression(&pivot.this)?;
22541
22542            if !pivot.expressions.is_empty() {
22543                self.write_space();
22544                self.write_keyword("ON");
22545                self.write_space();
22546                for (i, expr) in pivot.expressions.iter().enumerate() {
22547                    if i > 0 {
22548                        self.write(", ");
22549                    }
22550                    self.generate_expression(expr)?;
22551                }
22552            }
22553
22554            // INTO (for UNPIVOT)
22555            if let Some(into) = &pivot.into {
22556                self.write_space();
22557                self.write_keyword("INTO");
22558                self.write_space();
22559                self.generate_expression(into)?;
22560            }
22561
22562            // USING (for PIVOT)
22563            if !pivot.using.is_empty() {
22564                self.write_space();
22565                self.write_keyword("USING");
22566                self.write_space();
22567                for (i, expr) in pivot.using.iter().enumerate() {
22568                    if i > 0 {
22569                        self.write(", ");
22570                    }
22571                    self.generate_expression(expr)?;
22572                }
22573            }
22574
22575            // GROUP BY
22576            if let Some(group) = &pivot.group {
22577                self.write_space();
22578                self.generate_expression(group)?;
22579            }
22580        } else {
22581            // Standard syntax:
22582            //   table PIVOT(agg [AS alias], ... FOR col IN (val [AS alias], ...) [GROUP BY ...])
22583            //   table UNPIVOT(value_col FOR name_col IN (col1, col2, ...))
22584            // Only output the table expression if it's not a Null (null is used when PIVOT comes after JOIN ON)
22585            if !matches!(&pivot.this, Expression::Null(_)) {
22586                self.generate_expression(&pivot.this)?;
22587                self.write_space();
22588            }
22589            self.write_keyword(direction);
22590            self.write("(");
22591
22592            // Aggregation expressions
22593            for (i, expr) in pivot.expressions.iter().enumerate() {
22594                if i > 0 {
22595                    self.write(", ");
22596                }
22597                self.generate_expression(expr)?;
22598            }
22599
22600            // FOR...IN fields
22601            if !pivot.fields.is_empty() {
22602                if !pivot.expressions.is_empty() {
22603                    self.write_space();
22604                }
22605                self.write_keyword("FOR");
22606                self.write_space();
22607                for (i, field) in pivot.fields.iter().enumerate() {
22608                    if i > 0 {
22609                        self.write_space();
22610                    }
22611                    // field is an In expression: column IN (values)
22612                    self.generate_expression(field)?;
22613                }
22614            }
22615
22616            // DEFAULT ON NULL
22617            if let Some(default_val) = &pivot.default_on_null {
22618                self.write_space();
22619                self.write_keyword("DEFAULT ON NULL");
22620                self.write(" (");
22621                self.generate_expression(default_val)?;
22622                self.write(")");
22623            }
22624
22625            // GROUP BY inside PIVOT parens
22626            if let Some(group) = &pivot.group {
22627                self.write_space();
22628                self.generate_expression(group)?;
22629            }
22630
22631            self.write(")");
22632        }
22633
22634        // Alias
22635        if let Some(alias) = &pivot.alias {
22636            self.write_space();
22637            self.write_keyword("AS");
22638            self.write_space();
22639            self.generate_identifier(alias)?;
22640        }
22641
22642        Ok(())
22643    }
22644
22645    fn generate_unpivot(&mut self, unpivot: &Unpivot) -> Result<()> {
22646        self.generate_expression(&unpivot.this)?;
22647        self.write_space();
22648        self.write_keyword("UNPIVOT");
22649        // Output INCLUDE NULLS or EXCLUDE NULLS if specified
22650        if let Some(include) = unpivot.include_nulls {
22651            self.write_space();
22652            if include {
22653                self.write_keyword("INCLUDE NULLS");
22654            } else {
22655                self.write_keyword("EXCLUDE NULLS");
22656            }
22657            self.write_space();
22658        }
22659        self.write("(");
22660        if unpivot.value_column_parenthesized {
22661            self.write("(");
22662        }
22663        self.generate_identifier(&unpivot.value_column)?;
22664        // Output additional value columns if present
22665        for extra_col in &unpivot.extra_value_columns {
22666            self.write(", ");
22667            self.generate_identifier(extra_col)?;
22668        }
22669        if unpivot.value_column_parenthesized {
22670            self.write(")");
22671        }
22672        self.write_space();
22673        self.write_keyword("FOR");
22674        self.write_space();
22675        self.generate_identifier(&unpivot.name_column)?;
22676        self.write_space();
22677        self.write_keyword("IN");
22678        self.write(" (");
22679        for (i, col) in unpivot.columns.iter().enumerate() {
22680            if i > 0 {
22681                self.write(", ");
22682            }
22683            self.generate_expression(col)?;
22684        }
22685        self.write("))");
22686        if let Some(alias) = &unpivot.alias {
22687            self.write_space();
22688            self.write_keyword("AS");
22689            self.write_space();
22690            self.generate_identifier(alias)?;
22691        }
22692        Ok(())
22693    }
22694
22695    fn generate_values(&mut self, values: &Values) -> Result<()> {
22696        self.write_keyword("VALUES");
22697        for (i, row) in values.expressions.iter().enumerate() {
22698            if i > 0 {
22699                self.write(",");
22700            }
22701            self.write(" (");
22702            for (j, expr) in row.expressions.iter().enumerate() {
22703                if j > 0 {
22704                    self.write(", ");
22705                }
22706                self.generate_expression(expr)?;
22707            }
22708            self.write(")");
22709        }
22710        if let Some(alias) = &values.alias {
22711            self.write_space();
22712            self.write_keyword("AS");
22713            self.write_space();
22714            self.generate_identifier(alias)?;
22715            if !values.column_aliases.is_empty() {
22716                self.write("(");
22717                for (i, col) in values.column_aliases.iter().enumerate() {
22718                    if i > 0 {
22719                        self.write(", ");
22720                    }
22721                    self.generate_identifier(col)?;
22722                }
22723                self.write(")");
22724            }
22725        }
22726        Ok(())
22727    }
22728
22729    fn generate_array(&mut self, arr: &Array) -> Result<()> {
22730        // Apply struct name inheritance for target dialects that need it
22731        let needs_inheritance = matches!(
22732            self.config.dialect,
22733            Some(DialectType::DuckDB)
22734                | Some(DialectType::Spark)
22735                | Some(DialectType::Databricks)
22736                | Some(DialectType::Hive)
22737                | Some(DialectType::Snowflake)
22738                | Some(DialectType::Presto)
22739                | Some(DialectType::Trino)
22740        );
22741        let propagated: Vec<Expression>;
22742        let expressions = if needs_inheritance && arr.expressions.len() > 1 {
22743            propagated = Self::inherit_struct_field_names(&arr.expressions);
22744            &propagated
22745        } else {
22746            &arr.expressions
22747        };
22748
22749        // Generic mode: ARRAY(1, 2, 3) with parentheses
22750        // Dialect mode: ARRAY[1, 2, 3] with brackets (or just [1, 2, 3] if array_bracket_only)
22751        let use_parens =
22752            self.config.dialect.is_none() || self.config.dialect == Some(DialectType::Generic);
22753        if !self.config.array_bracket_only {
22754            self.write_keyword("ARRAY");
22755        }
22756        if use_parens {
22757            self.write("(");
22758        } else {
22759            self.write("[");
22760        }
22761        for (i, expr) in expressions.iter().enumerate() {
22762            if i > 0 {
22763                self.write(", ");
22764            }
22765            self.generate_expression(expr)?;
22766        }
22767        if use_parens {
22768            self.write(")");
22769        } else {
22770            self.write("]");
22771        }
22772        Ok(())
22773    }
22774
22775    fn generate_tuple(&mut self, tuple: &Tuple) -> Result<()> {
22776        // Special case: Tuple(function/expr, TableAlias) pattern for table functions with typed aliases
22777        // Used for PostgreSQL functions like JSON_TO_RECORDSET: FUNC(args) AS alias(col1 type1, col2 type2)
22778        if tuple.expressions.len() == 2 {
22779            if let Expression::TableAlias(_) = &tuple.expressions[1] {
22780                // First element is the function/expression, second is the TableAlias
22781                self.generate_expression(&tuple.expressions[0])?;
22782                self.write_space();
22783                self.write_keyword("AS");
22784                self.write_space();
22785                self.generate_expression(&tuple.expressions[1])?;
22786                return Ok(());
22787            }
22788        }
22789
22790        // In pretty mode, format long tuples with each element on a new line
22791        // Only expand if total width exceeds threshold
22792        let expand_tuple = if self.config.pretty && tuple.expressions.len() > 1 {
22793            let mut expr_strings: Vec<String> = Vec::with_capacity(tuple.expressions.len());
22794            for expr in &tuple.expressions {
22795                expr_strings.push(self.generate_to_string(expr)?);
22796            }
22797            self.too_wide(&expr_strings)
22798        } else {
22799            false
22800        };
22801
22802        if expand_tuple {
22803            self.write("(");
22804            self.write_newline();
22805            self.indent_level += 1;
22806            for (i, expr) in tuple.expressions.iter().enumerate() {
22807                if i > 0 {
22808                    self.write(",");
22809                    self.write_newline();
22810                }
22811                self.write_indent();
22812                self.generate_expression(expr)?;
22813            }
22814            self.indent_level -= 1;
22815            self.write_newline();
22816            self.write_indent();
22817            self.write(")");
22818        } else {
22819            self.write("(");
22820            for (i, expr) in tuple.expressions.iter().enumerate() {
22821                if i > 0 {
22822                    self.write(", ");
22823                }
22824                self.generate_expression(expr)?;
22825            }
22826            self.write(")");
22827        }
22828        Ok(())
22829    }
22830
22831    fn generate_pipe_operator(&mut self, pipe: &PipeOperator) -> Result<()> {
22832        self.generate_expression(&pipe.this)?;
22833        self.write(" |> ");
22834        self.generate_expression(&pipe.expression)?;
22835        Ok(())
22836    }
22837
22838    fn generate_ordered(&mut self, ordered: &Ordered) -> Result<()> {
22839        self.generate_expression(&ordered.this)?;
22840        if ordered.desc {
22841            self.write_space();
22842            self.write_keyword("DESC");
22843        } else if ordered.explicit_asc {
22844            self.write_space();
22845            self.write_keyword("ASC");
22846        }
22847        if let Some(nulls_first) = ordered.nulls_first {
22848            if self.config.null_ordering_supported
22849                || !matches!(self.config.dialect, Some(DialectType::Fabric))
22850            {
22851                // Determine if we should skip outputting NULLS FIRST/LAST when it's the default
22852                // for the dialect. Different dialects have different NULL ordering defaults:
22853                //
22854                // nulls_are_large (Oracle, Postgres, Snowflake, etc.):
22855                //   - ASC: NULLS LAST is default (omit NULLS LAST for ASC)
22856                //   - DESC: NULLS FIRST is default (omit NULLS FIRST for DESC)
22857                //
22858                // nulls_are_small (Spark, Hive, BigQuery, most others):
22859                //   - ASC: NULLS FIRST is default
22860                //   - DESC: NULLS LAST is default
22861                //
22862                // nulls_are_last (DuckDB, Presto, Trino, Dremio, etc.):
22863                //   - NULLS LAST is always the default regardless of sort direction
22864                let is_asc = !ordered.desc;
22865                let is_nulls_are_large = matches!(
22866                    self.config.dialect,
22867                    Some(DialectType::Oracle)
22868                        | Some(DialectType::PostgreSQL)
22869                        | Some(DialectType::Redshift)
22870                        | Some(DialectType::Snowflake)
22871                );
22872                let is_nulls_are_last = matches!(
22873                    self.config.dialect,
22874                    Some(DialectType::Dremio)
22875                        | Some(DialectType::DuckDB)
22876                        | Some(DialectType::Presto)
22877                        | Some(DialectType::Trino)
22878                        | Some(DialectType::Athena)
22879                        | Some(DialectType::ClickHouse)
22880                        | Some(DialectType::Drill)
22881                        | Some(DialectType::Exasol)
22882                );
22883
22884                // Check if the NULLS ordering matches the default for this dialect
22885                let is_default_nulls = if is_nulls_are_large {
22886                    // For nulls_are_large: ASC + NULLS LAST or DESC + NULLS FIRST is default
22887                    (is_asc && !nulls_first) || (!is_asc && nulls_first)
22888                } else if is_nulls_are_last {
22889                    // For nulls_are_last: NULLS LAST is always default
22890                    !nulls_first
22891                } else {
22892                    false
22893                };
22894
22895                if !is_default_nulls {
22896                    self.write_space();
22897                    self.write_keyword("NULLS");
22898                    self.write_space();
22899                    self.write_keyword(if nulls_first { "FIRST" } else { "LAST" });
22900                }
22901            }
22902        }
22903        // WITH FILL clause (ClickHouse)
22904        if let Some(ref with_fill) = ordered.with_fill {
22905            self.write_space();
22906            self.generate_with_fill(with_fill)?;
22907        }
22908        Ok(())
22909    }
22910
22911    /// Write a ClickHouse type string, wrapping in Nullable unless in map key context.
22912    fn write_clickhouse_type(&mut self, type_str: &str) {
22913        if self.clickhouse_nullable_depth < 0 {
22914            // Map key context: don't wrap in Nullable
22915            self.write(type_str);
22916        } else {
22917            self.write(&format!("Nullable({})", type_str));
22918        }
22919    }
22920
22921    fn generate_data_type(&mut self, dt: &DataType) -> Result<()> {
22922        use crate::dialects::DialectType;
22923
22924        match dt {
22925            DataType::Boolean => {
22926                // Dialect-specific boolean type mappings
22927                match self.config.dialect {
22928                    Some(DialectType::TSQL) => self.write_keyword("BIT"),
22929                    Some(DialectType::MySQL) => self.write_keyword("BOOLEAN"), // alias for TINYINT(1)
22930                    Some(DialectType::Oracle) => {
22931                        // Oracle 23c+ supports BOOLEAN, older versions use NUMBER(1)
22932                        self.write_keyword("NUMBER(1)")
22933                    }
22934                    Some(DialectType::ClickHouse) => self.write("Bool"), // ClickHouse uses Bool (case-sensitive)
22935                    _ => self.write_keyword("BOOLEAN"),
22936                }
22937            }
22938            DataType::TinyInt { length } => {
22939                // PostgreSQL, Oracle, and Exasol don't have TINYINT, use SMALLINT
22940                // Dremio maps TINYINT to INT
22941                // ClickHouse maps TINYINT to Int8
22942                match self.config.dialect {
22943                    Some(DialectType::PostgreSQL)
22944                    | Some(DialectType::Redshift)
22945                    | Some(DialectType::Oracle)
22946                    | Some(DialectType::Exasol) => {
22947                        self.write_keyword("SMALLINT");
22948                    }
22949                    Some(DialectType::Teradata) => {
22950                        // Teradata uses BYTEINT for smallest integer
22951                        self.write_keyword("BYTEINT");
22952                    }
22953                    Some(DialectType::Dremio) => {
22954                        // Dremio maps TINYINT to INT
22955                        self.write_keyword("INT");
22956                    }
22957                    Some(DialectType::ClickHouse) => {
22958                        self.write_clickhouse_type("Int8");
22959                    }
22960                    _ => {
22961                        self.write_keyword("TINYINT");
22962                    }
22963                }
22964                if let Some(n) = length {
22965                    if !matches!(
22966                        self.config.dialect,
22967                        Some(DialectType::Dremio) | Some(DialectType::ClickHouse)
22968                    ) {
22969                        self.write(&format!("({})", n));
22970                    }
22971                }
22972            }
22973            DataType::SmallInt { length } => {
22974                // Dremio maps SMALLINT to INT, SQLite/Drill maps SMALLINT to INTEGER
22975                match self.config.dialect {
22976                    Some(DialectType::Dremio) => {
22977                        self.write_keyword("INT");
22978                    }
22979                    Some(DialectType::SQLite) | Some(DialectType::Drill) => {
22980                        self.write_keyword("INTEGER");
22981                    }
22982                    Some(DialectType::BigQuery) => {
22983                        self.write_keyword("INT64");
22984                    }
22985                    Some(DialectType::ClickHouse) => {
22986                        self.write_clickhouse_type("Int16");
22987                    }
22988                    _ => {
22989                        self.write_keyword("SMALLINT");
22990                        if let Some(n) = length {
22991                            self.write(&format!("({})", n));
22992                        }
22993                    }
22994                }
22995            }
22996            DataType::Int {
22997                length,
22998                integer_spelling: _,
22999            } => {
23000                // BigQuery uses INT64 for INT
23001                if matches!(self.config.dialect, Some(DialectType::BigQuery)) {
23002                    self.write_keyword("INT64");
23003                } else if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
23004                    self.write_clickhouse_type("Int32");
23005                } else {
23006                    // TSQL, Presto, Trino, SQLite, Redshift use INTEGER as the canonical form
23007                    let use_integer = match self.config.dialect {
23008                        Some(DialectType::TSQL)
23009                        | Some(DialectType::Fabric)
23010                        | Some(DialectType::Presto)
23011                        | Some(DialectType::Trino)
23012                        | Some(DialectType::SQLite)
23013                        | Some(DialectType::Redshift) => true,
23014                        _ => false,
23015                    };
23016                    if use_integer {
23017                        self.write_keyword("INTEGER");
23018                    } else {
23019                        self.write_keyword("INT");
23020                    }
23021                    if let Some(n) = length {
23022                        self.write(&format!("({})", n));
23023                    }
23024                }
23025            }
23026            DataType::BigInt { length } => {
23027                // Dialect-specific bigint type mappings
23028                match self.config.dialect {
23029                    Some(DialectType::Oracle) => {
23030                        // Oracle doesn't have BIGINT, uses INT
23031                        self.write_keyword("INT");
23032                    }
23033                    Some(DialectType::ClickHouse) => {
23034                        self.write_clickhouse_type("Int64");
23035                    }
23036                    _ => {
23037                        self.write_keyword("BIGINT");
23038                        if let Some(n) = length {
23039                            self.write(&format!("({})", n));
23040                        }
23041                    }
23042                }
23043            }
23044            DataType::Float {
23045                precision,
23046                scale,
23047                real_spelling,
23048            } => {
23049                // Dialect-specific float type mappings
23050                // If real_spelling is true, preserve REAL; otherwise use dialect default
23051                // Spark/Hive don't support REAL, always use FLOAT
23052                if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
23053                    self.write_clickhouse_type("Float32");
23054                } else if *real_spelling
23055                    && !matches!(
23056                        self.config.dialect,
23057                        Some(DialectType::Spark)
23058                            | Some(DialectType::Databricks)
23059                            | Some(DialectType::Hive)
23060                            | Some(DialectType::Snowflake)
23061                            | Some(DialectType::MySQL)
23062                            | Some(DialectType::BigQuery)
23063                    )
23064                {
23065                    self.write_keyword("REAL")
23066                } else {
23067                    match self.config.dialect {
23068                        Some(DialectType::PostgreSQL) => self.write_keyword("REAL"),
23069                        Some(DialectType::BigQuery) => self.write_keyword("FLOAT64"),
23070                        _ => self.write_keyword("FLOAT"),
23071                    }
23072                }
23073                // MySQL supports FLOAT(precision) or FLOAT(precision, scale)
23074                // Spark/Hive don't support FLOAT(precision)
23075                if !matches!(
23076                    self.config.dialect,
23077                    Some(DialectType::Spark)
23078                        | Some(DialectType::Databricks)
23079                        | Some(DialectType::Hive)
23080                        | Some(DialectType::Presto)
23081                        | Some(DialectType::Trino)
23082                ) {
23083                    if let Some(p) = precision {
23084                        self.write(&format!("({}", p));
23085                        if let Some(s) = scale {
23086                            self.write(&format!(", {})", s));
23087                        } else {
23088                            self.write(")");
23089                        }
23090                    }
23091                }
23092            }
23093            DataType::Double { precision, scale } => {
23094                // Dialect-specific double type mappings
23095                match self.config.dialect {
23096                    Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
23097                        self.write_keyword("FLOAT")
23098                    } // SQL Server/Fabric FLOAT is double
23099                    Some(DialectType::Oracle) => self.write_keyword("DOUBLE PRECISION"),
23100                    Some(DialectType::ClickHouse) => self.write_clickhouse_type("Float64"),
23101                    Some(DialectType::BigQuery) => self.write_keyword("FLOAT64"),
23102                    Some(DialectType::SQLite) => self.write_keyword("REAL"),
23103                    Some(DialectType::PostgreSQL)
23104                    | Some(DialectType::Redshift)
23105                    | Some(DialectType::Teradata)
23106                    | Some(DialectType::Materialize) => self.write_keyword("DOUBLE PRECISION"),
23107                    _ => self.write_keyword("DOUBLE"),
23108                }
23109                // MySQL supports DOUBLE(precision, scale)
23110                if let Some(p) = precision {
23111                    self.write(&format!("({}", p));
23112                    if let Some(s) = scale {
23113                        self.write(&format!(", {})", s));
23114                    } else {
23115                        self.write(")");
23116                    }
23117                }
23118            }
23119            DataType::Decimal { precision, scale } => {
23120                // Dialect-specific decimal type mappings
23121                match self.config.dialect {
23122                    Some(DialectType::ClickHouse) => {
23123                        self.write("Decimal");
23124                        if let Some(p) = precision {
23125                            self.write(&format!("({}", p));
23126                            if let Some(s) = scale {
23127                                self.write(&format!(", {}", s));
23128                            }
23129                            self.write(")");
23130                        }
23131                    }
23132                    Some(DialectType::Oracle) => {
23133                        // Oracle uses NUMBER instead of DECIMAL
23134                        self.write_keyword("NUMBER");
23135                        if let Some(p) = precision {
23136                            self.write(&format!("({}", p));
23137                            if let Some(s) = scale {
23138                                self.write(&format!(", {}", s));
23139                            }
23140                            self.write(")");
23141                        }
23142                    }
23143                    Some(DialectType::BigQuery) => {
23144                        // BigQuery uses NUMERIC instead of DECIMAL
23145                        self.write_keyword("NUMERIC");
23146                        if let Some(p) = precision {
23147                            self.write(&format!("({}", p));
23148                            if let Some(s) = scale {
23149                                self.write(&format!(", {}", s));
23150                            }
23151                            self.write(")");
23152                        }
23153                    }
23154                    _ => {
23155                        self.write_keyword("DECIMAL");
23156                        if let Some(p) = precision {
23157                            self.write(&format!("({}", p));
23158                            if let Some(s) = scale {
23159                                self.write(&format!(", {}", s));
23160                            }
23161                            self.write(")");
23162                        }
23163                    }
23164                }
23165            }
23166            DataType::Char { length } => {
23167                // Dialect-specific char type mappings
23168                match self.config.dialect {
23169                    Some(DialectType::DuckDB) | Some(DialectType::SQLite) => {
23170                        // DuckDB/SQLite maps CHAR to TEXT
23171                        self.write_keyword("TEXT");
23172                    }
23173                    Some(DialectType::Hive)
23174                    | Some(DialectType::Spark)
23175                    | Some(DialectType::Databricks) => {
23176                        // Hive/Spark/Databricks maps CHAR to STRING (when no length)
23177                        // CHAR(n) with explicit length is kept as CHAR(n) for Spark/Databricks
23178                        if length.is_some()
23179                            && !matches!(self.config.dialect, Some(DialectType::Hive))
23180                        {
23181                            self.write_keyword("CHAR");
23182                            if let Some(n) = length {
23183                                self.write(&format!("({})", n));
23184                            }
23185                        } else {
23186                            self.write_keyword("STRING");
23187                        }
23188                    }
23189                    Some(DialectType::Dremio) => {
23190                        // Dremio maps CHAR to VARCHAR
23191                        self.write_keyword("VARCHAR");
23192                        if let Some(n) = length {
23193                            self.write(&format!("({})", n));
23194                        }
23195                    }
23196                    _ => {
23197                        self.write_keyword("CHAR");
23198                        if let Some(n) = length {
23199                            self.write(&format!("({})", n));
23200                        }
23201                    }
23202                }
23203            }
23204            DataType::VarChar {
23205                length,
23206                parenthesized_length,
23207            } => {
23208                // Dialect-specific varchar type mappings
23209                match self.config.dialect {
23210                    Some(DialectType::Oracle) => {
23211                        self.write_keyword("VARCHAR2");
23212                        if let Some(n) = length {
23213                            self.write(&format!("({})", n));
23214                        }
23215                    }
23216                    Some(DialectType::DuckDB) => {
23217                        // DuckDB maps VARCHAR to TEXT, preserving length
23218                        self.write_keyword("TEXT");
23219                        if let Some(n) = length {
23220                            self.write(&format!("({})", n));
23221                        }
23222                    }
23223                    Some(DialectType::SQLite) => {
23224                        // SQLite maps VARCHAR to TEXT, preserving length
23225                        self.write_keyword("TEXT");
23226                        if let Some(n) = length {
23227                            self.write(&format!("({})", n));
23228                        }
23229                    }
23230                    Some(DialectType::MySQL) if length.is_none() => {
23231                        // MySQL requires VARCHAR to have a size - if it doesn't, use TEXT
23232                        self.write_keyword("TEXT");
23233                    }
23234                    Some(DialectType::Hive)
23235                    | Some(DialectType::Spark)
23236                    | Some(DialectType::Databricks)
23237                        if length.is_none() =>
23238                    {
23239                        // Hive/Spark/Databricks: VARCHAR without length → STRING
23240                        self.write_keyword("STRING");
23241                    }
23242                    _ => {
23243                        self.write_keyword("VARCHAR");
23244                        if let Some(n) = length {
23245                            // Hive uses VARCHAR((n)) with extra parentheses in STRUCT definitions
23246                            if *parenthesized_length {
23247                                self.write(&format!("(({}))", n));
23248                            } else {
23249                                self.write(&format!("({})", n));
23250                            }
23251                        }
23252                    }
23253                }
23254            }
23255            DataType::Text => {
23256                // Dialect-specific text type mappings
23257                match self.config.dialect {
23258                    Some(DialectType::Oracle) => self.write_keyword("CLOB"),
23259                    Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
23260                        self.write_keyword("VARCHAR(MAX)")
23261                    }
23262                    Some(DialectType::BigQuery) => self.write_keyword("STRING"),
23263                    Some(DialectType::Snowflake)
23264                    | Some(DialectType::Dremio)
23265                    | Some(DialectType::Drill) => self.write_keyword("VARCHAR"),
23266                    Some(DialectType::Exasol) => self.write_keyword("LONG VARCHAR"),
23267                    Some(DialectType::Presto)
23268                    | Some(DialectType::Trino)
23269                    | Some(DialectType::Athena) => self.write_keyword("VARCHAR"),
23270                    Some(DialectType::Spark)
23271                    | Some(DialectType::Databricks)
23272                    | Some(DialectType::Hive) => self.write_keyword("STRING"),
23273                    Some(DialectType::Redshift) => self.write_keyword("VARCHAR(MAX)"),
23274                    Some(DialectType::StarRocks) | Some(DialectType::Doris) => {
23275                        self.write_keyword("STRING")
23276                    }
23277                    Some(DialectType::ClickHouse) => self.write_clickhouse_type("String"),
23278                    _ => self.write_keyword("TEXT"),
23279                }
23280            }
23281            DataType::TextWithLength { length } => {
23282                // TEXT(n) - dialect-specific type with length
23283                match self.config.dialect {
23284                    Some(DialectType::Oracle) => self.write(&format!("CLOB({})", length)),
23285                    Some(DialectType::Hive)
23286                    | Some(DialectType::Spark)
23287                    | Some(DialectType::Databricks) => {
23288                        self.write(&format!("VARCHAR({})", length));
23289                    }
23290                    Some(DialectType::Redshift) => self.write(&format!("VARCHAR({})", length)),
23291                    Some(DialectType::BigQuery) => self.write(&format!("STRING({})", length)),
23292                    Some(DialectType::Snowflake)
23293                    | Some(DialectType::Presto)
23294                    | Some(DialectType::Trino)
23295                    | Some(DialectType::Athena)
23296                    | Some(DialectType::Drill)
23297                    | Some(DialectType::Dremio) => {
23298                        self.write(&format!("VARCHAR({})", length));
23299                    }
23300                    Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
23301                        self.write(&format!("VARCHAR({})", length))
23302                    }
23303                    Some(DialectType::StarRocks) | Some(DialectType::Doris) => {
23304                        self.write(&format!("STRING({})", length))
23305                    }
23306                    Some(DialectType::ClickHouse) => self.write_clickhouse_type("String"),
23307                    _ => self.write(&format!("TEXT({})", length)),
23308                }
23309            }
23310            DataType::String { length } => {
23311                // STRING type with optional length (BigQuery STRING(n))
23312                match self.config.dialect {
23313                    Some(DialectType::ClickHouse) => {
23314                        // ClickHouse uses String with specific casing
23315                        self.write("String");
23316                        if let Some(n) = length {
23317                            self.write(&format!("({})", n));
23318                        }
23319                    }
23320                    Some(DialectType::BigQuery)
23321                    | Some(DialectType::Hive)
23322                    | Some(DialectType::Spark)
23323                    | Some(DialectType::Databricks)
23324                    | Some(DialectType::StarRocks)
23325                    | Some(DialectType::Doris) => {
23326                        self.write_keyword("STRING");
23327                        if let Some(n) = length {
23328                            self.write(&format!("({})", n));
23329                        }
23330                    }
23331                    Some(DialectType::PostgreSQL) => {
23332                        // PostgreSQL doesn't have STRING - use VARCHAR or TEXT
23333                        if let Some(n) = length {
23334                            self.write_keyword("VARCHAR");
23335                            self.write(&format!("({})", n));
23336                        } else {
23337                            self.write_keyword("TEXT");
23338                        }
23339                    }
23340                    Some(DialectType::Redshift) => {
23341                        // Redshift: STRING -> VARCHAR(MAX)
23342                        if let Some(n) = length {
23343                            self.write_keyword("VARCHAR");
23344                            self.write(&format!("({})", n));
23345                        } else {
23346                            self.write_keyword("VARCHAR(MAX)");
23347                        }
23348                    }
23349                    Some(DialectType::MySQL) => {
23350                        // MySQL doesn't have STRING - use VARCHAR or TEXT
23351                        if let Some(n) = length {
23352                            self.write_keyword("VARCHAR");
23353                            self.write(&format!("({})", n));
23354                        } else {
23355                            self.write_keyword("TEXT");
23356                        }
23357                    }
23358                    Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
23359                        // TSQL: STRING -> VARCHAR(MAX)
23360                        if let Some(n) = length {
23361                            self.write_keyword("VARCHAR");
23362                            self.write(&format!("({})", n));
23363                        } else {
23364                            self.write_keyword("VARCHAR(MAX)");
23365                        }
23366                    }
23367                    Some(DialectType::Oracle) => {
23368                        // Oracle: STRING -> CLOB
23369                        self.write_keyword("CLOB");
23370                    }
23371                    Some(DialectType::DuckDB) | Some(DialectType::Materialize) => {
23372                        // DuckDB/Materialize uses TEXT for string types
23373                        self.write_keyword("TEXT");
23374                        if let Some(n) = length {
23375                            self.write(&format!("({})", n));
23376                        }
23377                    }
23378                    Some(DialectType::Presto)
23379                    | Some(DialectType::Trino)
23380                    | Some(DialectType::Drill)
23381                    | Some(DialectType::Dremio) => {
23382                        // Presto/Trino/Drill use VARCHAR for string types
23383                        self.write_keyword("VARCHAR");
23384                        if let Some(n) = length {
23385                            self.write(&format!("({})", n));
23386                        }
23387                    }
23388                    Some(DialectType::Snowflake) => {
23389                        // Snowflake: STRING stays as STRING (identity/DDL)
23390                        // CAST context STRING -> VARCHAR is handled in generate_cast
23391                        self.write_keyword("STRING");
23392                        if let Some(n) = length {
23393                            self.write(&format!("({})", n));
23394                        }
23395                    }
23396                    _ => {
23397                        // Default: output STRING with optional length
23398                        self.write_keyword("STRING");
23399                        if let Some(n) = length {
23400                            self.write(&format!("({})", n));
23401                        }
23402                    }
23403                }
23404            }
23405            DataType::Binary { length } => {
23406                // Dialect-specific binary type mappings
23407                match self.config.dialect {
23408                    Some(DialectType::PostgreSQL) | Some(DialectType::Materialize) => {
23409                        self.write_keyword("BYTEA");
23410                        if let Some(n) = length {
23411                            self.write(&format!("({})", n));
23412                        }
23413                    }
23414                    Some(DialectType::Redshift) => {
23415                        self.write_keyword("VARBYTE");
23416                        if let Some(n) = length {
23417                            self.write(&format!("({})", n));
23418                        }
23419                    }
23420                    Some(DialectType::DuckDB)
23421                    | Some(DialectType::SQLite)
23422                    | Some(DialectType::Oracle) => {
23423                        // DuckDB/SQLite/Oracle maps BINARY to BLOB
23424                        self.write_keyword("BLOB");
23425                        if let Some(n) = length {
23426                            self.write(&format!("({})", n));
23427                        }
23428                    }
23429                    Some(DialectType::Presto)
23430                    | Some(DialectType::Trino)
23431                    | Some(DialectType::Athena)
23432                    | Some(DialectType::Drill)
23433                    | Some(DialectType::Dremio) => {
23434                        // These dialects map BINARY to VARBINARY
23435                        self.write_keyword("VARBINARY");
23436                        if let Some(n) = length {
23437                            self.write(&format!("({})", n));
23438                        }
23439                    }
23440                    Some(DialectType::ClickHouse) => {
23441                        // ClickHouse: wrap BINARY in Nullable (unless map key context)
23442                        if self.clickhouse_nullable_depth < 0 {
23443                            self.write("BINARY");
23444                        } else {
23445                            self.write("Nullable(BINARY");
23446                        }
23447                        if let Some(n) = length {
23448                            self.write(&format!("({})", n));
23449                        }
23450                        if self.clickhouse_nullable_depth >= 0 {
23451                            self.write(")");
23452                        }
23453                    }
23454                    _ => {
23455                        self.write_keyword("BINARY");
23456                        if let Some(n) = length {
23457                            self.write(&format!("({})", n));
23458                        }
23459                    }
23460                }
23461            }
23462            DataType::VarBinary { length } => {
23463                // Dialect-specific varbinary type mappings
23464                match self.config.dialect {
23465                    Some(DialectType::PostgreSQL) | Some(DialectType::Materialize) => {
23466                        self.write_keyword("BYTEA");
23467                        if let Some(n) = length {
23468                            self.write(&format!("({})", n));
23469                        }
23470                    }
23471                    Some(DialectType::Redshift) => {
23472                        self.write_keyword("VARBYTE");
23473                        if let Some(n) = length {
23474                            self.write(&format!("({})", n));
23475                        }
23476                    }
23477                    Some(DialectType::DuckDB)
23478                    | Some(DialectType::SQLite)
23479                    | Some(DialectType::Oracle) => {
23480                        // DuckDB/SQLite/Oracle maps VARBINARY to BLOB
23481                        self.write_keyword("BLOB");
23482                        if let Some(n) = length {
23483                            self.write(&format!("({})", n));
23484                        }
23485                    }
23486                    Some(DialectType::Exasol) => {
23487                        // Exasol maps VARBINARY to VARCHAR
23488                        self.write_keyword("VARCHAR");
23489                    }
23490                    Some(DialectType::Spark)
23491                    | Some(DialectType::Hive)
23492                    | Some(DialectType::Databricks) => {
23493                        // Spark/Hive use BINARY instead of VARBINARY
23494                        self.write_keyword("BINARY");
23495                        if let Some(n) = length {
23496                            self.write(&format!("({})", n));
23497                        }
23498                    }
23499                    Some(DialectType::ClickHouse) => {
23500                        // ClickHouse maps VARBINARY to String (wrapped in Nullable unless map key)
23501                        self.write_clickhouse_type("String");
23502                    }
23503                    _ => {
23504                        self.write_keyword("VARBINARY");
23505                        if let Some(n) = length {
23506                            self.write(&format!("({})", n));
23507                        }
23508                    }
23509                }
23510            }
23511            DataType::Blob => {
23512                // Dialect-specific blob type mappings
23513                match self.config.dialect {
23514                    Some(DialectType::PostgreSQL) => self.write_keyword("BYTEA"),
23515                    Some(DialectType::Redshift) => self.write_keyword("VARBYTE"),
23516                    Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
23517                        self.write_keyword("VARBINARY")
23518                    }
23519                    Some(DialectType::BigQuery) => self.write_keyword("BYTES"),
23520                    Some(DialectType::Exasol) => self.write_keyword("VARCHAR"),
23521                    Some(DialectType::Presto)
23522                    | Some(DialectType::Trino)
23523                    | Some(DialectType::Athena) => self.write_keyword("VARBINARY"),
23524                    Some(DialectType::DuckDB) => {
23525                        // Python sqlglot: BLOB -> VARBINARY for DuckDB (base TYPE_MAPPING)
23526                        // DuckDB identity works via: BLOB -> transform VarBinary -> generator BLOB
23527                        self.write_keyword("VARBINARY");
23528                    }
23529                    Some(DialectType::Spark)
23530                    | Some(DialectType::Databricks)
23531                    | Some(DialectType::Hive) => self.write_keyword("BINARY"),
23532                    Some(DialectType::ClickHouse) => {
23533                        // BLOB maps to Nullable(String) in ClickHouse, even in column defs
23534                        // where we normally suppress Nullable wrapping (clickhouse_nullable_depth = -1).
23535                        // This matches Python sqlglot behavior.
23536                        self.write("Nullable(String)");
23537                    }
23538                    _ => self.write_keyword("BLOB"),
23539                }
23540            }
23541            DataType::Bit { length } => {
23542                // Dialect-specific bit type mappings
23543                match self.config.dialect {
23544                    Some(DialectType::Dremio)
23545                    | Some(DialectType::Spark)
23546                    | Some(DialectType::Databricks)
23547                    | Some(DialectType::Hive)
23548                    | Some(DialectType::Snowflake)
23549                    | Some(DialectType::BigQuery)
23550                    | Some(DialectType::Presto)
23551                    | Some(DialectType::Trino)
23552                    | Some(DialectType::ClickHouse)
23553                    | Some(DialectType::Redshift) => {
23554                        // These dialects don't support BIT type, use BOOLEAN
23555                        self.write_keyword("BOOLEAN");
23556                    }
23557                    _ => {
23558                        self.write_keyword("BIT");
23559                        if let Some(n) = length {
23560                            self.write(&format!("({})", n));
23561                        }
23562                    }
23563                }
23564            }
23565            DataType::VarBit { length } => {
23566                self.write_keyword("VARBIT");
23567                if let Some(n) = length {
23568                    self.write(&format!("({})", n));
23569                }
23570            }
23571            DataType::Date => self.write_keyword("DATE"),
23572            DataType::Time {
23573                precision,
23574                timezone,
23575            } => {
23576                if *timezone {
23577                    // Dialect-specific TIME WITH TIME ZONE output
23578                    match self.config.dialect {
23579                        Some(DialectType::DuckDB) => {
23580                            // DuckDB: TIMETZ (drops precision)
23581                            self.write_keyword("TIMETZ");
23582                        }
23583                        Some(DialectType::PostgreSQL) => {
23584                            // PostgreSQL: TIMETZ or TIMETZ(p)
23585                            self.write_keyword("TIMETZ");
23586                            if let Some(p) = precision {
23587                                self.write(&format!("({})", p));
23588                            }
23589                        }
23590                        _ => {
23591                            // Presto/Trino/Redshift/others: TIME(p) WITH TIME ZONE
23592                            self.write_keyword("TIME");
23593                            if let Some(p) = precision {
23594                                self.write(&format!("({})", p));
23595                            }
23596                            self.write_keyword(" WITH TIME ZONE");
23597                        }
23598                    }
23599                } else {
23600                    // Spark/Hive/Databricks: TIME -> TIMESTAMP (TIME not supported)
23601                    if matches!(
23602                        self.config.dialect,
23603                        Some(DialectType::Spark)
23604                            | Some(DialectType::Databricks)
23605                            | Some(DialectType::Hive)
23606                    ) {
23607                        self.write_keyword("TIMESTAMP");
23608                    } else {
23609                        self.write_keyword("TIME");
23610                        if let Some(p) = precision {
23611                            self.write(&format!("({})", p));
23612                        }
23613                    }
23614                }
23615            }
23616            DataType::Timestamp {
23617                precision,
23618                timezone,
23619            } => {
23620                // Dialect-specific timestamp type mappings
23621                match self.config.dialect {
23622                    Some(DialectType::ClickHouse) => {
23623                        self.write("DateTime");
23624                        if let Some(p) = precision {
23625                            self.write(&format!("({})", p));
23626                        }
23627                    }
23628                    Some(DialectType::TSQL) => {
23629                        if *timezone {
23630                            self.write_keyword("DATETIMEOFFSET");
23631                        } else {
23632                            self.write_keyword("DATETIME2");
23633                        }
23634                        if let Some(p) = precision {
23635                            self.write(&format!("({})", p));
23636                        }
23637                    }
23638                    Some(DialectType::MySQL) => {
23639                        // MySQL: TIMESTAMP stays as TIMESTAMP in DDL; CAST mapping handled separately
23640                        self.write_keyword("TIMESTAMP");
23641                        if let Some(p) = precision {
23642                            self.write(&format!("({})", p));
23643                        }
23644                    }
23645                    Some(DialectType::Doris) | Some(DialectType::StarRocks) => {
23646                        // Doris/StarRocks: TIMESTAMP -> DATETIME
23647                        self.write_keyword("DATETIME");
23648                        if let Some(p) = precision {
23649                            self.write(&format!("({})", p));
23650                        }
23651                    }
23652                    Some(DialectType::BigQuery) => {
23653                        // BigQuery: TIMESTAMP is always UTC, DATETIME is timezone-naive
23654                        if *timezone {
23655                            self.write_keyword("TIMESTAMP");
23656                        } else {
23657                            self.write_keyword("DATETIME");
23658                        }
23659                    }
23660                    Some(DialectType::DuckDB) => {
23661                        // DuckDB: TIMESTAMPTZ shorthand
23662                        if *timezone {
23663                            self.write_keyword("TIMESTAMPTZ");
23664                        } else {
23665                            self.write_keyword("TIMESTAMP");
23666                            if let Some(p) = precision {
23667                                self.write(&format!("({})", p));
23668                            }
23669                        }
23670                    }
23671                    _ => {
23672                        if *timezone && !self.config.tz_to_with_time_zone {
23673                            // Use TIMESTAMPTZ shorthand when dialect doesn't prefer WITH TIME ZONE
23674                            self.write_keyword("TIMESTAMPTZ");
23675                            if let Some(p) = precision {
23676                                self.write(&format!("({})", p));
23677                            }
23678                        } else {
23679                            self.write_keyword("TIMESTAMP");
23680                            if let Some(p) = precision {
23681                                self.write(&format!("({})", p));
23682                            }
23683                            if *timezone {
23684                                self.write_space();
23685                                self.write_keyword("WITH TIME ZONE");
23686                            }
23687                        }
23688                    }
23689                }
23690            }
23691            DataType::Interval { unit, to } => {
23692                self.write_keyword("INTERVAL");
23693                if let Some(u) = unit {
23694                    self.write_space();
23695                    self.write_keyword(u);
23696                }
23697                // Handle range intervals like DAY TO HOUR
23698                if let Some(t) = to {
23699                    self.write_space();
23700                    self.write_keyword("TO");
23701                    self.write_space();
23702                    self.write_keyword(t);
23703                }
23704            }
23705            DataType::Json => {
23706                // Dialect-specific JSON type mappings
23707                match self.config.dialect {
23708                    Some(DialectType::Oracle) => self.write_keyword("JSON"), // Oracle 21c+
23709                    Some(DialectType::TSQL) => self.write_keyword("NVARCHAR(MAX)"), // No native JSON type
23710                    Some(DialectType::MySQL) => self.write_keyword("JSON"),
23711                    Some(DialectType::Snowflake) => self.write_keyword("VARIANT"),
23712                    _ => self.write_keyword("JSON"),
23713                }
23714            }
23715            DataType::JsonB => {
23716                // JSONB is PostgreSQL specific, but Doris also supports it
23717                match self.config.dialect {
23718                    Some(DialectType::PostgreSQL) => self.write_keyword("JSONB"),
23719                    Some(DialectType::Doris) => self.write_keyword("JSONB"),
23720                    Some(DialectType::Snowflake) => self.write_keyword("VARIANT"),
23721                    Some(DialectType::TSQL) => self.write_keyword("NVARCHAR(MAX)"),
23722                    Some(DialectType::DuckDB) => self.write_keyword("JSON"), // DuckDB maps JSONB to JSON
23723                    _ => self.write_keyword("JSON"), // Fall back to JSON for other dialects
23724                }
23725            }
23726            DataType::Uuid => {
23727                // Dialect-specific UUID type mappings
23728                match self.config.dialect {
23729                    Some(DialectType::TSQL) => self.write_keyword("UNIQUEIDENTIFIER"),
23730                    Some(DialectType::MySQL) => self.write_keyword("CHAR(36)"),
23731                    Some(DialectType::Oracle) => self.write_keyword("RAW(16)"),
23732                    Some(DialectType::BigQuery)
23733                    | Some(DialectType::Spark)
23734                    | Some(DialectType::Databricks) => self.write_keyword("STRING"),
23735                    _ => self.write_keyword("UUID"),
23736                }
23737            }
23738            DataType::Array {
23739                element_type,
23740                dimension,
23741            } => {
23742                // Dialect-specific array syntax
23743                match self.config.dialect {
23744                    Some(DialectType::PostgreSQL)
23745                    | Some(DialectType::Redshift)
23746                    | Some(DialectType::DuckDB) => {
23747                        // PostgreSQL uses TYPE[] or TYPE[N] syntax
23748                        self.generate_data_type(element_type)?;
23749                        if let Some(dim) = dimension {
23750                            self.write(&format!("[{}]", dim));
23751                        } else {
23752                            self.write("[]");
23753                        }
23754                    }
23755                    Some(DialectType::BigQuery) => {
23756                        self.write_keyword("ARRAY<");
23757                        self.generate_data_type(element_type)?;
23758                        self.write(">");
23759                    }
23760                    Some(DialectType::Snowflake)
23761                    | Some(DialectType::Presto)
23762                    | Some(DialectType::Trino)
23763                    | Some(DialectType::ClickHouse) => {
23764                        // These dialects use Array(TYPE) parentheses syntax
23765                        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
23766                            self.write("Array(");
23767                        } else {
23768                            self.write_keyword("ARRAY(");
23769                        }
23770                        self.generate_data_type(element_type)?;
23771                        self.write(")");
23772                    }
23773                    Some(DialectType::TSQL)
23774                    | Some(DialectType::MySQL)
23775                    | Some(DialectType::Oracle) => {
23776                        // These dialects don't have native array types
23777                        // Fall back to JSON or use native workarounds
23778                        match self.config.dialect {
23779                            Some(DialectType::MySQL) => self.write_keyword("JSON"),
23780                            Some(DialectType::TSQL) => self.write_keyword("NVARCHAR(MAX)"),
23781                            _ => self.write_keyword("JSON"),
23782                        }
23783                    }
23784                    _ => {
23785                        // Default: use angle bracket syntax (ARRAY<T>)
23786                        self.write_keyword("ARRAY<");
23787                        self.generate_data_type(element_type)?;
23788                        self.write(">");
23789                    }
23790                }
23791            }
23792            DataType::List { element_type } => {
23793                // Materialize: element_type LIST (postfix syntax)
23794                self.generate_data_type(element_type)?;
23795                self.write_keyword(" LIST");
23796            }
23797            DataType::Map {
23798                key_type,
23799                value_type,
23800            } => {
23801                // Use parentheses for Snowflake and RisingWave, bracket syntax for Materialize, angle brackets for others
23802                match self.config.dialect {
23803                    Some(DialectType::Materialize) => {
23804                        // Materialize: MAP[key_type => value_type]
23805                        self.write_keyword("MAP[");
23806                        self.generate_data_type(key_type)?;
23807                        self.write(" => ");
23808                        self.generate_data_type(value_type)?;
23809                        self.write("]");
23810                    }
23811                    Some(DialectType::Snowflake)
23812                    | Some(DialectType::RisingWave)
23813                    | Some(DialectType::DuckDB)
23814                    | Some(DialectType::Presto)
23815                    | Some(DialectType::Trino)
23816                    | Some(DialectType::Athena) => {
23817                        self.write_keyword("MAP(");
23818                        self.generate_data_type(key_type)?;
23819                        self.write(", ");
23820                        self.generate_data_type(value_type)?;
23821                        self.write(")");
23822                    }
23823                    Some(DialectType::ClickHouse) => {
23824                        // ClickHouse: Map(key_type, value_type) with parenthesized syntax
23825                        // Key types must NOT be wrapped in Nullable
23826                        self.write("Map(");
23827                        self.clickhouse_nullable_depth = -1; // suppress Nullable for key
23828                        self.generate_data_type(key_type)?;
23829                        self.clickhouse_nullable_depth = 0;
23830                        self.write(", ");
23831                        self.generate_data_type(value_type)?;
23832                        self.write(")");
23833                    }
23834                    _ => {
23835                        self.write_keyword("MAP<");
23836                        self.generate_data_type(key_type)?;
23837                        self.write(", ");
23838                        self.generate_data_type(value_type)?;
23839                        self.write(">");
23840                    }
23841                }
23842            }
23843            DataType::Vector {
23844                element_type,
23845                dimension,
23846            } => {
23847                if matches!(self.config.dialect, Some(DialectType::SingleStore)) {
23848                    // SingleStore format: VECTOR(dimension, type_alias)
23849                    self.write_keyword("VECTOR(");
23850                    if let Some(dim) = dimension {
23851                        self.write(&dim.to_string());
23852                    }
23853                    // Map type back to SingleStore alias
23854                    let type_alias = element_type.as_ref().and_then(|et| match et.as_ref() {
23855                        DataType::TinyInt { .. } => Some("I8"),
23856                        DataType::SmallInt { .. } => Some("I16"),
23857                        DataType::Int { .. } => Some("I32"),
23858                        DataType::BigInt { .. } => Some("I64"),
23859                        DataType::Float { .. } => Some("F32"),
23860                        DataType::Double { .. } => Some("F64"),
23861                        _ => None,
23862                    });
23863                    if let Some(alias) = type_alias {
23864                        if dimension.is_some() {
23865                            self.write(", ");
23866                        }
23867                        self.write(alias);
23868                    }
23869                    self.write(")");
23870                } else {
23871                    // Snowflake format: VECTOR(type, dimension)
23872                    self.write_keyword("VECTOR(");
23873                    if let Some(ref et) = element_type {
23874                        self.generate_data_type(et)?;
23875                        if dimension.is_some() {
23876                            self.write(", ");
23877                        }
23878                    }
23879                    if let Some(dim) = dimension {
23880                        self.write(&dim.to_string());
23881                    }
23882                    self.write(")");
23883                }
23884            }
23885            DataType::Object { fields, modifier } => {
23886                self.write_keyword("OBJECT(");
23887                for (i, (name, dt, not_null)) in fields.iter().enumerate() {
23888                    if i > 0 {
23889                        self.write(", ");
23890                    }
23891                    self.write(name);
23892                    self.write(" ");
23893                    self.generate_data_type(dt)?;
23894                    if *not_null {
23895                        self.write_keyword(" NOT NULL");
23896                    }
23897                }
23898                self.write(")");
23899                if let Some(mod_str) = modifier {
23900                    self.write(" ");
23901                    self.write_keyword(mod_str);
23902                }
23903            }
23904            DataType::Struct { fields, nested } => {
23905                // Dialect-specific struct type mappings
23906                match self.config.dialect {
23907                    Some(DialectType::Snowflake) => {
23908                        // Snowflake maps STRUCT to OBJECT
23909                        self.write_keyword("OBJECT(");
23910                        for (i, field) in fields.iter().enumerate() {
23911                            if i > 0 {
23912                                self.write(", ");
23913                            }
23914                            if !field.name.is_empty() {
23915                                self.write(&field.name);
23916                                self.write(" ");
23917                            }
23918                            self.generate_data_type(&field.data_type)?;
23919                        }
23920                        self.write(")");
23921                    }
23922                    Some(DialectType::Presto) | Some(DialectType::Trino) => {
23923                        // Presto/Trino use ROW(name TYPE, ...) syntax
23924                        self.write_keyword("ROW(");
23925                        for (i, field) in fields.iter().enumerate() {
23926                            if i > 0 {
23927                                self.write(", ");
23928                            }
23929                            if !field.name.is_empty() {
23930                                self.write(&field.name);
23931                                self.write(" ");
23932                            }
23933                            self.generate_data_type(&field.data_type)?;
23934                        }
23935                        self.write(")");
23936                    }
23937                    Some(DialectType::DuckDB) => {
23938                        // DuckDB uses parenthesized syntax: STRUCT(name TYPE, ...)
23939                        self.write_keyword("STRUCT(");
23940                        for (i, field) in fields.iter().enumerate() {
23941                            if i > 0 {
23942                                self.write(", ");
23943                            }
23944                            if !field.name.is_empty() {
23945                                self.write(&field.name);
23946                                self.write(" ");
23947                            }
23948                            self.generate_data_type(&field.data_type)?;
23949                        }
23950                        self.write(")");
23951                    }
23952                    Some(DialectType::ClickHouse) => {
23953                        // ClickHouse uses Tuple(name TYPE, ...) for struct types
23954                        self.write("Tuple(");
23955                        for (i, field) in fields.iter().enumerate() {
23956                            if i > 0 {
23957                                self.write(", ");
23958                            }
23959                            if !field.name.is_empty() {
23960                                self.write(&field.name);
23961                                self.write(" ");
23962                            }
23963                            self.generate_data_type(&field.data_type)?;
23964                        }
23965                        self.write(")");
23966                    }
23967                    Some(DialectType::SingleStore) => {
23968                        // SingleStore uses RECORD(name TYPE, ...) for struct types
23969                        self.write_keyword("RECORD(");
23970                        for (i, field) in fields.iter().enumerate() {
23971                            if i > 0 {
23972                                self.write(", ");
23973                            }
23974                            if !field.name.is_empty() {
23975                                self.write(&field.name);
23976                                self.write(" ");
23977                            }
23978                            self.generate_data_type(&field.data_type)?;
23979                        }
23980                        self.write(")");
23981                    }
23982                    _ => {
23983                        // Hive/Spark always use angle bracket syntax: STRUCT<name: TYPE>
23984                        let force_angle_brackets = matches!(
23985                            self.config.dialect,
23986                            Some(DialectType::Hive)
23987                                | Some(DialectType::Spark)
23988                                | Some(DialectType::Databricks)
23989                        );
23990                        if *nested && !force_angle_brackets {
23991                            self.write_keyword("STRUCT(");
23992                            for (i, field) in fields.iter().enumerate() {
23993                                if i > 0 {
23994                                    self.write(", ");
23995                                }
23996                                if !field.name.is_empty() {
23997                                    self.write(&field.name);
23998                                    self.write(" ");
23999                                }
24000                                self.generate_data_type(&field.data_type)?;
24001                            }
24002                            self.write(")");
24003                        } else {
24004                            self.write_keyword("STRUCT<");
24005                            for (i, field) in fields.iter().enumerate() {
24006                                if i > 0 {
24007                                    self.write(", ");
24008                                }
24009                                if !field.name.is_empty() {
24010                                    // Named field: name TYPE (with configurable separator for Hive)
24011                                    self.write(&field.name);
24012                                    self.write(self.config.struct_field_sep);
24013                                }
24014                                // For anonymous fields, just output the type
24015                                self.generate_data_type(&field.data_type)?;
24016                                // Spark/Databricks: Output COMMENT clause if present
24017                                if let Some(comment) = &field.comment {
24018                                    self.write(" COMMENT '");
24019                                    self.write(comment);
24020                                    self.write("'");
24021                                }
24022                                // BigQuery: Output OPTIONS clause if present
24023                                if !field.options.is_empty() {
24024                                    self.write(" ");
24025                                    self.generate_options_clause(&field.options)?;
24026                                }
24027                            }
24028                            self.write(">");
24029                        }
24030                    }
24031                }
24032            }
24033            DataType::Enum {
24034                values,
24035                assignments,
24036            } => {
24037                // DuckDB ENUM type: ENUM('RED', 'GREEN', 'BLUE')
24038                // ClickHouse: Enum('hello' = 1, 'world' = 2)
24039                if self.config.dialect == Some(DialectType::ClickHouse) {
24040                    self.write("Enum(");
24041                } else {
24042                    self.write_keyword("ENUM(");
24043                }
24044                for (i, val) in values.iter().enumerate() {
24045                    if i > 0 {
24046                        self.write(", ");
24047                    }
24048                    self.write("'");
24049                    self.write(val);
24050                    self.write("'");
24051                    if let Some(Some(assignment)) = assignments.get(i) {
24052                        self.write(" = ");
24053                        self.write(assignment);
24054                    }
24055                }
24056                self.write(")");
24057            }
24058            DataType::Set { values } => {
24059                // MySQL SET type: SET('a', 'b', 'c')
24060                self.write_keyword("SET(");
24061                for (i, val) in values.iter().enumerate() {
24062                    if i > 0 {
24063                        self.write(", ");
24064                    }
24065                    self.write("'");
24066                    self.write(val);
24067                    self.write("'");
24068                }
24069                self.write(")");
24070            }
24071            DataType::Union { fields } => {
24072                // DuckDB UNION type: UNION(num INT, str TEXT)
24073                self.write_keyword("UNION(");
24074                for (i, (name, dt)) in fields.iter().enumerate() {
24075                    if i > 0 {
24076                        self.write(", ");
24077                    }
24078                    if !name.is_empty() {
24079                        self.write(name);
24080                        self.write(" ");
24081                    }
24082                    self.generate_data_type(dt)?;
24083                }
24084                self.write(")");
24085            }
24086            DataType::Nullable { inner } => {
24087                // ClickHouse: Nullable(T), other dialects: just the inner type
24088                if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
24089                    self.write("Nullable(");
24090                    // Suppress inner Nullable wrapping to prevent Nullable(Nullable(...))
24091                    let saved_depth = self.clickhouse_nullable_depth;
24092                    self.clickhouse_nullable_depth = -1;
24093                    self.generate_data_type(inner)?;
24094                    self.clickhouse_nullable_depth = saved_depth;
24095                    self.write(")");
24096                } else {
24097                    // Map ClickHouse-specific custom type names to standard types
24098                    match inner.as_ref() {
24099                        DataType::Custom { name } if name.eq_ignore_ascii_case("DATETIME") => {
24100                            self.generate_data_type(&DataType::Timestamp {
24101                                precision: None,
24102                                timezone: false,
24103                            })?;
24104                        }
24105                        _ => {
24106                            self.generate_data_type(inner)?;
24107                        }
24108                    }
24109                }
24110            }
24111            DataType::Custom { name } => {
24112                // Handle dialect-specific type transformations
24113                let name_upper = name.to_ascii_uppercase();
24114                match self.config.dialect {
24115                    Some(DialectType::ClickHouse) => {
24116                        let (base_upper, suffix) = if let Some(idx) = name.find('(') {
24117                            (name_upper[..idx].to_string(), &name[idx..])
24118                        } else {
24119                            (name_upper.clone(), "")
24120                        };
24121                        let mapped = match base_upper.as_str() {
24122                            "DATETIME" | "TIMESTAMPTZ" | "TIMESTAMP" | "TIMESTAMPNTZ"
24123                            | "SMALLDATETIME" | "DATETIME2" => "DateTime",
24124                            "DATETIME64" => "DateTime64",
24125                            "DATE32" => "Date32",
24126                            "INT" => "Int32",
24127                            "MEDIUMINT" => "Int32",
24128                            "INT8" => "Int8",
24129                            "INT16" => "Int16",
24130                            "INT32" => "Int32",
24131                            "INT64" => "Int64",
24132                            "INT128" => "Int128",
24133                            "INT256" => "Int256",
24134                            "UINT8" => "UInt8",
24135                            "UINT16" => "UInt16",
24136                            "UINT32" => "UInt32",
24137                            "UINT64" => "UInt64",
24138                            "UINT128" => "UInt128",
24139                            "UINT256" => "UInt256",
24140                            "FLOAT32" => "Float32",
24141                            "FLOAT64" => "Float64",
24142                            "DECIMAL32" => "Decimal32",
24143                            "DECIMAL64" => "Decimal64",
24144                            "DECIMAL128" => "Decimal128",
24145                            "DECIMAL256" => "Decimal256",
24146                            "ENUM" => "Enum",
24147                            "ENUM8" => "Enum8",
24148                            "ENUM16" => "Enum16",
24149                            "FIXEDSTRING" => "FixedString",
24150                            "NESTED" => "Nested",
24151                            "LOWCARDINALITY" => "LowCardinality",
24152                            "NULLABLE" => "Nullable",
24153                            "IPV4" => "IPv4",
24154                            "IPV6" => "IPv6",
24155                            "POINT" => "Point",
24156                            "RING" => "Ring",
24157                            "LINESTRING" => "LineString",
24158                            "MULTILINESTRING" => "MultiLineString",
24159                            "POLYGON" => "Polygon",
24160                            "MULTIPOLYGON" => "MultiPolygon",
24161                            "AGGREGATEFUNCTION" => "AggregateFunction",
24162                            "SIMPLEAGGREGATEFUNCTION" => "SimpleAggregateFunction",
24163                            "DYNAMIC" => "Dynamic",
24164                            _ => "",
24165                        };
24166                        if mapped.is_empty() {
24167                            self.write(name);
24168                        } else {
24169                            self.write(mapped);
24170                            self.write(suffix);
24171                        }
24172                    }
24173                    Some(DialectType::MySQL)
24174                        if name_upper == "TIMESTAMPTZ" || name_upper == "TIMESTAMPLTZ" =>
24175                    {
24176                        // MySQL doesn't support TIMESTAMPTZ/TIMESTAMPLTZ, use TIMESTAMP
24177                        self.write_keyword("TIMESTAMP");
24178                    }
24179                    Some(DialectType::TSQL) if name_upper == "VARIANT" => {
24180                        self.write_keyword("SQL_VARIANT");
24181                    }
24182                    Some(DialectType::DuckDB) if name_upper == "DECFLOAT" => {
24183                        self.write_keyword("DECIMAL(38, 5)");
24184                    }
24185                    Some(DialectType::Exasol) => {
24186                        // Exasol type mappings for custom types
24187                        match name_upper.as_str() {
24188                            // Binary types → VARCHAR
24189                            "LONGBLOB" | "MEDIUMBLOB" | "TINYBLOB" => self.write_keyword("VARCHAR"),
24190                            // Text types → VARCHAR (TEXT → LONG VARCHAR is handled by DataType::Text)
24191                            "LONGTEXT" | "MEDIUMTEXT" | "TINYTEXT" => self.write_keyword("VARCHAR"),
24192                            // Integer types
24193                            "MEDIUMINT" => self.write_keyword("INT"),
24194                            // Decimal types → DECIMAL
24195                            "DECIMAL32" | "DECIMAL64" | "DECIMAL128" | "DECIMAL256" => {
24196                                self.write_keyword("DECIMAL")
24197                            }
24198                            // Timestamp types
24199                            "DATETIME" => self.write_keyword("TIMESTAMP"),
24200                            "TIMESTAMPLTZ" => self.write_keyword("TIMESTAMP WITH LOCAL TIME ZONE"),
24201                            _ => self.write(name),
24202                        }
24203                    }
24204                    Some(DialectType::Dremio) => {
24205                        // Dremio type mappings for custom types
24206                        match name_upper.as_str() {
24207                            "TIMESTAMPNTZ" | "DATETIME" => self.write_keyword("TIMESTAMP"),
24208                            "ARRAY" => self.write_keyword("LIST"),
24209                            "NCHAR" => self.write_keyword("VARCHAR"),
24210                            _ => self.write(name),
24211                        }
24212                    }
24213                    // Map dialect-specific custom types to standard SQL types for other dialects
24214                    _ => {
24215                        // Extract base name and args for types with parenthesized args (e.g., DATETIME2(3))
24216                        let (base_upper, _args_str) = if let Some(idx) = name_upper.find('(') {
24217                            (name_upper[..idx].to_string(), Some(&name[idx..]))
24218                        } else {
24219                            (name_upper.clone(), None)
24220                        };
24221
24222                        match base_upper.as_str() {
24223                            "INT64"
24224                                if !matches!(self.config.dialect, Some(DialectType::BigQuery)) =>
24225                            {
24226                                self.write_keyword("BIGINT");
24227                            }
24228                            "FLOAT64"
24229                                if !matches!(self.config.dialect, Some(DialectType::BigQuery)) =>
24230                            {
24231                                self.write_keyword("DOUBLE");
24232                            }
24233                            "BOOL"
24234                                if !matches!(self.config.dialect, Some(DialectType::BigQuery)) =>
24235                            {
24236                                self.write_keyword("BOOLEAN");
24237                            }
24238                            "BYTES"
24239                                if matches!(
24240                                    self.config.dialect,
24241                                    Some(DialectType::Spark)
24242                                        | Some(DialectType::Hive)
24243                                        | Some(DialectType::Databricks)
24244                                ) =>
24245                            {
24246                                self.write_keyword("BINARY");
24247                            }
24248                            "BYTES"
24249                                if !matches!(self.config.dialect, Some(DialectType::BigQuery)) =>
24250                            {
24251                                self.write_keyword("VARBINARY");
24252                            }
24253                            // TSQL DATETIME2/SMALLDATETIME -> TIMESTAMP
24254                            "DATETIME2" | "SMALLDATETIME"
24255                                if !matches!(
24256                                    self.config.dialect,
24257                                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
24258                                ) =>
24259                            {
24260                                // PostgreSQL preserves precision, others don't
24261                                if matches!(
24262                                    self.config.dialect,
24263                                    Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
24264                                ) {
24265                                    self.write_keyword("TIMESTAMP");
24266                                    if let Some(args) = _args_str {
24267                                        self.write(args);
24268                                    }
24269                                } else {
24270                                    self.write_keyword("TIMESTAMP");
24271                                }
24272                            }
24273                            // TSQL DATETIMEOFFSET -> TIMESTAMPTZ
24274                            "DATETIMEOFFSET"
24275                                if !matches!(
24276                                    self.config.dialect,
24277                                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
24278                                ) =>
24279                            {
24280                                if matches!(
24281                                    self.config.dialect,
24282                                    Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
24283                                ) {
24284                                    self.write_keyword("TIMESTAMPTZ");
24285                                    if let Some(args) = _args_str {
24286                                        self.write(args);
24287                                    }
24288                                } else {
24289                                    self.write_keyword("TIMESTAMPTZ");
24290                                }
24291                            }
24292                            // TSQL UNIQUEIDENTIFIER -> UUID or STRING
24293                            "UNIQUEIDENTIFIER"
24294                                if !matches!(
24295                                    self.config.dialect,
24296                                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
24297                                ) =>
24298                            {
24299                                match self.config.dialect {
24300                                    Some(DialectType::Spark)
24301                                    | Some(DialectType::Databricks)
24302                                    | Some(DialectType::Hive) => self.write_keyword("STRING"),
24303                                    _ => self.write_keyword("UUID"),
24304                                }
24305                            }
24306                            // TSQL BIT -> BOOLEAN for most dialects
24307                            "BIT"
24308                                if !matches!(
24309                                    self.config.dialect,
24310                                    Some(DialectType::TSQL)
24311                                        | Some(DialectType::Fabric)
24312                                        | Some(DialectType::PostgreSQL)
24313                                        | Some(DialectType::MySQL)
24314                                        | Some(DialectType::DuckDB)
24315                                ) =>
24316                            {
24317                                self.write_keyword("BOOLEAN");
24318                            }
24319                            // TSQL NVARCHAR -> VARCHAR (with default size 30 for some dialects)
24320                            "NVARCHAR"
24321                                if !matches!(
24322                                    self.config.dialect,
24323                                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
24324                                ) =>
24325                            {
24326                                match self.config.dialect {
24327                                    Some(DialectType::Oracle) => {
24328                                        // Oracle: NVARCHAR -> NVARCHAR2
24329                                        self.write_keyword("NVARCHAR2");
24330                                        if let Some(args) = _args_str {
24331                                            self.write(args);
24332                                        }
24333                                    }
24334                                    Some(DialectType::BigQuery) => {
24335                                        // BigQuery: NVARCHAR -> STRING
24336                                        self.write_keyword("STRING");
24337                                    }
24338                                    Some(DialectType::SQLite) | Some(DialectType::DuckDB) => {
24339                                        self.write_keyword("TEXT");
24340                                        if let Some(args) = _args_str {
24341                                            self.write(args);
24342                                        }
24343                                    }
24344                                    Some(DialectType::Hive) => {
24345                                        // Hive: NVARCHAR -> STRING
24346                                        self.write_keyword("STRING");
24347                                    }
24348                                    Some(DialectType::Spark) | Some(DialectType::Databricks) => {
24349                                        if _args_str.is_some() {
24350                                            self.write_keyword("VARCHAR");
24351                                            self.write(_args_str.unwrap());
24352                                        } else {
24353                                            self.write_keyword("STRING");
24354                                        }
24355                                    }
24356                                    _ => {
24357                                        self.write_keyword("VARCHAR");
24358                                        if let Some(args) = _args_str {
24359                                            self.write(args);
24360                                        }
24361                                    }
24362                                }
24363                            }
24364                            // NCHAR -> CHAR (NCHAR for Oracle/TSQL, STRING for BigQuery/Hive)
24365                            "NCHAR"
24366                                if !matches!(
24367                                    self.config.dialect,
24368                                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
24369                                ) =>
24370                            {
24371                                match self.config.dialect {
24372                                    Some(DialectType::Oracle) => {
24373                                        // Oracle natively supports NCHAR
24374                                        self.write_keyword("NCHAR");
24375                                        if let Some(args) = _args_str {
24376                                            self.write(args);
24377                                        }
24378                                    }
24379                                    Some(DialectType::BigQuery) => {
24380                                        // BigQuery: NCHAR -> STRING
24381                                        self.write_keyword("STRING");
24382                                    }
24383                                    Some(DialectType::Hive) => {
24384                                        // Hive: NCHAR -> STRING
24385                                        self.write_keyword("STRING");
24386                                    }
24387                                    Some(DialectType::SQLite) | Some(DialectType::DuckDB) => {
24388                                        self.write_keyword("TEXT");
24389                                        if let Some(args) = _args_str {
24390                                            self.write(args);
24391                                        }
24392                                    }
24393                                    Some(DialectType::Spark) | Some(DialectType::Databricks) => {
24394                                        if _args_str.is_some() {
24395                                            self.write_keyword("CHAR");
24396                                            self.write(_args_str.unwrap());
24397                                        } else {
24398                                            self.write_keyword("STRING");
24399                                        }
24400                                    }
24401                                    _ => {
24402                                        self.write_keyword("CHAR");
24403                                        if let Some(args) = _args_str {
24404                                            self.write(args);
24405                                        }
24406                                    }
24407                                }
24408                            }
24409                            // MySQL text variant types -> map to appropriate target type
24410                            // For MySQL/SingleStore: keep original name (column definitions), CAST handling is in generate_cast
24411                            "LONGTEXT" | "MEDIUMTEXT" | "TINYTEXT" => match self.config.dialect {
24412                                Some(DialectType::MySQL)
24413                                | Some(DialectType::SingleStore)
24414                                | Some(DialectType::TiDB) => self.write_keyword(&base_upper),
24415                                Some(DialectType::Spark)
24416                                | Some(DialectType::Databricks)
24417                                | Some(DialectType::Hive) => self.write_keyword("TEXT"),
24418                                Some(DialectType::BigQuery) => self.write_keyword("STRING"),
24419                                Some(DialectType::Presto)
24420                                | Some(DialectType::Trino)
24421                                | Some(DialectType::Athena) => self.write_keyword("VARCHAR"),
24422                                Some(DialectType::Snowflake)
24423                                | Some(DialectType::Redshift)
24424                                | Some(DialectType::Dremio) => self.write_keyword("VARCHAR"),
24425                                _ => self.write_keyword("TEXT"),
24426                            },
24427                            // MySQL blob variant types -> map to appropriate target type
24428                            // For MySQL/SingleStore: keep original name (column definitions), CAST handling is in generate_cast
24429                            "LONGBLOB" | "MEDIUMBLOB" | "TINYBLOB" => match self.config.dialect {
24430                                Some(DialectType::MySQL)
24431                                | Some(DialectType::SingleStore)
24432                                | Some(DialectType::TiDB) => self.write_keyword(&base_upper),
24433                                Some(DialectType::Spark)
24434                                | Some(DialectType::Databricks)
24435                                | Some(DialectType::Hive) => self.write_keyword("BLOB"),
24436                                Some(DialectType::DuckDB) => self.write_keyword("VARBINARY"),
24437                                Some(DialectType::BigQuery) => self.write_keyword("BYTES"),
24438                                Some(DialectType::Presto)
24439                                | Some(DialectType::Trino)
24440                                | Some(DialectType::Athena) => self.write_keyword("VARBINARY"),
24441                                Some(DialectType::Snowflake)
24442                                | Some(DialectType::Redshift)
24443                                | Some(DialectType::Dremio) => self.write_keyword("VARBINARY"),
24444                                _ => self.write_keyword("BLOB"),
24445                            },
24446                            // LONGVARCHAR -> TEXT for SQLite, VARCHAR for others
24447                            "LONGVARCHAR" => match self.config.dialect {
24448                                Some(DialectType::SQLite) => self.write_keyword("TEXT"),
24449                                _ => self.write_keyword("VARCHAR"),
24450                            },
24451                            // DATETIME -> TIMESTAMP for most, DATETIME for MySQL/Doris/StarRocks/Snowflake
24452                            "DATETIME" => {
24453                                match self.config.dialect {
24454                                    Some(DialectType::MySQL)
24455                                    | Some(DialectType::Doris)
24456                                    | Some(DialectType::StarRocks)
24457                                    | Some(DialectType::TSQL)
24458                                    | Some(DialectType::Fabric)
24459                                    | Some(DialectType::BigQuery)
24460                                    | Some(DialectType::SQLite)
24461                                    | Some(DialectType::Snowflake) => {
24462                                        self.write_keyword("DATETIME");
24463                                        if let Some(args) = _args_str {
24464                                            self.write(args);
24465                                        }
24466                                    }
24467                                    Some(_) => {
24468                                        // Only map to TIMESTAMP when we have a specific target dialect
24469                                        self.write_keyword("TIMESTAMP");
24470                                        if let Some(args) = _args_str {
24471                                            self.write(args);
24472                                        }
24473                                    }
24474                                    None => {
24475                                        // No dialect - preserve original
24476                                        self.write(name);
24477                                    }
24478                                }
24479                            }
24480                            // VARCHAR2/NVARCHAR2 (Oracle) -> VARCHAR for non-Oracle targets
24481                            "VARCHAR2"
24482                                if !matches!(self.config.dialect, Some(DialectType::Oracle)) =>
24483                            {
24484                                match self.config.dialect {
24485                                    Some(DialectType::DuckDB) | Some(DialectType::SQLite) => {
24486                                        self.write_keyword("TEXT");
24487                                    }
24488                                    Some(DialectType::Hive)
24489                                    | Some(DialectType::Spark)
24490                                    | Some(DialectType::Databricks)
24491                                    | Some(DialectType::BigQuery)
24492                                    | Some(DialectType::ClickHouse)
24493                                    | Some(DialectType::StarRocks)
24494                                    | Some(DialectType::Doris) => {
24495                                        self.write_keyword("STRING");
24496                                    }
24497                                    _ => {
24498                                        self.write_keyword("VARCHAR");
24499                                        if let Some(args) = _args_str {
24500                                            self.write(args);
24501                                        }
24502                                    }
24503                                }
24504                            }
24505                            "NVARCHAR2"
24506                                if !matches!(self.config.dialect, Some(DialectType::Oracle)) =>
24507                            {
24508                                match self.config.dialect {
24509                                    Some(DialectType::DuckDB) | Some(DialectType::SQLite) => {
24510                                        self.write_keyword("TEXT");
24511                                    }
24512                                    Some(DialectType::Hive)
24513                                    | Some(DialectType::Spark)
24514                                    | Some(DialectType::Databricks)
24515                                    | Some(DialectType::BigQuery)
24516                                    | Some(DialectType::ClickHouse)
24517                                    | Some(DialectType::StarRocks)
24518                                    | Some(DialectType::Doris) => {
24519                                        self.write_keyword("STRING");
24520                                    }
24521                                    _ => {
24522                                        self.write_keyword("VARCHAR");
24523                                        if let Some(args) = _args_str {
24524                                            self.write(args);
24525                                        }
24526                                    }
24527                                }
24528                            }
24529                            _ => self.write(name),
24530                        }
24531                    }
24532                }
24533            }
24534            DataType::Geometry { subtype, srid } => {
24535                // Dialect-specific geometry type mappings
24536                match self.config.dialect {
24537                    Some(DialectType::MySQL) => {
24538                        // MySQL uses POINT SRID 4326 syntax for specific types
24539                        if let Some(sub) = subtype {
24540                            self.write_keyword(sub);
24541                            if let Some(s) = srid {
24542                                self.write(" SRID ");
24543                                self.write(&s.to_string());
24544                            }
24545                        } else {
24546                            self.write_keyword("GEOMETRY");
24547                        }
24548                    }
24549                    Some(DialectType::BigQuery) => {
24550                        // BigQuery only supports GEOGRAPHY, not GEOMETRY
24551                        self.write_keyword("GEOGRAPHY");
24552                    }
24553                    Some(DialectType::Teradata) => {
24554                        // Teradata uses ST_GEOMETRY
24555                        self.write_keyword("ST_GEOMETRY");
24556                        if subtype.is_some() || srid.is_some() {
24557                            self.write("(");
24558                            if let Some(sub) = subtype {
24559                                self.write_keyword(sub);
24560                            }
24561                            if let Some(s) = srid {
24562                                if subtype.is_some() {
24563                                    self.write(", ");
24564                                }
24565                                self.write(&s.to_string());
24566                            }
24567                            self.write(")");
24568                        }
24569                    }
24570                    _ => {
24571                        // PostgreSQL, Snowflake, DuckDB use GEOMETRY(subtype, srid) syntax
24572                        self.write_keyword("GEOMETRY");
24573                        if subtype.is_some() || srid.is_some() {
24574                            self.write("(");
24575                            if let Some(sub) = subtype {
24576                                self.write_keyword(sub);
24577                            }
24578                            if let Some(s) = srid {
24579                                if subtype.is_some() {
24580                                    self.write(", ");
24581                                }
24582                                self.write(&s.to_string());
24583                            }
24584                            self.write(")");
24585                        }
24586                    }
24587                }
24588            }
24589            DataType::Geography { subtype, srid } => {
24590                // Dialect-specific geography type mappings
24591                match self.config.dialect {
24592                    Some(DialectType::MySQL) => {
24593                        // MySQL doesn't have native GEOGRAPHY, use GEOMETRY with SRID 4326
24594                        if let Some(sub) = subtype {
24595                            self.write_keyword(sub);
24596                        } else {
24597                            self.write_keyword("GEOMETRY");
24598                        }
24599                        // Geography implies SRID 4326 (WGS84)
24600                        let effective_srid = srid.unwrap_or(4326);
24601                        self.write(" SRID ");
24602                        self.write(&effective_srid.to_string());
24603                    }
24604                    Some(DialectType::BigQuery) => {
24605                        // BigQuery uses simple GEOGRAPHY without parameters
24606                        self.write_keyword("GEOGRAPHY");
24607                    }
24608                    Some(DialectType::Snowflake) => {
24609                        // Snowflake uses GEOGRAPHY without parameters
24610                        self.write_keyword("GEOGRAPHY");
24611                    }
24612                    _ => {
24613                        // PostgreSQL uses GEOGRAPHY(subtype, srid) syntax
24614                        self.write_keyword("GEOGRAPHY");
24615                        if subtype.is_some() || srid.is_some() {
24616                            self.write("(");
24617                            if let Some(sub) = subtype {
24618                                self.write_keyword(sub);
24619                            }
24620                            if let Some(s) = srid {
24621                                if subtype.is_some() {
24622                                    self.write(", ");
24623                                }
24624                                self.write(&s.to_string());
24625                            }
24626                            self.write(")");
24627                        }
24628                    }
24629                }
24630            }
24631            DataType::CharacterSet { name } => {
24632                // For MySQL CONVERT USING - output as CHAR CHARACTER SET name
24633                self.write_keyword("CHAR CHARACTER SET ");
24634                self.write(name);
24635            }
24636            _ => self.write("UNKNOWN"),
24637        }
24638        Ok(())
24639    }
24640
24641    // === Helper methods ===
24642
24643    #[inline]
24644    fn write(&mut self, s: &str) {
24645        self.output.push_str(s);
24646    }
24647
24648    #[inline]
24649    fn write_space(&mut self) {
24650        self.output.push(' ');
24651    }
24652
24653    #[inline]
24654    fn write_keyword(&mut self, keyword: &str) {
24655        if self.config.uppercase_keywords {
24656            self.output.push_str(keyword);
24657        } else {
24658            for b in keyword.bytes() {
24659                self.output.push(b.to_ascii_lowercase() as char);
24660            }
24661        }
24662    }
24663
24664    /// Write a function name respecting the normalize_functions config setting
24665    fn write_func_name(&mut self, name: &str) {
24666        let normalized = self.normalize_func_name(name);
24667        self.output.push_str(normalized.as_ref());
24668    }
24669
24670    /// Convert strptime format string to Exasol format string
24671    /// Exasol TIME_MAPPING (reverse of Python sqlglot):
24672    /// %Y -> YYYY, %y -> YY, %m -> MM, %d -> DD, %H -> HH, %M -> MI, %S -> SS, %a -> DY
24673    fn convert_strptime_to_exasol_format(format: &str) -> String {
24674        let mut result = String::new();
24675        let chars: Vec<char> = format.chars().collect();
24676        let mut i = 0;
24677        while i < chars.len() {
24678            if chars[i] == '%' && i + 1 < chars.len() {
24679                let spec = chars[i + 1];
24680                let exasol_spec = match spec {
24681                    'Y' => "YYYY",
24682                    'y' => "YY",
24683                    'm' => "MM",
24684                    'd' => "DD",
24685                    'H' => "HH",
24686                    'M' => "MI",
24687                    'S' => "SS",
24688                    'a' => "DY",    // abbreviated weekday name
24689                    'A' => "DAY",   // full weekday name
24690                    'b' => "MON",   // abbreviated month name
24691                    'B' => "MONTH", // full month name
24692                    'I' => "H12",   // 12-hour format
24693                    'u' => "ID",    // ISO weekday (1-7)
24694                    'V' => "IW",    // ISO week number
24695                    'G' => "IYYY",  // ISO year
24696                    'W' => "UW",    // Week number (Monday as first day)
24697                    'U' => "UW",    // Week number (Sunday as first day)
24698                    'z' => "Z",     // timezone offset
24699                    _ => {
24700                        // Unknown specifier, keep as-is
24701                        result.push('%');
24702                        result.push(spec);
24703                        i += 2;
24704                        continue;
24705                    }
24706                };
24707                result.push_str(exasol_spec);
24708                i += 2;
24709            } else {
24710                result.push(chars[i]);
24711                i += 1;
24712            }
24713        }
24714        result
24715    }
24716
24717    /// Convert strptime format string to PostgreSQL/Redshift format string
24718    /// PostgreSQL INVERSE_TIME_MAPPING from Python sqlglot:
24719    /// %Y -> YYYY, %y -> YY, %m -> MM, %d -> DD, %H -> HH24, %M -> MI, %S -> SS, %f -> US, etc.
24720    fn convert_strptime_to_postgres_format(format: &str) -> String {
24721        let mut result = String::new();
24722        let chars: Vec<char> = format.chars().collect();
24723        let mut i = 0;
24724        while i < chars.len() {
24725            if chars[i] == '%' && i + 1 < chars.len() {
24726                // Check for %-d, %-m, etc. (non-padded, 3-char sequence)
24727                if chars[i + 1] == '-' && i + 2 < chars.len() {
24728                    let spec = chars[i + 2];
24729                    let pg_spec = match spec {
24730                        'd' => "FMDD",
24731                        'm' => "FMMM",
24732                        'H' => "FMHH24",
24733                        'M' => "FMMI",
24734                        'S' => "FMSS",
24735                        _ => {
24736                            result.push('%');
24737                            result.push('-');
24738                            result.push(spec);
24739                            i += 3;
24740                            continue;
24741                        }
24742                    };
24743                    result.push_str(pg_spec);
24744                    i += 3;
24745                    continue;
24746                }
24747                let spec = chars[i + 1];
24748                let pg_spec = match spec {
24749                    'Y' => "YYYY",
24750                    'y' => "YY",
24751                    'm' => "MM",
24752                    'd' => "DD",
24753                    'H' => "HH24",
24754                    'I' => "HH12",
24755                    'M' => "MI",
24756                    'S' => "SS",
24757                    'f' => "US",      // microseconds
24758                    'u' => "D",       // day of week (1=Monday)
24759                    'j' => "DDD",     // day of year
24760                    'z' => "OF",      // UTC offset
24761                    'Z' => "TZ",      // timezone name
24762                    'A' => "TMDay",   // full weekday name
24763                    'a' => "TMDy",    // abbreviated weekday name
24764                    'b' => "TMMon",   // abbreviated month name
24765                    'B' => "TMMonth", // full month name
24766                    'U' => "WW",      // week number
24767                    _ => {
24768                        // Unknown specifier, keep as-is
24769                        result.push('%');
24770                        result.push(spec);
24771                        i += 2;
24772                        continue;
24773                    }
24774                };
24775                result.push_str(pg_spec);
24776                i += 2;
24777            } else {
24778                result.push(chars[i]);
24779                i += 1;
24780            }
24781        }
24782        result
24783    }
24784
24785    /// Write a LIMIT expression value, evaluating constant expressions if limit_only_literals is set
24786    fn write_limit_expr(&mut self, expr: &Expression) -> Result<()> {
24787        if self.config.limit_only_literals {
24788            if let Some(value) = Self::try_evaluate_constant(expr) {
24789                self.write(&value.to_string());
24790                return Ok(());
24791            }
24792        }
24793        self.generate_expression(expr)
24794    }
24795
24796    /// Format a comment with proper spacing.
24797    /// Converts `/*text*/` to `/* text */` (adding internal spaces if not present).
24798    /// Python SQLGlot normalizes comment format to have spaces inside block comments.
24799    fn write_formatted_comment(&mut self, comment: &str) {
24800        // Normalize all comments to block comment format /* ... */
24801        // This matches Python sqlglot behavior which always outputs block comments
24802        let content = if comment.starts_with("/*") && comment.ends_with("*/") {
24803            // Already block comment - extract inner content
24804            // Preserve internal whitespace, but ensure at least one space padding
24805            &comment[2..comment.len() - 2]
24806        } else if comment.starts_with("--") {
24807            // Line comment - extract content after --
24808            // Preserve internal whitespace (e.g., "--       x" -> "/*       x */")
24809            &comment[2..]
24810        } else {
24811            // Raw content (no delimiters)
24812            comment
24813        };
24814        // Skip empty comments (e.g., bare "--" with no content)
24815        if content.trim().is_empty() {
24816            return;
24817        }
24818        // Escape nested block comment markers to prevent premature closure or unintended nesting.
24819        // This matches Python sqlglot's sanitize_comment behavior.
24820        let sanitized = content.replace("*/", "* /").replace("/*", "/ *");
24821        let content = &sanitized;
24822        // Ensure at least one space after /* and before */
24823        self.output.push_str("/*");
24824        if !content.starts_with(' ') {
24825            self.output.push(' ');
24826        }
24827        self.output.push_str(content);
24828        if !content.ends_with(' ') {
24829            self.output.push(' ');
24830        }
24831        self.output.push_str("*/");
24832    }
24833
24834    /// Escape a raw block content (from dollar-quoted string) for single-quoted output.
24835    /// Escapes single quotes with backslash, and for Snowflake also escapes backslashes.
24836    fn escape_block_for_single_quote(&self, block: &str) -> String {
24837        let escape_backslash = matches!(
24838            self.config.dialect,
24839            Some(crate::dialects::DialectType::Snowflake)
24840        );
24841        let mut escaped = String::with_capacity(block.len() + 4);
24842        for ch in block.chars() {
24843            if ch == '\'' {
24844                escaped.push('\\');
24845                escaped.push('\'');
24846            } else if escape_backslash && ch == '\\' {
24847                escaped.push('\\');
24848                escaped.push('\\');
24849            } else {
24850                escaped.push(ch);
24851            }
24852        }
24853        escaped
24854    }
24855
24856    fn write_newline(&mut self) {
24857        self.output.push('\n');
24858    }
24859
24860    fn write_indent(&mut self) {
24861        for _ in 0..self.indent_level {
24862            self.output.push_str(self.config.indent);
24863        }
24864    }
24865
24866    // === SQLGlot-style pretty printing helpers ===
24867
24868    /// Returns the separator string for pretty printing.
24869    /// Check if the total length of arguments exceeds max_text_width.
24870    /// Used for dynamic line breaking in expressions() formatting.
24871    fn too_wide(&self, args: &[String]) -> bool {
24872        args.iter().map(|s| s.len()).sum::<usize>() > self.config.max_text_width
24873    }
24874
24875    /// Generate an expression to a string using a temporary non-pretty generator.
24876    /// Useful for width calculations before deciding on formatting.
24877    fn generate_to_string(&self, expr: &Expression) -> Result<String> {
24878        let config = GeneratorConfig {
24879            pretty: false,
24880            dialect: self.config.dialect,
24881            ..Default::default()
24882        };
24883        let mut gen = Generator::with_config(config);
24884        gen.generate_expression(expr)?;
24885        Ok(gen.output)
24886    }
24887
24888    /// Writes a clause with a single condition (WHERE, HAVING, QUALIFY).
24889    /// In pretty mode: newline + indented keyword + newline + indented condition
24890    fn write_clause_condition(&mut self, keyword: &str, condition: &Expression) -> Result<()> {
24891        if self.config.pretty {
24892            self.write_newline();
24893            self.write_indent();
24894            self.write_keyword(keyword);
24895            self.write_newline();
24896            self.indent_level += 1;
24897            self.write_indent();
24898            self.generate_expression(condition)?;
24899            self.indent_level -= 1;
24900        } else {
24901            self.write_space();
24902            self.write_keyword(keyword);
24903            self.write_space();
24904            self.generate_expression(condition)?;
24905        }
24906        Ok(())
24907    }
24908
24909    /// Writes a clause with a list of expressions (GROUP BY, DISTRIBUTE BY, CLUSTER BY).
24910    /// In pretty mode: each expression on new line with indentation
24911    fn write_clause_expressions(&mut self, keyword: &str, exprs: &[Expression]) -> Result<()> {
24912        if exprs.is_empty() {
24913            return Ok(());
24914        }
24915
24916        if self.config.pretty {
24917            self.write_newline();
24918            self.write_indent();
24919            self.write_keyword(keyword);
24920            self.write_newline();
24921            self.indent_level += 1;
24922            for (i, expr) in exprs.iter().enumerate() {
24923                if i > 0 {
24924                    self.write(",");
24925                    self.write_newline();
24926                }
24927                self.write_indent();
24928                self.generate_expression(expr)?;
24929            }
24930            self.indent_level -= 1;
24931        } else {
24932            self.write_space();
24933            self.write_keyword(keyword);
24934            self.write_space();
24935            for (i, expr) in exprs.iter().enumerate() {
24936                if i > 0 {
24937                    self.write(", ");
24938                }
24939                self.generate_expression(expr)?;
24940            }
24941        }
24942        Ok(())
24943    }
24944
24945    /// Writes ORDER BY / SORT BY clause with Ordered expressions
24946    fn write_order_clause(&mut self, keyword: &str, orderings: &[Ordered]) -> Result<()> {
24947        if orderings.is_empty() {
24948            return Ok(());
24949        }
24950
24951        if self.config.pretty {
24952            self.write_newline();
24953            self.write_indent();
24954            self.write_keyword(keyword);
24955            self.write_newline();
24956            self.indent_level += 1;
24957            for (i, ordered) in orderings.iter().enumerate() {
24958                if i > 0 {
24959                    self.write(",");
24960                    self.write_newline();
24961                }
24962                self.write_indent();
24963                self.generate_ordered(ordered)?;
24964            }
24965            self.indent_level -= 1;
24966        } else {
24967            self.write_space();
24968            self.write_keyword(keyword);
24969            self.write_space();
24970            for (i, ordered) in orderings.iter().enumerate() {
24971                if i > 0 {
24972                    self.write(", ");
24973                }
24974                self.generate_ordered(ordered)?;
24975            }
24976        }
24977        Ok(())
24978    }
24979
24980    /// Writes WINDOW clause with named window definitions
24981    fn write_window_clause(&mut self, windows: &[NamedWindow]) -> Result<()> {
24982        if windows.is_empty() {
24983            return Ok(());
24984        }
24985
24986        if self.config.pretty {
24987            self.write_newline();
24988            self.write_indent();
24989            self.write_keyword("WINDOW");
24990            self.write_newline();
24991            self.indent_level += 1;
24992            for (i, named_window) in windows.iter().enumerate() {
24993                if i > 0 {
24994                    self.write(",");
24995                    self.write_newline();
24996                }
24997                self.write_indent();
24998                self.generate_identifier(&named_window.name)?;
24999                self.write_space();
25000                self.write_keyword("AS");
25001                self.write(" (");
25002                self.generate_over(&named_window.spec)?;
25003                self.write(")");
25004            }
25005            self.indent_level -= 1;
25006        } else {
25007            self.write_space();
25008            self.write_keyword("WINDOW");
25009            self.write_space();
25010            for (i, named_window) in windows.iter().enumerate() {
25011                if i > 0 {
25012                    self.write(", ");
25013                }
25014                self.generate_identifier(&named_window.name)?;
25015                self.write_space();
25016                self.write_keyword("AS");
25017                self.write(" (");
25018                self.generate_over(&named_window.spec)?;
25019                self.write(")");
25020            }
25021        }
25022        Ok(())
25023    }
25024
25025    // === BATCH-GENERATED STUB METHODS (481 variants) ===
25026    fn generate_ai_agg(&mut self, e: &AIAgg) -> Result<()> {
25027        // AI_AGG(this, expression)
25028        self.write_keyword("AI_AGG");
25029        self.write("(");
25030        self.generate_expression(&e.this)?;
25031        self.write(", ");
25032        self.generate_expression(&e.expression)?;
25033        self.write(")");
25034        Ok(())
25035    }
25036
25037    fn generate_ai_classify(&mut self, e: &AIClassify) -> Result<()> {
25038        // AI_CLASSIFY(input, [categories], [config])
25039        self.write_keyword("AI_CLASSIFY");
25040        self.write("(");
25041        self.generate_expression(&e.this)?;
25042        if let Some(categories) = &e.categories {
25043            self.write(", ");
25044            self.generate_expression(categories)?;
25045        }
25046        if let Some(config) = &e.config {
25047            self.write(", ");
25048            self.generate_expression(config)?;
25049        }
25050        self.write(")");
25051        Ok(())
25052    }
25053
25054    fn generate_add_partition(&mut self, e: &AddPartition) -> Result<()> {
25055        // Python: return f"ADD {exists}{self.sql(expression.this)}{location}"
25056        self.write_keyword("ADD");
25057        self.write_space();
25058        if e.exists {
25059            self.write_keyword("IF NOT EXISTS");
25060            self.write_space();
25061        }
25062        self.generate_expression(&e.this)?;
25063        if let Some(location) = &e.location {
25064            self.write_space();
25065            self.generate_expression(location)?;
25066        }
25067        Ok(())
25068    }
25069
25070    fn generate_algorithm_property(&mut self, e: &AlgorithmProperty) -> Result<()> {
25071        // Python: return f"ALGORITHM={self.sql(expression, 'this')}"
25072        self.write_keyword("ALGORITHM");
25073        self.write("=");
25074        self.generate_expression(&e.this)?;
25075        Ok(())
25076    }
25077
25078    fn generate_aliases(&mut self, e: &Aliases) -> Result<()> {
25079        // Python: return f"{self.sql(expression, 'this')} AS ({self.expressions(expression, flat=True)})"
25080        self.generate_expression(&e.this)?;
25081        self.write_space();
25082        self.write_keyword("AS");
25083        self.write(" (");
25084        for (i, expr) in e.expressions.iter().enumerate() {
25085            if i > 0 {
25086                self.write(", ");
25087            }
25088            self.generate_expression(expr)?;
25089        }
25090        self.write(")");
25091        Ok(())
25092    }
25093
25094    fn generate_allowed_values_property(&mut self, e: &AllowedValuesProperty) -> Result<()> {
25095        // Python: return f"ALLOWED_VALUES {self.expressions(e, flat=True)}"
25096        self.write_keyword("ALLOWED_VALUES");
25097        self.write_space();
25098        for (i, expr) in e.expressions.iter().enumerate() {
25099            if i > 0 {
25100                self.write(", ");
25101            }
25102            self.generate_expression(expr)?;
25103        }
25104        Ok(())
25105    }
25106
25107    fn generate_alter_column(&mut self, e: &AlterColumn) -> Result<()> {
25108        // Python: complex logic based on dtype, default, comment, visible, etc.
25109        self.write_keyword("ALTER COLUMN");
25110        self.write_space();
25111        self.generate_expression(&e.this)?;
25112
25113        if let Some(dtype) = &e.dtype {
25114            self.write_space();
25115            self.write_keyword("SET DATA TYPE");
25116            self.write_space();
25117            self.generate_expression(dtype)?;
25118            if let Some(collate) = &e.collate {
25119                self.write_space();
25120                self.write_keyword("COLLATE");
25121                self.write_space();
25122                self.generate_expression(collate)?;
25123            }
25124            if let Some(using) = &e.using {
25125                self.write_space();
25126                self.write_keyword("USING");
25127                self.write_space();
25128                self.generate_expression(using)?;
25129            }
25130        } else if let Some(default) = &e.default {
25131            self.write_space();
25132            self.write_keyword("SET DEFAULT");
25133            self.write_space();
25134            self.generate_expression(default)?;
25135        } else if let Some(comment) = &e.comment {
25136            self.write_space();
25137            self.write_keyword("COMMENT");
25138            self.write_space();
25139            self.generate_expression(comment)?;
25140        } else if let Some(drop) = &e.drop {
25141            self.write_space();
25142            self.write_keyword("DROP");
25143            self.write_space();
25144            self.generate_expression(drop)?;
25145        } else if let Some(visible) = &e.visible {
25146            self.write_space();
25147            self.generate_expression(visible)?;
25148        } else if let Some(rename_to) = &e.rename_to {
25149            self.write_space();
25150            self.write_keyword("RENAME TO");
25151            self.write_space();
25152            self.generate_expression(rename_to)?;
25153        } else if let Some(allow_null) = &e.allow_null {
25154            self.write_space();
25155            self.generate_expression(allow_null)?;
25156        }
25157        Ok(())
25158    }
25159
25160    fn generate_alter_session(&mut self, e: &AlterSession) -> Result<()> {
25161        // Python: keyword = "UNSET" if expression.args.get("unset") else "SET"; return f"{keyword} {items_sql}"
25162        self.write_keyword("ALTER SESSION");
25163        self.write_space();
25164        if e.unset.is_some() {
25165            self.write_keyword("UNSET");
25166        } else {
25167            self.write_keyword("SET");
25168        }
25169        self.write_space();
25170        for (i, expr) in e.expressions.iter().enumerate() {
25171            if i > 0 {
25172                self.write(", ");
25173            }
25174            self.generate_expression(expr)?;
25175        }
25176        Ok(())
25177    }
25178
25179    fn generate_alter_set(&mut self, e: &AlterSet) -> Result<()> {
25180        // Python (Snowflake): return f"SET{exprs}{file_format}{copy_options}{tag}"
25181        self.write_keyword("SET");
25182
25183        // Generate option (e.g., AUTHORIZATION, LOGGED, UNLOGGED, etc.)
25184        if let Some(opt) = &e.option {
25185            self.write_space();
25186            self.generate_expression(opt)?;
25187        }
25188
25189        // Generate PROPERTIES (for Trino SET PROPERTIES x = y, ...)
25190        // Check if expressions look like property assignments
25191        if !e.expressions.is_empty() {
25192            // Check if this looks like property assignments (for SET PROPERTIES)
25193            let is_properties = e
25194                .expressions
25195                .iter()
25196                .any(|expr| matches!(expr, Expression::Eq(_)));
25197            if is_properties && e.option.is_none() {
25198                self.write_space();
25199                self.write_keyword("PROPERTIES");
25200            }
25201            self.write_space();
25202            for (i, expr) in e.expressions.iter().enumerate() {
25203                if i > 0 {
25204                    self.write(", ");
25205                }
25206                self.generate_expression(expr)?;
25207            }
25208        }
25209
25210        // Generate STAGE_FILE_FORMAT = (...) with space-separated properties
25211        if let Some(file_format) = &e.file_format {
25212            self.write(" ");
25213            self.write_keyword("STAGE_FILE_FORMAT");
25214            self.write(" = (");
25215            self.generate_space_separated_properties(file_format)?;
25216            self.write(")");
25217        }
25218
25219        // Generate STAGE_COPY_OPTIONS = (...) with space-separated properties
25220        if let Some(copy_options) = &e.copy_options {
25221            self.write(" ");
25222            self.write_keyword("STAGE_COPY_OPTIONS");
25223            self.write(" = (");
25224            self.generate_space_separated_properties(copy_options)?;
25225            self.write(")");
25226        }
25227
25228        // Generate TAG ...
25229        if let Some(tag) = &e.tag {
25230            self.write(" ");
25231            self.write_keyword("TAG");
25232            self.write(" ");
25233            self.generate_expression(tag)?;
25234        }
25235
25236        Ok(())
25237    }
25238
25239    /// Generate space-separated properties (for Snowflake STAGE_FILE_FORMAT, etc.)
25240    fn generate_space_separated_properties(&mut self, expr: &Expression) -> Result<()> {
25241        match expr {
25242            Expression::Tuple(t) => {
25243                for (i, prop) in t.expressions.iter().enumerate() {
25244                    if i > 0 {
25245                        self.write(" ");
25246                    }
25247                    self.generate_expression(prop)?;
25248                }
25249            }
25250            _ => {
25251                self.generate_expression(expr)?;
25252            }
25253        }
25254        Ok(())
25255    }
25256
25257    fn generate_alter_sort_key(&mut self, e: &AlterSortKey) -> Result<()> {
25258        // Python: return f"ALTER{compound} SORTKEY {this or expressions}"
25259        self.write_keyword("ALTER");
25260        if e.compound.is_some() {
25261            self.write_space();
25262            self.write_keyword("COMPOUND");
25263        }
25264        self.write_space();
25265        self.write_keyword("SORTKEY");
25266        self.write_space();
25267        if let Some(this) = &e.this {
25268            self.generate_expression(this)?;
25269        } else if !e.expressions.is_empty() {
25270            self.write("(");
25271            for (i, expr) in e.expressions.iter().enumerate() {
25272                if i > 0 {
25273                    self.write(", ");
25274                }
25275                self.generate_expression(expr)?;
25276            }
25277            self.write(")");
25278        }
25279        Ok(())
25280    }
25281
25282    fn generate_analyze(&mut self, e: &Analyze) -> Result<()> {
25283        // Python: return f"ANALYZE{options}{kind}{this}{partition}{mode}{inner_expression}{properties}"
25284        self.write_keyword("ANALYZE");
25285        if !e.options.is_empty() {
25286            self.write_space();
25287            for (i, opt) in e.options.iter().enumerate() {
25288                if i > 0 {
25289                    self.write_space();
25290                }
25291                // Write options as keywords (not identifiers) to avoid quoting reserved words like FULL
25292                if let Expression::Identifier(id) = opt {
25293                    self.write_keyword(&id.name);
25294                } else {
25295                    self.generate_expression(opt)?;
25296                }
25297            }
25298        }
25299        if let Some(kind) = &e.kind {
25300            self.write_space();
25301            self.write_keyword(kind);
25302        }
25303        if let Some(this) = &e.this {
25304            self.write_space();
25305            self.generate_expression(this)?;
25306        }
25307        // Column list: ANALYZE tbl(col1, col2) (PostgreSQL)
25308        if !e.columns.is_empty() {
25309            self.write("(");
25310            for (i, col) in e.columns.iter().enumerate() {
25311                if i > 0 {
25312                    self.write(", ");
25313                }
25314                self.write(col);
25315            }
25316            self.write(")");
25317        }
25318        if let Some(partition) = &e.partition {
25319            self.write_space();
25320            self.generate_expression(partition)?;
25321        }
25322        if let Some(mode) = &e.mode {
25323            self.write_space();
25324            self.generate_expression(mode)?;
25325        }
25326        if let Some(expression) = &e.expression {
25327            self.write_space();
25328            self.generate_expression(expression)?;
25329        }
25330        if !e.properties.is_empty() {
25331            self.write_space();
25332            self.write_keyword(self.config.with_properties_prefix);
25333            self.write(" (");
25334            for (i, prop) in e.properties.iter().enumerate() {
25335                if i > 0 {
25336                    self.write(", ");
25337                }
25338                self.generate_expression(prop)?;
25339            }
25340            self.write(")");
25341        }
25342        Ok(())
25343    }
25344
25345    fn generate_analyze_delete(&mut self, e: &AnalyzeDelete) -> Result<()> {
25346        // Python: return f"DELETE{kind} STATISTICS"
25347        self.write_keyword("DELETE");
25348        if let Some(kind) = &e.kind {
25349            self.write_space();
25350            self.write_keyword(kind);
25351        }
25352        self.write_space();
25353        self.write_keyword("STATISTICS");
25354        Ok(())
25355    }
25356
25357    fn generate_analyze_histogram(&mut self, e: &AnalyzeHistogram) -> Result<()> {
25358        // Python: return f"{this} HISTOGRAM ON {columns}{inner_expression}{update_options}"
25359        // Write `this` (UPDATE or DROP) as keyword to avoid quoting reserved words
25360        if let Expression::Identifier(id) = e.this.as_ref() {
25361            self.write_keyword(&id.name);
25362        } else {
25363            self.generate_expression(&e.this)?;
25364        }
25365        self.write_space();
25366        self.write_keyword("HISTOGRAM ON");
25367        self.write_space();
25368        for (i, expr) in e.expressions.iter().enumerate() {
25369            if i > 0 {
25370                self.write(", ");
25371            }
25372            self.generate_expression(expr)?;
25373        }
25374        if let Some(expression) = &e.expression {
25375            self.write_space();
25376            self.generate_expression(expression)?;
25377        }
25378        if let Some(update_options) = &e.update_options {
25379            self.write_space();
25380            self.generate_expression(update_options)?;
25381            self.write_space();
25382            self.write_keyword("UPDATE");
25383        }
25384        Ok(())
25385    }
25386
25387    fn generate_analyze_list_chained_rows(&mut self, e: &AnalyzeListChainedRows) -> Result<()> {
25388        // Python: return f"LIST CHAINED ROWS{inner_expression}"
25389        self.write_keyword("LIST CHAINED ROWS");
25390        if let Some(expression) = &e.expression {
25391            self.write_space();
25392            self.write_keyword("INTO");
25393            self.write_space();
25394            self.generate_expression(expression)?;
25395        }
25396        Ok(())
25397    }
25398
25399    fn generate_analyze_sample(&mut self, e: &AnalyzeSample) -> Result<()> {
25400        // Python: return f"SAMPLE {sample} {kind}"
25401        self.write_keyword("SAMPLE");
25402        self.write_space();
25403        if let Some(sample) = &e.sample {
25404            self.generate_expression(sample)?;
25405            self.write_space();
25406        }
25407        self.write_keyword(&e.kind);
25408        Ok(())
25409    }
25410
25411    fn generate_analyze_statistics(&mut self, e: &AnalyzeStatistics) -> Result<()> {
25412        // Python: return f"{kind}{option} STATISTICS{this}{columns}"
25413        self.write_keyword(&e.kind);
25414        if let Some(option) = &e.option {
25415            self.write_space();
25416            self.generate_expression(option)?;
25417        }
25418        self.write_space();
25419        self.write_keyword("STATISTICS");
25420        if let Some(this) = &e.this {
25421            self.write_space();
25422            self.generate_expression(this)?;
25423        }
25424        if !e.expressions.is_empty() {
25425            self.write_space();
25426            for (i, expr) in e.expressions.iter().enumerate() {
25427                if i > 0 {
25428                    self.write(", ");
25429                }
25430                self.generate_expression(expr)?;
25431            }
25432        }
25433        Ok(())
25434    }
25435
25436    fn generate_analyze_validate(&mut self, e: &AnalyzeValidate) -> Result<()> {
25437        // Python: return f"VALIDATE {kind}{this}{inner_expression}"
25438        self.write_keyword("VALIDATE");
25439        self.write_space();
25440        self.write_keyword(&e.kind);
25441        if let Some(this) = &e.this {
25442            self.write_space();
25443            // this is a keyword string like "UPDATE", "CASCADE FAST", etc. - write as keywords
25444            if let Expression::Identifier(id) = this.as_ref() {
25445                self.write_keyword(&id.name);
25446            } else {
25447                self.generate_expression(this)?;
25448            }
25449        }
25450        if let Some(expression) = &e.expression {
25451            self.write_space();
25452            self.write_keyword("INTO");
25453            self.write_space();
25454            self.generate_expression(expression)?;
25455        }
25456        Ok(())
25457    }
25458
25459    fn generate_analyze_with(&mut self, e: &AnalyzeWith) -> Result<()> {
25460        // Python: return f"WITH {expressions}"
25461        self.write_keyword("WITH");
25462        self.write_space();
25463        for (i, expr) in e.expressions.iter().enumerate() {
25464            if i > 0 {
25465                self.write(", ");
25466            }
25467            self.generate_expression(expr)?;
25468        }
25469        Ok(())
25470    }
25471
25472    fn generate_anonymous(&mut self, e: &Anonymous) -> Result<()> {
25473        // Anonymous represents a generic function call: FUNC_NAME(args...)
25474        // Python: return self.func(self.sql(expression, "this"), *expression.expressions)
25475        self.generate_expression(&e.this)?;
25476        self.write("(");
25477        for (i, arg) in e.expressions.iter().enumerate() {
25478            if i > 0 {
25479                self.write(", ");
25480            }
25481            self.generate_expression(arg)?;
25482        }
25483        self.write(")");
25484        Ok(())
25485    }
25486
25487    fn generate_anonymous_agg_func(&mut self, e: &AnonymousAggFunc) -> Result<()> {
25488        // Same as Anonymous but for aggregate functions
25489        self.generate_expression(&e.this)?;
25490        self.write("(");
25491        for (i, arg) in e.expressions.iter().enumerate() {
25492            if i > 0 {
25493                self.write(", ");
25494            }
25495            self.generate_expression(arg)?;
25496        }
25497        self.write(")");
25498        Ok(())
25499    }
25500
25501    fn generate_apply(&mut self, e: &Apply) -> Result<()> {
25502        // Python: return f"{this} APPLY({expr})"
25503        self.generate_expression(&e.this)?;
25504        self.write_space();
25505        self.write_keyword("APPLY");
25506        self.write("(");
25507        self.generate_expression(&e.expression)?;
25508        self.write(")");
25509        Ok(())
25510    }
25511
25512    fn generate_approx_percentile_estimate(&mut self, e: &ApproxPercentileEstimate) -> Result<()> {
25513        // APPROX_PERCENTILE_ESTIMATE(this, percentile)
25514        self.write_keyword("APPROX_PERCENTILE_ESTIMATE");
25515        self.write("(");
25516        self.generate_expression(&e.this)?;
25517        if let Some(percentile) = &e.percentile {
25518            self.write(", ");
25519            self.generate_expression(percentile)?;
25520        }
25521        self.write(")");
25522        Ok(())
25523    }
25524
25525    fn generate_approx_quantile(&mut self, e: &ApproxQuantile) -> Result<()> {
25526        // APPROX_QUANTILE(this, quantile[, accuracy][, weight])
25527        self.write_keyword("APPROX_QUANTILE");
25528        self.write("(");
25529        self.generate_expression(&e.this)?;
25530        if let Some(quantile) = &e.quantile {
25531            self.write(", ");
25532            self.generate_expression(quantile)?;
25533        }
25534        if let Some(accuracy) = &e.accuracy {
25535            self.write(", ");
25536            self.generate_expression(accuracy)?;
25537        }
25538        if let Some(weight) = &e.weight {
25539            self.write(", ");
25540            self.generate_expression(weight)?;
25541        }
25542        self.write(")");
25543        Ok(())
25544    }
25545
25546    fn generate_approx_quantiles(&mut self, e: &ApproxQuantiles) -> Result<()> {
25547        // APPROX_QUANTILES(this, expression)
25548        self.write_keyword("APPROX_QUANTILES");
25549        self.write("(");
25550        self.generate_expression(&e.this)?;
25551        if let Some(expression) = &e.expression {
25552            self.write(", ");
25553            self.generate_expression(expression)?;
25554        }
25555        self.write(")");
25556        Ok(())
25557    }
25558
25559    fn generate_approx_top_k(&mut self, e: &ApproxTopK) -> Result<()> {
25560        // APPROX_TOP_K(this[, expression][, counters])
25561        self.write_keyword("APPROX_TOP_K");
25562        self.write("(");
25563        self.generate_expression(&e.this)?;
25564        if let Some(expression) = &e.expression {
25565            self.write(", ");
25566            self.generate_expression(expression)?;
25567        }
25568        if let Some(counters) = &e.counters {
25569            self.write(", ");
25570            self.generate_expression(counters)?;
25571        }
25572        self.write(")");
25573        Ok(())
25574    }
25575
25576    fn generate_approx_top_k_accumulate(&mut self, e: &ApproxTopKAccumulate) -> Result<()> {
25577        // APPROX_TOP_K_ACCUMULATE(this[, expression])
25578        self.write_keyword("APPROX_TOP_K_ACCUMULATE");
25579        self.write("(");
25580        self.generate_expression(&e.this)?;
25581        if let Some(expression) = &e.expression {
25582            self.write(", ");
25583            self.generate_expression(expression)?;
25584        }
25585        self.write(")");
25586        Ok(())
25587    }
25588
25589    fn generate_approx_top_k_combine(&mut self, e: &ApproxTopKCombine) -> Result<()> {
25590        // APPROX_TOP_K_COMBINE(this[, expression])
25591        self.write_keyword("APPROX_TOP_K_COMBINE");
25592        self.write("(");
25593        self.generate_expression(&e.this)?;
25594        if let Some(expression) = &e.expression {
25595            self.write(", ");
25596            self.generate_expression(expression)?;
25597        }
25598        self.write(")");
25599        Ok(())
25600    }
25601
25602    fn generate_approx_top_k_estimate(&mut self, e: &ApproxTopKEstimate) -> Result<()> {
25603        // APPROX_TOP_K_ESTIMATE(this[, expression])
25604        self.write_keyword("APPROX_TOP_K_ESTIMATE");
25605        self.write("(");
25606        self.generate_expression(&e.this)?;
25607        if let Some(expression) = &e.expression {
25608            self.write(", ");
25609            self.generate_expression(expression)?;
25610        }
25611        self.write(")");
25612        Ok(())
25613    }
25614
25615    fn generate_approx_top_sum(&mut self, e: &ApproxTopSum) -> Result<()> {
25616        // APPROX_TOP_SUM(this, expression[, count])
25617        self.write_keyword("APPROX_TOP_SUM");
25618        self.write("(");
25619        self.generate_expression(&e.this)?;
25620        self.write(", ");
25621        self.generate_expression(&e.expression)?;
25622        if let Some(count) = &e.count {
25623            self.write(", ");
25624            self.generate_expression(count)?;
25625        }
25626        self.write(")");
25627        Ok(())
25628    }
25629
25630    fn generate_arg_max(&mut self, e: &ArgMax) -> Result<()> {
25631        // ARG_MAX(this, expression[, count])
25632        self.write_keyword("ARG_MAX");
25633        self.write("(");
25634        self.generate_expression(&e.this)?;
25635        self.write(", ");
25636        self.generate_expression(&e.expression)?;
25637        if let Some(count) = &e.count {
25638            self.write(", ");
25639            self.generate_expression(count)?;
25640        }
25641        self.write(")");
25642        Ok(())
25643    }
25644
25645    fn generate_arg_min(&mut self, e: &ArgMin) -> Result<()> {
25646        // ARG_MIN(this, expression[, count])
25647        self.write_keyword("ARG_MIN");
25648        self.write("(");
25649        self.generate_expression(&e.this)?;
25650        self.write(", ");
25651        self.generate_expression(&e.expression)?;
25652        if let Some(count) = &e.count {
25653            self.write(", ");
25654            self.generate_expression(count)?;
25655        }
25656        self.write(")");
25657        Ok(())
25658    }
25659
25660    fn generate_array_all(&mut self, e: &ArrayAll) -> Result<()> {
25661        // ARRAY_ALL(this, expression)
25662        self.write_keyword("ARRAY_ALL");
25663        self.write("(");
25664        self.generate_expression(&e.this)?;
25665        self.write(", ");
25666        self.generate_expression(&e.expression)?;
25667        self.write(")");
25668        Ok(())
25669    }
25670
25671    fn generate_array_any(&mut self, e: &ArrayAny) -> Result<()> {
25672        // ARRAY_ANY(this, expression) - fallback implementation
25673        self.write_keyword("ARRAY_ANY");
25674        self.write("(");
25675        self.generate_expression(&e.this)?;
25676        self.write(", ");
25677        self.generate_expression(&e.expression)?;
25678        self.write(")");
25679        Ok(())
25680    }
25681
25682    fn generate_array_construct_compact(&mut self, e: &ArrayConstructCompact) -> Result<()> {
25683        // ARRAY_CONSTRUCT_COMPACT(expressions...)
25684        self.write_keyword("ARRAY_CONSTRUCT_COMPACT");
25685        self.write("(");
25686        for (i, expr) in e.expressions.iter().enumerate() {
25687            if i > 0 {
25688                self.write(", ");
25689            }
25690            self.generate_expression(expr)?;
25691        }
25692        self.write(")");
25693        Ok(())
25694    }
25695
25696    fn generate_array_sum(&mut self, e: &ArraySum) -> Result<()> {
25697        // ARRAY_SUM(this[, expression])
25698        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
25699            self.write("arraySum");
25700        } else {
25701            self.write_keyword("ARRAY_SUM");
25702        }
25703        self.write("(");
25704        self.generate_expression(&e.this)?;
25705        if let Some(expression) = &e.expression {
25706            self.write(", ");
25707            self.generate_expression(expression)?;
25708        }
25709        self.write(")");
25710        Ok(())
25711    }
25712
25713    fn generate_at_index(&mut self, e: &AtIndex) -> Result<()> {
25714        // Python: return f"{this} AT {index}"
25715        self.generate_expression(&e.this)?;
25716        self.write_space();
25717        self.write_keyword("AT");
25718        self.write_space();
25719        self.generate_expression(&e.expression)?;
25720        Ok(())
25721    }
25722
25723    fn generate_attach(&mut self, e: &Attach) -> Result<()> {
25724        // Python: return f"ATTACH{exists_sql} {this}{expressions}"
25725        self.write_keyword("ATTACH");
25726        if e.exists {
25727            self.write_space();
25728            self.write_keyword("IF NOT EXISTS");
25729        }
25730        self.write_space();
25731        self.generate_expression(&e.this)?;
25732        if !e.expressions.is_empty() {
25733            self.write(" (");
25734            for (i, expr) in e.expressions.iter().enumerate() {
25735                if i > 0 {
25736                    self.write(", ");
25737                }
25738                self.generate_expression(expr)?;
25739            }
25740            self.write(")");
25741        }
25742        Ok(())
25743    }
25744
25745    fn generate_attach_option(&mut self, e: &AttachOption) -> Result<()> {
25746        // AttachOption: this [expression]
25747        // Python sqlglot: no equals sign, just space-separated
25748        self.generate_expression(&e.this)?;
25749        if let Some(expression) = &e.expression {
25750            self.write_space();
25751            self.generate_expression(expression)?;
25752        }
25753        Ok(())
25754    }
25755
25756    /// Generate the auto_increment keyword and options for a column definition.
25757    /// Different dialects use different syntax: IDENTITY, AUTOINCREMENT, AUTO_INCREMENT,
25758    /// GENERATED AS IDENTITY, etc.
25759    fn generate_auto_increment_keyword(
25760        &mut self,
25761        col: &crate::expressions::ColumnDef,
25762    ) -> Result<()> {
25763        use crate::dialects::DialectType;
25764        if matches!(self.config.dialect, Some(DialectType::Redshift)) {
25765            self.write_keyword("IDENTITY");
25766            if col.auto_increment_start.is_some() || col.auto_increment_increment.is_some() {
25767                self.write("(");
25768                if let Some(ref start) = col.auto_increment_start {
25769                    self.generate_expression(start)?;
25770                } else {
25771                    self.write("0");
25772                }
25773                self.write(", ");
25774                if let Some(ref inc) = col.auto_increment_increment {
25775                    self.generate_expression(inc)?;
25776                } else {
25777                    self.write("1");
25778                }
25779                self.write(")");
25780            }
25781        } else if matches!(
25782            self.config.dialect,
25783            Some(DialectType::Snowflake) | Some(DialectType::SQLite)
25784        ) {
25785            self.write_keyword("AUTOINCREMENT");
25786            if let Some(ref start) = col.auto_increment_start {
25787                self.write_space();
25788                self.write_keyword("START");
25789                self.write_space();
25790                self.generate_expression(start)?;
25791            }
25792            if let Some(ref inc) = col.auto_increment_increment {
25793                self.write_space();
25794                self.write_keyword("INCREMENT");
25795                self.write_space();
25796                self.generate_expression(inc)?;
25797            }
25798            if let Some(order) = col.auto_increment_order {
25799                self.write_space();
25800                if order {
25801                    self.write_keyword("ORDER");
25802                } else {
25803                    self.write_keyword("NOORDER");
25804                }
25805            }
25806        } else if matches!(self.config.dialect, Some(DialectType::PostgreSQL)) {
25807            self.write_keyword("GENERATED BY DEFAULT AS IDENTITY");
25808            if col.auto_increment_start.is_some() || col.auto_increment_increment.is_some() {
25809                self.write(" (");
25810                let mut first = true;
25811                if let Some(ref start) = col.auto_increment_start {
25812                    self.write_keyword("START WITH");
25813                    self.write_space();
25814                    self.generate_expression(start)?;
25815                    first = false;
25816                }
25817                if let Some(ref inc) = col.auto_increment_increment {
25818                    if !first {
25819                        self.write_space();
25820                    }
25821                    self.write_keyword("INCREMENT BY");
25822                    self.write_space();
25823                    self.generate_expression(inc)?;
25824                }
25825                self.write(")");
25826            }
25827        } else if matches!(self.config.dialect, Some(DialectType::Databricks)) {
25828            // IDENTITY(start, increment) -> GENERATED BY DEFAULT AS IDENTITY
25829            // Plain IDENTITY/AUTO_INCREMENT -> GENERATED ALWAYS AS IDENTITY
25830            if col.auto_increment_start.is_some() || col.auto_increment_increment.is_some() {
25831                self.write_keyword("GENERATED BY DEFAULT AS IDENTITY");
25832            } else {
25833                self.write_keyword("GENERATED ALWAYS AS IDENTITY");
25834            }
25835            if col.auto_increment_start.is_some() || col.auto_increment_increment.is_some() {
25836                self.write(" (");
25837                let mut first = true;
25838                if let Some(ref start) = col.auto_increment_start {
25839                    self.write_keyword("START WITH");
25840                    self.write_space();
25841                    self.generate_expression(start)?;
25842                    first = false;
25843                }
25844                if let Some(ref inc) = col.auto_increment_increment {
25845                    if !first {
25846                        self.write_space();
25847                    }
25848                    self.write_keyword("INCREMENT BY");
25849                    self.write_space();
25850                    self.generate_expression(inc)?;
25851                }
25852                self.write(")");
25853            }
25854        } else if matches!(
25855            self.config.dialect,
25856            Some(DialectType::TSQL) | Some(DialectType::Fabric)
25857        ) {
25858            self.write_keyword("IDENTITY");
25859            if col.auto_increment_start.is_some() || col.auto_increment_increment.is_some() {
25860                self.write("(");
25861                if let Some(ref start) = col.auto_increment_start {
25862                    self.generate_expression(start)?;
25863                } else {
25864                    self.write("0");
25865                }
25866                self.write(", ");
25867                if let Some(ref inc) = col.auto_increment_increment {
25868                    self.generate_expression(inc)?;
25869                } else {
25870                    self.write("1");
25871                }
25872                self.write(")");
25873            }
25874        } else {
25875            self.write_keyword("AUTO_INCREMENT");
25876            if let Some(ref start) = col.auto_increment_start {
25877                self.write_space();
25878                self.write_keyword("START");
25879                self.write_space();
25880                self.generate_expression(start)?;
25881            }
25882            if let Some(ref inc) = col.auto_increment_increment {
25883                self.write_space();
25884                self.write_keyword("INCREMENT");
25885                self.write_space();
25886                self.generate_expression(inc)?;
25887            }
25888            if let Some(order) = col.auto_increment_order {
25889                self.write_space();
25890                if order {
25891                    self.write_keyword("ORDER");
25892                } else {
25893                    self.write_keyword("NOORDER");
25894                }
25895            }
25896        }
25897        Ok(())
25898    }
25899
25900    fn generate_auto_increment_property(&mut self, e: &AutoIncrementProperty) -> Result<()> {
25901        // AUTO_INCREMENT=value
25902        self.write_keyword("AUTO_INCREMENT");
25903        self.write("=");
25904        self.generate_expression(&e.this)?;
25905        Ok(())
25906    }
25907
25908    fn generate_auto_refresh_property(&mut self, e: &AutoRefreshProperty) -> Result<()> {
25909        // AUTO_REFRESH=value
25910        self.write_keyword("AUTO_REFRESH");
25911        self.write("=");
25912        self.generate_expression(&e.this)?;
25913        Ok(())
25914    }
25915
25916    fn generate_backup_property(&mut self, e: &BackupProperty) -> Result<()> {
25917        // BACKUP YES|NO (Redshift syntax uses space, not equals)
25918        self.write_keyword("BACKUP");
25919        self.write_space();
25920        self.generate_expression(&e.this)?;
25921        Ok(())
25922    }
25923
25924    fn generate_base64_decode_binary(&mut self, e: &Base64DecodeBinary) -> Result<()> {
25925        // BASE64_DECODE_BINARY(this[, alphabet])
25926        self.write_keyword("BASE64_DECODE_BINARY");
25927        self.write("(");
25928        self.generate_expression(&e.this)?;
25929        if let Some(alphabet) = &e.alphabet {
25930            self.write(", ");
25931            self.generate_expression(alphabet)?;
25932        }
25933        self.write(")");
25934        Ok(())
25935    }
25936
25937    fn generate_base64_decode_string(&mut self, e: &Base64DecodeString) -> Result<()> {
25938        // BASE64_DECODE_STRING(this[, alphabet])
25939        self.write_keyword("BASE64_DECODE_STRING");
25940        self.write("(");
25941        self.generate_expression(&e.this)?;
25942        if let Some(alphabet) = &e.alphabet {
25943            self.write(", ");
25944            self.generate_expression(alphabet)?;
25945        }
25946        self.write(")");
25947        Ok(())
25948    }
25949
25950    fn generate_base64_encode(&mut self, e: &Base64Encode) -> Result<()> {
25951        // BASE64_ENCODE(this[, max_line_length][, alphabet])
25952        self.write_keyword("BASE64_ENCODE");
25953        self.write("(");
25954        self.generate_expression(&e.this)?;
25955        if let Some(max_line_length) = &e.max_line_length {
25956            self.write(", ");
25957            self.generate_expression(max_line_length)?;
25958        }
25959        if let Some(alphabet) = &e.alphabet {
25960            self.write(", ");
25961            self.generate_expression(alphabet)?;
25962        }
25963        self.write(")");
25964        Ok(())
25965    }
25966
25967    fn generate_block_compression_property(&mut self, e: &BlockCompressionProperty) -> Result<()> {
25968        // BLOCKCOMPRESSION=... (complex Teradata property)
25969        self.write_keyword("BLOCKCOMPRESSION");
25970        self.write("=");
25971        if let Some(autotemp) = &e.autotemp {
25972            self.write_keyword("AUTOTEMP");
25973            self.write("(");
25974            self.generate_expression(autotemp)?;
25975            self.write(")");
25976        }
25977        if let Some(always) = &e.always {
25978            self.generate_expression(always)?;
25979        }
25980        if let Some(default) = &e.default {
25981            self.generate_expression(default)?;
25982        }
25983        if let Some(manual) = &e.manual {
25984            self.generate_expression(manual)?;
25985        }
25986        if let Some(never) = &e.never {
25987            self.generate_expression(never)?;
25988        }
25989        Ok(())
25990    }
25991
25992    fn generate_booland(&mut self, e: &Booland) -> Result<()> {
25993        // Python: return f"(({self.sql(expression, 'this')}) AND ({self.sql(expression, 'expression')}))"
25994        self.write("((");
25995        self.generate_expression(&e.this)?;
25996        self.write(") ");
25997        self.write_keyword("AND");
25998        self.write(" (");
25999        self.generate_expression(&e.expression)?;
26000        self.write("))");
26001        Ok(())
26002    }
26003
26004    fn generate_boolor(&mut self, e: &Boolor) -> Result<()> {
26005        // Python: return f"(({self.sql(expression, 'this')}) OR ({self.sql(expression, 'expression')}))"
26006        self.write("((");
26007        self.generate_expression(&e.this)?;
26008        self.write(") ");
26009        self.write_keyword("OR");
26010        self.write(" (");
26011        self.generate_expression(&e.expression)?;
26012        self.write("))");
26013        Ok(())
26014    }
26015
26016    fn generate_build_property(&mut self, e: &BuildProperty) -> Result<()> {
26017        // BUILD value (e.g., BUILD IMMEDIATE, BUILD DEFERRED)
26018        self.write_keyword("BUILD");
26019        self.write_space();
26020        self.generate_expression(&e.this)?;
26021        Ok(())
26022    }
26023
26024    fn generate_byte_string(&mut self, e: &ByteString) -> Result<()> {
26025        // Byte string literal like B'...' or X'...'
26026        self.generate_expression(&e.this)?;
26027        Ok(())
26028    }
26029
26030    fn generate_case_specific_column_constraint(
26031        &mut self,
26032        e: &CaseSpecificColumnConstraint,
26033    ) -> Result<()> {
26034        // CASESPECIFIC or NOT CASESPECIFIC (Teradata)
26035        if e.not_.is_some() {
26036            self.write_keyword("NOT");
26037            self.write_space();
26038        }
26039        self.write_keyword("CASESPECIFIC");
26040        Ok(())
26041    }
26042
26043    fn generate_cast_to_str_type(&mut self, e: &CastToStrType) -> Result<()> {
26044        // Cast to string type (dialect-specific)
26045        self.write_keyword("CAST");
26046        self.write("(");
26047        self.generate_expression(&e.this)?;
26048        if self.config.dialect == Some(DialectType::ClickHouse) {
26049            // ClickHouse: CAST(expr, 'type_string')
26050            self.write(", ");
26051        } else {
26052            self.write_space();
26053            self.write_keyword("AS");
26054            self.write_space();
26055        }
26056        if let Some(to) = &e.to {
26057            self.generate_expression(to)?;
26058        }
26059        self.write(")");
26060        Ok(())
26061    }
26062
26063    fn generate_changes(&mut self, e: &Changes) -> Result<()> {
26064        // CHANGES (INFORMATION => value) AT|BEFORE (...) END (...)
26065        // Python: f"CHANGES ({information}){at_before}{end}"
26066        self.write_keyword("CHANGES");
26067        self.write(" (");
26068        if let Some(information) = &e.information {
26069            self.write_keyword("INFORMATION");
26070            self.write(" => ");
26071            self.generate_expression(information)?;
26072        }
26073        self.write(")");
26074        // at_before and end are HistoricalData expressions that generate their own keywords
26075        if let Some(at_before) = &e.at_before {
26076            self.write(" ");
26077            self.generate_expression(at_before)?;
26078        }
26079        if let Some(end) = &e.end {
26080            self.write(" ");
26081            self.generate_expression(end)?;
26082        }
26083        Ok(())
26084    }
26085
26086    fn generate_character_set_column_constraint(
26087        &mut self,
26088        e: &CharacterSetColumnConstraint,
26089    ) -> Result<()> {
26090        // CHARACTER SET charset_name
26091        self.write_keyword("CHARACTER SET");
26092        self.write_space();
26093        self.generate_expression(&e.this)?;
26094        Ok(())
26095    }
26096
26097    fn generate_character_set_property(&mut self, e: &CharacterSetProperty) -> Result<()> {
26098        // [DEFAULT] CHARACTER SET=value
26099        if e.default.is_some() {
26100            self.write_keyword("DEFAULT");
26101            self.write_space();
26102        }
26103        self.write_keyword("CHARACTER SET");
26104        self.write("=");
26105        self.generate_expression(&e.this)?;
26106        Ok(())
26107    }
26108
26109    fn generate_check_column_constraint(&mut self, e: &CheckColumnConstraint) -> Result<()> {
26110        // Python: return f"CHECK ({self.sql(expression, 'this')}){enforced}"
26111        self.write_keyword("CHECK");
26112        self.write(" (");
26113        self.generate_expression(&e.this)?;
26114        self.write(")");
26115        if e.enforced.is_some() {
26116            self.write_space();
26117            self.write_keyword("ENFORCED");
26118        }
26119        Ok(())
26120    }
26121
26122    fn generate_assume_column_constraint(&mut self, e: &AssumeColumnConstraint) -> Result<()> {
26123        // Python: return f"ASSUME ({self.sql(e, 'this')})"
26124        self.write_keyword("ASSUME");
26125        self.write(" (");
26126        self.generate_expression(&e.this)?;
26127        self.write(")");
26128        Ok(())
26129    }
26130
26131    fn generate_check_json(&mut self, e: &CheckJson) -> Result<()> {
26132        // CHECK_JSON(this)
26133        self.write_keyword("CHECK_JSON");
26134        self.write("(");
26135        self.generate_expression(&e.this)?;
26136        self.write(")");
26137        Ok(())
26138    }
26139
26140    fn generate_check_xml(&mut self, e: &CheckXml) -> Result<()> {
26141        // CHECK_XML(this)
26142        self.write_keyword("CHECK_XML");
26143        self.write("(");
26144        self.generate_expression(&e.this)?;
26145        self.write(")");
26146        Ok(())
26147    }
26148
26149    fn generate_checksum_property(&mut self, e: &ChecksumProperty) -> Result<()> {
26150        // CHECKSUM=[ON|OFF|DEFAULT]
26151        self.write_keyword("CHECKSUM");
26152        self.write("=");
26153        if e.on.is_some() {
26154            self.write_keyword("ON");
26155        } else if e.default.is_some() {
26156            self.write_keyword("DEFAULT");
26157        } else {
26158            self.write_keyword("OFF");
26159        }
26160        Ok(())
26161    }
26162
26163    fn generate_clone(&mut self, e: &Clone) -> Result<()> {
26164        // Python: return f"{shallow}{keyword} {this}"
26165        if e.shallow.is_some() {
26166            self.write_keyword("SHALLOW");
26167            self.write_space();
26168        }
26169        if e.copy.is_some() {
26170            self.write_keyword("COPY");
26171        } else {
26172            self.write_keyword("CLONE");
26173        }
26174        self.write_space();
26175        self.generate_expression(&e.this)?;
26176        Ok(())
26177    }
26178
26179    fn generate_cluster_by(&mut self, e: &ClusterBy) -> Result<()> {
26180        // CLUSTER BY (expressions)
26181        self.write_keyword("CLUSTER BY");
26182        self.write(" (");
26183        for (i, ord) in e.expressions.iter().enumerate() {
26184            if i > 0 {
26185                self.write(", ");
26186            }
26187            self.generate_ordered(ord)?;
26188        }
26189        self.write(")");
26190        Ok(())
26191    }
26192
26193    fn generate_cluster_by_columns_property(&mut self, e: &ClusterByColumnsProperty) -> Result<()> {
26194        // BigQuery table property: CLUSTER BY col1, col2
26195        self.write_keyword("CLUSTER BY");
26196        self.write_space();
26197        for (i, col) in e.columns.iter().enumerate() {
26198            if i > 0 {
26199                self.write(", ");
26200            }
26201            self.generate_identifier(col)?;
26202        }
26203        Ok(())
26204    }
26205
26206    fn generate_clustered_by_property(&mut self, e: &ClusteredByProperty) -> Result<()> {
26207        // Python: return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS"
26208        self.write_keyword("CLUSTERED BY");
26209        self.write(" (");
26210        for (i, expr) in e.expressions.iter().enumerate() {
26211            if i > 0 {
26212                self.write(", ");
26213            }
26214            self.generate_expression(expr)?;
26215        }
26216        self.write(")");
26217        if let Some(sorted_by) = &e.sorted_by {
26218            self.write_space();
26219            self.write_keyword("SORTED BY");
26220            self.write(" (");
26221            // Unwrap Tuple to avoid double parentheses
26222            if let Expression::Tuple(t) = sorted_by.as_ref() {
26223                for (i, expr) in t.expressions.iter().enumerate() {
26224                    if i > 0 {
26225                        self.write(", ");
26226                    }
26227                    self.generate_expression(expr)?;
26228                }
26229            } else {
26230                self.generate_expression(sorted_by)?;
26231            }
26232            self.write(")");
26233        }
26234        if let Some(buckets) = &e.buckets {
26235            self.write_space();
26236            self.write_keyword("INTO");
26237            self.write_space();
26238            self.generate_expression(buckets)?;
26239            self.write_space();
26240            self.write_keyword("BUCKETS");
26241        }
26242        Ok(())
26243    }
26244
26245    fn generate_collate_property(&mut self, e: &CollateProperty) -> Result<()> {
26246        // [DEFAULT] COLLATE [=] value
26247        // BigQuery uses space: DEFAULT COLLATE 'en'
26248        // Others use equals: COLLATE='en'
26249        if e.default.is_some() {
26250            self.write_keyword("DEFAULT");
26251            self.write_space();
26252        }
26253        self.write_keyword("COLLATE");
26254        // BigQuery uses space between COLLATE and value
26255        match self.config.dialect {
26256            Some(DialectType::BigQuery) => self.write_space(),
26257            _ => self.write("="),
26258        }
26259        self.generate_expression(&e.this)?;
26260        Ok(())
26261    }
26262
26263    fn generate_column_constraint(&mut self, e: &ColumnConstraint) -> Result<()> {
26264        // ColumnConstraint is an enum
26265        match e {
26266            ColumnConstraint::NotNull => {
26267                self.write_keyword("NOT NULL");
26268            }
26269            ColumnConstraint::Null => {
26270                self.write_keyword("NULL");
26271            }
26272            ColumnConstraint::Unique => {
26273                self.write_keyword("UNIQUE");
26274            }
26275            ColumnConstraint::PrimaryKey => {
26276                self.write_keyword("PRIMARY KEY");
26277            }
26278            ColumnConstraint::Default(expr) => {
26279                self.write_keyword("DEFAULT");
26280                self.write_space();
26281                self.generate_expression(expr)?;
26282            }
26283            ColumnConstraint::Check(expr) => {
26284                self.write_keyword("CHECK");
26285                self.write(" (");
26286                self.generate_expression(expr)?;
26287                self.write(")");
26288            }
26289            ColumnConstraint::References(fk_ref) => {
26290                if fk_ref.has_foreign_key_keywords {
26291                    self.write_keyword("FOREIGN KEY");
26292                    self.write_space();
26293                }
26294                self.write_keyword("REFERENCES");
26295                self.write_space();
26296                self.generate_table(&fk_ref.table)?;
26297                if !fk_ref.columns.is_empty() {
26298                    self.write(" (");
26299                    for (i, col) in fk_ref.columns.iter().enumerate() {
26300                        if i > 0 {
26301                            self.write(", ");
26302                        }
26303                        self.generate_identifier(col)?;
26304                    }
26305                    self.write(")");
26306                }
26307            }
26308            ColumnConstraint::GeneratedAsIdentity(gen) => {
26309                self.write_keyword("GENERATED");
26310                self.write_space();
26311                if gen.always {
26312                    self.write_keyword("ALWAYS");
26313                } else {
26314                    self.write_keyword("BY DEFAULT");
26315                    if gen.on_null {
26316                        self.write_space();
26317                        self.write_keyword("ON NULL");
26318                    }
26319                }
26320                self.write_space();
26321                self.write_keyword("AS IDENTITY");
26322            }
26323            ColumnConstraint::Collate(collation) => {
26324                self.write_keyword("COLLATE");
26325                self.write_space();
26326                self.generate_identifier(collation)?;
26327            }
26328            ColumnConstraint::Comment(comment) => {
26329                self.write_keyword("COMMENT");
26330                self.write(" '");
26331                self.write(comment);
26332                self.write("'");
26333            }
26334            ColumnConstraint::ComputedColumn(cc) => {
26335                self.generate_computed_column_inline(cc)?;
26336            }
26337            ColumnConstraint::GeneratedAsRow(gar) => {
26338                self.generate_generated_as_row_inline(gar)?;
26339            }
26340            ColumnConstraint::Tags(tags) => {
26341                self.write_keyword("TAG");
26342                self.write(" (");
26343                for (i, expr) in tags.expressions.iter().enumerate() {
26344                    if i > 0 {
26345                        self.write(", ");
26346                    }
26347                    self.generate_expression(expr)?;
26348                }
26349                self.write(")");
26350            }
26351            ColumnConstraint::Path(path_expr) => {
26352                self.write_keyword("PATH");
26353                self.write_space();
26354                self.generate_expression(path_expr)?;
26355            }
26356        }
26357        Ok(())
26358    }
26359
26360    fn generate_column_position(&mut self, e: &ColumnPosition) -> Result<()> {
26361        // ColumnPosition is an enum
26362        match e {
26363            ColumnPosition::First => {
26364                self.write_keyword("FIRST");
26365            }
26366            ColumnPosition::After(ident) => {
26367                self.write_keyword("AFTER");
26368                self.write_space();
26369                self.generate_identifier(ident)?;
26370            }
26371        }
26372        Ok(())
26373    }
26374
26375    fn generate_column_prefix(&mut self, e: &ColumnPrefix) -> Result<()> {
26376        // column(prefix)
26377        self.generate_expression(&e.this)?;
26378        self.write("(");
26379        self.generate_expression(&e.expression)?;
26380        self.write(")");
26381        Ok(())
26382    }
26383
26384    fn generate_columns(&mut self, e: &Columns) -> Result<()> {
26385        // If unpack is true, this came from * COLUMNS(pattern)
26386        // DuckDB syntax: * COLUMNS(c ILIKE '%suffix') or COLUMNS(pattern)
26387        if let Some(ref unpack) = e.unpack {
26388            if let Expression::Boolean(b) = unpack.as_ref() {
26389                if b.value {
26390                    self.write("*");
26391                }
26392            }
26393        }
26394        self.write_keyword("COLUMNS");
26395        self.write("(");
26396        self.generate_expression(&e.this)?;
26397        self.write(")");
26398        Ok(())
26399    }
26400
26401    fn generate_combined_agg_func(&mut self, e: &CombinedAggFunc) -> Result<()> {
26402        // Combined aggregate: FUNC(args) combined
26403        self.generate_expression(&e.this)?;
26404        self.write("(");
26405        for (i, expr) in e.expressions.iter().enumerate() {
26406            if i > 0 {
26407                self.write(", ");
26408            }
26409            self.generate_expression(expr)?;
26410        }
26411        self.write(")");
26412        Ok(())
26413    }
26414
26415    fn generate_combined_parameterized_agg(&mut self, e: &CombinedParameterizedAgg) -> Result<()> {
26416        // Combined parameterized aggregate: FUNC(params)(expressions)
26417        self.generate_expression(&e.this)?;
26418        self.write("(");
26419        for (i, param) in e.params.iter().enumerate() {
26420            if i > 0 {
26421                self.write(", ");
26422            }
26423            self.generate_expression(param)?;
26424        }
26425        self.write(")(");
26426        for (i, expr) in e.expressions.iter().enumerate() {
26427            if i > 0 {
26428                self.write(", ");
26429            }
26430            self.generate_expression(expr)?;
26431        }
26432        self.write(")");
26433        Ok(())
26434    }
26435
26436    fn generate_commit(&mut self, e: &Commit) -> Result<()> {
26437        // COMMIT [TRANSACTION [transaction_name]] [WITH (DELAYED_DURABILITY = ON|OFF)] [AND [NO] CHAIN]
26438        self.write_keyword("COMMIT");
26439
26440        // TSQL always uses COMMIT TRANSACTION
26441        if e.this.is_none()
26442            && matches!(
26443                self.config.dialect,
26444                Some(DialectType::TSQL) | Some(DialectType::Fabric)
26445            )
26446        {
26447            self.write_space();
26448            self.write_keyword("TRANSACTION");
26449        }
26450
26451        // Check if this has TRANSACTION keyword or transaction name
26452        if let Some(this) = &e.this {
26453            // Check if it's just the "TRANSACTION" marker or an actual transaction name
26454            let is_transaction_marker = matches!(
26455                this.as_ref(),
26456                Expression::Identifier(id) if id.name == "TRANSACTION"
26457            );
26458
26459            self.write_space();
26460            self.write_keyword("TRANSACTION");
26461
26462            // If it's a real transaction name, output it
26463            if !is_transaction_marker {
26464                self.write_space();
26465                self.generate_expression(this)?;
26466            }
26467        }
26468
26469        // Output WITH (DELAYED_DURABILITY = ON|OFF) for TSQL
26470        if let Some(durability) = &e.durability {
26471            self.write_space();
26472            self.write_keyword("WITH");
26473            self.write(" (");
26474            self.write_keyword("DELAYED_DURABILITY");
26475            self.write(" = ");
26476            if let Expression::Boolean(BooleanLiteral { value: true }) = durability.as_ref() {
26477                self.write_keyword("ON");
26478            } else {
26479                self.write_keyword("OFF");
26480            }
26481            self.write(")");
26482        }
26483
26484        // Output AND [NO] CHAIN
26485        if let Some(chain) = &e.chain {
26486            self.write_space();
26487            if let Expression::Boolean(BooleanLiteral { value: false }) = chain.as_ref() {
26488                self.write_keyword("AND NO CHAIN");
26489            } else {
26490                self.write_keyword("AND CHAIN");
26491            }
26492        }
26493        Ok(())
26494    }
26495
26496    fn generate_comprehension(&mut self, e: &Comprehension) -> Result<()> {
26497        // Python-style comprehension: [expr FOR var[, pos] IN iterator IF condition]
26498        self.write("[");
26499        self.generate_expression(&e.this)?;
26500        self.write_space();
26501        self.write_keyword("FOR");
26502        self.write_space();
26503        self.generate_expression(&e.expression)?;
26504        // Handle optional position variable (for enumerate-like syntax)
26505        if let Some(pos) = &e.position {
26506            self.write(", ");
26507            self.generate_expression(pos)?;
26508        }
26509        if let Some(iterator) = &e.iterator {
26510            self.write_space();
26511            self.write_keyword("IN");
26512            self.write_space();
26513            self.generate_expression(iterator)?;
26514        }
26515        if let Some(condition) = &e.condition {
26516            self.write_space();
26517            self.write_keyword("IF");
26518            self.write_space();
26519            self.generate_expression(condition)?;
26520        }
26521        self.write("]");
26522        Ok(())
26523    }
26524
26525    fn generate_compress(&mut self, e: &Compress) -> Result<()> {
26526        // COMPRESS(this[, method])
26527        self.write_keyword("COMPRESS");
26528        self.write("(");
26529        self.generate_expression(&e.this)?;
26530        if let Some(method) = &e.method {
26531            self.write(", '");
26532            self.write(method);
26533            self.write("'");
26534        }
26535        self.write(")");
26536        Ok(())
26537    }
26538
26539    fn generate_compress_column_constraint(&mut self, e: &CompressColumnConstraint) -> Result<()> {
26540        // Python: return f"COMPRESS {this}"
26541        self.write_keyword("COMPRESS");
26542        if let Some(this) = &e.this {
26543            self.write_space();
26544            self.generate_expression(this)?;
26545        }
26546        Ok(())
26547    }
26548
26549    fn generate_computed_column_constraint(&mut self, e: &ComputedColumnConstraint) -> Result<()> {
26550        // Python: return f"AS {this}{persisted}"
26551        self.write_keyword("AS");
26552        self.write_space();
26553        self.generate_expression(&e.this)?;
26554        if e.not_null.is_some() {
26555            self.write_space();
26556            self.write_keyword("PERSISTED NOT NULL");
26557        } else if e.persisted.is_some() {
26558            self.write_space();
26559            self.write_keyword("PERSISTED");
26560        }
26561        Ok(())
26562    }
26563
26564    /// Generate a ComputedColumn constraint inline within a column definition.
26565    /// Handles MySQL/PostgreSQL: GENERATED ALWAYS AS (expr) STORED|VIRTUAL
26566    /// Handles TSQL: AS (expr) [PERSISTED] [NOT NULL]
26567    fn generate_computed_column_inline(&mut self, cc: &ComputedColumn) -> Result<()> {
26568        let computed_expr = if matches!(
26569            self.config.dialect,
26570            Some(DialectType::TSQL) | Some(DialectType::Fabric)
26571        ) {
26572            match &*cc.expression {
26573                Expression::Year(y) if !matches!(&y.this, Expression::Cast(c) if matches!(c.to, DataType::Date)) =>
26574                {
26575                    let wrapped = Expression::Cast(Box::new(Cast {
26576                        this: y.this.clone(),
26577                        to: DataType::Date,
26578                        trailing_comments: Vec::new(),
26579                        double_colon_syntax: false,
26580                        format: None,
26581                        default: None,
26582                        inferred_type: None,
26583                    }));
26584                    Expression::Year(Box::new(UnaryFunc::new(wrapped)))
26585                }
26586                Expression::Function(f)
26587                    if f.name.eq_ignore_ascii_case("YEAR")
26588                        && f.args.len() == 1
26589                        && !matches!(&f.args[0], Expression::Cast(c) if matches!(c.to, DataType::Date)) =>
26590                {
26591                    let wrapped = Expression::Cast(Box::new(Cast {
26592                        this: f.args[0].clone(),
26593                        to: DataType::Date,
26594                        trailing_comments: Vec::new(),
26595                        double_colon_syntax: false,
26596                        format: None,
26597                        default: None,
26598                        inferred_type: None,
26599                    }));
26600                    Expression::Function(Box::new(Function::new("YEAR".to_string(), vec![wrapped])))
26601                }
26602                _ => *cc.expression.clone(),
26603            }
26604        } else {
26605            *cc.expression.clone()
26606        };
26607
26608        match cc.persistence_kind.as_deref() {
26609            Some("STORED") | Some("VIRTUAL") => {
26610                // MySQL/PostgreSQL: GENERATED ALWAYS AS (expr) STORED|VIRTUAL
26611                self.write_keyword("GENERATED ALWAYS AS");
26612                self.write(" (");
26613                self.generate_expression(&computed_expr)?;
26614                self.write(")");
26615                self.write_space();
26616                if cc.persisted {
26617                    self.write_keyword("STORED");
26618                } else {
26619                    self.write_keyword("VIRTUAL");
26620                }
26621            }
26622            Some("PERSISTED") => {
26623                // TSQL/SingleStore: AS (expr) PERSISTED [TYPE] [NOT NULL]
26624                self.write_keyword("AS");
26625                self.write(" (");
26626                self.generate_expression(&computed_expr)?;
26627                self.write(")");
26628                self.write_space();
26629                self.write_keyword("PERSISTED");
26630                // Output data type if present (SingleStore: PERSISTED TYPE NOT NULL)
26631                if let Some(ref dt) = cc.data_type {
26632                    self.write_space();
26633                    self.generate_data_type(dt)?;
26634                }
26635                if cc.not_null {
26636                    self.write_space();
26637                    self.write_keyword("NOT NULL");
26638                }
26639            }
26640            _ => {
26641                // Spark/Databricks/Hive: GENERATED ALWAYS AS (expr)
26642                // TSQL computed column without PERSISTED: AS (expr)
26643                if matches!(
26644                    self.config.dialect,
26645                    Some(DialectType::Spark)
26646                        | Some(DialectType::Databricks)
26647                        | Some(DialectType::Hive)
26648                ) {
26649                    self.write_keyword("GENERATED ALWAYS AS");
26650                    self.write(" (");
26651                    self.generate_expression(&computed_expr)?;
26652                    self.write(")");
26653                } else if matches!(
26654                    self.config.dialect,
26655                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
26656                ) {
26657                    self.write_keyword("AS");
26658                    let omit_parens = matches!(computed_expr, Expression::Year(_))
26659                        || matches!(&computed_expr, Expression::Function(f) if f.name.eq_ignore_ascii_case("YEAR"));
26660                    if omit_parens {
26661                        self.write_space();
26662                        self.generate_expression(&computed_expr)?;
26663                    } else {
26664                        self.write(" (");
26665                        self.generate_expression(&computed_expr)?;
26666                        self.write(")");
26667                    }
26668                } else {
26669                    self.write_keyword("AS");
26670                    self.write(" (");
26671                    self.generate_expression(&computed_expr)?;
26672                    self.write(")");
26673                }
26674            }
26675        }
26676        Ok(())
26677    }
26678
26679    /// Generate a GeneratedAsRow constraint inline within a column definition.
26680    /// TSQL temporal: GENERATED ALWAYS AS ROW START|END [HIDDEN]
26681    fn generate_generated_as_row_inline(&mut self, gar: &GeneratedAsRow) -> Result<()> {
26682        self.write_keyword("GENERATED ALWAYS AS ROW ");
26683        if gar.start {
26684            self.write_keyword("START");
26685        } else {
26686            self.write_keyword("END");
26687        }
26688        if gar.hidden {
26689            self.write_space();
26690            self.write_keyword("HIDDEN");
26691        }
26692        Ok(())
26693    }
26694
26695    /// Generate just the SYSTEM_VERSIONING=ON(...) content without WITH() wrapper.
26696    fn generate_system_versioning_content(
26697        &mut self,
26698        e: &WithSystemVersioningProperty,
26699    ) -> Result<()> {
26700        let mut parts = Vec::new();
26701
26702        if let Some(this) = &e.this {
26703            let mut s = String::from("HISTORY_TABLE=");
26704            let mut gen = Generator::with_arc_config(self.config.clone());
26705            gen.generate_expression(this)?;
26706            s.push_str(&gen.output);
26707            parts.push(s);
26708        }
26709
26710        if let Some(data_consistency) = &e.data_consistency {
26711            let mut s = String::from("DATA_CONSISTENCY_CHECK=");
26712            let mut gen = Generator::with_arc_config(self.config.clone());
26713            gen.generate_expression(data_consistency)?;
26714            s.push_str(&gen.output);
26715            parts.push(s);
26716        }
26717
26718        if let Some(retention_period) = &e.retention_period {
26719            let mut s = String::from("HISTORY_RETENTION_PERIOD=");
26720            let mut gen = Generator::with_arc_config(self.config.clone());
26721            gen.generate_expression(retention_period)?;
26722            s.push_str(&gen.output);
26723            parts.push(s);
26724        }
26725
26726        self.write_keyword("SYSTEM_VERSIONING");
26727        self.write("=");
26728
26729        if !parts.is_empty() {
26730            self.write_keyword("ON");
26731            self.write("(");
26732            self.write(&parts.join(", "));
26733            self.write(")");
26734        } else if e.on.is_some() {
26735            self.write_keyword("ON");
26736        } else {
26737            self.write_keyword("OFF");
26738        }
26739
26740        Ok(())
26741    }
26742
26743    fn generate_conditional_insert(&mut self, e: &ConditionalInsert) -> Result<()> {
26744        // Conditional INSERT for multi-table inserts
26745        // Output: [WHEN cond THEN | ELSE] INTO table [(cols)] [VALUES (...)]
26746        if e.else_.is_some() {
26747            self.write_keyword("ELSE");
26748            self.write_space();
26749        } else if let Some(expression) = &e.expression {
26750            self.write_keyword("WHEN");
26751            self.write_space();
26752            self.generate_expression(expression)?;
26753            self.write_space();
26754            self.write_keyword("THEN");
26755            self.write_space();
26756        }
26757
26758        // Handle Insert expression specially - output "INTO table (cols) VALUES (...)"
26759        // without the "INSERT " prefix
26760        if let Expression::Insert(insert) = e.this.as_ref() {
26761            self.write_keyword("INTO");
26762            self.write_space();
26763            self.generate_table(&insert.table)?;
26764
26765            // Optional column list
26766            if !insert.columns.is_empty() {
26767                self.write(" (");
26768                for (i, col) in insert.columns.iter().enumerate() {
26769                    if i > 0 {
26770                        self.write(", ");
26771                    }
26772                    self.generate_identifier(col)?;
26773                }
26774                self.write(")");
26775            }
26776
26777            // Optional VALUES clause
26778            if !insert.values.is_empty() {
26779                self.write_space();
26780                self.write_keyword("VALUES");
26781                for (row_idx, row) in insert.values.iter().enumerate() {
26782                    if row_idx > 0 {
26783                        self.write(", ");
26784                    }
26785                    self.write(" (");
26786                    for (i, val) in row.iter().enumerate() {
26787                        if i > 0 {
26788                            self.write(", ");
26789                        }
26790                        self.generate_expression(val)?;
26791                    }
26792                    self.write(")");
26793                }
26794            }
26795        } else {
26796            // Fallback for non-Insert expressions
26797            self.generate_expression(&e.this)?;
26798        }
26799        Ok(())
26800    }
26801
26802    fn generate_constraint(&mut self, e: &Constraint) -> Result<()> {
26803        // Python: return f"CONSTRAINT {this} {expressions}"
26804        self.write_keyword("CONSTRAINT");
26805        self.write_space();
26806        self.generate_expression(&e.this)?;
26807        if !e.expressions.is_empty() {
26808            self.write_space();
26809            for (i, expr) in e.expressions.iter().enumerate() {
26810                if i > 0 {
26811                    self.write_space();
26812                }
26813                self.generate_expression(expr)?;
26814            }
26815        }
26816        Ok(())
26817    }
26818
26819    fn generate_convert_timezone(&mut self, e: &ConvertTimezone) -> Result<()> {
26820        // CONVERT_TIMEZONE([source_tz,] target_tz, timestamp)
26821        self.write_keyword("CONVERT_TIMEZONE");
26822        self.write("(");
26823        let mut first = true;
26824        if let Some(source_tz) = &e.source_tz {
26825            self.generate_expression(source_tz)?;
26826            first = false;
26827        }
26828        if let Some(target_tz) = &e.target_tz {
26829            if !first {
26830                self.write(", ");
26831            }
26832            self.generate_expression(target_tz)?;
26833            first = false;
26834        }
26835        if let Some(timestamp) = &e.timestamp {
26836            if !first {
26837                self.write(", ");
26838            }
26839            self.generate_expression(timestamp)?;
26840        }
26841        self.write(")");
26842        Ok(())
26843    }
26844
26845    fn generate_convert_to_charset(&mut self, e: &ConvertToCharset) -> Result<()> {
26846        // CONVERT(this USING dest)
26847        self.write_keyword("CONVERT");
26848        self.write("(");
26849        self.generate_expression(&e.this)?;
26850        if let Some(dest) = &e.dest {
26851            self.write_space();
26852            self.write_keyword("USING");
26853            self.write_space();
26854            self.generate_expression(dest)?;
26855        }
26856        self.write(")");
26857        Ok(())
26858    }
26859
26860    fn generate_copy(&mut self, e: &CopyStmt) -> Result<()> {
26861        self.write_keyword("COPY");
26862        if e.is_into {
26863            self.write_space();
26864            self.write_keyword("INTO");
26865        }
26866        self.write_space();
26867
26868        // Generate target table or query (or stage for COPY INTO @stage)
26869        if let Expression::Literal(lit) = &e.this {
26870            if let Literal::String(s) = lit.as_ref() {
26871                if s.starts_with('@') {
26872                    self.write(s);
26873                } else {
26874                    self.generate_expression(&e.this)?;
26875                }
26876            }
26877        } else {
26878            self.generate_expression(&e.this)?;
26879        }
26880
26881        // FROM or TO based on kind
26882        if e.kind {
26883            // kind=true means FROM (loading into table)
26884            if self.config.pretty {
26885                self.write_newline();
26886            } else {
26887                self.write_space();
26888            }
26889            self.write_keyword("FROM");
26890            self.write_space();
26891        } else if !e.files.is_empty() {
26892            // kind=false means TO (exporting)
26893            if self.config.pretty {
26894                self.write_newline();
26895            } else {
26896                self.write_space();
26897            }
26898            self.write_keyword("TO");
26899            self.write_space();
26900        }
26901
26902        // Generate source/destination files
26903        for (i, file) in e.files.iter().enumerate() {
26904            if i > 0 {
26905                self.write_space();
26906            }
26907            // For stage references (strings starting with @), output without quotes
26908            if let Expression::Literal(lit) = file {
26909                if let Literal::String(s) = lit.as_ref() {
26910                    if s.starts_with('@') {
26911                        self.write(s);
26912                    } else {
26913                        self.generate_expression(file)?;
26914                    }
26915                }
26916            } else if let Expression::Identifier(id) = file {
26917                // Backtick-quoted file path (Databricks style: `s3://link`)
26918                if id.quoted {
26919                    self.write("`");
26920                    self.write(&id.name);
26921                    self.write("`");
26922                } else {
26923                    self.generate_expression(file)?;
26924                }
26925            } else {
26926                self.generate_expression(file)?;
26927            }
26928        }
26929
26930        // Generate credentials if present (Snowflake style - not wrapped in WITH)
26931        if !e.with_wrapped {
26932            if let Some(ref creds) = e.credentials {
26933                if let Some(ref storage) = creds.storage {
26934                    if self.config.pretty {
26935                        self.write_newline();
26936                    } else {
26937                        self.write_space();
26938                    }
26939                    self.write_keyword("STORAGE_INTEGRATION");
26940                    self.write(" = ");
26941                    self.write(storage);
26942                }
26943                if creds.credentials.is_empty() {
26944                    // Empty credentials: CREDENTIALS = ()
26945                    if self.config.pretty {
26946                        self.write_newline();
26947                    } else {
26948                        self.write_space();
26949                    }
26950                    self.write_keyword("CREDENTIALS");
26951                    self.write(" = ()");
26952                } else {
26953                    if self.config.pretty {
26954                        self.write_newline();
26955                    } else {
26956                        self.write_space();
26957                    }
26958                    self.write_keyword("CREDENTIALS");
26959                    // Check if this is Redshift-style (single value with empty key)
26960                    // vs Snowflake-style (multiple key=value pairs)
26961                    if creds.credentials.len() == 1 && creds.credentials[0].0.is_empty() {
26962                        // Redshift style: CREDENTIALS 'value'
26963                        self.write(" '");
26964                        self.write(&creds.credentials[0].1);
26965                        self.write("'");
26966                    } else {
26967                        // Snowflake style: CREDENTIALS = (KEY='value' ...)
26968                        self.write(" = (");
26969                        for (i, (k, v)) in creds.credentials.iter().enumerate() {
26970                            if i > 0 {
26971                                self.write_space();
26972                            }
26973                            self.write(k);
26974                            self.write("='");
26975                            self.write(v);
26976                            self.write("'");
26977                        }
26978                        self.write(")");
26979                    }
26980                }
26981                if let Some(ref encryption) = creds.encryption {
26982                    self.write_space();
26983                    self.write_keyword("ENCRYPTION");
26984                    self.write(" = ");
26985                    self.write(encryption);
26986                }
26987            }
26988        }
26989
26990        // Generate parameters
26991        if !e.params.is_empty() {
26992            if e.with_wrapped {
26993                // DuckDB/PostgreSQL/TSQL WITH (...) format
26994                self.write_space();
26995                self.write_keyword("WITH");
26996                self.write(" (");
26997                for (i, param) in e.params.iter().enumerate() {
26998                    if i > 0 {
26999                        self.write(", ");
27000                    }
27001                    self.generate_copy_param_with_format(param)?;
27002                }
27003                self.write(")");
27004            } else {
27005                // Snowflake/Redshift format: KEY = VALUE or KEY VALUE (space separated, no WITH wrapper)
27006                // For Redshift: IAM_ROLE value, CREDENTIALS 'value', REGION 'value', FORMAT type
27007                // For Snowflake: KEY = VALUE
27008                for param in &e.params {
27009                    if self.config.pretty {
27010                        self.write_newline();
27011                    } else {
27012                        self.write_space();
27013                    }
27014                    // Preserve original case of parameter name (important for Redshift COPY options)
27015                    self.write(&param.name);
27016                    if let Some(ref value) = param.value {
27017                        // Use = only if it was present in the original (param.eq)
27018                        if param.eq {
27019                            self.write(" = ");
27020                        } else {
27021                            self.write(" ");
27022                        }
27023                        if !param.values.is_empty() {
27024                            self.write("(");
27025                            for (i, v) in param.values.iter().enumerate() {
27026                                if i > 0 {
27027                                    self.write_space();
27028                                }
27029                                self.generate_copy_nested_param(v)?;
27030                            }
27031                            self.write(")");
27032                        } else {
27033                            // For COPY parameter values, output identifiers without quoting
27034                            self.generate_copy_param_value(value)?;
27035                        }
27036                    } else if !param.values.is_empty() {
27037                        // For varlen options like FORMAT_OPTIONS, COPY_OPTIONS - no = before (
27038                        if param.eq {
27039                            self.write(" = (");
27040                        } else {
27041                            self.write(" (");
27042                        }
27043                        // Determine separator for values inside parentheses:
27044                        // - Snowflake FILE_FORMAT = (TYPE=CSV FIELD_DELIMITER='|') → space-separated (has = before parens)
27045                        // - Databricks FORMAT_OPTIONS ('opt1'='true', 'opt2'='test') → comma-separated (no = before parens)
27046                        // - Simple value lists like FILES = ('file1', 'file2') → comma-separated
27047                        let is_key_value_pairs = param
27048                            .values
27049                            .first()
27050                            .map_or(false, |v| matches!(v, Expression::Eq(_)));
27051                        let sep = if is_key_value_pairs && param.eq {
27052                            " "
27053                        } else {
27054                            ", "
27055                        };
27056                        for (i, v) in param.values.iter().enumerate() {
27057                            if i > 0 {
27058                                self.write(sep);
27059                            }
27060                            self.generate_copy_nested_param(v)?;
27061                        }
27062                        self.write(")");
27063                    }
27064                }
27065            }
27066        }
27067
27068        Ok(())
27069    }
27070
27071    /// Generate a COPY parameter in WITH (...) format
27072    /// Handles both KEY = VALUE (TSQL) and KEY VALUE (DuckDB/PostgreSQL) formats
27073    fn generate_copy_param_with_format(&mut self, param: &CopyParameter) -> Result<()> {
27074        self.write_keyword(&param.name);
27075        if !param.values.is_empty() {
27076            // Nested values: CREDENTIAL = (IDENTITY='...', SECRET='...')
27077            self.write(" = (");
27078            for (i, v) in param.values.iter().enumerate() {
27079                if i > 0 {
27080                    self.write(", ");
27081                }
27082                self.generate_copy_nested_param(v)?;
27083            }
27084            self.write(")");
27085        } else if let Some(ref value) = param.value {
27086            if param.eq {
27087                self.write(" = ");
27088            } else {
27089                self.write(" ");
27090            }
27091            self.generate_expression(value)?;
27092        }
27093        Ok(())
27094    }
27095
27096    /// Generate nested parameter for COPY statements (KEY=VALUE without spaces)
27097    fn generate_copy_nested_param(&mut self, expr: &Expression) -> Result<()> {
27098        match expr {
27099            Expression::Eq(eq) => {
27100                // Generate key
27101                match &eq.left {
27102                    Expression::Column(c) => self.write(&c.name.name),
27103                    _ => self.generate_expression(&eq.left)?,
27104                }
27105                self.write("=");
27106                // Generate value
27107                match &eq.right {
27108                    Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
27109                        let Literal::String(s) = lit.as_ref() else {
27110                            unreachable!()
27111                        };
27112                        self.write("'");
27113                        self.write(s);
27114                        self.write("'");
27115                    }
27116                    Expression::Tuple(t) => {
27117                        // For lists like NULL_IF=('', 'str1')
27118                        self.write("(");
27119                        if self.config.pretty {
27120                            self.write_newline();
27121                            self.indent_level += 1;
27122                            for (i, item) in t.expressions.iter().enumerate() {
27123                                if i > 0 {
27124                                    self.write(", ");
27125                                }
27126                                self.write_indent();
27127                                self.generate_expression(item)?;
27128                            }
27129                            self.write_newline();
27130                            self.indent_level -= 1;
27131                        } else {
27132                            for (i, item) in t.expressions.iter().enumerate() {
27133                                if i > 0 {
27134                                    self.write(", ");
27135                                }
27136                                self.generate_expression(item)?;
27137                            }
27138                        }
27139                        self.write(")");
27140                    }
27141                    _ => self.generate_expression(&eq.right)?,
27142                }
27143                Ok(())
27144            }
27145            Expression::Column(c) => {
27146                // Standalone keyword like COMPRESSION
27147                self.write(&c.name.name);
27148                Ok(())
27149            }
27150            _ => self.generate_expression(expr),
27151        }
27152    }
27153
27154    /// Generate a COPY parameter value, outputting identifiers/columns without quoting
27155    /// This is needed for Redshift-style COPY params like: IAM_ROLE default, FORMAT orc
27156    fn generate_copy_param_value(&mut self, expr: &Expression) -> Result<()> {
27157        match expr {
27158            Expression::Column(c) => {
27159                // Output identifier, preserving quotes if originally quoted
27160                if c.name.quoted {
27161                    self.write("\"");
27162                    self.write(&c.name.name);
27163                    self.write("\"");
27164                } else {
27165                    self.write(&c.name.name);
27166                }
27167                Ok(())
27168            }
27169            Expression::Identifier(id) => {
27170                // Output identifier, preserving quotes if originally quoted
27171                if id.quoted {
27172                    self.write("\"");
27173                    self.write(&id.name);
27174                    self.write("\"");
27175                } else {
27176                    self.write(&id.name);
27177                }
27178                Ok(())
27179            }
27180            Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
27181                let Literal::String(s) = lit.as_ref() else {
27182                    unreachable!()
27183                };
27184                // Output string with quotes
27185                self.write("'");
27186                self.write(s);
27187                self.write("'");
27188                Ok(())
27189            }
27190            _ => self.generate_expression(expr),
27191        }
27192    }
27193
27194    fn generate_copy_parameter(&mut self, e: &CopyParameter) -> Result<()> {
27195        self.write_keyword(&e.name);
27196        if let Some(ref value) = e.value {
27197            if e.eq {
27198                self.write(" = ");
27199            } else {
27200                self.write(" ");
27201            }
27202            self.generate_expression(value)?;
27203        }
27204        if !e.values.is_empty() {
27205            if e.eq {
27206                self.write(" = ");
27207            } else {
27208                self.write(" ");
27209            }
27210            self.write("(");
27211            for (i, v) in e.values.iter().enumerate() {
27212                if i > 0 {
27213                    self.write(", ");
27214                }
27215                self.generate_expression(v)?;
27216            }
27217            self.write(")");
27218        }
27219        Ok(())
27220    }
27221
27222    fn generate_corr(&mut self, e: &Corr) -> Result<()> {
27223        // CORR(this, expression)
27224        self.write_keyword("CORR");
27225        self.write("(");
27226        self.generate_expression(&e.this)?;
27227        self.write(", ");
27228        self.generate_expression(&e.expression)?;
27229        self.write(")");
27230        Ok(())
27231    }
27232
27233    fn generate_cosine_distance(&mut self, e: &CosineDistance) -> Result<()> {
27234        // COSINE_DISTANCE(this, expression)
27235        self.write_keyword("COSINE_DISTANCE");
27236        self.write("(");
27237        self.generate_expression(&e.this)?;
27238        self.write(", ");
27239        self.generate_expression(&e.expression)?;
27240        self.write(")");
27241        Ok(())
27242    }
27243
27244    fn generate_covar_pop(&mut self, e: &CovarPop) -> Result<()> {
27245        // COVAR_POP(this, expression)
27246        self.write_keyword("COVAR_POP");
27247        self.write("(");
27248        self.generate_expression(&e.this)?;
27249        self.write(", ");
27250        self.generate_expression(&e.expression)?;
27251        self.write(")");
27252        Ok(())
27253    }
27254
27255    fn generate_covar_samp(&mut self, e: &CovarSamp) -> Result<()> {
27256        // COVAR_SAMP(this, expression)
27257        self.write_keyword("COVAR_SAMP");
27258        self.write("(");
27259        self.generate_expression(&e.this)?;
27260        self.write(", ");
27261        self.generate_expression(&e.expression)?;
27262        self.write(")");
27263        Ok(())
27264    }
27265
27266    fn generate_credentials(&mut self, e: &Credentials) -> Result<()> {
27267        // CREDENTIALS (key1='value1', key2='value2')
27268        self.write_keyword("CREDENTIALS");
27269        self.write(" (");
27270        for (i, (key, value)) in e.credentials.iter().enumerate() {
27271            if i > 0 {
27272                self.write(", ");
27273            }
27274            self.write(key);
27275            self.write("='");
27276            self.write(value);
27277            self.write("'");
27278        }
27279        self.write(")");
27280        Ok(())
27281    }
27282
27283    fn generate_credentials_property(&mut self, e: &CredentialsProperty) -> Result<()> {
27284        // CREDENTIALS=(expressions)
27285        self.write_keyword("CREDENTIALS");
27286        self.write("=(");
27287        for (i, expr) in e.expressions.iter().enumerate() {
27288            if i > 0 {
27289                self.write(", ");
27290            }
27291            self.generate_expression(expr)?;
27292        }
27293        self.write(")");
27294        Ok(())
27295    }
27296
27297    fn generate_cte(&mut self, e: &Cte) -> Result<()> {
27298        use crate::dialects::DialectType;
27299
27300        // Python: return f"{alias_sql}{key_expressions} AS {materialized or ''}{self.wrap(expression)}"
27301        // Output: alias [(col1, col2, ...)] AS [MATERIALIZED|NOT MATERIALIZED] (subquery)
27302        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) && !e.alias_first {
27303            self.generate_expression(&e.this)?;
27304            self.write_space();
27305            self.write_keyword("AS");
27306            self.write_space();
27307            self.generate_identifier(&e.alias)?;
27308            return Ok(());
27309        }
27310        self.write(&e.alias.name);
27311
27312        // BigQuery doesn't support column aliases in CTE definitions
27313        let skip_cte_columns = matches!(self.config.dialect, Some(DialectType::BigQuery));
27314
27315        if !e.columns.is_empty() && !skip_cte_columns {
27316            self.write("(");
27317            for (i, col) in e.columns.iter().enumerate() {
27318                if i > 0 {
27319                    self.write(", ");
27320                }
27321                self.write(&col.name);
27322            }
27323            self.write(")");
27324        }
27325        // USING KEY (columns) for DuckDB recursive CTEs
27326        if !e.key_expressions.is_empty() {
27327            self.write_space();
27328            self.write_keyword("USING KEY");
27329            self.write(" (");
27330            for (i, key) in e.key_expressions.iter().enumerate() {
27331                if i > 0 {
27332                    self.write(", ");
27333                }
27334                self.write(&key.name);
27335            }
27336            self.write(")");
27337        }
27338        self.write_space();
27339        self.write_keyword("AS");
27340        self.write_space();
27341        if let Some(materialized) = e.materialized {
27342            if materialized {
27343                self.write_keyword("MATERIALIZED");
27344            } else {
27345                self.write_keyword("NOT MATERIALIZED");
27346            }
27347            self.write_space();
27348        }
27349        self.write("(");
27350        self.generate_expression(&e.this)?;
27351        self.write(")");
27352        Ok(())
27353    }
27354
27355    fn generate_cube(&mut self, e: &Cube) -> Result<()> {
27356        // Python: return f"CUBE {self.wrap(expressions)}" if expressions else "WITH CUBE"
27357        if e.expressions.is_empty() {
27358            self.write_keyword("WITH CUBE");
27359        } else {
27360            self.write_keyword("CUBE");
27361            self.write("(");
27362            for (i, expr) in e.expressions.iter().enumerate() {
27363                if i > 0 {
27364                    self.write(", ");
27365                }
27366                self.generate_expression(expr)?;
27367            }
27368            self.write(")");
27369        }
27370        Ok(())
27371    }
27372
27373    fn generate_current_datetime(&mut self, e: &CurrentDatetime) -> Result<()> {
27374        // CURRENT_DATETIME or CURRENT_DATETIME(timezone)
27375        self.write_keyword("CURRENT_DATETIME");
27376        if let Some(this) = &e.this {
27377            self.write("(");
27378            self.generate_expression(this)?;
27379            self.write(")");
27380        }
27381        Ok(())
27382    }
27383
27384    fn generate_current_schema(&mut self, _e: &CurrentSchema) -> Result<()> {
27385        // CURRENT_SCHEMA - no arguments
27386        self.write_keyword("CURRENT_SCHEMA");
27387        Ok(())
27388    }
27389
27390    fn generate_current_schemas(&mut self, e: &CurrentSchemas) -> Result<()> {
27391        // CURRENT_SCHEMAS(include_implicit)
27392        self.write_keyword("CURRENT_SCHEMAS");
27393        self.write("(");
27394        // Snowflake: drop the argument (CURRENT_SCHEMAS() takes no args)
27395        if !matches!(
27396            self.config.dialect,
27397            Some(crate::dialects::DialectType::Snowflake)
27398        ) {
27399            if let Some(this) = &e.this {
27400                self.generate_expression(this)?;
27401            }
27402        }
27403        self.write(")");
27404        Ok(())
27405    }
27406
27407    fn generate_current_user(&mut self, e: &CurrentUser) -> Result<()> {
27408        // CURRENT_USER or CURRENT_USER()
27409        self.write_keyword("CURRENT_USER");
27410        // Some dialects always need parens: Snowflake, Spark, Hive, DuckDB, BigQuery, MySQL, Databricks
27411        let needs_parens = e.this.is_some()
27412            || matches!(
27413                self.config.dialect,
27414                Some(DialectType::Snowflake)
27415                    | Some(DialectType::Spark)
27416                    | Some(DialectType::Hive)
27417                    | Some(DialectType::DuckDB)
27418                    | Some(DialectType::BigQuery)
27419                    | Some(DialectType::MySQL)
27420                    | Some(DialectType::Databricks)
27421            );
27422        if needs_parens {
27423            self.write("()");
27424        }
27425        Ok(())
27426    }
27427
27428    fn generate_d_pipe(&mut self, e: &DPipe) -> Result<()> {
27429        // In Solr, || is OR, not string concatenation (DPIPE_IS_STRING_CONCAT = False)
27430        if self.config.dialect == Some(DialectType::Solr) {
27431            self.generate_expression(&e.this)?;
27432            self.write(" ");
27433            self.write_keyword("OR");
27434            self.write(" ");
27435            self.generate_expression(&e.expression)?;
27436        } else if self.config.dialect == Some(DialectType::MySQL) {
27437            self.generate_mysql_concat_from_dpipe(e)?;
27438        } else {
27439            // String concatenation: this || expression
27440            self.generate_expression(&e.this)?;
27441            self.write(" || ");
27442            self.generate_expression(&e.expression)?;
27443        }
27444        Ok(())
27445    }
27446
27447    fn generate_data_blocksize_property(&mut self, e: &DataBlocksizeProperty) -> Result<()> {
27448        // DATABLOCKSIZE=... (Teradata)
27449        self.write_keyword("DATABLOCKSIZE");
27450        self.write("=");
27451        if let Some(size) = e.size {
27452            self.write(&size.to_string());
27453            if let Some(units) = &e.units {
27454                self.write_space();
27455                self.generate_expression(units)?;
27456            }
27457        } else if e.minimum.is_some() {
27458            self.write_keyword("MINIMUM");
27459        } else if e.maximum.is_some() {
27460            self.write_keyword("MAXIMUM");
27461        } else if e.default.is_some() {
27462            self.write_keyword("DEFAULT");
27463        }
27464        Ok(())
27465    }
27466
27467    fn generate_data_deletion_property(&mut self, e: &DataDeletionProperty) -> Result<()> {
27468        // DATA_DELETION=ON or DATA_DELETION=OFF or DATA_DELETION=ON(FILTER_COLUMN=col, RETENTION_PERIOD=...)
27469        self.write_keyword("DATA_DELETION");
27470        self.write("=");
27471
27472        let is_on = matches!(&*e.on, Expression::Boolean(BooleanLiteral { value: true }));
27473        let has_options = e.filter_column.is_some() || e.retention_period.is_some();
27474
27475        if is_on {
27476            self.write_keyword("ON");
27477            if has_options {
27478                self.write("(");
27479                let mut first = true;
27480                if let Some(filter_column) = &e.filter_column {
27481                    self.write_keyword("FILTER_COLUMN");
27482                    self.write("=");
27483                    self.generate_expression(filter_column)?;
27484                    first = false;
27485                }
27486                if let Some(retention_period) = &e.retention_period {
27487                    if !first {
27488                        self.write(", ");
27489                    }
27490                    self.write_keyword("RETENTION_PERIOD");
27491                    self.write("=");
27492                    self.generate_expression(retention_period)?;
27493                }
27494                self.write(")");
27495            }
27496        } else {
27497            self.write_keyword("OFF");
27498        }
27499        Ok(())
27500    }
27501
27502    /// Generate a Date function expression
27503    /// For Exasol: {d'value'} -> TO_DATE('value')
27504    /// For other dialects: DATE('value')
27505    fn generate_date_func(&mut self, e: &UnaryFunc) -> Result<()> {
27506        use crate::dialects::DialectType;
27507        use crate::expressions::Literal;
27508
27509        match self.config.dialect {
27510            // Exasol uses TO_DATE for Date expressions
27511            Some(DialectType::Exasol) => {
27512                self.write_keyword("TO_DATE");
27513                self.write("(");
27514                // Extract the string value from the expression if it's a string literal
27515                match &e.this {
27516                    Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
27517                        let Literal::String(s) = lit.as_ref() else {
27518                            unreachable!()
27519                        };
27520                        self.write("'");
27521                        self.write(s);
27522                        self.write("'");
27523                    }
27524                    _ => {
27525                        self.generate_expression(&e.this)?;
27526                    }
27527                }
27528                self.write(")");
27529            }
27530            // Standard: DATE(value)
27531            _ => {
27532                self.write_keyword("DATE");
27533                self.write("(");
27534                self.generate_expression(&e.this)?;
27535                self.write(")");
27536            }
27537        }
27538        Ok(())
27539    }
27540
27541    fn generate_date_bin(&mut self, e: &DateBin) -> Result<()> {
27542        // DATE_BIN(interval, timestamp[, origin])
27543        self.write_keyword("DATE_BIN");
27544        self.write("(");
27545        self.generate_expression(&e.this)?;
27546        self.write(", ");
27547        self.generate_expression(&e.expression)?;
27548        if let Some(origin) = &e.origin {
27549            self.write(", ");
27550            self.generate_expression(origin)?;
27551        }
27552        self.write(")");
27553        Ok(())
27554    }
27555
27556    fn generate_date_format_column_constraint(
27557        &mut self,
27558        e: &DateFormatColumnConstraint,
27559    ) -> Result<()> {
27560        // FORMAT 'format_string' (Teradata)
27561        self.write_keyword("FORMAT");
27562        self.write_space();
27563        self.generate_expression(&e.this)?;
27564        Ok(())
27565    }
27566
27567    fn generate_date_from_parts(&mut self, e: &DateFromParts) -> Result<()> {
27568        // DATE_FROM_PARTS(year, month, day) or DATEFROMPARTS(year, month, day)
27569        self.write_keyword("DATE_FROM_PARTS");
27570        self.write("(");
27571        let mut first = true;
27572        if let Some(year) = &e.year {
27573            self.generate_expression(year)?;
27574            first = false;
27575        }
27576        if let Some(month) = &e.month {
27577            if !first {
27578                self.write(", ");
27579            }
27580            self.generate_expression(month)?;
27581            first = false;
27582        }
27583        if let Some(day) = &e.day {
27584            if !first {
27585                self.write(", ");
27586            }
27587            self.generate_expression(day)?;
27588        }
27589        self.write(")");
27590        Ok(())
27591    }
27592
27593    fn generate_datetime(&mut self, e: &Datetime) -> Result<()> {
27594        // DATETIME(this) or DATETIME(this, expression)
27595        self.write_keyword("DATETIME");
27596        self.write("(");
27597        self.generate_expression(&e.this)?;
27598        if let Some(expr) = &e.expression {
27599            self.write(", ");
27600            self.generate_expression(expr)?;
27601        }
27602        self.write(")");
27603        Ok(())
27604    }
27605
27606    fn generate_datetime_add(&mut self, e: &DatetimeAdd) -> Result<()> {
27607        // DATETIME_ADD(this, expression, unit)
27608        self.write_keyword("DATETIME_ADD");
27609        self.write("(");
27610        self.generate_expression(&e.this)?;
27611        self.write(", ");
27612        self.generate_expression(&e.expression)?;
27613        if let Some(unit) = &e.unit {
27614            self.write(", ");
27615            self.write_keyword(unit);
27616        }
27617        self.write(")");
27618        Ok(())
27619    }
27620
27621    fn generate_datetime_diff(&mut self, e: &DatetimeDiff) -> Result<()> {
27622        // DATETIME_DIFF(this, expression, unit)
27623        self.write_keyword("DATETIME_DIFF");
27624        self.write("(");
27625        self.generate_expression(&e.this)?;
27626        self.write(", ");
27627        self.generate_expression(&e.expression)?;
27628        if let Some(unit) = &e.unit {
27629            self.write(", ");
27630            self.write_keyword(unit);
27631        }
27632        self.write(")");
27633        Ok(())
27634    }
27635
27636    fn generate_datetime_sub(&mut self, e: &DatetimeSub) -> Result<()> {
27637        // DATETIME_SUB(this, expression, unit)
27638        self.write_keyword("DATETIME_SUB");
27639        self.write("(");
27640        self.generate_expression(&e.this)?;
27641        self.write(", ");
27642        self.generate_expression(&e.expression)?;
27643        if let Some(unit) = &e.unit {
27644            self.write(", ");
27645            self.write_keyword(unit);
27646        }
27647        self.write(")");
27648        Ok(())
27649    }
27650
27651    fn generate_datetime_trunc(&mut self, e: &DatetimeTrunc) -> Result<()> {
27652        // DATETIME_TRUNC(this, unit, zone)
27653        self.write_keyword("DATETIME_TRUNC");
27654        self.write("(");
27655        self.generate_expression(&e.this)?;
27656        self.write(", ");
27657        self.write_keyword(&e.unit);
27658        if let Some(zone) = &e.zone {
27659            self.write(", ");
27660            self.generate_expression(zone)?;
27661        }
27662        self.write(")");
27663        Ok(())
27664    }
27665
27666    fn generate_dayname(&mut self, e: &Dayname) -> Result<()> {
27667        // DAYNAME(this)
27668        self.write_keyword("DAYNAME");
27669        self.write("(");
27670        self.generate_expression(&e.this)?;
27671        self.write(")");
27672        Ok(())
27673    }
27674
27675    fn generate_declare(&mut self, e: &Declare) -> Result<()> {
27676        // DECLARE [OR REPLACE] var1 AS type1, var2 AS type2, ...
27677        self.write_keyword("DECLARE");
27678        self.write_space();
27679        if e.replace {
27680            self.write_keyword("OR");
27681            self.write_space();
27682            self.write_keyword("REPLACE");
27683            self.write_space();
27684        }
27685        for (i, expr) in e.expressions.iter().enumerate() {
27686            if i > 0 {
27687                self.write(", ");
27688            }
27689            self.generate_expression(expr)?;
27690        }
27691        Ok(())
27692    }
27693
27694    fn generate_declare_item(&mut self, e: &DeclareItem) -> Result<()> {
27695        use crate::dialects::DialectType;
27696
27697        // variable TYPE [DEFAULT default]
27698        self.generate_expression(&e.this)?;
27699        // BigQuery multi-variable: DECLARE X, Y, Z INT64
27700        for name in &e.additional_names {
27701            self.write(", ");
27702            self.generate_expression(name)?;
27703        }
27704        if let Some(kind) = &e.kind {
27705            self.write_space();
27706            // BigQuery uses: DECLARE x INT64 DEFAULT value (no AS)
27707            // TSQL: Always includes AS (normalization)
27708            // Others: Include AS if present in original
27709            match self.config.dialect {
27710                Some(DialectType::BigQuery) => {
27711                    self.write(kind);
27712                }
27713                Some(DialectType::TSQL) => {
27714                    // TSQL DECLARE: no AS keyword (sqlglot convention)
27715                    // Normalize INT to INTEGER for simple declarations
27716                    // Complex TABLE declarations (with CLUSTERED/INDEX) are preserved as-is
27717                    let is_complex_table = kind.starts_with("TABLE")
27718                        && (kind.contains("CLUSTERED") || kind.contains("INDEX"));
27719                    if is_complex_table {
27720                        self.write(kind);
27721                    } else if kind == "INT" {
27722                        self.write("INTEGER");
27723                    } else if kind.starts_with("TABLE") {
27724                        // Normalize INT to INTEGER inside simple TABLE column definitions
27725                        let normalized = kind
27726                            .replace(" INT ", " INTEGER ")
27727                            .replace(" INT,", " INTEGER,")
27728                            .replace(" INT)", " INTEGER)")
27729                            .replace("(INT ", "(INTEGER ");
27730                        self.write(&normalized);
27731                    } else {
27732                        self.write(kind);
27733                    }
27734                }
27735                _ => {
27736                    if e.has_as {
27737                        self.write_keyword("AS");
27738                        self.write_space();
27739                    }
27740                    self.write(kind);
27741                }
27742            }
27743        }
27744        if let Some(default) = &e.default {
27745            // BigQuery uses DEFAULT, others use =
27746            match self.config.dialect {
27747                Some(DialectType::BigQuery) => {
27748                    self.write_space();
27749                    self.write_keyword("DEFAULT");
27750                    self.write_space();
27751                }
27752                _ => {
27753                    self.write(" = ");
27754                }
27755            }
27756            self.generate_expression(default)?;
27757        }
27758        Ok(())
27759    }
27760
27761    fn generate_decode_case(&mut self, e: &DecodeCase) -> Result<()> {
27762        // DECODE(expr, search1, result1, search2, result2, ..., default)
27763        self.write_keyword("DECODE");
27764        self.write("(");
27765        for (i, expr) in e.expressions.iter().enumerate() {
27766            if i > 0 {
27767                self.write(", ");
27768            }
27769            self.generate_expression(expr)?;
27770        }
27771        self.write(")");
27772        Ok(())
27773    }
27774
27775    fn generate_decompress_binary(&mut self, e: &DecompressBinary) -> Result<()> {
27776        // DECOMPRESS(expr, 'method')
27777        self.write_keyword("DECOMPRESS");
27778        self.write("(");
27779        self.generate_expression(&e.this)?;
27780        self.write(", '");
27781        self.write(&e.method);
27782        self.write("')");
27783        Ok(())
27784    }
27785
27786    fn generate_decompress_string(&mut self, e: &DecompressString) -> Result<()> {
27787        // DECOMPRESS(expr, 'method')
27788        self.write_keyword("DECOMPRESS");
27789        self.write("(");
27790        self.generate_expression(&e.this)?;
27791        self.write(", '");
27792        self.write(&e.method);
27793        self.write("')");
27794        Ok(())
27795    }
27796
27797    fn generate_decrypt(&mut self, e: &Decrypt) -> Result<()> {
27798        // DECRYPT(value, passphrase [, aad [, algorithm]])
27799        self.write_keyword("DECRYPT");
27800        self.write("(");
27801        self.generate_expression(&e.this)?;
27802        if let Some(passphrase) = &e.passphrase {
27803            self.write(", ");
27804            self.generate_expression(passphrase)?;
27805        }
27806        if let Some(aad) = &e.aad {
27807            self.write(", ");
27808            self.generate_expression(aad)?;
27809        }
27810        if let Some(method) = &e.encryption_method {
27811            self.write(", ");
27812            self.generate_expression(method)?;
27813        }
27814        self.write(")");
27815        Ok(())
27816    }
27817
27818    fn generate_decrypt_raw(&mut self, e: &DecryptRaw) -> Result<()> {
27819        // DECRYPT_RAW(value, key [, iv [, aad [, algorithm]]])
27820        self.write_keyword("DECRYPT_RAW");
27821        self.write("(");
27822        self.generate_expression(&e.this)?;
27823        if let Some(key) = &e.key {
27824            self.write(", ");
27825            self.generate_expression(key)?;
27826        }
27827        if let Some(iv) = &e.iv {
27828            self.write(", ");
27829            self.generate_expression(iv)?;
27830        }
27831        if let Some(aad) = &e.aad {
27832            self.write(", ");
27833            self.generate_expression(aad)?;
27834        }
27835        if let Some(method) = &e.encryption_method {
27836            self.write(", ");
27837            self.generate_expression(method)?;
27838        }
27839        self.write(")");
27840        Ok(())
27841    }
27842
27843    fn generate_definer_property(&mut self, e: &DefinerProperty) -> Result<()> {
27844        // DEFINER = user
27845        self.write_keyword("DEFINER");
27846        self.write(" = ");
27847        self.generate_expression(&e.this)?;
27848        Ok(())
27849    }
27850
27851    fn generate_detach(&mut self, e: &Detach) -> Result<()> {
27852        // Python: DETACH[DATABASE IF EXISTS] this
27853        self.write_keyword("DETACH");
27854        if e.exists {
27855            self.write_keyword(" DATABASE IF EXISTS");
27856        }
27857        self.write_space();
27858        self.generate_expression(&e.this)?;
27859        Ok(())
27860    }
27861
27862    fn generate_dict_property(&mut self, e: &DictProperty) -> Result<()> {
27863        let property_name = match e.this.as_ref() {
27864            Expression::Identifier(id) => id.name.as_str(),
27865            Expression::Var(v) => v.this.as_str(),
27866            _ => "DICTIONARY",
27867        };
27868        self.write_keyword(property_name);
27869        self.write("(");
27870        self.write(&e.kind);
27871        if let Some(settings) = &e.settings {
27872            self.write("(");
27873            if let Expression::Tuple(t) = settings.as_ref() {
27874                if self.config.pretty && !t.expressions.is_empty() {
27875                    self.write_newline();
27876                    self.indent_level += 1;
27877                    for (i, pair) in t.expressions.iter().enumerate() {
27878                        if i > 0 {
27879                            self.write(",");
27880                            self.write_newline();
27881                        }
27882                        self.write_indent();
27883                        if let Expression::Tuple(pair_tuple) = pair {
27884                            if let Some(k) = pair_tuple.expressions.first() {
27885                                self.generate_expression(k)?;
27886                            }
27887                            if let Some(v) = pair_tuple.expressions.get(1) {
27888                                self.write(" ");
27889                                self.generate_expression(v)?;
27890                            }
27891                        } else {
27892                            self.generate_expression(pair)?;
27893                        }
27894                    }
27895                    self.indent_level -= 1;
27896                    self.write_newline();
27897                    self.write_indent();
27898                } else {
27899                    for (i, pair) in t.expressions.iter().enumerate() {
27900                        if i > 0 {
27901                            // ClickHouse dict properties are space-separated, not comma-separated
27902                            self.write(" ");
27903                        }
27904                        if let Expression::Tuple(pair_tuple) = pair {
27905                            if let Some(k) = pair_tuple.expressions.first() {
27906                                self.generate_expression(k)?;
27907                            }
27908                            if let Some(v) = pair_tuple.expressions.get(1) {
27909                                self.write(" ");
27910                                self.generate_expression(v)?;
27911                            }
27912                        } else {
27913                            self.generate_expression(pair)?;
27914                        }
27915                    }
27916                }
27917            } else {
27918                self.generate_expression(settings)?;
27919            }
27920            self.write(")");
27921        } else {
27922            // No settings but kind had parens (e.g., SOURCE(NULL()), LAYOUT(FLAT()))
27923            self.write("()");
27924        }
27925        self.write(")");
27926        Ok(())
27927    }
27928
27929    fn generate_dict_range(&mut self, e: &DictRange) -> Result<()> {
27930        let property_name = match e.this.as_ref() {
27931            Expression::Identifier(id) => id.name.as_str(),
27932            Expression::Var(v) => v.this.as_str(),
27933            _ => "RANGE",
27934        };
27935        self.write_keyword(property_name);
27936        self.write("(");
27937        if let Some(min) = &e.min {
27938            self.write_keyword("MIN");
27939            self.write_space();
27940            self.generate_expression(min)?;
27941        }
27942        if let Some(max) = &e.max {
27943            self.write_space();
27944            self.write_keyword("MAX");
27945            self.write_space();
27946            self.generate_expression(max)?;
27947        }
27948        self.write(")");
27949        Ok(())
27950    }
27951
27952    fn generate_directory(&mut self, e: &Directory) -> Result<()> {
27953        // Python: {local}DIRECTORY {this}{row_format}
27954        if e.local.is_some() {
27955            self.write_keyword("LOCAL ");
27956        }
27957        self.write_keyword("DIRECTORY");
27958        self.write_space();
27959        self.generate_expression(&e.this)?;
27960        if let Some(row_format) = &e.row_format {
27961            self.write_space();
27962            self.generate_expression(row_format)?;
27963        }
27964        Ok(())
27965    }
27966
27967    fn generate_dist_key_property(&mut self, e: &DistKeyProperty) -> Result<()> {
27968        // Redshift: DISTKEY(column)
27969        self.write_keyword("DISTKEY");
27970        self.write("(");
27971        self.generate_expression(&e.this)?;
27972        self.write(")");
27973        Ok(())
27974    }
27975
27976    fn generate_dist_style_property(&mut self, e: &DistStyleProperty) -> Result<()> {
27977        // Redshift: DISTSTYLE KEY|ALL|EVEN|AUTO
27978        self.write_keyword("DISTSTYLE");
27979        self.write_space();
27980        self.generate_expression(&e.this)?;
27981        Ok(())
27982    }
27983
27984    fn generate_distribute_by(&mut self, e: &DistributeBy) -> Result<()> {
27985        // Python: "DISTRIBUTE BY" expressions
27986        self.write_keyword("DISTRIBUTE BY");
27987        self.write_space();
27988        for (i, expr) in e.expressions.iter().enumerate() {
27989            if i > 0 {
27990                self.write(", ");
27991            }
27992            self.generate_expression(expr)?;
27993        }
27994        Ok(())
27995    }
27996
27997    fn generate_distributed_by_property(&mut self, e: &DistributedByProperty) -> Result<()> {
27998        // Python: DISTRIBUTED BY kind (expressions) BUCKETS buckets order
27999        self.write_keyword("DISTRIBUTED BY");
28000        self.write_space();
28001        self.write(&e.kind);
28002        if !e.expressions.is_empty() {
28003            self.write(" (");
28004            for (i, expr) in e.expressions.iter().enumerate() {
28005                if i > 0 {
28006                    self.write(", ");
28007                }
28008                self.generate_expression(expr)?;
28009            }
28010            self.write(")");
28011        }
28012        if let Some(buckets) = &e.buckets {
28013            self.write_space();
28014            self.write_keyword("BUCKETS");
28015            self.write_space();
28016            self.generate_expression(buckets)?;
28017        }
28018        if let Some(order) = &e.order {
28019            self.write_space();
28020            self.generate_expression(order)?;
28021        }
28022        Ok(())
28023    }
28024
28025    fn generate_dot_product(&mut self, e: &DotProduct) -> Result<()> {
28026        // DOT_PRODUCT(vector1, vector2)
28027        self.write_keyword("DOT_PRODUCT");
28028        self.write("(");
28029        self.generate_expression(&e.this)?;
28030        self.write(", ");
28031        self.generate_expression(&e.expression)?;
28032        self.write(")");
28033        Ok(())
28034    }
28035
28036    fn generate_drop_partition(&mut self, e: &DropPartition) -> Result<()> {
28037        // Python: DROP{IF EXISTS }expressions
28038        self.write_keyword("DROP");
28039        if e.exists {
28040            self.write_keyword(" IF EXISTS ");
28041        } else {
28042            self.write_space();
28043        }
28044        for (i, expr) in e.expressions.iter().enumerate() {
28045            if i > 0 {
28046                self.write(", ");
28047            }
28048            self.generate_expression(expr)?;
28049        }
28050        Ok(())
28051    }
28052
28053    fn generate_duplicate_key_property(&mut self, e: &DuplicateKeyProperty) -> Result<()> {
28054        // Python: DUPLICATE KEY (expressions)
28055        self.write_keyword("DUPLICATE KEY");
28056        self.write(" (");
28057        for (i, expr) in e.expressions.iter().enumerate() {
28058            if i > 0 {
28059                self.write(", ");
28060            }
28061            self.generate_expression(expr)?;
28062        }
28063        self.write(")");
28064        Ok(())
28065    }
28066
28067    fn generate_elt(&mut self, e: &Elt) -> Result<()> {
28068        // ELT(index, str1, str2, ...)
28069        self.write_keyword("ELT");
28070        self.write("(");
28071        self.generate_expression(&e.this)?;
28072        for expr in &e.expressions {
28073            self.write(", ");
28074            self.generate_expression(expr)?;
28075        }
28076        self.write(")");
28077        Ok(())
28078    }
28079
28080    fn generate_encode(&mut self, e: &Encode) -> Result<()> {
28081        // ENCODE(string, charset)
28082        self.write_keyword("ENCODE");
28083        self.write("(");
28084        self.generate_expression(&e.this)?;
28085        if let Some(charset) = &e.charset {
28086            self.write(", ");
28087            self.generate_expression(charset)?;
28088        }
28089        self.write(")");
28090        Ok(())
28091    }
28092
28093    fn generate_encode_property(&mut self, e: &EncodeProperty) -> Result<()> {
28094        // Python: [KEY ]ENCODE this [properties]
28095        if e.key.is_some() {
28096            self.write_keyword("KEY ");
28097        }
28098        self.write_keyword("ENCODE");
28099        self.write_space();
28100        self.generate_expression(&e.this)?;
28101        if !e.properties.is_empty() {
28102            self.write(" (");
28103            for (i, prop) in e.properties.iter().enumerate() {
28104                if i > 0 {
28105                    self.write(", ");
28106                }
28107                self.generate_expression(prop)?;
28108            }
28109            self.write(")");
28110        }
28111        Ok(())
28112    }
28113
28114    fn generate_encrypt(&mut self, e: &Encrypt) -> Result<()> {
28115        // ENCRYPT(value, passphrase [, aad [, algorithm]])
28116        self.write_keyword("ENCRYPT");
28117        self.write("(");
28118        self.generate_expression(&e.this)?;
28119        if let Some(passphrase) = &e.passphrase {
28120            self.write(", ");
28121            self.generate_expression(passphrase)?;
28122        }
28123        if let Some(aad) = &e.aad {
28124            self.write(", ");
28125            self.generate_expression(aad)?;
28126        }
28127        if let Some(method) = &e.encryption_method {
28128            self.write(", ");
28129            self.generate_expression(method)?;
28130        }
28131        self.write(")");
28132        Ok(())
28133    }
28134
28135    fn generate_encrypt_raw(&mut self, e: &EncryptRaw) -> Result<()> {
28136        // ENCRYPT_RAW(value, key [, iv [, aad [, algorithm]]])
28137        self.write_keyword("ENCRYPT_RAW");
28138        self.write("(");
28139        self.generate_expression(&e.this)?;
28140        if let Some(key) = &e.key {
28141            self.write(", ");
28142            self.generate_expression(key)?;
28143        }
28144        if let Some(iv) = &e.iv {
28145            self.write(", ");
28146            self.generate_expression(iv)?;
28147        }
28148        if let Some(aad) = &e.aad {
28149            self.write(", ");
28150            self.generate_expression(aad)?;
28151        }
28152        if let Some(method) = &e.encryption_method {
28153            self.write(", ");
28154            self.generate_expression(method)?;
28155        }
28156        self.write(")");
28157        Ok(())
28158    }
28159
28160    fn generate_engine_property(&mut self, e: &EngineProperty) -> Result<()> {
28161        // MySQL: ENGINE = InnoDB
28162        self.write_keyword("ENGINE");
28163        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
28164            self.write("=");
28165        } else {
28166            self.write(" = ");
28167        }
28168        self.generate_expression(&e.this)?;
28169        Ok(())
28170    }
28171
28172    fn generate_enviroment_property(&mut self, e: &EnviromentProperty) -> Result<()> {
28173        // ENVIRONMENT (expressions)
28174        self.write_keyword("ENVIRONMENT");
28175        self.write(" (");
28176        for (i, expr) in e.expressions.iter().enumerate() {
28177            if i > 0 {
28178                self.write(", ");
28179            }
28180            self.generate_expression(expr)?;
28181        }
28182        self.write(")");
28183        Ok(())
28184    }
28185
28186    fn generate_ephemeral_column_constraint(
28187        &mut self,
28188        e: &EphemeralColumnConstraint,
28189    ) -> Result<()> {
28190        // MySQL: EPHEMERAL [expr]
28191        self.write_keyword("EPHEMERAL");
28192        if let Some(this) = &e.this {
28193            self.write_space();
28194            self.generate_expression(this)?;
28195        }
28196        Ok(())
28197    }
28198
28199    fn generate_equal_null(&mut self, e: &EqualNull) -> Result<()> {
28200        // Snowflake: EQUAL_NULL(a, b)
28201        self.write_keyword("EQUAL_NULL");
28202        self.write("(");
28203        self.generate_expression(&e.this)?;
28204        self.write(", ");
28205        self.generate_expression(&e.expression)?;
28206        self.write(")");
28207        Ok(())
28208    }
28209
28210    fn generate_euclidean_distance(&mut self, e: &EuclideanDistance) -> Result<()> {
28211        use crate::dialects::DialectType;
28212
28213        // PostgreSQL uses <-> operator syntax
28214        match self.config.dialect {
28215            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift) => {
28216                self.generate_expression(&e.this)?;
28217                self.write(" <-> ");
28218                self.generate_expression(&e.expression)?;
28219            }
28220            _ => {
28221                // Other dialects use EUCLIDEAN_DISTANCE function
28222                self.write_keyword("EUCLIDEAN_DISTANCE");
28223                self.write("(");
28224                self.generate_expression(&e.this)?;
28225                self.write(", ");
28226                self.generate_expression(&e.expression)?;
28227                self.write(")");
28228            }
28229        }
28230        Ok(())
28231    }
28232
28233    fn generate_execute_as_property(&mut self, e: &ExecuteAsProperty) -> Result<()> {
28234        // EXECUTE AS CALLER|OWNER|user
28235        self.write_keyword("EXECUTE AS");
28236        self.write_space();
28237        self.generate_expression(&e.this)?;
28238        Ok(())
28239    }
28240
28241    fn generate_export(&mut self, e: &Export) -> Result<()> {
28242        // BigQuery: EXPORT DATA [WITH CONNECTION connection] OPTIONS (...) AS query
28243        self.write_keyword("EXPORT DATA");
28244        if let Some(connection) = &e.connection {
28245            self.write_space();
28246            self.write_keyword("WITH CONNECTION");
28247            self.write_space();
28248            self.generate_expression(connection)?;
28249        }
28250        if !e.options.is_empty() {
28251            self.write_space();
28252            self.generate_options_clause(&e.options)?;
28253        }
28254        self.write_space();
28255        self.write_keyword("AS");
28256        self.write_space();
28257        self.generate_expression(&e.this)?;
28258        Ok(())
28259    }
28260
28261    fn generate_external_property(&mut self, e: &ExternalProperty) -> Result<()> {
28262        // EXTERNAL [this]
28263        self.write_keyword("EXTERNAL");
28264        if let Some(this) = &e.this {
28265            self.write_space();
28266            self.generate_expression(this)?;
28267        }
28268        Ok(())
28269    }
28270
28271    fn generate_fallback_property(&mut self, e: &FallbackProperty) -> Result<()> {
28272        // Python: {no}FALLBACK{protection}
28273        if e.no.is_some() {
28274            self.write_keyword("NO ");
28275        }
28276        self.write_keyword("FALLBACK");
28277        if e.protection.is_some() {
28278            self.write_keyword(" PROTECTION");
28279        }
28280        Ok(())
28281    }
28282
28283    fn generate_farm_fingerprint(&mut self, e: &FarmFingerprint) -> Result<()> {
28284        // BigQuery: FARM_FINGERPRINT(value)
28285        self.write_keyword("FARM_FINGERPRINT");
28286        self.write("(");
28287        for (i, expr) in e.expressions.iter().enumerate() {
28288            if i > 0 {
28289                self.write(", ");
28290            }
28291            self.generate_expression(expr)?;
28292        }
28293        self.write(")");
28294        Ok(())
28295    }
28296
28297    fn generate_features_at_time(&mut self, e: &FeaturesAtTime) -> Result<()> {
28298        // BigQuery ML: FEATURES_AT_TIME(feature_view, time, [num_rows], [ignore_feature_nulls])
28299        self.write_keyword("FEATURES_AT_TIME");
28300        self.write("(");
28301        self.generate_expression(&e.this)?;
28302        if let Some(time) = &e.time {
28303            self.write(", ");
28304            self.generate_expression(time)?;
28305        }
28306        if let Some(num_rows) = &e.num_rows {
28307            self.write(", ");
28308            self.generate_expression(num_rows)?;
28309        }
28310        if let Some(ignore_nulls) = &e.ignore_feature_nulls {
28311            self.write(", ");
28312            self.generate_expression(ignore_nulls)?;
28313        }
28314        self.write(")");
28315        Ok(())
28316    }
28317
28318    fn generate_fetch(&mut self, e: &Fetch) -> Result<()> {
28319        // For dialects that prefer LIMIT, convert simple FETCH to LIMIT
28320        let use_limit = !e.percent
28321            && !e.with_ties
28322            && e.count.is_some()
28323            && matches!(
28324                self.config.dialect,
28325                Some(DialectType::Spark)
28326                    | Some(DialectType::Hive)
28327                    | Some(DialectType::DuckDB)
28328                    | Some(DialectType::SQLite)
28329                    | Some(DialectType::MySQL)
28330                    | Some(DialectType::BigQuery)
28331                    | Some(DialectType::Databricks)
28332                    | Some(DialectType::StarRocks)
28333                    | Some(DialectType::Doris)
28334                    | Some(DialectType::Athena)
28335                    | Some(DialectType::ClickHouse)
28336            );
28337
28338        if use_limit {
28339            self.write_keyword("LIMIT");
28340            self.write_space();
28341            self.generate_expression(e.count.as_ref().unwrap())?;
28342            return Ok(());
28343        }
28344
28345        // Python: FETCH direction count limit_options
28346        self.write_keyword("FETCH");
28347        if !e.direction.is_empty() {
28348            self.write_space();
28349            self.write_keyword(&e.direction);
28350        }
28351        if let Some(count) = &e.count {
28352            self.write_space();
28353            self.generate_expression(count)?;
28354        }
28355        // Generate PERCENT, ROWS, WITH TIES/ONLY
28356        if e.percent {
28357            self.write_keyword(" PERCENT");
28358        }
28359        if e.rows {
28360            self.write_keyword(" ROWS");
28361        }
28362        if e.with_ties {
28363            self.write_keyword(" WITH TIES");
28364        } else if e.rows {
28365            self.write_keyword(" ONLY");
28366        } else {
28367            self.write_keyword(" ROWS ONLY");
28368        }
28369        Ok(())
28370    }
28371
28372    fn generate_file_format_property(&mut self, e: &FileFormatProperty) -> Result<()> {
28373        // For Hive format: STORED AS this or STORED AS INPUTFORMAT x OUTPUTFORMAT y
28374        // For Spark/Databricks without hive_format: USING this
28375        // For Snowflake/others: FILE_FORMAT = this or FILE_FORMAT = (expressions)
28376        if e.hive_format.is_some() {
28377            // Hive format: STORED AS ...
28378            self.write_keyword("STORED AS");
28379            self.write_space();
28380            if let Some(this) = &e.this {
28381                // Uppercase the format name (e.g., parquet -> PARQUET)
28382                if let Expression::Identifier(id) = this.as_ref() {
28383                    self.write_keyword(&id.name.to_ascii_uppercase());
28384                } else {
28385                    self.generate_expression(this)?;
28386                }
28387            }
28388        } else if matches!(self.config.dialect, Some(DialectType::Hive)) {
28389            // Hive: STORED AS format
28390            self.write_keyword("STORED AS");
28391            self.write_space();
28392            if let Some(this) = &e.this {
28393                if let Expression::Identifier(id) = this.as_ref() {
28394                    self.write_keyword(&id.name.to_ascii_uppercase());
28395                } else {
28396                    self.generate_expression(this)?;
28397                }
28398            }
28399        } else if matches!(
28400            self.config.dialect,
28401            Some(DialectType::Spark) | Some(DialectType::Databricks)
28402        ) {
28403            // Spark/Databricks: USING format (e.g., USING DELTA)
28404            self.write_keyword("USING");
28405            self.write_space();
28406            if let Some(this) = &e.this {
28407                self.generate_expression(this)?;
28408            }
28409        } else {
28410            // Snowflake/standard format
28411            self.write_keyword("FILE_FORMAT");
28412            self.write(" = ");
28413            if let Some(this) = &e.this {
28414                self.generate_expression(this)?;
28415            } else if !e.expressions.is_empty() {
28416                self.write("(");
28417                for (i, expr) in e.expressions.iter().enumerate() {
28418                    if i > 0 {
28419                        self.write(", ");
28420                    }
28421                    self.generate_expression(expr)?;
28422                }
28423                self.write(")");
28424            }
28425        }
28426        Ok(())
28427    }
28428
28429    fn generate_filter(&mut self, e: &Filter) -> Result<()> {
28430        // agg_func FILTER(WHERE condition)
28431        self.generate_expression(&e.this)?;
28432        self.write_space();
28433        self.write_keyword("FILTER");
28434        self.write("(");
28435        self.write_keyword("WHERE");
28436        self.write_space();
28437        self.generate_expression(&e.expression)?;
28438        self.write(")");
28439        Ok(())
28440    }
28441
28442    fn generate_float64(&mut self, e: &Float64) -> Result<()> {
28443        // FLOAT64(this) or FLOAT64(this, expression)
28444        self.write_keyword("FLOAT64");
28445        self.write("(");
28446        self.generate_expression(&e.this)?;
28447        if let Some(expr) = &e.expression {
28448            self.write(", ");
28449            self.generate_expression(expr)?;
28450        }
28451        self.write(")");
28452        Ok(())
28453    }
28454
28455    fn generate_for_in(&mut self, e: &ForIn) -> Result<()> {
28456        // FOR this DO expression
28457        self.write_keyword("FOR");
28458        self.write_space();
28459        self.generate_expression(&e.this)?;
28460        self.write_space();
28461        self.write_keyword("DO");
28462        self.write_space();
28463        self.generate_expression(&e.expression)?;
28464        Ok(())
28465    }
28466
28467    fn generate_foreign_key(&mut self, e: &ForeignKey) -> Result<()> {
28468        // FOREIGN KEY (cols) REFERENCES table(cols) ON DELETE action ON UPDATE action
28469        self.write_keyword("FOREIGN KEY");
28470        if !e.expressions.is_empty() {
28471            self.write(" (");
28472            for (i, expr) in e.expressions.iter().enumerate() {
28473                if i > 0 {
28474                    self.write(", ");
28475                }
28476                self.generate_expression(expr)?;
28477            }
28478            self.write(")");
28479        }
28480        if let Some(reference) = &e.reference {
28481            self.write_space();
28482            self.generate_expression(reference)?;
28483        }
28484        if let Some(delete) = &e.delete {
28485            self.write_space();
28486            self.write_keyword("ON DELETE");
28487            self.write_space();
28488            self.generate_expression(delete)?;
28489        }
28490        if let Some(update) = &e.update {
28491            self.write_space();
28492            self.write_keyword("ON UPDATE");
28493            self.write_space();
28494            self.generate_expression(update)?;
28495        }
28496        if !e.options.is_empty() {
28497            self.write_space();
28498            for (i, opt) in e.options.iter().enumerate() {
28499                if i > 0 {
28500                    self.write_space();
28501                }
28502                self.generate_expression(opt)?;
28503            }
28504        }
28505        Ok(())
28506    }
28507
28508    fn generate_format(&mut self, e: &Format) -> Result<()> {
28509        // FORMAT(this, expressions...)
28510        self.write_keyword("FORMAT");
28511        self.write("(");
28512        self.generate_expression(&e.this)?;
28513        for expr in &e.expressions {
28514            self.write(", ");
28515            self.generate_expression(expr)?;
28516        }
28517        self.write(")");
28518        Ok(())
28519    }
28520
28521    fn generate_format_phrase(&mut self, e: &FormatPhrase) -> Result<()> {
28522        // Teradata: column (FORMAT 'format_string')
28523        self.generate_expression(&e.this)?;
28524        self.write(" (");
28525        self.write_keyword("FORMAT");
28526        self.write(" '");
28527        self.write(&e.format);
28528        self.write("')");
28529        Ok(())
28530    }
28531
28532    fn generate_freespace_property(&mut self, e: &FreespaceProperty) -> Result<()> {
28533        // Python: FREESPACE=this[PERCENT]
28534        self.write_keyword("FREESPACE");
28535        self.write("=");
28536        self.generate_expression(&e.this)?;
28537        if e.percent.is_some() {
28538            self.write_keyword(" PERCENT");
28539        }
28540        Ok(())
28541    }
28542
28543    fn generate_from(&mut self, e: &From) -> Result<()> {
28544        // Python: return f"{self.seg('FROM')} {self.sql(expression, 'this')}"
28545        self.write_keyword("FROM");
28546        self.write_space();
28547
28548        // BigQuery, Hive, Spark, Databricks, SQLite, and ClickHouse prefer explicit CROSS JOIN over comma syntax
28549        // But keep commas when TABLESAMPLE is present
28550        // Also keep commas when the source dialect is Generic/None and target is one of these dialects
28551        use crate::dialects::DialectType;
28552        let has_tablesample = e
28553            .expressions
28554            .iter()
28555            .any(|expr| matches!(expr, Expression::TableSample(_)));
28556        let is_cross_join_dialect = matches!(
28557            self.config.dialect,
28558            Some(DialectType::BigQuery)
28559                | Some(DialectType::Hive)
28560                | Some(DialectType::Spark)
28561                | Some(DialectType::Databricks)
28562                | Some(DialectType::SQLite)
28563                | Some(DialectType::ClickHouse)
28564        );
28565        let source_is_same_as_target2 = self.config.source_dialect.is_some()
28566            && self.config.source_dialect == self.config.dialect;
28567        let source_is_cross_join_dialect2 = matches!(
28568            self.config.source_dialect,
28569            Some(DialectType::BigQuery)
28570                | Some(DialectType::Hive)
28571                | Some(DialectType::Spark)
28572                | Some(DialectType::Databricks)
28573                | Some(DialectType::SQLite)
28574                | Some(DialectType::ClickHouse)
28575        );
28576        let use_cross_join = !has_tablesample
28577            && is_cross_join_dialect
28578            && (source_is_same_as_target2
28579                || source_is_cross_join_dialect2
28580                || self.config.source_dialect.is_none());
28581
28582        // Snowflake wraps standalone VALUES in FROM clause with parentheses
28583        let wrap_values_in_parens = matches!(self.config.dialect, Some(DialectType::Snowflake));
28584
28585        for (i, expr) in e.expressions.iter().enumerate() {
28586            if i > 0 {
28587                if use_cross_join {
28588                    self.write(" CROSS JOIN ");
28589                } else {
28590                    self.write(", ");
28591                }
28592            }
28593            if wrap_values_in_parens && matches!(expr, Expression::Values(_)) {
28594                self.write("(");
28595                self.generate_expression(expr)?;
28596                self.write(")");
28597            } else {
28598                self.generate_expression(expr)?;
28599            }
28600            // Output leading comments that were on the table name before FROM
28601            // (e.g., FROM \n/* comment */\n tbl PIVOT(...) -> ... PIVOT(...) /* comment */)
28602            let leading = Self::extract_table_leading_comments(expr);
28603            for comment in &leading {
28604                self.write_space();
28605                self.write_formatted_comment(comment);
28606            }
28607        }
28608        Ok(())
28609    }
28610
28611    /// Extract leading_comments from a table expression (possibly wrapped in PIVOT/UNPIVOT)
28612    fn extract_table_leading_comments(expr: &Expression) -> Vec<String> {
28613        match expr {
28614            Expression::Table(t) => t.leading_comments.clone(),
28615            Expression::Pivot(p) => {
28616                if let Expression::Table(t) = &p.this {
28617                    t.leading_comments.clone()
28618                } else {
28619                    Vec::new()
28620                }
28621            }
28622            _ => Vec::new(),
28623        }
28624    }
28625
28626    fn generate_from_base(&mut self, e: &FromBase) -> Result<()> {
28627        // FROM_BASE(this, expression) - convert from base N
28628        self.write_keyword("FROM_BASE");
28629        self.write("(");
28630        self.generate_expression(&e.this)?;
28631        self.write(", ");
28632        self.generate_expression(&e.expression)?;
28633        self.write(")");
28634        Ok(())
28635    }
28636
28637    fn generate_from_time_zone(&mut self, e: &FromTimeZone) -> Result<()> {
28638        // this AT TIME ZONE zone AT TIME ZONE 'UTC'
28639        self.generate_expression(&e.this)?;
28640        if let Some(zone) = &e.zone {
28641            self.write_space();
28642            self.write_keyword("AT TIME ZONE");
28643            self.write_space();
28644            self.generate_expression(zone)?;
28645            self.write_space();
28646            self.write_keyword("AT TIME ZONE");
28647            self.write(" 'UTC'");
28648        }
28649        Ok(())
28650    }
28651
28652    fn generate_gap_fill(&mut self, e: &GapFill) -> Result<()> {
28653        // GAP_FILL(this, ts_column, bucket_width, ...)
28654        self.write_keyword("GAP_FILL");
28655        self.write("(");
28656        self.generate_expression(&e.this)?;
28657        if let Some(ts_column) = &e.ts_column {
28658            self.write(", ");
28659            self.generate_expression(ts_column)?;
28660        }
28661        if let Some(bucket_width) = &e.bucket_width {
28662            self.write(", ");
28663            self.generate_expression(bucket_width)?;
28664        }
28665        if let Some(partitioning_columns) = &e.partitioning_columns {
28666            self.write(", ");
28667            self.generate_expression(partitioning_columns)?;
28668        }
28669        if let Some(value_columns) = &e.value_columns {
28670            self.write(", ");
28671            self.generate_expression(value_columns)?;
28672        }
28673        self.write(")");
28674        Ok(())
28675    }
28676
28677    fn generate_generate_date_array(&mut self, e: &GenerateDateArray) -> Result<()> {
28678        // GENERATE_DATE_ARRAY(start, end, step)
28679        self.write_keyword("GENERATE_DATE_ARRAY");
28680        self.write("(");
28681        let mut first = true;
28682        if let Some(start) = &e.start {
28683            self.generate_expression(start)?;
28684            first = false;
28685        }
28686        if let Some(end) = &e.end {
28687            if !first {
28688                self.write(", ");
28689            }
28690            self.generate_expression(end)?;
28691            first = false;
28692        }
28693        if let Some(step) = &e.step {
28694            if !first {
28695                self.write(", ");
28696            }
28697            self.generate_expression(step)?;
28698        }
28699        self.write(")");
28700        Ok(())
28701    }
28702
28703    fn generate_generate_embedding(&mut self, e: &GenerateEmbedding) -> Result<()> {
28704        // ML.GENERATE_EMBEDDING(model, content, params)
28705        self.write_keyword("ML.GENERATE_EMBEDDING");
28706        self.write("(");
28707        self.generate_expression(&e.this)?;
28708        self.write(", ");
28709        self.generate_expression(&e.expression)?;
28710        if let Some(params) = &e.params_struct {
28711            self.write(", ");
28712            self.generate_expression(params)?;
28713        }
28714        self.write(")");
28715        Ok(())
28716    }
28717
28718    fn generate_generate_series(&mut self, e: &GenerateSeries) -> Result<()> {
28719        // Dialect-specific function name
28720        let fn_name = match self.config.dialect {
28721            Some(DialectType::Presto)
28722            | Some(DialectType::Trino)
28723            | Some(DialectType::Athena)
28724            | Some(DialectType::Spark)
28725            | Some(DialectType::Databricks)
28726            | Some(DialectType::Hive) => "SEQUENCE",
28727            _ => "GENERATE_SERIES",
28728        };
28729        self.write_keyword(fn_name);
28730        self.write("(");
28731        let mut first = true;
28732        if let Some(start) = &e.start {
28733            self.generate_expression(start)?;
28734            first = false;
28735        }
28736        if let Some(end) = &e.end {
28737            if !first {
28738                self.write(", ");
28739            }
28740            self.generate_expression(end)?;
28741            first = false;
28742        }
28743        if let Some(step) = &e.step {
28744            if !first {
28745                self.write(", ");
28746            }
28747            // For Presto/Trino: convert WEEK intervals to DAY multiples
28748            // e.g., INTERVAL '1' WEEK -> (1 * INTERVAL '7' DAY)
28749            if matches!(
28750                self.config.dialect,
28751                Some(DialectType::Presto) | Some(DialectType::Trino) | Some(DialectType::Athena)
28752            ) {
28753                if let Some(converted) = self.convert_week_interval_to_day(step) {
28754                    self.generate_expression(&converted)?;
28755                } else {
28756                    self.generate_expression(step)?;
28757                }
28758            } else {
28759                self.generate_expression(step)?;
28760            }
28761        }
28762        self.write(")");
28763        Ok(())
28764    }
28765
28766    /// Convert a WEEK interval to a DAY-based multiplication expression for Presto/Trino.
28767    /// INTERVAL N WEEK -> (N * INTERVAL '7' DAY)
28768    fn convert_week_interval_to_day(&self, expr: &Expression) -> Option<Expression> {
28769        use crate::expressions::*;
28770        if let Expression::Interval(ref iv) = expr {
28771            // Check for structured WEEK unit
28772            let (is_week, count_str) = if let Some(IntervalUnitSpec::Simple {
28773                unit: IntervalUnit::Week,
28774                ..
28775            }) = &iv.unit
28776            {
28777                // Value is in iv.this
28778                let count = match &iv.this {
28779                    Some(Expression::Literal(lit)) => match lit.as_ref() {
28780                        Literal::String(s) | Literal::Number(s) => s.clone(),
28781                        _ => return None,
28782                    },
28783                    _ => return None,
28784                };
28785                (true, count)
28786            } else if iv.unit.is_none() {
28787                // Check for string-encoded interval like "1 WEEK"
28788                if let Some(Expression::Literal(lit)) = &iv.this {
28789                    if let Literal::String(s) = lit.as_ref() {
28790                        let parts: Vec<&str> = s.trim().splitn(2, char::is_whitespace).collect();
28791                        if parts.len() == 2 && parts[1].eq_ignore_ascii_case("WEEK") {
28792                            (true, parts[0].to_string())
28793                        } else {
28794                            (false, String::new())
28795                        }
28796                    } else {
28797                        (false, String::new())
28798                    }
28799                } else {
28800                    (false, String::new())
28801                }
28802            } else {
28803                (false, String::new())
28804            };
28805
28806            if is_week {
28807                // Build: (N * INTERVAL '7' DAY)
28808                let count_expr = Expression::Literal(Box::new(Literal::Number(count_str)));
28809                let day_interval = Expression::Interval(Box::new(Interval {
28810                    this: Some(Expression::Literal(Box::new(Literal::String(
28811                        "7".to_string(),
28812                    )))),
28813                    unit: Some(IntervalUnitSpec::Simple {
28814                        unit: IntervalUnit::Day,
28815                        use_plural: false,
28816                    }),
28817                }));
28818                let mul = Expression::Mul(Box::new(BinaryOp {
28819                    left: count_expr,
28820                    right: day_interval,
28821                    left_comments: vec![],
28822                    operator_comments: vec![],
28823                    trailing_comments: vec![],
28824                    inferred_type: None,
28825                }));
28826                return Some(Expression::Paren(Box::new(Paren {
28827                    this: mul,
28828                    trailing_comments: vec![],
28829                })));
28830            }
28831        }
28832        None
28833    }
28834
28835    fn generate_generate_timestamp_array(&mut self, e: &GenerateTimestampArray) -> Result<()> {
28836        // GENERATE_TIMESTAMP_ARRAY(start, end, step)
28837        self.write_keyword("GENERATE_TIMESTAMP_ARRAY");
28838        self.write("(");
28839        let mut first = true;
28840        if let Some(start) = &e.start {
28841            self.generate_expression(start)?;
28842            first = false;
28843        }
28844        if let Some(end) = &e.end {
28845            if !first {
28846                self.write(", ");
28847            }
28848            self.generate_expression(end)?;
28849            first = false;
28850        }
28851        if let Some(step) = &e.step {
28852            if !first {
28853                self.write(", ");
28854            }
28855            self.generate_expression(step)?;
28856        }
28857        self.write(")");
28858        Ok(())
28859    }
28860
28861    fn generate_generated_as_identity_column_constraint(
28862        &mut self,
28863        e: &GeneratedAsIdentityColumnConstraint,
28864    ) -> Result<()> {
28865        use crate::dialects::DialectType;
28866
28867        // For Snowflake, use AUTOINCREMENT START x INCREMENT y syntax
28868        if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
28869            self.write_keyword("AUTOINCREMENT");
28870            if let Some(start) = &e.start {
28871                self.write_keyword(" START ");
28872                self.generate_expression(start)?;
28873            }
28874            if let Some(increment) = &e.increment {
28875                self.write_keyword(" INCREMENT ");
28876                self.generate_expression(increment)?;
28877            }
28878            return Ok(());
28879        }
28880
28881        // Python: GENERATED [ALWAYS|BY DEFAULT [ON NULL]] AS IDENTITY [(start, increment, ...)]
28882        self.write_keyword("GENERATED");
28883        if let Some(this) = &e.this {
28884            // Check if it's a truthy boolean expression
28885            if let Expression::Boolean(b) = this.as_ref() {
28886                if b.value {
28887                    self.write_keyword(" ALWAYS");
28888                } else {
28889                    self.write_keyword(" BY DEFAULT");
28890                    if e.on_null.is_some() {
28891                        self.write_keyword(" ON NULL");
28892                    }
28893                }
28894            } else {
28895                self.write_keyword(" ALWAYS");
28896            }
28897        }
28898        self.write_keyword(" AS IDENTITY");
28899        // Add sequence options if any
28900        let has_options = e.start.is_some()
28901            || e.increment.is_some()
28902            || e.minvalue.is_some()
28903            || e.maxvalue.is_some();
28904        if has_options {
28905            self.write(" (");
28906            let mut first = true;
28907            if let Some(start) = &e.start {
28908                self.write_keyword("START WITH ");
28909                self.generate_expression(start)?;
28910                first = false;
28911            }
28912            if let Some(increment) = &e.increment {
28913                if !first {
28914                    self.write(" ");
28915                }
28916                self.write_keyword("INCREMENT BY ");
28917                self.generate_expression(increment)?;
28918                first = false;
28919            }
28920            if let Some(minvalue) = &e.minvalue {
28921                if !first {
28922                    self.write(" ");
28923                }
28924                self.write_keyword("MINVALUE ");
28925                self.generate_expression(minvalue)?;
28926                first = false;
28927            }
28928            if let Some(maxvalue) = &e.maxvalue {
28929                if !first {
28930                    self.write(" ");
28931                }
28932                self.write_keyword("MAXVALUE ");
28933                self.generate_expression(maxvalue)?;
28934            }
28935            self.write(")");
28936        }
28937        Ok(())
28938    }
28939
28940    fn generate_generated_as_row_column_constraint(
28941        &mut self,
28942        e: &GeneratedAsRowColumnConstraint,
28943    ) -> Result<()> {
28944        // Python: GENERATED ALWAYS AS ROW START|END [HIDDEN]
28945        self.write_keyword("GENERATED ALWAYS AS ROW ");
28946        if e.start.is_some() {
28947            self.write_keyword("START");
28948        } else {
28949            self.write_keyword("END");
28950        }
28951        if e.hidden.is_some() {
28952            self.write_keyword(" HIDDEN");
28953        }
28954        Ok(())
28955    }
28956
28957    fn generate_get(&mut self, e: &Get) -> Result<()> {
28958        // GET this target properties
28959        self.write_keyword("GET");
28960        self.write_space();
28961        self.generate_expression(&e.this)?;
28962        if let Some(target) = &e.target {
28963            self.write_space();
28964            self.generate_expression(target)?;
28965        }
28966        for prop in &e.properties {
28967            self.write_space();
28968            self.generate_expression(prop)?;
28969        }
28970        Ok(())
28971    }
28972
28973    fn generate_get_extract(&mut self, e: &GetExtract) -> Result<()> {
28974        // GetExtract generates bracket access: this[expression]
28975        self.generate_expression(&e.this)?;
28976        self.write("[");
28977        self.generate_expression(&e.expression)?;
28978        self.write("]");
28979        Ok(())
28980    }
28981
28982    fn generate_getbit(&mut self, e: &Getbit) -> Result<()> {
28983        // GETBIT(this, expression) or GET_BIT(this, expression)
28984        self.write_keyword("GETBIT");
28985        self.write("(");
28986        self.generate_expression(&e.this)?;
28987        self.write(", ");
28988        self.generate_expression(&e.expression)?;
28989        self.write(")");
28990        Ok(())
28991    }
28992
28993    fn generate_grant_principal(&mut self, e: &GrantPrincipal) -> Result<()> {
28994        // [ROLE|GROUP|SHARE] name (e.g., "ROLE admin", "GROUP qa_users", "SHARE s1", or just "user1")
28995        if e.is_role {
28996            self.write_keyword("ROLE");
28997            self.write_space();
28998        } else if e.is_group {
28999            self.write_keyword("GROUP");
29000            self.write_space();
29001        } else if e.is_share {
29002            self.write_keyword("SHARE");
29003            self.write_space();
29004        }
29005        self.write(&e.name.name);
29006        Ok(())
29007    }
29008
29009    fn generate_grant_privilege(&mut self, e: &GrantPrivilege) -> Result<()> {
29010        // privilege(columns) or just privilege
29011        self.generate_expression(&e.this)?;
29012        if !e.expressions.is_empty() {
29013            self.write("(");
29014            for (i, expr) in e.expressions.iter().enumerate() {
29015                if i > 0 {
29016                    self.write(", ");
29017                }
29018                self.generate_expression(expr)?;
29019            }
29020            self.write(")");
29021        }
29022        Ok(())
29023    }
29024
29025    fn generate_group(&mut self, e: &Group) -> Result<()> {
29026        // Python handles GROUP BY ALL/DISTINCT modifiers and grouping expressions
29027        self.write_keyword("GROUP BY");
29028        // Handle ALL/DISTINCT modifier: Some(true) = ALL, Some(false) = DISTINCT
29029        match e.all {
29030            Some(true) => {
29031                self.write_space();
29032                self.write_keyword("ALL");
29033            }
29034            Some(false) => {
29035                self.write_space();
29036                self.write_keyword("DISTINCT");
29037            }
29038            None => {}
29039        }
29040        if !e.expressions.is_empty() {
29041            self.write_space();
29042            for (i, expr) in e.expressions.iter().enumerate() {
29043                if i > 0 {
29044                    self.write(", ");
29045                }
29046                self.generate_expression(expr)?;
29047            }
29048        }
29049        // Handle CUBE, ROLLUP, GROUPING SETS
29050        if let Some(cube) = &e.cube {
29051            if !e.expressions.is_empty() {
29052                self.write(", ");
29053            } else {
29054                self.write_space();
29055            }
29056            self.generate_expression(cube)?;
29057        }
29058        if let Some(rollup) = &e.rollup {
29059            if !e.expressions.is_empty() || e.cube.is_some() {
29060                self.write(", ");
29061            } else {
29062                self.write_space();
29063            }
29064            self.generate_expression(rollup)?;
29065        }
29066        if let Some(grouping_sets) = &e.grouping_sets {
29067            if !e.expressions.is_empty() || e.cube.is_some() || e.rollup.is_some() {
29068                self.write(", ");
29069            } else {
29070                self.write_space();
29071            }
29072            self.generate_expression(grouping_sets)?;
29073        }
29074        if let Some(totals) = &e.totals {
29075            self.write_space();
29076            self.write_keyword("WITH TOTALS");
29077            self.generate_expression(totals)?;
29078        }
29079        Ok(())
29080    }
29081
29082    fn generate_group_by(&mut self, e: &GroupBy) -> Result<()> {
29083        // GROUP BY expressions
29084        self.write_keyword("GROUP BY");
29085        // Handle ALL/DISTINCT modifier: Some(true) = ALL, Some(false) = DISTINCT
29086        match e.all {
29087            Some(true) => {
29088                self.write_space();
29089                self.write_keyword("ALL");
29090            }
29091            Some(false) => {
29092                self.write_space();
29093                self.write_keyword("DISTINCT");
29094            }
29095            None => {}
29096        }
29097
29098        // Check for trailing WITH CUBE or WITH ROLLUP (Hive/MySQL syntax)
29099        // These are represented as Cube/Rollup expressions with empty expressions at the end
29100        let mut trailing_cube = false;
29101        let mut trailing_rollup = false;
29102        let mut regular_expressions: Vec<&Expression> = Vec::new();
29103
29104        for expr in &e.expressions {
29105            match expr {
29106                Expression::Cube(c) if c.expressions.is_empty() => {
29107                    trailing_cube = true;
29108                }
29109                Expression::Rollup(r) if r.expressions.is_empty() => {
29110                    trailing_rollup = true;
29111                }
29112                _ => {
29113                    regular_expressions.push(expr);
29114                }
29115            }
29116        }
29117
29118        // In pretty mode, put columns on separate lines
29119        if self.config.pretty {
29120            self.write_newline();
29121            self.indent_level += 1;
29122            for (i, expr) in regular_expressions.iter().enumerate() {
29123                if i > 0 {
29124                    self.write(",");
29125                    self.write_newline();
29126                }
29127                self.write_indent();
29128                self.generate_expression(expr)?;
29129            }
29130            self.indent_level -= 1;
29131        } else {
29132            self.write_space();
29133            for (i, expr) in regular_expressions.iter().enumerate() {
29134                if i > 0 {
29135                    self.write(", ");
29136                }
29137                self.generate_expression(expr)?;
29138            }
29139        }
29140
29141        // Output trailing WITH CUBE or WITH ROLLUP
29142        if trailing_cube {
29143            self.write_space();
29144            self.write_keyword("WITH CUBE");
29145        } else if trailing_rollup {
29146            self.write_space();
29147            self.write_keyword("WITH ROLLUP");
29148        }
29149
29150        // ClickHouse: WITH TOTALS
29151        if e.totals {
29152            self.write_space();
29153            self.write_keyword("WITH TOTALS");
29154        }
29155
29156        Ok(())
29157    }
29158
29159    fn generate_grouping(&mut self, e: &Grouping) -> Result<()> {
29160        // GROUPING(col1, col2, ...)
29161        self.write_keyword("GROUPING");
29162        self.write("(");
29163        for (i, expr) in e.expressions.iter().enumerate() {
29164            if i > 0 {
29165                self.write(", ");
29166            }
29167            self.generate_expression(expr)?;
29168        }
29169        self.write(")");
29170        Ok(())
29171    }
29172
29173    fn generate_grouping_id(&mut self, e: &GroupingId) -> Result<()> {
29174        // GROUPING_ID(col1, col2, ...)
29175        self.write_keyword("GROUPING_ID");
29176        self.write("(");
29177        for (i, expr) in e.expressions.iter().enumerate() {
29178            if i > 0 {
29179                self.write(", ");
29180            }
29181            self.generate_expression(expr)?;
29182        }
29183        self.write(")");
29184        Ok(())
29185    }
29186
29187    fn generate_grouping_sets(&mut self, e: &GroupingSets) -> Result<()> {
29188        // Python: return f"GROUPING SETS {self.wrap(grouping_sets)}"
29189        self.write_keyword("GROUPING SETS");
29190        self.write(" (");
29191        for (i, expr) in e.expressions.iter().enumerate() {
29192            if i > 0 {
29193                self.write(", ");
29194            }
29195            self.generate_expression(expr)?;
29196        }
29197        self.write(")");
29198        Ok(())
29199    }
29200
29201    fn generate_hash_agg(&mut self, e: &HashAgg) -> Result<()> {
29202        // HASH_AGG(this, expressions...)
29203        self.write_keyword("HASH_AGG");
29204        self.write("(");
29205        self.generate_expression(&e.this)?;
29206        for expr in &e.expressions {
29207            self.write(", ");
29208            self.generate_expression(expr)?;
29209        }
29210        self.write(")");
29211        Ok(())
29212    }
29213
29214    fn generate_having(&mut self, e: &Having) -> Result<()> {
29215        // Python: return f"{self.seg('HAVING')}{self.sep()}{this}"
29216        self.write_keyword("HAVING");
29217        self.write_space();
29218        self.generate_expression(&e.this)?;
29219        Ok(())
29220    }
29221
29222    fn generate_having_max(&mut self, e: &HavingMax) -> Result<()> {
29223        // Python: this HAVING MAX|MIN expression
29224        self.generate_expression(&e.this)?;
29225        self.write_space();
29226        self.write_keyword("HAVING");
29227        self.write_space();
29228        if e.max.is_some() {
29229            self.write_keyword("MAX");
29230        } else {
29231            self.write_keyword("MIN");
29232        }
29233        self.write_space();
29234        self.generate_expression(&e.expression)?;
29235        Ok(())
29236    }
29237
29238    fn generate_heredoc(&mut self, e: &Heredoc) -> Result<()> {
29239        use crate::dialects::DialectType;
29240        // DuckDB: convert dollar-tagged strings to single-quoted
29241        if matches!(self.config.dialect, Some(DialectType::DuckDB)) {
29242            // Extract the string content and output as single-quoted
29243            if let Expression::Literal(ref lit) = *e.this {
29244                if let Literal::String(ref s) = lit.as_ref() {
29245                    return self.generate_string_literal(s);
29246                }
29247            }
29248        }
29249        // PostgreSQL: preserve dollar-quoting
29250        if matches!(
29251            self.config.dialect,
29252            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
29253        ) {
29254            self.write("$");
29255            if let Some(tag) = &e.tag {
29256                self.generate_expression(tag)?;
29257            }
29258            self.write("$");
29259            self.generate_expression(&e.this)?;
29260            self.write("$");
29261            if let Some(tag) = &e.tag {
29262                self.generate_expression(tag)?;
29263            }
29264            self.write("$");
29265            return Ok(());
29266        }
29267        // Default: output as dollar-tagged
29268        self.write("$");
29269        if let Some(tag) = &e.tag {
29270            self.generate_expression(tag)?;
29271        }
29272        self.write("$");
29273        self.generate_expression(&e.this)?;
29274        self.write("$");
29275        if let Some(tag) = &e.tag {
29276            self.generate_expression(tag)?;
29277        }
29278        self.write("$");
29279        Ok(())
29280    }
29281
29282    fn generate_hex_encode(&mut self, e: &HexEncode) -> Result<()> {
29283        // HEX_ENCODE(this)
29284        self.write_keyword("HEX_ENCODE");
29285        self.write("(");
29286        self.generate_expression(&e.this)?;
29287        self.write(")");
29288        Ok(())
29289    }
29290
29291    fn generate_historical_data(&mut self, e: &HistoricalData) -> Result<()> {
29292        // Python: this (kind => expression)
29293        // Write the keyword (AT/BEFORE/END) directly to avoid quoting it as a reserved word
29294        match e.this.as_ref() {
29295            Expression::Identifier(id) => self.write(&id.name),
29296            other => self.generate_expression(other)?,
29297        }
29298        self.write(" (");
29299        self.write(&e.kind);
29300        self.write(" => ");
29301        self.generate_expression(&e.expression)?;
29302        self.write(")");
29303        Ok(())
29304    }
29305
29306    fn generate_hll(&mut self, e: &Hll) -> Result<()> {
29307        // HLL(this, expressions...)
29308        self.write_keyword("HLL");
29309        self.write("(");
29310        self.generate_expression(&e.this)?;
29311        for expr in &e.expressions {
29312            self.write(", ");
29313            self.generate_expression(expr)?;
29314        }
29315        self.write(")");
29316        Ok(())
29317    }
29318
29319    fn generate_in_out_column_constraint(&mut self, e: &InOutColumnConstraint) -> Result<()> {
29320        // Python: IN|OUT|IN OUT
29321        if e.input_.is_some() && e.output.is_some() {
29322            self.write_keyword("IN OUT");
29323        } else if e.input_.is_some() {
29324            self.write_keyword("IN");
29325        } else if e.output.is_some() {
29326            self.write_keyword("OUT");
29327        }
29328        Ok(())
29329    }
29330
29331    fn generate_include_property(&mut self, e: &IncludeProperty) -> Result<()> {
29332        // Python: INCLUDE this [column_def] [AS alias]
29333        self.write_keyword("INCLUDE");
29334        self.write_space();
29335        self.generate_expression(&e.this)?;
29336        if let Some(column_def) = &e.column_def {
29337            self.write_space();
29338            self.generate_expression(column_def)?;
29339        }
29340        if let Some(alias) = &e.alias {
29341            self.write_space();
29342            self.write_keyword("AS");
29343            self.write_space();
29344            self.write(alias);
29345        }
29346        Ok(())
29347    }
29348
29349    fn generate_index(&mut self, e: &Index) -> Result<()> {
29350        // [UNIQUE] [PRIMARY] [AMP] INDEX [name] [ON table] (params)
29351        if e.unique {
29352            self.write_keyword("UNIQUE");
29353            self.write_space();
29354        }
29355        if e.primary.is_some() {
29356            self.write_keyword("PRIMARY");
29357            self.write_space();
29358        }
29359        if e.amp.is_some() {
29360            self.write_keyword("AMP");
29361            self.write_space();
29362        }
29363        if e.table.is_none() {
29364            self.write_keyword("INDEX");
29365            self.write_space();
29366        }
29367        if let Some(name) = &e.this {
29368            self.generate_expression(name)?;
29369            self.write_space();
29370        }
29371        if let Some(table) = &e.table {
29372            self.write_keyword("ON");
29373            self.write_space();
29374            self.generate_expression(table)?;
29375        }
29376        if !e.params.is_empty() {
29377            self.write("(");
29378            for (i, param) in e.params.iter().enumerate() {
29379                if i > 0 {
29380                    self.write(", ");
29381                }
29382                self.generate_expression(param)?;
29383            }
29384            self.write(")");
29385        }
29386        Ok(())
29387    }
29388
29389    fn generate_index_column_constraint(&mut self, e: &IndexColumnConstraint) -> Result<()> {
29390        // Python: kind INDEX [this] [USING index_type] (expressions) [options]
29391        if let Some(kind) = &e.kind {
29392            self.write(kind);
29393            self.write_space();
29394        }
29395        self.write_keyword("INDEX");
29396        if let Some(this) = &e.this {
29397            self.write_space();
29398            self.generate_expression(this)?;
29399        }
29400        if let Some(index_type) = &e.index_type {
29401            self.write_space();
29402            self.write_keyword("USING");
29403            self.write_space();
29404            self.generate_expression(index_type)?;
29405        }
29406        if !e.expressions.is_empty() {
29407            self.write(" (");
29408            for (i, expr) in e.expressions.iter().enumerate() {
29409                if i > 0 {
29410                    self.write(", ");
29411                }
29412                self.generate_expression(expr)?;
29413            }
29414            self.write(")");
29415        }
29416        for opt in &e.options {
29417            self.write_space();
29418            self.generate_expression(opt)?;
29419        }
29420        Ok(())
29421    }
29422
29423    fn generate_index_constraint_option(&mut self, e: &IndexConstraintOption) -> Result<()> {
29424        // Python: KEY_BLOCK_SIZE = x | USING x | WITH PARSER x | COMMENT x | visible | engine_attr | secondary_engine_attr
29425        if let Some(key_block_size) = &e.key_block_size {
29426            self.write_keyword("KEY_BLOCK_SIZE");
29427            self.write(" = ");
29428            self.generate_expression(key_block_size)?;
29429        } else if let Some(using) = &e.using {
29430            self.write_keyword("USING");
29431            self.write_space();
29432            self.generate_expression(using)?;
29433        } else if let Some(parser) = &e.parser {
29434            self.write_keyword("WITH PARSER");
29435            self.write_space();
29436            self.generate_expression(parser)?;
29437        } else if let Some(comment) = &e.comment {
29438            self.write_keyword("COMMENT");
29439            self.write_space();
29440            self.generate_expression(comment)?;
29441        } else if let Some(visible) = &e.visible {
29442            self.generate_expression(visible)?;
29443        } else if let Some(engine_attr) = &e.engine_attr {
29444            self.write_keyword("ENGINE_ATTRIBUTE");
29445            self.write(" = ");
29446            self.generate_expression(engine_attr)?;
29447        } else if let Some(secondary_engine_attr) = &e.secondary_engine_attr {
29448            self.write_keyword("SECONDARY_ENGINE_ATTRIBUTE");
29449            self.write(" = ");
29450            self.generate_expression(secondary_engine_attr)?;
29451        }
29452        Ok(())
29453    }
29454
29455    fn generate_index_parameters(&mut self, e: &IndexParameters) -> Result<()> {
29456        // Python: [USING using] (columns) [PARTITION BY partition_by] [where] [INCLUDE (include)] [WITH (with_storage)] [USING INDEX TABLESPACE tablespace]
29457        if let Some(using) = &e.using {
29458            self.write_keyword("USING");
29459            self.write_space();
29460            self.generate_expression(using)?;
29461        }
29462        if !e.columns.is_empty() {
29463            self.write("(");
29464            for (i, col) in e.columns.iter().enumerate() {
29465                if i > 0 {
29466                    self.write(", ");
29467                }
29468                self.generate_expression(col)?;
29469            }
29470            self.write(")");
29471        }
29472        if let Some(partition_by) = &e.partition_by {
29473            self.write_space();
29474            self.write_keyword("PARTITION BY");
29475            self.write_space();
29476            self.generate_expression(partition_by)?;
29477        }
29478        if let Some(where_) = &e.where_ {
29479            self.write_space();
29480            self.generate_expression(where_)?;
29481        }
29482        if let Some(include) = &e.include {
29483            self.write_space();
29484            self.write_keyword("INCLUDE");
29485            self.write(" (");
29486            self.generate_expression(include)?;
29487            self.write(")");
29488        }
29489        if let Some(with_storage) = &e.with_storage {
29490            self.write_space();
29491            self.write_keyword("WITH");
29492            self.write(" (");
29493            self.generate_expression(with_storage)?;
29494            self.write(")");
29495        }
29496        if let Some(tablespace) = &e.tablespace {
29497            self.write_space();
29498            self.write_keyword("USING INDEX TABLESPACE");
29499            self.write_space();
29500            self.generate_expression(tablespace)?;
29501        }
29502        Ok(())
29503    }
29504
29505    fn generate_index_table_hint(&mut self, e: &IndexTableHint) -> Result<()> {
29506        // Python: this INDEX [FOR target] (expressions)
29507        // Write hint type (USE/IGNORE/FORCE) as keyword, not through generate_expression
29508        // to avoid quoting reserved keywords like IGNORE, FORCE, JOIN
29509        if let Expression::Identifier(id) = &*e.this {
29510            self.write_keyword(&id.name);
29511        } else {
29512            self.generate_expression(&e.this)?;
29513        }
29514        self.write_space();
29515        self.write_keyword("INDEX");
29516        if let Some(target) = &e.target {
29517            self.write_space();
29518            self.write_keyword("FOR");
29519            self.write_space();
29520            if let Expression::Identifier(id) = &**target {
29521                self.write_keyword(&id.name);
29522            } else {
29523                self.generate_expression(target)?;
29524            }
29525        }
29526        // Always output parentheses (even if empty, e.g. USE INDEX ())
29527        self.write(" (");
29528        for (i, expr) in e.expressions.iter().enumerate() {
29529            if i > 0 {
29530                self.write(", ");
29531            }
29532            self.generate_expression(expr)?;
29533        }
29534        self.write(")");
29535        Ok(())
29536    }
29537
29538    fn generate_inherits_property(&mut self, e: &InheritsProperty) -> Result<()> {
29539        // INHERITS (table1, table2, ...)
29540        self.write_keyword("INHERITS");
29541        self.write(" (");
29542        for (i, expr) in e.expressions.iter().enumerate() {
29543            if i > 0 {
29544                self.write(", ");
29545            }
29546            self.generate_expression(expr)?;
29547        }
29548        self.write(")");
29549        Ok(())
29550    }
29551
29552    fn generate_input_model_property(&mut self, e: &InputModelProperty) -> Result<()> {
29553        // INPUT(model)
29554        self.write_keyword("INPUT");
29555        self.write("(");
29556        self.generate_expression(&e.this)?;
29557        self.write(")");
29558        Ok(())
29559    }
29560
29561    fn generate_input_output_format(&mut self, e: &InputOutputFormat) -> Result<()> {
29562        // Python: INPUTFORMAT input_format OUTPUTFORMAT output_format
29563        if let Some(input_format) = &e.input_format {
29564            self.write_keyword("INPUTFORMAT");
29565            self.write_space();
29566            self.generate_expression(input_format)?;
29567        }
29568        if let Some(output_format) = &e.output_format {
29569            if e.input_format.is_some() {
29570                self.write(" ");
29571            }
29572            self.write_keyword("OUTPUTFORMAT");
29573            self.write_space();
29574            self.generate_expression(output_format)?;
29575        }
29576        Ok(())
29577    }
29578
29579    fn generate_install(&mut self, e: &Install) -> Result<()> {
29580        // [FORCE] INSTALL extension [FROM source]
29581        if e.force.is_some() {
29582            self.write_keyword("FORCE");
29583            self.write_space();
29584        }
29585        self.write_keyword("INSTALL");
29586        self.write_space();
29587        self.generate_expression(&e.this)?;
29588        if let Some(from) = &e.from_ {
29589            self.write_space();
29590            self.write_keyword("FROM");
29591            self.write_space();
29592            self.generate_expression(from)?;
29593        }
29594        Ok(())
29595    }
29596
29597    fn generate_interval_op(&mut self, e: &IntervalOp) -> Result<()> {
29598        // INTERVAL 'expression' unit
29599        self.write_keyword("INTERVAL");
29600        self.write_space();
29601        // When a unit is specified and the expression is a number,
29602        self.generate_expression(&e.expression)?;
29603        if let Some(unit) = &e.unit {
29604            self.write_space();
29605            self.write(unit);
29606        }
29607        Ok(())
29608    }
29609
29610    fn generate_interval_span(&mut self, e: &IntervalSpan) -> Result<()> {
29611        // unit TO unit (e.g., HOUR TO SECOND)
29612        self.write(&format!("{:?}", e.this).to_ascii_uppercase());
29613        self.write_space();
29614        self.write_keyword("TO");
29615        self.write_space();
29616        self.write(&format!("{:?}", e.expression).to_ascii_uppercase());
29617        Ok(())
29618    }
29619
29620    fn generate_into_clause(&mut self, e: &IntoClause) -> Result<()> {
29621        // INTO [TEMPORARY|UNLOGGED] table
29622        self.write_keyword("INTO");
29623        if e.temporary {
29624            self.write_keyword(" TEMPORARY");
29625        }
29626        if e.unlogged.is_some() {
29627            self.write_keyword(" UNLOGGED");
29628        }
29629        if let Some(this) = &e.this {
29630            self.write_space();
29631            self.generate_expression(this)?;
29632        }
29633        if !e.expressions.is_empty() {
29634            self.write(" (");
29635            for (i, expr) in e.expressions.iter().enumerate() {
29636                if i > 0 {
29637                    self.write(", ");
29638                }
29639                self.generate_expression(expr)?;
29640            }
29641            self.write(")");
29642        }
29643        Ok(())
29644    }
29645
29646    fn generate_introducer(&mut self, e: &Introducer) -> Result<()> {
29647        // Python: this expression (e.g., _utf8 'string')
29648        self.generate_expression(&e.this)?;
29649        self.write_space();
29650        self.generate_expression(&e.expression)?;
29651        Ok(())
29652    }
29653
29654    fn generate_isolated_loading_property(&mut self, e: &IsolatedLoadingProperty) -> Result<()> {
29655        // Python: WITH [NO] [CONCURRENT] ISOLATED LOADING [target]
29656        self.write_keyword("WITH");
29657        if e.no.is_some() {
29658            self.write_keyword(" NO");
29659        }
29660        if e.concurrent.is_some() {
29661            self.write_keyword(" CONCURRENT");
29662        }
29663        self.write_keyword(" ISOLATED LOADING");
29664        if let Some(target) = &e.target {
29665            self.write_space();
29666            self.generate_expression(target)?;
29667        }
29668        Ok(())
29669    }
29670
29671    fn generate_json(&mut self, e: &JSON) -> Result<()> {
29672        // Python: JSON [this] [WITHOUT|WITH] [UNIQUE KEYS]
29673        self.write_keyword("JSON");
29674        if let Some(this) = &e.this {
29675            self.write_space();
29676            self.generate_expression(this)?;
29677        }
29678        if let Some(with_) = &e.with_ {
29679            // Check if it's a truthy boolean
29680            if let Expression::Boolean(b) = with_.as_ref() {
29681                if b.value {
29682                    self.write_keyword(" WITH");
29683                } else {
29684                    self.write_keyword(" WITHOUT");
29685                }
29686            }
29687        }
29688        if e.unique {
29689            self.write_keyword(" UNIQUE KEYS");
29690        }
29691        Ok(())
29692    }
29693
29694    fn generate_json_array(&mut self, e: &JSONArray) -> Result<()> {
29695        // Python: return self.func("JSON_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})")
29696        self.write_keyword("JSON_ARRAY");
29697        self.write("(");
29698        for (i, expr) in e.expressions.iter().enumerate() {
29699            if i > 0 {
29700                self.write(", ");
29701            }
29702            self.generate_expression(expr)?;
29703        }
29704        if let Some(null_handling) = &e.null_handling {
29705            self.write_space();
29706            self.generate_expression(null_handling)?;
29707        }
29708        if let Some(return_type) = &e.return_type {
29709            self.write_space();
29710            self.write_keyword("RETURNING");
29711            self.write_space();
29712            self.generate_expression(return_type)?;
29713        }
29714        if e.strict.is_some() {
29715            self.write_space();
29716            self.write_keyword("STRICT");
29717        }
29718        self.write(")");
29719        Ok(())
29720    }
29721
29722    fn generate_json_array_agg_struct(&mut self, e: &JSONArrayAgg) -> Result<()> {
29723        // JSON_ARRAYAGG(this [ORDER BY ...] [NULL ON NULL | ABSENT ON NULL] [RETURNING type] [STRICT])
29724        self.write_keyword("JSON_ARRAYAGG");
29725        self.write("(");
29726        self.generate_expression(&e.this)?;
29727        if let Some(order) = &e.order {
29728            self.write_space();
29729            // Order is stored as an OrderBy expression
29730            if let Expression::OrderBy(ob) = order.as_ref() {
29731                self.write_keyword("ORDER BY");
29732                self.write_space();
29733                for (i, ord) in ob.expressions.iter().enumerate() {
29734                    if i > 0 {
29735                        self.write(", ");
29736                    }
29737                    self.generate_ordered(ord)?;
29738                }
29739            } else {
29740                // Fallback: generate the expression directly
29741                self.generate_expression(order)?;
29742            }
29743        }
29744        if let Some(null_handling) = &e.null_handling {
29745            self.write_space();
29746            self.generate_expression(null_handling)?;
29747        }
29748        if let Some(return_type) = &e.return_type {
29749            self.write_space();
29750            self.write_keyword("RETURNING");
29751            self.write_space();
29752            self.generate_expression(return_type)?;
29753        }
29754        if e.strict.is_some() {
29755            self.write_space();
29756            self.write_keyword("STRICT");
29757        }
29758        self.write(")");
29759        Ok(())
29760    }
29761
29762    fn generate_json_object_agg_struct(&mut self, e: &JSONObjectAgg) -> Result<()> {
29763        // JSON_OBJECTAGG(key: value [NULL ON NULL | ABSENT ON NULL] [WITH UNIQUE KEYS] [RETURNING type])
29764        self.write_keyword("JSON_OBJECTAGG");
29765        self.write("(");
29766        for (i, expr) in e.expressions.iter().enumerate() {
29767            if i > 0 {
29768                self.write(", ");
29769            }
29770            self.generate_expression(expr)?;
29771        }
29772        if let Some(null_handling) = &e.null_handling {
29773            self.write_space();
29774            self.generate_expression(null_handling)?;
29775        }
29776        if let Some(unique_keys) = &e.unique_keys {
29777            self.write_space();
29778            if let Expression::Boolean(b) = unique_keys.as_ref() {
29779                if b.value {
29780                    self.write_keyword("WITH UNIQUE KEYS");
29781                } else {
29782                    self.write_keyword("WITHOUT UNIQUE KEYS");
29783                }
29784            }
29785        }
29786        if let Some(return_type) = &e.return_type {
29787            self.write_space();
29788            self.write_keyword("RETURNING");
29789            self.write_space();
29790            self.generate_expression(return_type)?;
29791        }
29792        self.write(")");
29793        Ok(())
29794    }
29795
29796    fn generate_json_array_append(&mut self, e: &JSONArrayAppend) -> Result<()> {
29797        // JSON_ARRAY_APPEND(this, path, value, ...)
29798        self.write_keyword("JSON_ARRAY_APPEND");
29799        self.write("(");
29800        self.generate_expression(&e.this)?;
29801        for expr in &e.expressions {
29802            self.write(", ");
29803            self.generate_expression(expr)?;
29804        }
29805        self.write(")");
29806        Ok(())
29807    }
29808
29809    fn generate_json_array_contains(&mut self, e: &JSONArrayContains) -> Result<()> {
29810        // JSON_ARRAY_CONTAINS(this, expression)
29811        self.write_keyword("JSON_ARRAY_CONTAINS");
29812        self.write("(");
29813        self.generate_expression(&e.this)?;
29814        self.write(", ");
29815        self.generate_expression(&e.expression)?;
29816        self.write(")");
29817        Ok(())
29818    }
29819
29820    fn generate_json_array_insert(&mut self, e: &JSONArrayInsert) -> Result<()> {
29821        // JSON_ARRAY_INSERT(this, path, value, ...)
29822        self.write_keyword("JSON_ARRAY_INSERT");
29823        self.write("(");
29824        self.generate_expression(&e.this)?;
29825        for expr in &e.expressions {
29826            self.write(", ");
29827            self.generate_expression(expr)?;
29828        }
29829        self.write(")");
29830        Ok(())
29831    }
29832
29833    fn generate_jsonb_exists(&mut self, e: &JSONBExists) -> Result<()> {
29834        // JSONB_EXISTS(this, path)
29835        self.write_keyword("JSONB_EXISTS");
29836        self.write("(");
29837        self.generate_expression(&e.this)?;
29838        if let Some(path) = &e.path {
29839            self.write(", ");
29840            self.generate_expression(path)?;
29841        }
29842        self.write(")");
29843        Ok(())
29844    }
29845
29846    fn generate_jsonb_extract_scalar(&mut self, e: &JSONBExtractScalar) -> Result<()> {
29847        // JSONB_EXTRACT_SCALAR(this, expression)
29848        self.write_keyword("JSONB_EXTRACT_SCALAR");
29849        self.write("(");
29850        self.generate_expression(&e.this)?;
29851        self.write(", ");
29852        self.generate_expression(&e.expression)?;
29853        self.write(")");
29854        Ok(())
29855    }
29856
29857    fn generate_jsonb_object_agg(&mut self, e: &JSONBObjectAgg) -> Result<()> {
29858        // JSONB_OBJECT_AGG(this, expression)
29859        self.write_keyword("JSONB_OBJECT_AGG");
29860        self.write("(");
29861        self.generate_expression(&e.this)?;
29862        self.write(", ");
29863        self.generate_expression(&e.expression)?;
29864        self.write(")");
29865        Ok(())
29866    }
29867
29868    fn generate_json_column_def(&mut self, e: &JSONColumnDef) -> Result<()> {
29869        // Python: NESTED PATH path schema | this kind PATH path [FOR ORDINALITY]
29870        if let Some(nested_schema) = &e.nested_schema {
29871            self.write_keyword("NESTED");
29872            if let Some(path) = &e.path {
29873                self.write_space();
29874                self.write_keyword("PATH");
29875                self.write_space();
29876                self.generate_expression(path)?;
29877            }
29878            self.write_space();
29879            self.generate_expression(nested_schema)?;
29880        } else {
29881            if let Some(this) = &e.this {
29882                self.generate_expression(this)?;
29883            }
29884            if let Some(kind) = &e.kind {
29885                self.write_space();
29886                self.write(kind);
29887            }
29888            if let Some(path) = &e.path {
29889                self.write_space();
29890                self.write_keyword("PATH");
29891                self.write_space();
29892                self.generate_expression(path)?;
29893            }
29894            if e.ordinality.is_some() {
29895                self.write_keyword(" FOR ORDINALITY");
29896            }
29897        }
29898        Ok(())
29899    }
29900
29901    fn generate_json_exists(&mut self, e: &JSONExists) -> Result<()> {
29902        // JSON_EXISTS(this, path PASSING vars ON ERROR/EMPTY condition)
29903        self.write_keyword("JSON_EXISTS");
29904        self.write("(");
29905        self.generate_expression(&e.this)?;
29906        if let Some(path) = &e.path {
29907            self.write(", ");
29908            self.generate_expression(path)?;
29909        }
29910        if let Some(passing) = &e.passing {
29911            self.write_space();
29912            self.write_keyword("PASSING");
29913            self.write_space();
29914            self.generate_expression(passing)?;
29915        }
29916        if let Some(on_condition) = &e.on_condition {
29917            self.write_space();
29918            self.generate_expression(on_condition)?;
29919        }
29920        self.write(")");
29921        Ok(())
29922    }
29923
29924    fn generate_json_cast(&mut self, e: &JSONCast) -> Result<()> {
29925        self.generate_expression(&e.this)?;
29926        self.write(".:");
29927        // If the data type has nested type parameters (like Array(JSON), Map(String, Int)),
29928        // wrap the entire type string in double quotes.
29929        // This matches Python sqlglot's ClickHouse _json_cast_sql behavior.
29930        if Self::data_type_has_nested_expressions(&e.to) {
29931            // Generate the data type to a temporary string buffer, then wrap in quotes
29932            let saved = std::mem::take(&mut self.output);
29933            self.generate_data_type(&e.to)?;
29934            let type_sql = std::mem::replace(&mut self.output, saved);
29935            self.write("\"");
29936            self.write(&type_sql);
29937            self.write("\"");
29938        } else {
29939            self.generate_data_type(&e.to)?;
29940        }
29941        Ok(())
29942    }
29943
29944    /// Check if a DataType has nested type expressions (sub-types).
29945    /// This corresponds to Python sqlglot's `to.expressions` being non-empty.
29946    fn data_type_has_nested_expressions(dt: &DataType) -> bool {
29947        matches!(
29948            dt,
29949            DataType::Array { .. } | DataType::Map { .. } | DataType::Struct { .. }
29950        )
29951    }
29952
29953    fn generate_json_extract_array(&mut self, e: &JSONExtractArray) -> Result<()> {
29954        // JSON_EXTRACT_ARRAY(this, expression)
29955        self.write_keyword("JSON_EXTRACT_ARRAY");
29956        self.write("(");
29957        self.generate_expression(&e.this)?;
29958        if let Some(expr) = &e.expression {
29959            self.write(", ");
29960            self.generate_expression(expr)?;
29961        }
29962        self.write(")");
29963        Ok(())
29964    }
29965
29966    fn generate_json_extract_quote(&mut self, e: &JSONExtractQuote) -> Result<()> {
29967        // Snowflake: KEEP [OMIT] QUOTES [SCALAR_ONLY] for JSON extraction
29968        if let Some(option) = &e.option {
29969            self.generate_expression(option)?;
29970            self.write_space();
29971        }
29972        self.write_keyword("QUOTES");
29973        if e.scalar.is_some() {
29974            self.write_keyword(" SCALAR_ONLY");
29975        }
29976        Ok(())
29977    }
29978
29979    fn generate_json_extract_scalar(&mut self, e: &JSONExtractScalar) -> Result<()> {
29980        // JSON_EXTRACT_SCALAR(this, expression)
29981        self.write_keyword("JSON_EXTRACT_SCALAR");
29982        self.write("(");
29983        self.generate_expression(&e.this)?;
29984        self.write(", ");
29985        self.generate_expression(&e.expression)?;
29986        self.write(")");
29987        Ok(())
29988    }
29989
29990    fn generate_json_extract_path(&mut self, e: &JSONExtract) -> Result<()> {
29991        // For variant_extract (Snowflake/Databricks colon syntax like a:field)
29992        // Databricks uses col:path syntax, Snowflake uses GET_PATH(col, 'path')
29993        // Otherwise output JSON_EXTRACT(this, expression)
29994        if e.variant_extract.is_some() {
29995            use crate::dialects::DialectType;
29996            if matches!(self.config.dialect, Some(DialectType::Databricks)) {
29997                // Databricks: output col:path syntax (e.g., c1:price, c1:price.foo, c1:price.bar[1])
29998                // Keys that are not safe identifiers (contain hyphens, spaces, etc.) must use
29999                // bracket notation: c:["x-y"] instead of c:x-y
30000                self.generate_expression(&e.this)?;
30001                self.write(":");
30002                match e.expression.as_ref() {
30003                    Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
30004                        let Literal::String(s) = lit.as_ref() else {
30005                            unreachable!()
30006                        };
30007                        self.write_databricks_json_path(s);
30008                    }
30009                    _ => {
30010                        // Fallback: generate as-is (shouldn't happen in typical cases)
30011                        self.generate_expression(&e.expression)?;
30012                    }
30013                }
30014            } else {
30015                // Snowflake and others: use GET_PATH(col, 'path')
30016                self.write_keyword("GET_PATH");
30017                self.write("(");
30018                self.generate_expression(&e.this)?;
30019                self.write(", ");
30020                self.generate_expression(&e.expression)?;
30021                self.write(")");
30022            }
30023        } else {
30024            self.write_keyword("JSON_EXTRACT");
30025            self.write("(");
30026            self.generate_expression(&e.this)?;
30027            self.write(", ");
30028            self.generate_expression(&e.expression)?;
30029            for expr in &e.expressions {
30030                self.write(", ");
30031                self.generate_expression(expr)?;
30032            }
30033            self.write(")");
30034        }
30035        Ok(())
30036    }
30037
30038    /// Write a Databricks JSON colon-path, using bracket notation for keys
30039    /// that are not safe identifiers (e.g., contain hyphens, spaces, etc.)
30040    /// Safe identifier regex: ^[_a-zA-Z]\w*$
30041    fn write_databricks_json_path(&mut self, path: &str) {
30042        // If the path already starts with bracket notation (e.g., '["fr\'uit"]'),
30043        // it was already formatted by the parser - output as-is
30044        if path.starts_with("[\"") || path.starts_with("['") {
30045            self.write(path);
30046            return;
30047        }
30048        // Split the path into segments at '.' boundaries, but preserve bracket subscripts
30049        // e.g., "price.items[0].name" -> ["price", "items[0]", "name"]
30050        // e.g., "x-y" -> ["x-y"]
30051        let mut first = true;
30052        for segment in path.split('.') {
30053            if !first {
30054                self.write(".");
30055            }
30056            first = false;
30057            // Check if there's a bracket subscript in this segment: "items[0]"
30058            if let Some(bracket_pos) = segment.find('[') {
30059                let key = &segment[..bracket_pos];
30060                let subscript = &segment[bracket_pos..];
30061                if key.is_empty() {
30062                    // Bracket notation at start of segment (e.g., already formatted)
30063                    self.write(segment);
30064                } else if Self::is_safe_json_path_key(key) {
30065                    self.write(key);
30066                    self.write(subscript);
30067                } else {
30068                    self.write("[\"");
30069                    self.write(key);
30070                    self.write("\"]");
30071                    self.write(subscript);
30072                }
30073            } else if Self::is_safe_json_path_key(segment) {
30074                self.write(segment);
30075            } else {
30076                self.write("[\"");
30077                self.write(segment);
30078                self.write("\"]");
30079            }
30080        }
30081    }
30082
30083    /// Check if a JSON path key is a safe identifier that doesn't need bracket quoting.
30084    /// Matches Python sqlglot's SAFE_IDENTIFIER_RE: ^[_a-zA-Z]\w*$
30085    fn is_safe_json_path_key(key: &str) -> bool {
30086        if key.is_empty() {
30087            return false;
30088        }
30089        let mut chars = key.chars();
30090        let first = chars.next().unwrap();
30091        if first != '_' && !first.is_ascii_alphabetic() {
30092            return false;
30093        }
30094        chars.all(|c| c == '_' || c.is_ascii_alphanumeric())
30095    }
30096
30097    fn generate_json_format(&mut self, e: &JSONFormat) -> Result<()> {
30098        // Output: {expr} FORMAT JSON
30099        // This wraps an expression with FORMAT JSON suffix (Oracle JSON function syntax)
30100        if let Some(this) = &e.this {
30101            self.generate_expression(this)?;
30102            self.write_space();
30103        }
30104        self.write_keyword("FORMAT JSON");
30105        Ok(())
30106    }
30107
30108    fn generate_json_key_value(&mut self, e: &JSONKeyValue) -> Result<()> {
30109        // key: value (for JSON objects)
30110        self.generate_expression(&e.this)?;
30111        self.write(": ");
30112        self.generate_expression(&e.expression)?;
30113        Ok(())
30114    }
30115
30116    fn generate_json_keys(&mut self, e: &JSONKeys) -> Result<()> {
30117        // JSON_KEYS(this, expression, expressions...)
30118        self.write_keyword("JSON_KEYS");
30119        self.write("(");
30120        self.generate_expression(&e.this)?;
30121        if let Some(expr) = &e.expression {
30122            self.write(", ");
30123            self.generate_expression(expr)?;
30124        }
30125        for expr in &e.expressions {
30126            self.write(", ");
30127            self.generate_expression(expr)?;
30128        }
30129        self.write(")");
30130        Ok(())
30131    }
30132
30133    fn generate_json_keys_at_depth(&mut self, e: &JSONKeysAtDepth) -> Result<()> {
30134        // JSON_KEYS(this, expression)
30135        self.write_keyword("JSON_KEYS");
30136        self.write("(");
30137        self.generate_expression(&e.this)?;
30138        if let Some(expr) = &e.expression {
30139            self.write(", ");
30140            self.generate_expression(expr)?;
30141        }
30142        self.write(")");
30143        Ok(())
30144    }
30145
30146    fn generate_json_path_expr(&mut self, e: &JSONPath) -> Result<()> {
30147        // JSONPath expression: generates a quoted path like '$.foo' or '$[0]'
30148        // The path components are concatenated without spaces
30149        let mut path_str = String::new();
30150        for expr in &e.expressions {
30151            match expr {
30152                Expression::JSONPathRoot(_) => {
30153                    path_str.push('$');
30154                }
30155                Expression::JSONPathKey(k) => {
30156                    // .key or ."key" (quote if key has special characters)
30157                    if let Expression::Literal(lit) = k.this.as_ref() {
30158                        if let crate::expressions::Literal::String(s) = lit.as_ref() {
30159                            path_str.push('.');
30160                            // Quote the key if it contains non-alphanumeric characters (hyphens, spaces, etc.)
30161                            let needs_quoting = s.chars().any(|c| !c.is_alphanumeric() && c != '_');
30162                            if needs_quoting {
30163                                path_str.push('"');
30164                                path_str.push_str(s);
30165                                path_str.push('"');
30166                            } else {
30167                                path_str.push_str(s);
30168                            }
30169                        }
30170                    }
30171                }
30172                Expression::JSONPathSubscript(s) => {
30173                    // [index]
30174                    if let Expression::Literal(lit) = s.this.as_ref() {
30175                        if let crate::expressions::Literal::Number(n) = lit.as_ref() {
30176                            path_str.push('[');
30177                            path_str.push_str(n);
30178                            path_str.push(']');
30179                        }
30180                    }
30181                }
30182                _ => {
30183                    // For other path parts, try to generate them
30184                    let mut temp_gen = Self::with_arc_config(self.config.clone());
30185                    temp_gen.generate_expression(expr)?;
30186                    path_str.push_str(&temp_gen.output);
30187                }
30188            }
30189        }
30190        // Output as quoted string
30191        self.write("'");
30192        self.write(&path_str);
30193        self.write("'");
30194        Ok(())
30195    }
30196
30197    fn generate_json_path_filter(&mut self, e: &JSONPathFilter) -> Result<()> {
30198        // JSON path filter: ?(predicate)
30199        self.write("?(");
30200        self.generate_expression(&e.this)?;
30201        self.write(")");
30202        Ok(())
30203    }
30204
30205    fn generate_json_path_key(&mut self, e: &JSONPathKey) -> Result<()> {
30206        // JSON path key: .key or ["key"]
30207        self.write(".");
30208        self.generate_expression(&e.this)?;
30209        Ok(())
30210    }
30211
30212    fn generate_json_path_recursive(&mut self, e: &JSONPathRecursive) -> Result<()> {
30213        // JSON path recursive descent: ..
30214        self.write("..");
30215        if let Some(this) = &e.this {
30216            self.generate_expression(this)?;
30217        }
30218        Ok(())
30219    }
30220
30221    fn generate_json_path_root(&mut self) -> Result<()> {
30222        // JSON path root: $
30223        self.write("$");
30224        Ok(())
30225    }
30226
30227    fn generate_json_path_script(&mut self, e: &JSONPathScript) -> Result<()> {
30228        // JSON path script: (expression)
30229        self.write("(");
30230        self.generate_expression(&e.this)?;
30231        self.write(")");
30232        Ok(())
30233    }
30234
30235    fn generate_json_path_selector(&mut self, e: &JSONPathSelector) -> Result<()> {
30236        // JSON path selector: *
30237        self.generate_expression(&e.this)?;
30238        Ok(())
30239    }
30240
30241    fn generate_json_path_slice(&mut self, e: &JSONPathSlice) -> Result<()> {
30242        // JSON path slice: [start:end:step]
30243        self.write("[");
30244        if let Some(start) = &e.start {
30245            self.generate_expression(start)?;
30246        }
30247        self.write(":");
30248        if let Some(end) = &e.end {
30249            self.generate_expression(end)?;
30250        }
30251        if let Some(step) = &e.step {
30252            self.write(":");
30253            self.generate_expression(step)?;
30254        }
30255        self.write("]");
30256        Ok(())
30257    }
30258
30259    fn generate_json_path_subscript(&mut self, e: &JSONPathSubscript) -> Result<()> {
30260        // JSON path subscript: [index] or [*]
30261        self.write("[");
30262        self.generate_expression(&e.this)?;
30263        self.write("]");
30264        Ok(())
30265    }
30266
30267    fn generate_json_path_union(&mut self, e: &JSONPathUnion) -> Result<()> {
30268        // JSON path union: [key1, key2, ...]
30269        self.write("[");
30270        for (i, expr) in e.expressions.iter().enumerate() {
30271            if i > 0 {
30272                self.write(", ");
30273            }
30274            self.generate_expression(expr)?;
30275        }
30276        self.write("]");
30277        Ok(())
30278    }
30279
30280    fn generate_json_remove(&mut self, e: &JSONRemove) -> Result<()> {
30281        // JSON_REMOVE(this, path1, path2, ...)
30282        self.write_keyword("JSON_REMOVE");
30283        self.write("(");
30284        self.generate_expression(&e.this)?;
30285        for expr in &e.expressions {
30286            self.write(", ");
30287            self.generate_expression(expr)?;
30288        }
30289        self.write(")");
30290        Ok(())
30291    }
30292
30293    fn generate_json_schema(&mut self, e: &JSONSchema) -> Result<()> {
30294        // COLUMNS(col1 type, col2 type, ...)
30295        // When pretty printing and content is too wide, format with each column on a separate line
30296        self.write_keyword("COLUMNS");
30297        self.write("(");
30298
30299        if self.config.pretty && !e.expressions.is_empty() {
30300            // First, generate all expressions into strings to check width
30301            let mut expr_strings: Vec<String> = Vec::with_capacity(e.expressions.len());
30302            for expr in &e.expressions {
30303                let mut temp_gen = Generator::with_arc_config(self.config.clone());
30304                temp_gen.generate_expression(expr)?;
30305                expr_strings.push(temp_gen.output);
30306            }
30307
30308            // Check if total width exceeds max_text_width
30309            if self.too_wide(&expr_strings) {
30310                // Pretty print: each column on its own line
30311                self.write_newline();
30312                self.indent_level += 1;
30313                for (i, expr_str) in expr_strings.iter().enumerate() {
30314                    if i > 0 {
30315                        self.write(",");
30316                        self.write_newline();
30317                    }
30318                    self.write_indent();
30319                    self.write(expr_str);
30320                }
30321                self.write_newline();
30322                self.indent_level -= 1;
30323                self.write_indent();
30324            } else {
30325                // Compact: all on one line
30326                for (i, expr_str) in expr_strings.iter().enumerate() {
30327                    if i > 0 {
30328                        self.write(", ");
30329                    }
30330                    self.write(expr_str);
30331                }
30332            }
30333        } else {
30334            // Non-pretty mode: compact format
30335            for (i, expr) in e.expressions.iter().enumerate() {
30336                if i > 0 {
30337                    self.write(", ");
30338                }
30339                self.generate_expression(expr)?;
30340            }
30341        }
30342        self.write(")");
30343        Ok(())
30344    }
30345
30346    fn generate_json_set(&mut self, e: &JSONSet) -> Result<()> {
30347        // JSON_SET(this, path, value, ...)
30348        self.write_keyword("JSON_SET");
30349        self.write("(");
30350        self.generate_expression(&e.this)?;
30351        for expr in &e.expressions {
30352            self.write(", ");
30353            self.generate_expression(expr)?;
30354        }
30355        self.write(")");
30356        Ok(())
30357    }
30358
30359    fn generate_json_strip_nulls(&mut self, e: &JSONStripNulls) -> Result<()> {
30360        // JSON_STRIP_NULLS(this, expression)
30361        self.write_keyword("JSON_STRIP_NULLS");
30362        self.write("(");
30363        self.generate_expression(&e.this)?;
30364        if let Some(expr) = &e.expression {
30365            self.write(", ");
30366            self.generate_expression(expr)?;
30367        }
30368        self.write(")");
30369        Ok(())
30370    }
30371
30372    fn generate_json_table(&mut self, e: &JSONTable) -> Result<()> {
30373        // JSON_TABLE(this, path [error_handling] [empty_handling] schema)
30374        self.write_keyword("JSON_TABLE");
30375        self.write("(");
30376        self.generate_expression(&e.this)?;
30377        if let Some(path) = &e.path {
30378            self.write(", ");
30379            self.generate_expression(path)?;
30380        }
30381        if let Some(error_handling) = &e.error_handling {
30382            self.write_space();
30383            self.generate_expression(error_handling)?;
30384        }
30385        if let Some(empty_handling) = &e.empty_handling {
30386            self.write_space();
30387            self.generate_expression(empty_handling)?;
30388        }
30389        if let Some(schema) = &e.schema {
30390            self.write_space();
30391            self.generate_expression(schema)?;
30392        }
30393        self.write(")");
30394        Ok(())
30395    }
30396
30397    fn generate_json_type(&mut self, e: &JSONType) -> Result<()> {
30398        // JSON_TYPE(this)
30399        self.write_keyword("JSON_TYPE");
30400        self.write("(");
30401        self.generate_expression(&e.this)?;
30402        self.write(")");
30403        Ok(())
30404    }
30405
30406    fn generate_json_value(&mut self, e: &JSONValue) -> Result<()> {
30407        // JSON_VALUE(this, path RETURNING type ON condition)
30408        self.write_keyword("JSON_VALUE");
30409        self.write("(");
30410        self.generate_expression(&e.this)?;
30411        if let Some(path) = &e.path {
30412            self.write(", ");
30413            self.generate_expression(path)?;
30414        }
30415        if let Some(returning) = &e.returning {
30416            self.write_space();
30417            self.write_keyword("RETURNING");
30418            self.write_space();
30419            self.generate_expression(returning)?;
30420        }
30421        if let Some(on_condition) = &e.on_condition {
30422            self.write_space();
30423            self.generate_expression(on_condition)?;
30424        }
30425        self.write(")");
30426        Ok(())
30427    }
30428
30429    fn generate_json_value_array(&mut self, e: &JSONValueArray) -> Result<()> {
30430        // JSON_VALUE_ARRAY(this)
30431        self.write_keyword("JSON_VALUE_ARRAY");
30432        self.write("(");
30433        self.generate_expression(&e.this)?;
30434        self.write(")");
30435        Ok(())
30436    }
30437
30438    fn generate_jarowinkler_similarity(&mut self, e: &JarowinklerSimilarity) -> Result<()> {
30439        // JAROWINKLER_SIMILARITY(str1, str2)
30440        self.write_keyword("JAROWINKLER_SIMILARITY");
30441        self.write("(");
30442        self.generate_expression(&e.this)?;
30443        self.write(", ");
30444        self.generate_expression(&e.expression)?;
30445        self.write(")");
30446        Ok(())
30447    }
30448
30449    fn generate_join_hint(&mut self, e: &JoinHint) -> Result<()> {
30450        // Python: this(expressions)
30451        self.generate_expression(&e.this)?;
30452        self.write("(");
30453        for (i, expr) in e.expressions.iter().enumerate() {
30454            if i > 0 {
30455                self.write(", ");
30456            }
30457            self.generate_expression(expr)?;
30458        }
30459        self.write(")");
30460        Ok(())
30461    }
30462
30463    fn generate_journal_property(&mut self, e: &JournalProperty) -> Result<()> {
30464        // Python: {no}{local}{dual}{before}{after}JOURNAL
30465        if e.no.is_some() {
30466            self.write_keyword("NO ");
30467        }
30468        if let Some(local) = &e.local {
30469            self.generate_expression(local)?;
30470            self.write_space();
30471        }
30472        if e.dual.is_some() {
30473            self.write_keyword("DUAL ");
30474        }
30475        if e.before.is_some() {
30476            self.write_keyword("BEFORE ");
30477        }
30478        if e.after.is_some() {
30479            self.write_keyword("AFTER ");
30480        }
30481        self.write_keyword("JOURNAL");
30482        Ok(())
30483    }
30484
30485    fn generate_language_property(&mut self, e: &LanguageProperty) -> Result<()> {
30486        // LANGUAGE language_name
30487        self.write_keyword("LANGUAGE");
30488        self.write_space();
30489        self.generate_expression(&e.this)?;
30490        Ok(())
30491    }
30492
30493    fn generate_lateral(&mut self, e: &Lateral) -> Result<()> {
30494        // Python: handles LATERAL VIEW (Hive/Spark) and regular LATERAL
30495        if e.view.is_some() {
30496            // LATERAL VIEW [OUTER] expression [alias] [AS columns]
30497            self.write_keyword("LATERAL VIEW");
30498            if e.outer.is_some() {
30499                self.write_space();
30500                self.write_keyword("OUTER");
30501            }
30502            self.write_space();
30503            self.generate_expression(&e.this)?;
30504            if let Some(alias) = &e.alias {
30505                self.write_space();
30506                self.write(alias);
30507            }
30508        } else {
30509            // LATERAL subquery/function [WITH ORDINALITY] [AS alias(columns)]
30510            self.write_keyword("LATERAL");
30511            self.write_space();
30512            self.generate_expression(&e.this)?;
30513            if e.ordinality.is_some() {
30514                self.write_space();
30515                self.write_keyword("WITH ORDINALITY");
30516            }
30517            if let Some(alias) = &e.alias {
30518                self.write_space();
30519                self.write_keyword("AS");
30520                self.write_space();
30521                self.write(alias);
30522                if !e.column_aliases.is_empty() {
30523                    self.write("(");
30524                    for (i, col) in e.column_aliases.iter().enumerate() {
30525                        if i > 0 {
30526                            self.write(", ");
30527                        }
30528                        self.write(col);
30529                    }
30530                    self.write(")");
30531                }
30532            }
30533        }
30534        Ok(())
30535    }
30536
30537    fn generate_like_property(&mut self, e: &LikeProperty) -> Result<()> {
30538        // Python: LIKE this [options]
30539        self.write_keyword("LIKE");
30540        self.write_space();
30541        self.generate_expression(&e.this)?;
30542        for expr in &e.expressions {
30543            self.write_space();
30544            self.generate_expression(expr)?;
30545        }
30546        Ok(())
30547    }
30548
30549    fn generate_limit(&mut self, e: &Limit) -> Result<()> {
30550        self.write_keyword("LIMIT");
30551        self.write_space();
30552        self.write_limit_expr(&e.this)?;
30553        if e.percent {
30554            self.write_space();
30555            self.write_keyword("PERCENT");
30556        }
30557        // Emit any comments that were captured from before the LIMIT keyword
30558        for comment in &e.comments {
30559            self.write(" ");
30560            self.write_formatted_comment(comment);
30561        }
30562        Ok(())
30563    }
30564
30565    fn generate_limit_options(&mut self, e: &LimitOptions) -> Result<()> {
30566        // Python: [PERCENT][ROWS][WITH TIES|ONLY]
30567        if e.percent.is_some() {
30568            self.write_keyword(" PERCENT");
30569        }
30570        if e.rows.is_some() {
30571            self.write_keyword(" ROWS");
30572        }
30573        if e.with_ties.is_some() {
30574            self.write_keyword(" WITH TIES");
30575        } else if e.rows.is_some() {
30576            self.write_keyword(" ONLY");
30577        }
30578        Ok(())
30579    }
30580
30581    fn generate_list(&mut self, e: &List) -> Result<()> {
30582        use crate::dialects::DialectType;
30583        let is_materialize = matches!(self.config.dialect, Some(DialectType::Materialize));
30584
30585        // Check if this is a subquery-based list (LIST(SELECT ...))
30586        if e.expressions.len() == 1 {
30587            if let Expression::Select(_) = &e.expressions[0] {
30588                self.write_keyword("LIST");
30589                self.write("(");
30590                self.generate_expression(&e.expressions[0])?;
30591                self.write(")");
30592                return Ok(());
30593            }
30594        }
30595
30596        // For Materialize, output as LIST[expr, expr, ...]
30597        if is_materialize {
30598            self.write_keyword("LIST");
30599            self.write("[");
30600            for (i, expr) in e.expressions.iter().enumerate() {
30601                if i > 0 {
30602                    self.write(", ");
30603                }
30604                self.generate_expression(expr)?;
30605            }
30606            self.write("]");
30607        } else {
30608            // For other dialects, output as LIST(expr, expr, ...)
30609            self.write_keyword("LIST");
30610            self.write("(");
30611            for (i, expr) in e.expressions.iter().enumerate() {
30612                if i > 0 {
30613                    self.write(", ");
30614                }
30615                self.generate_expression(expr)?;
30616            }
30617            self.write(")");
30618        }
30619        Ok(())
30620    }
30621
30622    fn generate_tomap(&mut self, e: &ToMap) -> Result<()> {
30623        // Check if this is a subquery-based map (MAP(SELECT ...))
30624        if let Expression::Select(_) = &*e.this {
30625            self.write_keyword("MAP");
30626            self.write("(");
30627            self.generate_expression(&e.this)?;
30628            self.write(")");
30629            return Ok(());
30630        }
30631
30632        let is_duckdb = matches!(self.config.dialect, Some(DialectType::DuckDB));
30633
30634        // For Struct-based map: DuckDB uses MAP {'key': value}, Materialize uses MAP['key' => value]
30635        self.write_keyword("MAP");
30636        if is_duckdb {
30637            self.write(" {");
30638        } else {
30639            self.write("[");
30640        }
30641        if let Expression::Struct(s) = &*e.this {
30642            for (i, (_, expr)) in s.fields.iter().enumerate() {
30643                if i > 0 {
30644                    self.write(", ");
30645                }
30646                if let Expression::PropertyEQ(op) = expr {
30647                    self.generate_expression(&op.left)?;
30648                    if is_duckdb {
30649                        self.write(": ");
30650                    } else {
30651                        self.write(" => ");
30652                    }
30653                    self.generate_expression(&op.right)?;
30654                } else {
30655                    self.generate_expression(expr)?;
30656                }
30657            }
30658        }
30659        if is_duckdb {
30660            self.write("}");
30661        } else {
30662            self.write("]");
30663        }
30664        Ok(())
30665    }
30666
30667    fn generate_localtime(&mut self, e: &Localtime) -> Result<()> {
30668        // Python: LOCALTIME or LOCALTIME(precision)
30669        self.write_keyword("LOCALTIME");
30670        if let Some(precision) = &e.this {
30671            self.write("(");
30672            self.generate_expression(precision)?;
30673            self.write(")");
30674        }
30675        Ok(())
30676    }
30677
30678    fn generate_localtimestamp(&mut self, e: &Localtimestamp) -> Result<()> {
30679        // Python: LOCALTIMESTAMP or LOCALTIMESTAMP(precision)
30680        self.write_keyword("LOCALTIMESTAMP");
30681        if let Some(precision) = &e.this {
30682            self.write("(");
30683            self.generate_expression(precision)?;
30684            self.write(")");
30685        }
30686        Ok(())
30687    }
30688
30689    fn generate_location_property(&mut self, e: &LocationProperty) -> Result<()> {
30690        // LOCATION 'path'
30691        self.write_keyword("LOCATION");
30692        self.write_space();
30693        self.generate_expression(&e.this)?;
30694        Ok(())
30695    }
30696
30697    fn generate_lock(&mut self, e: &Lock) -> Result<()> {
30698        // Python: FOR UPDATE|FOR SHARE [OF tables] [NOWAIT|WAIT n]
30699        if e.update.is_some() {
30700            if e.key.is_some() {
30701                self.write_keyword("FOR NO KEY UPDATE");
30702            } else {
30703                self.write_keyword("FOR UPDATE");
30704            }
30705        } else {
30706            if e.key.is_some() {
30707                self.write_keyword("FOR KEY SHARE");
30708            } else {
30709                self.write_keyword("FOR SHARE");
30710            }
30711        }
30712        if !e.expressions.is_empty() {
30713            self.write_keyword(" OF ");
30714            for (i, expr) in e.expressions.iter().enumerate() {
30715                if i > 0 {
30716                    self.write(", ");
30717                }
30718                self.generate_expression(expr)?;
30719            }
30720        }
30721        // Handle wait option following Python sqlglot convention:
30722        // - Boolean(true) -> NOWAIT
30723        // - Boolean(false) -> SKIP LOCKED
30724        // - Literal (number) -> WAIT n
30725        if let Some(wait) = &e.wait {
30726            match wait.as_ref() {
30727                Expression::Boolean(b) => {
30728                    if b.value {
30729                        self.write_keyword(" NOWAIT");
30730                    } else {
30731                        self.write_keyword(" SKIP LOCKED");
30732                    }
30733                }
30734                _ => {
30735                    // It's a literal (number), output WAIT n
30736                    self.write_keyword(" WAIT ");
30737                    self.generate_expression(wait)?;
30738                }
30739            }
30740        }
30741        Ok(())
30742    }
30743
30744    fn generate_lock_property(&mut self, e: &LockProperty) -> Result<()> {
30745        // LOCK property
30746        self.write_keyword("LOCK");
30747        self.write_space();
30748        self.generate_expression(&e.this)?;
30749        Ok(())
30750    }
30751
30752    fn generate_locking_property(&mut self, e: &LockingProperty) -> Result<()> {
30753        // Python: LOCKING kind [this] [for_or_in] lock_type [OVERRIDE]
30754        self.write_keyword("LOCKING");
30755        self.write_space();
30756        self.write(&e.kind);
30757        if let Some(this) = &e.this {
30758            self.write_space();
30759            self.generate_expression(this)?;
30760        }
30761        if let Some(for_or_in) = &e.for_or_in {
30762            self.write_space();
30763            self.generate_expression(for_or_in)?;
30764        }
30765        if let Some(lock_type) = &e.lock_type {
30766            self.write_space();
30767            self.generate_expression(lock_type)?;
30768        }
30769        if e.override_.is_some() {
30770            self.write_keyword(" OVERRIDE");
30771        }
30772        Ok(())
30773    }
30774
30775    fn generate_locking_statement(&mut self, e: &LockingStatement) -> Result<()> {
30776        // this expression
30777        self.generate_expression(&e.this)?;
30778        self.write_space();
30779        self.generate_expression(&e.expression)?;
30780        Ok(())
30781    }
30782
30783    fn generate_log_property(&mut self, e: &LogProperty) -> Result<()> {
30784        // [NO] LOG
30785        if e.no.is_some() {
30786            self.write_keyword("NO ");
30787        }
30788        self.write_keyword("LOG");
30789        Ok(())
30790    }
30791
30792    fn generate_md5_digest(&mut self, e: &MD5Digest) -> Result<()> {
30793        // MD5(this, expressions...)
30794        self.write_keyword("MD5");
30795        self.write("(");
30796        self.generate_expression(&e.this)?;
30797        for expr in &e.expressions {
30798            self.write(", ");
30799            self.generate_expression(expr)?;
30800        }
30801        self.write(")");
30802        Ok(())
30803    }
30804
30805    fn generate_ml_forecast(&mut self, e: &MLForecast) -> Result<()> {
30806        // ML.FORECAST(model, [params])
30807        self.write_keyword("ML.FORECAST");
30808        self.write("(");
30809        self.generate_expression(&e.this)?;
30810        if let Some(expression) = &e.expression {
30811            self.write(", ");
30812            self.generate_expression(expression)?;
30813        }
30814        if let Some(params) = &e.params_struct {
30815            self.write(", ");
30816            self.generate_expression(params)?;
30817        }
30818        self.write(")");
30819        Ok(())
30820    }
30821
30822    fn generate_ml_translate(&mut self, e: &MLTranslate) -> Result<()> {
30823        // ML.TRANSLATE(model, input, [params])
30824        self.write_keyword("ML.TRANSLATE");
30825        self.write("(");
30826        self.generate_expression(&e.this)?;
30827        self.write(", ");
30828        self.generate_expression(&e.expression)?;
30829        if let Some(params) = &e.params_struct {
30830            self.write(", ");
30831            self.generate_expression(params)?;
30832        }
30833        self.write(")");
30834        Ok(())
30835    }
30836
30837    fn generate_make_interval(&mut self, e: &MakeInterval) -> Result<()> {
30838        // MAKE_INTERVAL(years => x, months => y, ...)
30839        self.write_keyword("MAKE_INTERVAL");
30840        self.write("(");
30841        let mut first = true;
30842        if let Some(year) = &e.year {
30843            self.write("years => ");
30844            self.generate_expression(year)?;
30845            first = false;
30846        }
30847        if let Some(month) = &e.month {
30848            if !first {
30849                self.write(", ");
30850            }
30851            self.write("months => ");
30852            self.generate_expression(month)?;
30853            first = false;
30854        }
30855        if let Some(week) = &e.week {
30856            if !first {
30857                self.write(", ");
30858            }
30859            self.write("weeks => ");
30860            self.generate_expression(week)?;
30861            first = false;
30862        }
30863        if let Some(day) = &e.day {
30864            if !first {
30865                self.write(", ");
30866            }
30867            self.write("days => ");
30868            self.generate_expression(day)?;
30869            first = false;
30870        }
30871        if let Some(hour) = &e.hour {
30872            if !first {
30873                self.write(", ");
30874            }
30875            self.write("hours => ");
30876            self.generate_expression(hour)?;
30877            first = false;
30878        }
30879        if let Some(minute) = &e.minute {
30880            if !first {
30881                self.write(", ");
30882            }
30883            self.write("mins => ");
30884            self.generate_expression(minute)?;
30885            first = false;
30886        }
30887        if let Some(second) = &e.second {
30888            if !first {
30889                self.write(", ");
30890            }
30891            self.write("secs => ");
30892            self.generate_expression(second)?;
30893        }
30894        self.write(")");
30895        Ok(())
30896    }
30897
30898    fn generate_manhattan_distance(&mut self, e: &ManhattanDistance) -> Result<()> {
30899        // MANHATTAN_DISTANCE(vector1, vector2)
30900        self.write_keyword("MANHATTAN_DISTANCE");
30901        self.write("(");
30902        self.generate_expression(&e.this)?;
30903        self.write(", ");
30904        self.generate_expression(&e.expression)?;
30905        self.write(")");
30906        Ok(())
30907    }
30908
30909    fn generate_map(&mut self, e: &Map) -> Result<()> {
30910        // MAP(key1, value1, key2, value2, ...)
30911        self.write_keyword("MAP");
30912        self.write("(");
30913        for (i, (key, value)) in e.keys.iter().zip(e.values.iter()).enumerate() {
30914            if i > 0 {
30915                self.write(", ");
30916            }
30917            self.generate_expression(key)?;
30918            self.write(", ");
30919            self.generate_expression(value)?;
30920        }
30921        self.write(")");
30922        Ok(())
30923    }
30924
30925    fn generate_map_cat(&mut self, e: &MapCat) -> Result<()> {
30926        // MAP_CAT(map1, map2)
30927        self.write_keyword("MAP_CAT");
30928        self.write("(");
30929        self.generate_expression(&e.this)?;
30930        self.write(", ");
30931        self.generate_expression(&e.expression)?;
30932        self.write(")");
30933        Ok(())
30934    }
30935
30936    fn generate_map_delete(&mut self, e: &MapDelete) -> Result<()> {
30937        // MAP_DELETE(map, key1, key2, ...)
30938        self.write_keyword("MAP_DELETE");
30939        self.write("(");
30940        self.generate_expression(&e.this)?;
30941        for expr in &e.expressions {
30942            self.write(", ");
30943            self.generate_expression(expr)?;
30944        }
30945        self.write(")");
30946        Ok(())
30947    }
30948
30949    fn generate_map_insert(&mut self, e: &MapInsert) -> Result<()> {
30950        // MAP_INSERT(map, key, value, [update_flag])
30951        self.write_keyword("MAP_INSERT");
30952        self.write("(");
30953        self.generate_expression(&e.this)?;
30954        if let Some(key) = &e.key {
30955            self.write(", ");
30956            self.generate_expression(key)?;
30957        }
30958        if let Some(value) = &e.value {
30959            self.write(", ");
30960            self.generate_expression(value)?;
30961        }
30962        if let Some(update_flag) = &e.update_flag {
30963            self.write(", ");
30964            self.generate_expression(update_flag)?;
30965        }
30966        self.write(")");
30967        Ok(())
30968    }
30969
30970    fn generate_map_pick(&mut self, e: &MapPick) -> Result<()> {
30971        // MAP_PICK(map, key1, key2, ...)
30972        self.write_keyword("MAP_PICK");
30973        self.write("(");
30974        self.generate_expression(&e.this)?;
30975        for expr in &e.expressions {
30976            self.write(", ");
30977            self.generate_expression(expr)?;
30978        }
30979        self.write(")");
30980        Ok(())
30981    }
30982
30983    fn generate_masking_policy_column_constraint(
30984        &mut self,
30985        e: &MaskingPolicyColumnConstraint,
30986    ) -> Result<()> {
30987        // Python: MASKING POLICY name [USING (cols)]
30988        self.write_keyword("MASKING POLICY");
30989        self.write_space();
30990        self.generate_expression(&e.this)?;
30991        if !e.expressions.is_empty() {
30992            self.write_keyword(" USING");
30993            self.write(" (");
30994            for (i, expr) in e.expressions.iter().enumerate() {
30995                if i > 0 {
30996                    self.write(", ");
30997                }
30998                self.generate_expression(expr)?;
30999            }
31000            self.write(")");
31001        }
31002        Ok(())
31003    }
31004
31005    fn generate_match_against(&mut self, e: &MatchAgainst) -> Result<()> {
31006        if matches!(
31007            self.config.dialect,
31008            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
31009        ) {
31010            if e.expressions.len() > 1 {
31011                self.write("(");
31012            }
31013            for (i, expr) in e.expressions.iter().enumerate() {
31014                if i > 0 {
31015                    self.write_keyword(" OR ");
31016                }
31017                self.generate_expression(expr)?;
31018                self.write_space();
31019                self.write("@@");
31020                self.write_space();
31021                self.generate_expression(&e.this)?;
31022            }
31023            if e.expressions.len() > 1 {
31024                self.write(")");
31025            }
31026            return Ok(());
31027        }
31028
31029        // MATCH(columns) AGAINST(expr [modifier])
31030        self.write_keyword("MATCH");
31031        self.write("(");
31032        for (i, expr) in e.expressions.iter().enumerate() {
31033            if i > 0 {
31034                self.write(", ");
31035            }
31036            self.generate_expression(expr)?;
31037        }
31038        self.write(")");
31039        self.write_keyword(" AGAINST");
31040        self.write("(");
31041        self.generate_expression(&e.this)?;
31042        if let Some(modifier) = &e.modifier {
31043            self.write_space();
31044            self.generate_expression(modifier)?;
31045        }
31046        self.write(")");
31047        Ok(())
31048    }
31049
31050    fn generate_match_recognize_measure(&mut self, e: &MatchRecognizeMeasure) -> Result<()> {
31051        // Python: [window_frame] this
31052        if let Some(window_frame) = &e.window_frame {
31053            self.write(&format!("{:?}", window_frame).to_ascii_uppercase());
31054            self.write_space();
31055        }
31056        self.generate_expression(&e.this)?;
31057        Ok(())
31058    }
31059
31060    fn generate_materialized_property(&mut self, e: &MaterializedProperty) -> Result<()> {
31061        // MATERIALIZED [this]
31062        self.write_keyword("MATERIALIZED");
31063        if let Some(this) = &e.this {
31064            self.write_space();
31065            self.generate_expression(this)?;
31066        }
31067        Ok(())
31068    }
31069
31070    fn generate_merge(&mut self, e: &Merge) -> Result<()> {
31071        // MERGE INTO target USING source ON condition WHEN ...
31072        // DuckDB variant: MERGE INTO target USING source USING (key_columns) WHEN ...
31073        if let Some(with_) = &e.with_ {
31074            if let Expression::With(with_clause) = with_.as_ref() {
31075                self.generate_with(with_clause)?;
31076                self.write_space();
31077            } else {
31078                self.generate_expression(with_)?;
31079                self.write_space();
31080            }
31081        }
31082        self.write_keyword("MERGE INTO");
31083        self.write_space();
31084        if matches!(self.config.dialect, Some(crate::DialectType::Oracle)) {
31085            if let Expression::Alias(alias) = e.this.as_ref() {
31086                self.generate_expression(&alias.this)?;
31087                self.write_space();
31088                self.generate_identifier(&alias.alias)?;
31089            } else {
31090                self.generate_expression(&e.this)?;
31091            }
31092        } else {
31093            self.generate_expression(&e.this)?;
31094        }
31095
31096        // USING clause - newline before in pretty mode
31097        if self.config.pretty {
31098            self.write_newline();
31099            self.write_indent();
31100        } else {
31101            self.write_space();
31102        }
31103        self.write_keyword("USING");
31104        self.write_space();
31105        self.generate_expression(&e.using)?;
31106
31107        // ON clause - newline before in pretty mode
31108        if let Some(on) = &e.on {
31109            if self.config.pretty {
31110                self.write_newline();
31111                self.write_indent();
31112            } else {
31113                self.write_space();
31114            }
31115            self.write_keyword("ON");
31116            self.write_space();
31117            self.generate_expression(on)?;
31118        }
31119        // DuckDB USING (key_columns) clause
31120        if let Some(using_cond) = &e.using_cond {
31121            self.write_space();
31122            self.write_keyword("USING");
31123            self.write_space();
31124            self.write("(");
31125            // using_cond is a Tuple containing the column identifiers
31126            if let Expression::Tuple(tuple) = using_cond.as_ref() {
31127                for (i, col) in tuple.expressions.iter().enumerate() {
31128                    if i > 0 {
31129                        self.write(", ");
31130                    }
31131                    self.generate_expression(col)?;
31132                }
31133            } else {
31134                self.generate_expression(using_cond)?;
31135            }
31136            self.write(")");
31137        }
31138        // For PostgreSQL dialect, extract target table name/alias to strip from UPDATE SET
31139        let saved_merge_strip = std::mem::take(&mut self.merge_strip_qualifiers);
31140        if matches!(
31141            self.config.dialect,
31142            Some(crate::DialectType::PostgreSQL)
31143                | Some(crate::DialectType::Redshift)
31144                | Some(crate::DialectType::Trino)
31145                | Some(crate::DialectType::Presto)
31146                | Some(crate::DialectType::Athena)
31147        ) {
31148            let mut names = Vec::new();
31149            match e.this.as_ref() {
31150                Expression::Alias(a) => {
31151                    // e.g., "x AS z" -> strip both "x" and "z"
31152                    if let Expression::Table(t) = &a.this {
31153                        names.push(t.name.name.clone());
31154                    } else if let Expression::Identifier(id) = &a.this {
31155                        names.push(id.name.clone());
31156                    }
31157                    names.push(a.alias.name.clone());
31158                }
31159                Expression::Table(t) => {
31160                    names.push(t.name.name.clone());
31161                }
31162                Expression::Identifier(id) => {
31163                    names.push(id.name.clone());
31164                }
31165                _ => {}
31166            }
31167            self.merge_strip_qualifiers = names;
31168        }
31169
31170        // WHEN clauses - newline before each in pretty mode
31171        if let Some(whens) = &e.whens {
31172            if self.config.pretty {
31173                self.write_newline();
31174                self.write_indent();
31175            } else {
31176                self.write_space();
31177            }
31178            self.generate_expression(whens)?;
31179        }
31180
31181        // Restore merge_strip_qualifiers
31182        self.merge_strip_qualifiers = saved_merge_strip;
31183
31184        // OUTPUT/RETURNING clause - newline before in pretty mode
31185        if let Some(returning) = &e.returning {
31186            if self.config.pretty {
31187                self.write_newline();
31188                self.write_indent();
31189            } else {
31190                self.write_space();
31191            }
31192            self.generate_expression(returning)?;
31193        }
31194        Ok(())
31195    }
31196
31197    fn generate_merge_block_ratio_property(&mut self, e: &MergeBlockRatioProperty) -> Result<()> {
31198        // Python: NO MERGEBLOCKRATIO | DEFAULT MERGEBLOCKRATIO | MERGEBLOCKRATIO=this [PERCENT]
31199        if e.no.is_some() {
31200            self.write_keyword("NO MERGEBLOCKRATIO");
31201        } else if e.default.is_some() {
31202            self.write_keyword("DEFAULT MERGEBLOCKRATIO");
31203        } else {
31204            self.write_keyword("MERGEBLOCKRATIO");
31205            self.write("=");
31206            if let Some(this) = &e.this {
31207                self.generate_expression(this)?;
31208            }
31209            if e.percent.is_some() {
31210                self.write_keyword(" PERCENT");
31211            }
31212        }
31213        Ok(())
31214    }
31215
31216    fn generate_merge_tree_ttl(&mut self, e: &MergeTreeTTL) -> Result<()> {
31217        // TTL expressions [WHERE where] [GROUP BY group] [SET aggregates]
31218        self.write_keyword("TTL");
31219        let pretty_clickhouse = self.config.pretty
31220            && matches!(
31221                self.config.dialect,
31222                Some(crate::dialects::DialectType::ClickHouse)
31223            );
31224
31225        if pretty_clickhouse {
31226            self.write_newline();
31227            self.indent_level += 1;
31228            for (i, expr) in e.expressions.iter().enumerate() {
31229                if i > 0 {
31230                    self.write(",");
31231                    self.write_newline();
31232                }
31233                self.write_indent();
31234                self.generate_expression(expr)?;
31235            }
31236            self.indent_level -= 1;
31237        } else {
31238            self.write_space();
31239            for (i, expr) in e.expressions.iter().enumerate() {
31240                if i > 0 {
31241                    self.write(", ");
31242                }
31243                self.generate_expression(expr)?;
31244            }
31245        }
31246
31247        if let Some(where_) = &e.where_ {
31248            if pretty_clickhouse {
31249                self.write_newline();
31250                if let Expression::Where(w) = where_.as_ref() {
31251                    self.write_indent();
31252                    self.write_keyword("WHERE");
31253                    self.write_newline();
31254                    self.indent_level += 1;
31255                    self.write_indent();
31256                    self.generate_expression(&w.this)?;
31257                    self.indent_level -= 1;
31258                } else {
31259                    self.write_indent();
31260                    self.generate_expression(where_)?;
31261                }
31262            } else {
31263                self.write_space();
31264                self.generate_expression(where_)?;
31265            }
31266        }
31267        if let Some(group) = &e.group {
31268            if pretty_clickhouse {
31269                self.write_newline();
31270                if let Expression::Group(g) = group.as_ref() {
31271                    self.write_indent();
31272                    self.write_keyword("GROUP BY");
31273                    self.write_newline();
31274                    self.indent_level += 1;
31275                    for (i, expr) in g.expressions.iter().enumerate() {
31276                        if i > 0 {
31277                            self.write(",");
31278                            self.write_newline();
31279                        }
31280                        self.write_indent();
31281                        self.generate_expression(expr)?;
31282                    }
31283                    self.indent_level -= 1;
31284                } else {
31285                    self.write_indent();
31286                    self.generate_expression(group)?;
31287                }
31288            } else {
31289                self.write_space();
31290                self.generate_expression(group)?;
31291            }
31292        }
31293        if let Some(aggregates) = &e.aggregates {
31294            if pretty_clickhouse {
31295                self.write_newline();
31296                self.write_indent();
31297                self.write_keyword("SET");
31298                self.write_newline();
31299                self.indent_level += 1;
31300                if let Expression::Tuple(t) = aggregates.as_ref() {
31301                    for (i, agg) in t.expressions.iter().enumerate() {
31302                        if i > 0 {
31303                            self.write(",");
31304                            self.write_newline();
31305                        }
31306                        self.write_indent();
31307                        self.generate_expression(agg)?;
31308                    }
31309                } else {
31310                    self.write_indent();
31311                    self.generate_expression(aggregates)?;
31312                }
31313                self.indent_level -= 1;
31314            } else {
31315                self.write_space();
31316                self.write_keyword("SET");
31317                self.write_space();
31318                self.generate_expression(aggregates)?;
31319            }
31320        }
31321        Ok(())
31322    }
31323
31324    fn generate_merge_tree_ttl_action(&mut self, e: &MergeTreeTTLAction) -> Result<()> {
31325        // Python: this [DELETE] [RECOMPRESS codec] [TO DISK disk] [TO VOLUME volume]
31326        self.generate_expression(&e.this)?;
31327        if e.delete.is_some() {
31328            self.write_keyword(" DELETE");
31329        }
31330        if let Some(recompress) = &e.recompress {
31331            self.write_keyword(" RECOMPRESS ");
31332            self.generate_expression(recompress)?;
31333        }
31334        if let Some(to_disk) = &e.to_disk {
31335            self.write_keyword(" TO DISK ");
31336            self.generate_expression(to_disk)?;
31337        }
31338        if let Some(to_volume) = &e.to_volume {
31339            self.write_keyword(" TO VOLUME ");
31340            self.generate_expression(to_volume)?;
31341        }
31342        Ok(())
31343    }
31344
31345    fn generate_minhash(&mut self, e: &Minhash) -> Result<()> {
31346        // MINHASH(this, expressions...)
31347        self.write_keyword("MINHASH");
31348        self.write("(");
31349        self.generate_expression(&e.this)?;
31350        for expr in &e.expressions {
31351            self.write(", ");
31352            self.generate_expression(expr)?;
31353        }
31354        self.write(")");
31355        Ok(())
31356    }
31357
31358    fn generate_model_attribute(&mut self, e: &ModelAttribute) -> Result<()> {
31359        // model!attribute - Snowflake syntax
31360        self.generate_expression(&e.this)?;
31361        self.write("!");
31362        self.generate_expression(&e.expression)?;
31363        Ok(())
31364    }
31365
31366    fn generate_monthname(&mut self, e: &Monthname) -> Result<()> {
31367        // MONTHNAME(this)
31368        self.write_keyword("MONTHNAME");
31369        self.write("(");
31370        self.generate_expression(&e.this)?;
31371        self.write(")");
31372        Ok(())
31373    }
31374
31375    fn generate_multitable_inserts(&mut self, e: &MultitableInserts) -> Result<()> {
31376        // Output leading comments
31377        for comment in &e.leading_comments {
31378            self.write_formatted_comment(comment);
31379            if self.config.pretty {
31380                self.write_newline();
31381                self.write_indent();
31382            } else {
31383                self.write_space();
31384            }
31385        }
31386        // Python: INSERT [OVERWRITE] kind expressions source
31387        self.write_keyword("INSERT");
31388        if e.overwrite {
31389            self.write_space();
31390            self.write_keyword("OVERWRITE");
31391        }
31392        self.write_space();
31393        self.write(&e.kind);
31394        if self.config.pretty {
31395            self.indent_level += 1;
31396            for expr in &e.expressions {
31397                self.write_newline();
31398                self.write_indent();
31399                self.generate_expression(expr)?;
31400            }
31401            self.indent_level -= 1;
31402        } else {
31403            for expr in &e.expressions {
31404                self.write_space();
31405                self.generate_expression(expr)?;
31406            }
31407        }
31408        if let Some(source) = &e.source {
31409            if self.config.pretty {
31410                self.write_newline();
31411                self.write_indent();
31412            } else {
31413                self.write_space();
31414            }
31415            self.generate_expression(source)?;
31416        }
31417        Ok(())
31418    }
31419
31420    fn generate_next_value_for(&mut self, e: &NextValueFor) -> Result<()> {
31421        // Python: NEXT VALUE FOR this [OVER (order)]
31422        self.write_keyword("NEXT VALUE FOR");
31423        self.write_space();
31424        self.generate_expression(&e.this)?;
31425        if let Some(order) = &e.order {
31426            self.write_space();
31427            self.write_keyword("OVER");
31428            self.write(" (");
31429            self.generate_expression(order)?;
31430            self.write(")");
31431        }
31432        Ok(())
31433    }
31434
31435    fn generate_normal(&mut self, e: &Normal) -> Result<()> {
31436        // NORMAL(mean, stddev, gen)
31437        self.write_keyword("NORMAL");
31438        self.write("(");
31439        self.generate_expression(&e.this)?;
31440        if let Some(stddev) = &e.stddev {
31441            self.write(", ");
31442            self.generate_expression(stddev)?;
31443        }
31444        if let Some(gen) = &e.gen {
31445            self.write(", ");
31446            self.generate_expression(gen)?;
31447        }
31448        self.write(")");
31449        Ok(())
31450    }
31451
31452    fn generate_normalize(&mut self, e: &Normalize) -> Result<()> {
31453        // NORMALIZE(this, form) or CASEFOLD version
31454        if e.is_casefold.is_some() {
31455            self.write_keyword("NORMALIZE_AND_CASEFOLD");
31456        } else {
31457            self.write_keyword("NORMALIZE");
31458        }
31459        self.write("(");
31460        self.generate_expression(&e.this)?;
31461        if let Some(form) = &e.form {
31462            self.write(", ");
31463            self.generate_expression(form)?;
31464        }
31465        self.write(")");
31466        Ok(())
31467    }
31468
31469    fn generate_not_null_column_constraint(&mut self, e: &NotNullColumnConstraint) -> Result<()> {
31470        // Python: [NOT ]NULL
31471        if e.allow_null.is_none() {
31472            self.write_keyword("NOT ");
31473        }
31474        self.write_keyword("NULL");
31475        Ok(())
31476    }
31477
31478    fn generate_nullif(&mut self, e: &Nullif) -> Result<()> {
31479        // NULLIF(this, expression)
31480        self.write_keyword("NULLIF");
31481        self.write("(");
31482        self.generate_expression(&e.this)?;
31483        self.write(", ");
31484        self.generate_expression(&e.expression)?;
31485        self.write(")");
31486        Ok(())
31487    }
31488
31489    fn generate_number_to_str(&mut self, e: &NumberToStr) -> Result<()> {
31490        // FORMAT(this, format, culture)
31491        self.write_keyword("FORMAT");
31492        self.write("(");
31493        self.generate_expression(&e.this)?;
31494        self.write(", '");
31495        self.write(&e.format);
31496        self.write("'");
31497        if let Some(culture) = &e.culture {
31498            self.write(", ");
31499            self.generate_expression(culture)?;
31500        }
31501        self.write(")");
31502        Ok(())
31503    }
31504
31505    fn generate_object_agg(&mut self, e: &ObjectAgg) -> Result<()> {
31506        // OBJECT_AGG(key, value)
31507        self.write_keyword("OBJECT_AGG");
31508        self.write("(");
31509        self.generate_expression(&e.this)?;
31510        self.write(", ");
31511        self.generate_expression(&e.expression)?;
31512        self.write(")");
31513        Ok(())
31514    }
31515
31516    fn generate_object_identifier(&mut self, e: &ObjectIdentifier) -> Result<()> {
31517        // Python: Just returns the name
31518        self.generate_expression(&e.this)?;
31519        Ok(())
31520    }
31521
31522    fn generate_object_insert(&mut self, e: &ObjectInsert) -> Result<()> {
31523        // OBJECT_INSERT(obj, key, value, [update_flag])
31524        self.write_keyword("OBJECT_INSERT");
31525        self.write("(");
31526        self.generate_expression(&e.this)?;
31527        if let Some(key) = &e.key {
31528            self.write(", ");
31529            self.generate_expression(key)?;
31530        }
31531        if let Some(value) = &e.value {
31532            self.write(", ");
31533            self.generate_expression(value)?;
31534        }
31535        if let Some(update_flag) = &e.update_flag {
31536            self.write(", ");
31537            self.generate_expression(update_flag)?;
31538        }
31539        self.write(")");
31540        Ok(())
31541    }
31542
31543    fn generate_offset(&mut self, e: &Offset) -> Result<()> {
31544        // OFFSET value [ROW|ROWS]
31545        self.write_keyword("OFFSET");
31546        self.write_space();
31547        self.generate_expression(&e.this)?;
31548        // Output ROWS keyword only for TSQL/Oracle targets
31549        if e.rows == Some(true)
31550            && matches!(
31551                self.config.dialect,
31552                Some(crate::dialects::DialectType::TSQL)
31553                    | Some(crate::dialects::DialectType::Oracle)
31554            )
31555        {
31556            self.write_space();
31557            self.write_keyword("ROWS");
31558        }
31559        Ok(())
31560    }
31561
31562    fn generate_qualify(&mut self, e: &Qualify) -> Result<()> {
31563        // QUALIFY condition (Snowflake/BigQuery)
31564        self.write_keyword("QUALIFY");
31565        self.write_space();
31566        self.generate_expression(&e.this)?;
31567        Ok(())
31568    }
31569
31570    fn generate_on_cluster(&mut self, e: &OnCluster) -> Result<()> {
31571        // ON CLUSTER cluster_name
31572        self.write_keyword("ON CLUSTER");
31573        self.write_space();
31574        self.generate_expression(&e.this)?;
31575        Ok(())
31576    }
31577
31578    fn generate_on_commit_property(&mut self, e: &OnCommitProperty) -> Result<()> {
31579        // ON COMMIT [DELETE ROWS | PRESERVE ROWS]
31580        self.write_keyword("ON COMMIT");
31581        if e.delete.is_some() {
31582            self.write_keyword(" DELETE ROWS");
31583        } else {
31584            self.write_keyword(" PRESERVE ROWS");
31585        }
31586        Ok(())
31587    }
31588
31589    fn generate_on_condition(&mut self, e: &OnCondition) -> Result<()> {
31590        // Python: error/empty/null handling
31591        if let Some(empty) = &e.empty {
31592            self.generate_expression(empty)?;
31593            self.write_keyword(" ON EMPTY");
31594        }
31595        if let Some(error) = &e.error {
31596            if e.empty.is_some() {
31597                self.write_space();
31598            }
31599            self.generate_expression(error)?;
31600            self.write_keyword(" ON ERROR");
31601        }
31602        if let Some(null) = &e.null {
31603            if e.empty.is_some() || e.error.is_some() {
31604                self.write_space();
31605            }
31606            self.generate_expression(null)?;
31607            self.write_keyword(" ON NULL");
31608        }
31609        Ok(())
31610    }
31611
31612    fn generate_on_conflict(&mut self, e: &OnConflict) -> Result<()> {
31613        // Materialize doesn't support ON CONFLICT - skip entirely
31614        if matches!(self.config.dialect, Some(DialectType::Materialize)) {
31615            return Ok(());
31616        }
31617        // Python: ON CONFLICT|ON DUPLICATE KEY [ON CONSTRAINT constraint] [conflict_keys] action
31618        if e.duplicate.is_some() {
31619            // MySQL: ON DUPLICATE KEY UPDATE col = val, ...
31620            self.write_keyword("ON DUPLICATE KEY UPDATE");
31621            for (i, expr) in e.expressions.iter().enumerate() {
31622                if i > 0 {
31623                    self.write(",");
31624                }
31625                self.write_space();
31626                self.generate_expression(expr)?;
31627            }
31628            return Ok(());
31629        } else {
31630            self.write_keyword("ON CONFLICT");
31631        }
31632        if let Some(constraint) = &e.constraint {
31633            self.write_keyword(" ON CONSTRAINT ");
31634            self.generate_expression(constraint)?;
31635        }
31636        if let Some(conflict_keys) = &e.conflict_keys {
31637            // conflict_keys can be a Tuple containing expressions
31638            if let Expression::Tuple(t) = conflict_keys.as_ref() {
31639                self.write("(");
31640                for (i, expr) in t.expressions.iter().enumerate() {
31641                    if i > 0 {
31642                        self.write(", ");
31643                    }
31644                    self.generate_expression(expr)?;
31645                }
31646                self.write(")");
31647            } else {
31648                self.write("(");
31649                self.generate_expression(conflict_keys)?;
31650                self.write(")");
31651            }
31652        }
31653        if let Some(index_predicate) = &e.index_predicate {
31654            self.write_keyword(" WHERE ");
31655            self.generate_expression(index_predicate)?;
31656        }
31657        if let Some(action) = &e.action {
31658            // Check if action is "NOTHING" or an UPDATE set
31659            if let Expression::Identifier(id) = action.as_ref() {
31660                if id.name.eq_ignore_ascii_case("NOTHING") {
31661                    self.write_keyword(" DO NOTHING");
31662                } else {
31663                    self.write_keyword(" DO ");
31664                    self.generate_expression(action)?;
31665                }
31666            } else if let Expression::Tuple(t) = action.as_ref() {
31667                // DO UPDATE SET col1 = val1, col2 = val2
31668                self.write_keyword(" DO UPDATE SET ");
31669                for (i, expr) in t.expressions.iter().enumerate() {
31670                    if i > 0 {
31671                        self.write(", ");
31672                    }
31673                    self.generate_expression(expr)?;
31674                }
31675            } else {
31676                self.write_keyword(" DO ");
31677                self.generate_expression(action)?;
31678            }
31679        }
31680        // WHERE clause for the UPDATE action
31681        if let Some(where_) = &e.where_ {
31682            self.write_keyword(" WHERE ");
31683            self.generate_expression(where_)?;
31684        }
31685        Ok(())
31686    }
31687
31688    fn generate_on_property(&mut self, e: &OnProperty) -> Result<()> {
31689        // ON property_value
31690        self.write_keyword("ON");
31691        self.write_space();
31692        self.generate_expression(&e.this)?;
31693        Ok(())
31694    }
31695
31696    fn generate_opclass(&mut self, e: &Opclass) -> Result<()> {
31697        // Python: this expression (e.g., column opclass)
31698        self.generate_expression(&e.this)?;
31699        self.write_space();
31700        self.generate_expression(&e.expression)?;
31701        Ok(())
31702    }
31703
31704    fn generate_open_json(&mut self, e: &OpenJSON) -> Result<()> {
31705        // Python: OPENJSON(this[, path]) [WITH (columns)]
31706        self.write_keyword("OPENJSON");
31707        self.write("(");
31708        self.generate_expression(&e.this)?;
31709        if let Some(path) = &e.path {
31710            self.write(", ");
31711            self.generate_expression(path)?;
31712        }
31713        self.write(")");
31714        if !e.expressions.is_empty() {
31715            self.write_keyword(" WITH");
31716            if self.config.pretty {
31717                self.write(" (\n");
31718                self.indent_level += 2;
31719                for (i, expr) in e.expressions.iter().enumerate() {
31720                    if i > 0 {
31721                        self.write(",\n");
31722                    }
31723                    self.write_indent();
31724                    self.generate_expression(expr)?;
31725                }
31726                self.write("\n");
31727                self.indent_level -= 2;
31728                self.write(")");
31729            } else {
31730                self.write(" (");
31731                for (i, expr) in e.expressions.iter().enumerate() {
31732                    if i > 0 {
31733                        self.write(", ");
31734                    }
31735                    self.generate_expression(expr)?;
31736                }
31737                self.write(")");
31738            }
31739        }
31740        Ok(())
31741    }
31742
31743    fn generate_open_json_column_def(&mut self, e: &OpenJSONColumnDef) -> Result<()> {
31744        // Python: this kind [path] [AS JSON]
31745        self.generate_expression(&e.this)?;
31746        self.write_space();
31747        // Use parsed data_type if available, otherwise fall back to kind string
31748        if let Some(ref dt) = e.data_type {
31749            self.generate_data_type(dt)?;
31750        } else if !e.kind.is_empty() {
31751            self.write(&e.kind);
31752        }
31753        if let Some(path) = &e.path {
31754            self.write_space();
31755            self.generate_expression(path)?;
31756        }
31757        if e.as_json.is_some() {
31758            self.write_keyword(" AS JSON");
31759        }
31760        Ok(())
31761    }
31762
31763    fn generate_operator(&mut self, e: &Operator) -> Result<()> {
31764        // this OPERATOR(op) expression
31765        self.generate_expression(&e.this)?;
31766        self.write_space();
31767        if let Some(op) = &e.operator {
31768            self.write_keyword("OPERATOR");
31769            self.write("(");
31770            self.generate_expression(op)?;
31771            self.write(")");
31772        }
31773        // Emit inline comments between OPERATOR() and the RHS
31774        for comment in &e.comments {
31775            self.write_space();
31776            self.write_formatted_comment(comment);
31777        }
31778        self.write_space();
31779        self.generate_expression(&e.expression)?;
31780        Ok(())
31781    }
31782
31783    fn generate_order_by(&mut self, e: &OrderBy) -> Result<()> {
31784        // ORDER BY expr1 [ASC|DESC] [NULLS FIRST|LAST], expr2 ...
31785        self.write_keyword("ORDER BY");
31786        let pretty_clickhouse_single_paren = self.config.pretty
31787            && matches!(self.config.dialect, Some(DialectType::ClickHouse))
31788            && e.expressions.len() == 1
31789            && matches!(e.expressions[0].this, Expression::Paren(ref p) if !matches!(p.this, Expression::Tuple(_)));
31790        let clickhouse_single_tuple = matches!(self.config.dialect, Some(DialectType::ClickHouse))
31791            && e.expressions.len() == 1
31792            && matches!(e.expressions[0].this, Expression::Tuple(_))
31793            && !e.expressions[0].desc
31794            && e.expressions[0].nulls_first.is_none();
31795
31796        if pretty_clickhouse_single_paren {
31797            self.write_space();
31798            if let Expression::Paren(p) = &e.expressions[0].this {
31799                self.write("(");
31800                self.write_newline();
31801                self.indent_level += 1;
31802                self.write_indent();
31803                self.generate_expression(&p.this)?;
31804                self.indent_level -= 1;
31805                self.write_newline();
31806                self.write(")");
31807            }
31808            return Ok(());
31809        }
31810
31811        if clickhouse_single_tuple {
31812            self.write_space();
31813            if let Expression::Tuple(t) = &e.expressions[0].this {
31814                self.write("(");
31815                for (i, expr) in t.expressions.iter().enumerate() {
31816                    if i > 0 {
31817                        self.write(", ");
31818                    }
31819                    self.generate_expression(expr)?;
31820                }
31821                self.write(")");
31822            }
31823            return Ok(());
31824        }
31825
31826        self.write_space();
31827        for (i, ordered) in e.expressions.iter().enumerate() {
31828            if i > 0 {
31829                self.write(", ");
31830            }
31831            self.generate_expression(&ordered.this)?;
31832            if ordered.desc {
31833                self.write_space();
31834                self.write_keyword("DESC");
31835            } else if ordered.explicit_asc {
31836                self.write_space();
31837                self.write_keyword("ASC");
31838            }
31839            if let Some(nulls_first) = ordered.nulls_first {
31840                // In Dremio, NULLS LAST is the default, so skip generating it
31841                let skip_nulls_last =
31842                    !nulls_first && matches!(self.config.dialect, Some(DialectType::Dremio));
31843                if !skip_nulls_last {
31844                    self.write_space();
31845                    self.write_keyword("NULLS");
31846                    self.write_space();
31847                    if nulls_first {
31848                        self.write_keyword("FIRST");
31849                    } else {
31850                        self.write_keyword("LAST");
31851                    }
31852                }
31853            }
31854        }
31855        Ok(())
31856    }
31857
31858    fn generate_output_model_property(&mut self, e: &OutputModelProperty) -> Result<()> {
31859        // OUTPUT(model)
31860        self.write_keyword("OUTPUT");
31861        self.write("(");
31862        if self.config.pretty {
31863            self.indent_level += 1;
31864            self.write_newline();
31865            self.write_indent();
31866            self.generate_expression(&e.this)?;
31867            self.indent_level -= 1;
31868            self.write_newline();
31869        } else {
31870            self.generate_expression(&e.this)?;
31871        }
31872        self.write(")");
31873        Ok(())
31874    }
31875
31876    fn generate_overflow_truncate_behavior(&mut self, e: &OverflowTruncateBehavior) -> Result<()> {
31877        // Python: TRUNCATE [filler] WITH|WITHOUT COUNT
31878        self.write_keyword("TRUNCATE");
31879        if let Some(this) = &e.this {
31880            self.write_space();
31881            self.generate_expression(this)?;
31882        }
31883        if e.with_count.is_some() {
31884            self.write_keyword(" WITH COUNT");
31885        } else {
31886            self.write_keyword(" WITHOUT COUNT");
31887        }
31888        Ok(())
31889    }
31890
31891    fn generate_parameterized_agg(&mut self, e: &ParameterizedAgg) -> Result<()> {
31892        // Python: name(expressions)(params)
31893        self.generate_expression(&e.this)?;
31894        self.write("(");
31895        for (i, expr) in e.expressions.iter().enumerate() {
31896            if i > 0 {
31897                self.write(", ");
31898            }
31899            self.generate_expression(expr)?;
31900        }
31901        self.write(")(");
31902        for (i, param) in e.params.iter().enumerate() {
31903            if i > 0 {
31904                self.write(", ");
31905            }
31906            self.generate_expression(param)?;
31907        }
31908        self.write(")");
31909        Ok(())
31910    }
31911
31912    fn generate_parse_datetime(&mut self, e: &ParseDatetime) -> Result<()> {
31913        // PARSE_DATETIME(format, this) or similar
31914        self.write_keyword("PARSE_DATETIME");
31915        self.write("(");
31916        if let Some(format) = &e.format {
31917            self.write("'");
31918            self.write(format);
31919            self.write("', ");
31920        }
31921        self.generate_expression(&e.this)?;
31922        if let Some(zone) = &e.zone {
31923            self.write(", ");
31924            self.generate_expression(zone)?;
31925        }
31926        self.write(")");
31927        Ok(())
31928    }
31929
31930    fn generate_parse_ip(&mut self, e: &ParseIp) -> Result<()> {
31931        // PARSE_IP(this, type, permissive)
31932        self.write_keyword("PARSE_IP");
31933        self.write("(");
31934        self.generate_expression(&e.this)?;
31935        if let Some(type_) = &e.type_ {
31936            self.write(", ");
31937            self.generate_expression(type_)?;
31938        }
31939        if let Some(permissive) = &e.permissive {
31940            self.write(", ");
31941            self.generate_expression(permissive)?;
31942        }
31943        self.write(")");
31944        Ok(())
31945    }
31946
31947    fn generate_parse_json(&mut self, e: &ParseJSON) -> Result<()> {
31948        // PARSE_JSON(this, [expression])
31949        self.write_keyword("PARSE_JSON");
31950        self.write("(");
31951        self.generate_expression(&e.this)?;
31952        if let Some(expression) = &e.expression {
31953            self.write(", ");
31954            self.generate_expression(expression)?;
31955        }
31956        self.write(")");
31957        Ok(())
31958    }
31959
31960    fn generate_parse_time(&mut self, e: &ParseTime) -> Result<()> {
31961        // PARSE_TIME(format, this) or STR_TO_TIME(this, format)
31962        self.write_keyword("PARSE_TIME");
31963        self.write("(");
31964        self.write(&format!("'{}'", e.format));
31965        self.write(", ");
31966        self.generate_expression(&e.this)?;
31967        self.write(")");
31968        Ok(())
31969    }
31970
31971    fn generate_parse_url(&mut self, e: &ParseUrl) -> Result<()> {
31972        // PARSE_URL(this, [part_to_extract], [key], [permissive])
31973        self.write_keyword("PARSE_URL");
31974        self.write("(");
31975        self.generate_expression(&e.this)?;
31976        if let Some(part) = &e.part_to_extract {
31977            self.write(", ");
31978            self.generate_expression(part)?;
31979        }
31980        if let Some(key) = &e.key {
31981            self.write(", ");
31982            self.generate_expression(key)?;
31983        }
31984        if let Some(permissive) = &e.permissive {
31985            self.write(", ");
31986            self.generate_expression(permissive)?;
31987        }
31988        self.write(")");
31989        Ok(())
31990    }
31991
31992    fn generate_partition_expr(&mut self, e: &Partition) -> Result<()> {
31993        // PARTITION(expr1, expr2, ...) or SUBPARTITION(expr1, expr2, ...)
31994        if e.subpartition {
31995            self.write_keyword("SUBPARTITION");
31996        } else {
31997            self.write_keyword("PARTITION");
31998        }
31999        self.write("(");
32000        for (i, expr) in e.expressions.iter().enumerate() {
32001            if i > 0 {
32002                self.write(", ");
32003            }
32004            self.generate_expression(expr)?;
32005        }
32006        self.write(")");
32007        Ok(())
32008    }
32009
32010    fn generate_partition_bound_spec(&mut self, e: &PartitionBoundSpec) -> Result<()> {
32011        // IN (values) or WITH (MODULUS this, REMAINDER expression) or FROM (from) TO (to)
32012        if let Some(this) = &e.this {
32013            if let Some(expression) = &e.expression {
32014                // WITH (MODULUS this, REMAINDER expression)
32015                self.write_keyword("WITH");
32016                self.write(" (");
32017                self.write_keyword("MODULUS");
32018                self.write_space();
32019                self.generate_expression(this)?;
32020                self.write(", ");
32021                self.write_keyword("REMAINDER");
32022                self.write_space();
32023                self.generate_expression(expression)?;
32024                self.write(")");
32025            } else {
32026                // IN (this) - this could be a list
32027                self.write_keyword("IN");
32028                self.write(" (");
32029                self.generate_partition_bound_values(this)?;
32030                self.write(")");
32031            }
32032        } else if let (Some(from), Some(to)) = (&e.from_expressions, &e.to_expressions) {
32033            // FROM (from_expressions) TO (to_expressions)
32034            self.write_keyword("FROM");
32035            self.write(" (");
32036            self.generate_partition_bound_values(from)?;
32037            self.write(") ");
32038            self.write_keyword("TO");
32039            self.write(" (");
32040            self.generate_partition_bound_values(to)?;
32041            self.write(")");
32042        }
32043        Ok(())
32044    }
32045
32046    /// Generate partition bound values - handles Tuple expressions by outputting
32047    /// contents without wrapping parens (since caller provides the parens)
32048    fn generate_partition_bound_values(&mut self, expr: &Expression) -> Result<()> {
32049        if let Expression::Tuple(t) = expr {
32050            for (i, e) in t.expressions.iter().enumerate() {
32051                if i > 0 {
32052                    self.write(", ");
32053                }
32054                self.generate_expression(e)?;
32055            }
32056            Ok(())
32057        } else {
32058            self.generate_expression(expr)
32059        }
32060    }
32061
32062    fn generate_partition_by_list_property(&mut self, e: &PartitionByListProperty) -> Result<()> {
32063        // PARTITION BY LIST (partition_expressions) (create_expressions)
32064        self.write_keyword("PARTITION BY LIST");
32065        if let Some(partition_exprs) = &e.partition_expressions {
32066            self.write(" (");
32067            // Unwrap Tuple for partition columns (don't generate outer parens from Tuple)
32068            self.generate_doris_partition_expressions(partition_exprs)?;
32069            self.write(")");
32070        }
32071        if let Some(create_exprs) = &e.create_expressions {
32072            self.write(" (");
32073            // Unwrap Tuple for partition definitions
32074            self.generate_doris_partition_definitions(create_exprs)?;
32075            self.write(")");
32076        }
32077        Ok(())
32078    }
32079
32080    fn generate_partition_by_range_property(&mut self, e: &PartitionByRangeProperty) -> Result<()> {
32081        // PARTITION BY RANGE (partition_expressions) (create_expressions)
32082        self.write_keyword("PARTITION BY RANGE");
32083        if let Some(partition_exprs) = &e.partition_expressions {
32084            self.write(" (");
32085            // Unwrap Tuple for partition columns (don't generate outer parens from Tuple)
32086            self.generate_doris_partition_expressions(partition_exprs)?;
32087            self.write(")");
32088        }
32089        if let Some(create_exprs) = &e.create_expressions {
32090            self.write(" (");
32091            // Check for dynamic partition (PartitionByRangePropertyDynamic) or static (Tuple of Partition)
32092            self.generate_doris_partition_definitions(create_exprs)?;
32093            self.write(")");
32094        }
32095        Ok(())
32096    }
32097
32098    /// Generate Doris partition column expressions (unwrap Tuple)
32099    fn generate_doris_partition_expressions(&mut self, expr: &Expression) -> Result<()> {
32100        if let Expression::Tuple(t) = expr {
32101            for (i, e) in t.expressions.iter().enumerate() {
32102                if i > 0 {
32103                    self.write(", ");
32104                }
32105                self.generate_expression(e)?;
32106            }
32107        } else {
32108            self.generate_expression(expr)?;
32109        }
32110        Ok(())
32111    }
32112
32113    /// Generate Doris partition definitions (comma-separated Partition expressions)
32114    fn generate_doris_partition_definitions(&mut self, expr: &Expression) -> Result<()> {
32115        match expr {
32116            Expression::Tuple(t) => {
32117                // Multiple partitions, comma-separated
32118                for (i, part) in t.expressions.iter().enumerate() {
32119                    if i > 0 {
32120                        self.write(", ");
32121                    }
32122                    // For Partition expressions, generate the inner PartitionRange/PartitionList directly
32123                    if let Expression::Partition(p) = part {
32124                        for (j, inner) in p.expressions.iter().enumerate() {
32125                            if j > 0 {
32126                                self.write(", ");
32127                            }
32128                            self.generate_expression(inner)?;
32129                        }
32130                    } else {
32131                        self.generate_expression(part)?;
32132                    }
32133                }
32134            }
32135            Expression::PartitionByRangePropertyDynamic(_) => {
32136                // Dynamic partition - FROM/TO/INTERVAL
32137                self.generate_expression(expr)?;
32138            }
32139            _ => {
32140                self.generate_expression(expr)?;
32141            }
32142        }
32143        Ok(())
32144    }
32145
32146    fn generate_partition_by_range_property_dynamic(
32147        &mut self,
32148        e: &PartitionByRangePropertyDynamic,
32149    ) -> Result<()> {
32150        if e.use_start_end {
32151            // StarRocks: START ('val') END ('val') EVERY (expr)
32152            if let Some(start) = &e.start {
32153                self.write_keyword("START");
32154                self.write(" (");
32155                self.generate_expression(start)?;
32156                self.write(")");
32157            }
32158            if let Some(end) = &e.end {
32159                self.write_space();
32160                self.write_keyword("END");
32161                self.write(" (");
32162                self.generate_expression(end)?;
32163                self.write(")");
32164            }
32165            if let Some(every) = &e.every {
32166                self.write_space();
32167                self.write_keyword("EVERY");
32168                self.write(" (");
32169                // Use unquoted interval format for StarRocks
32170                self.generate_doris_interval(every)?;
32171                self.write(")");
32172            }
32173        } else {
32174            // Doris: FROM (start) TO (end) INTERVAL n UNIT
32175            if let Some(start) = &e.start {
32176                self.write_keyword("FROM");
32177                self.write(" (");
32178                self.generate_expression(start)?;
32179                self.write(")");
32180            }
32181            if let Some(end) = &e.end {
32182                self.write_space();
32183                self.write_keyword("TO");
32184                self.write(" (");
32185                self.generate_expression(end)?;
32186                self.write(")");
32187            }
32188            if let Some(every) = &e.every {
32189                self.write_space();
32190                // Generate INTERVAL n UNIT (not quoted, for Doris dynamic partition)
32191                self.generate_doris_interval(every)?;
32192            }
32193        }
32194        Ok(())
32195    }
32196
32197    /// Generate Doris-style interval without quoting numbers: INTERVAL n UNIT
32198    fn generate_doris_interval(&mut self, expr: &Expression) -> Result<()> {
32199        if let Expression::Interval(interval) = expr {
32200            self.write_keyword("INTERVAL");
32201            if let Some(ref value) = interval.this {
32202                self.write_space();
32203                // If the value is a string literal that looks like a number,
32204                // output it without quotes (matching Python sqlglot's
32205                // partitionbyrangepropertydynamic_sql which converts back to number)
32206                match value {
32207                    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()) => {
32208                        if let Literal::String(s) = lit.as_ref() {
32209                            self.write(s);
32210                        }
32211                    }
32212                    _ => {
32213                        self.generate_expression(value)?;
32214                    }
32215                }
32216            }
32217            if let Some(ref unit_spec) = interval.unit {
32218                self.write_space();
32219                self.write_interval_unit_spec(unit_spec)?;
32220            }
32221            Ok(())
32222        } else {
32223            self.generate_expression(expr)
32224        }
32225    }
32226
32227    fn generate_partition_by_truncate(&mut self, e: &PartitionByTruncate) -> Result<()> {
32228        // TRUNCATE(expression, this)
32229        self.write_keyword("TRUNCATE");
32230        self.write("(");
32231        self.generate_expression(&e.expression)?;
32232        self.write(", ");
32233        self.generate_expression(&e.this)?;
32234        self.write(")");
32235        Ok(())
32236    }
32237
32238    fn generate_partition_list(&mut self, e: &PartitionList) -> Result<()> {
32239        // Doris: PARTITION name VALUES IN (val1, val2)
32240        self.write_keyword("PARTITION");
32241        self.write_space();
32242        self.generate_expression(&e.this)?;
32243        self.write_space();
32244        self.write_keyword("VALUES IN");
32245        self.write(" (");
32246        for (i, expr) in e.expressions.iter().enumerate() {
32247            if i > 0 {
32248                self.write(", ");
32249            }
32250            self.generate_expression(expr)?;
32251        }
32252        self.write(")");
32253        Ok(())
32254    }
32255
32256    fn generate_partition_range(&mut self, e: &PartitionRange) -> Result<()> {
32257        // Check if this is a TSQL-style simple range (e.g., "2 TO 5")
32258        // TSQL ranges have no expressions and just use `this TO expression`
32259        if e.expressions.is_empty() && e.expression.is_some() {
32260            // TSQL: simple range like "2 TO 5" - no PARTITION keyword
32261            self.generate_expression(&e.this)?;
32262            self.write_space();
32263            self.write_keyword("TO");
32264            self.write_space();
32265            self.generate_expression(e.expression.as_ref().unwrap())?;
32266            return Ok(());
32267        }
32268
32269        // Doris: PARTITION name VALUES LESS THAN (val) or PARTITION name VALUES [(val1), (val2))
32270        self.write_keyword("PARTITION");
32271        self.write_space();
32272        self.generate_expression(&e.this)?;
32273        self.write_space();
32274
32275        // Check if expressions contain Tuple (bracket notation) or single values (LESS THAN)
32276        if e.expressions.len() == 1 {
32277            // Single value: VALUES LESS THAN (val)
32278            self.write_keyword("VALUES LESS THAN");
32279            self.write(" (");
32280            self.generate_expression(&e.expressions[0])?;
32281            self.write(")");
32282        } else if !e.expressions.is_empty() {
32283            // Multiple values with Tuple: VALUES [(val1), (val2))
32284            self.write_keyword("VALUES");
32285            self.write(" [");
32286            for (i, expr) in e.expressions.iter().enumerate() {
32287                if i > 0 {
32288                    self.write(", ");
32289                }
32290                // If the expr is a Tuple, generate its contents wrapped in parens
32291                if let Expression::Tuple(t) = expr {
32292                    self.write("(");
32293                    for (j, inner) in t.expressions.iter().enumerate() {
32294                        if j > 0 {
32295                            self.write(", ");
32296                        }
32297                        self.generate_expression(inner)?;
32298                    }
32299                    self.write(")");
32300                } else {
32301                    self.write("(");
32302                    self.generate_expression(expr)?;
32303                    self.write(")");
32304                }
32305            }
32306            self.write(")");
32307        }
32308        Ok(())
32309    }
32310
32311    fn generate_partitioned_by_bucket(&mut self, e: &PartitionedByBucket) -> Result<()> {
32312        // BUCKET(this, expression)
32313        self.write_keyword("BUCKET");
32314        self.write("(");
32315        self.generate_expression(&e.this)?;
32316        self.write(", ");
32317        self.generate_expression(&e.expression)?;
32318        self.write(")");
32319        Ok(())
32320    }
32321
32322    fn generate_partition_by_property(&mut self, e: &PartitionByProperty) -> Result<()> {
32323        // BigQuery table property: PARTITION BY expression [, expression ...]
32324        self.write_keyword("PARTITION BY");
32325        self.write_space();
32326        for (i, expr) in e.expressions.iter().enumerate() {
32327            if i > 0 {
32328                self.write(", ");
32329            }
32330            self.generate_expression(expr)?;
32331        }
32332        Ok(())
32333    }
32334
32335    fn generate_partitioned_by_property(&mut self, e: &PartitionedByProperty) -> Result<()> {
32336        // PARTITIONED BY this (Teradata/ClickHouse use PARTITION BY)
32337        if matches!(
32338            self.config.dialect,
32339            Some(crate::dialects::DialectType::Teradata)
32340                | Some(crate::dialects::DialectType::ClickHouse)
32341        ) {
32342            self.write_keyword("PARTITION BY");
32343        } else {
32344            self.write_keyword("PARTITIONED BY");
32345        }
32346        self.write_space();
32347        // In pretty mode, always use multiline tuple format for PARTITIONED BY
32348        if self.config.pretty {
32349            if let Expression::Tuple(ref tuple) = *e.this {
32350                self.write("(");
32351                self.write_newline();
32352                self.indent_level += 1;
32353                for (i, expr) in tuple.expressions.iter().enumerate() {
32354                    if i > 0 {
32355                        self.write(",");
32356                        self.write_newline();
32357                    }
32358                    self.write_indent();
32359                    self.generate_expression(expr)?;
32360                }
32361                self.indent_level -= 1;
32362                self.write_newline();
32363                self.write(")");
32364            } else {
32365                self.generate_expression(&e.this)?;
32366            }
32367        } else {
32368            self.generate_expression(&e.this)?;
32369        }
32370        Ok(())
32371    }
32372
32373    fn generate_partitioned_of_property(&mut self, e: &PartitionedOfProperty) -> Result<()> {
32374        // PARTITION OF this FOR VALUES expression or PARTITION OF this DEFAULT
32375        self.write_keyword("PARTITION OF");
32376        self.write_space();
32377        self.generate_expression(&e.this)?;
32378        // Check if expression is a PartitionBoundSpec
32379        if let Expression::PartitionBoundSpec(_) = e.expression.as_ref() {
32380            self.write_space();
32381            self.write_keyword("FOR VALUES");
32382            self.write_space();
32383            self.generate_expression(&e.expression)?;
32384        } else {
32385            self.write_space();
32386            self.write_keyword("DEFAULT");
32387        }
32388        Ok(())
32389    }
32390
32391    fn generate_period_for_system_time_constraint(
32392        &mut self,
32393        e: &PeriodForSystemTimeConstraint,
32394    ) -> Result<()> {
32395        // PERIOD FOR SYSTEM_TIME (this, expression)
32396        self.write_keyword("PERIOD FOR SYSTEM_TIME");
32397        self.write(" (");
32398        self.generate_expression(&e.this)?;
32399        self.write(", ");
32400        self.generate_expression(&e.expression)?;
32401        self.write(")");
32402        Ok(())
32403    }
32404
32405    fn generate_pivot_alias(&mut self, e: &PivotAlias) -> Result<()> {
32406        // value AS alias
32407        // The alias can be an identifier or an expression (e.g., string concatenation)
32408        self.generate_expression(&e.this)?;
32409        self.write_space();
32410        self.write_keyword("AS");
32411        self.write_space();
32412        // When target dialect uses identifiers for UNPIVOT aliases, convert literals to identifiers
32413        if self.config.unpivot_aliases_are_identifiers {
32414            match &e.alias {
32415                Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
32416                    let Literal::String(s) = lit.as_ref() else {
32417                        unreachable!()
32418                    };
32419                    // Convert string literal to identifier
32420                    self.generate_identifier(&Identifier::new(s.clone()))?;
32421                }
32422                Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(_)) => {
32423                    let Literal::Number(n) = lit.as_ref() else {
32424                        unreachable!()
32425                    };
32426                    // Convert number literal to quoted identifier
32427                    let mut id = Identifier::new(n.clone());
32428                    id.quoted = true;
32429                    self.generate_identifier(&id)?;
32430                }
32431                other => {
32432                    self.generate_expression(other)?;
32433                }
32434            }
32435        } else {
32436            self.generate_expression(&e.alias)?;
32437        }
32438        Ok(())
32439    }
32440
32441    fn generate_pivot_any(&mut self, e: &PivotAny) -> Result<()> {
32442        // ANY or ANY [expression]
32443        self.write_keyword("ANY");
32444        if let Some(this) = &e.this {
32445            self.write_space();
32446            self.generate_expression(this)?;
32447        }
32448        Ok(())
32449    }
32450
32451    fn generate_predict(&mut self, e: &Predict) -> Result<()> {
32452        // ML.PREDICT(MODEL this, expression, [params_struct])
32453        self.write_keyword("ML.PREDICT");
32454        self.write("(");
32455        self.write_keyword("MODEL");
32456        self.write_space();
32457        self.generate_expression(&e.this)?;
32458        self.write(", ");
32459        self.generate_expression(&e.expression)?;
32460        if let Some(params) = &e.params_struct {
32461            self.write(", ");
32462            self.generate_expression(params)?;
32463        }
32464        self.write(")");
32465        Ok(())
32466    }
32467
32468    fn generate_previous_day(&mut self, e: &PreviousDay) -> Result<()> {
32469        // PREVIOUS_DAY(this, expression)
32470        self.write_keyword("PREVIOUS_DAY");
32471        self.write("(");
32472        self.generate_expression(&e.this)?;
32473        self.write(", ");
32474        self.generate_expression(&e.expression)?;
32475        self.write(")");
32476        Ok(())
32477    }
32478
32479    fn generate_primary_key(&mut self, e: &PrimaryKey) -> Result<()> {
32480        // PRIMARY KEY [name] (columns) [INCLUDE (...)] [options]
32481        self.write_keyword("PRIMARY KEY");
32482        if let Some(name) = &e.this {
32483            self.write_space();
32484            self.generate_expression(name)?;
32485        }
32486        if !e.expressions.is_empty() {
32487            self.write(" (");
32488            for (i, expr) in e.expressions.iter().enumerate() {
32489                if i > 0 {
32490                    self.write(", ");
32491                }
32492                self.generate_expression(expr)?;
32493            }
32494            self.write(")");
32495        }
32496        if let Some(include) = &e.include {
32497            self.write_space();
32498            self.generate_expression(include)?;
32499        }
32500        if !e.options.is_empty() {
32501            self.write_space();
32502            for (i, opt) in e.options.iter().enumerate() {
32503                if i > 0 {
32504                    self.write_space();
32505                }
32506                self.generate_expression(opt)?;
32507            }
32508        }
32509        Ok(())
32510    }
32511
32512    fn generate_primary_key_column_constraint(
32513        &mut self,
32514        _e: &PrimaryKeyColumnConstraint,
32515    ) -> Result<()> {
32516        // PRIMARY KEY constraint at column level
32517        self.write_keyword("PRIMARY KEY");
32518        Ok(())
32519    }
32520
32521    fn generate_path_column_constraint(&mut self, e: &PathColumnConstraint) -> Result<()> {
32522        // PATH 'xpath' constraint for XMLTABLE/JSON_TABLE columns
32523        self.write_keyword("PATH");
32524        self.write_space();
32525        self.generate_expression(&e.this)?;
32526        Ok(())
32527    }
32528
32529    fn generate_projection_def(&mut self, e: &ProjectionDef) -> Result<()> {
32530        // PROJECTION this (expression)
32531        self.write_keyword("PROJECTION");
32532        self.write_space();
32533        self.generate_expression(&e.this)?;
32534        self.write(" (");
32535        self.generate_expression(&e.expression)?;
32536        self.write(")");
32537        Ok(())
32538    }
32539
32540    fn generate_properties(&mut self, e: &Properties) -> Result<()> {
32541        // Properties list
32542        for (i, prop) in e.expressions.iter().enumerate() {
32543            if i > 0 {
32544                self.write(", ");
32545            }
32546            self.generate_expression(prop)?;
32547        }
32548        Ok(())
32549    }
32550
32551    fn generate_property(&mut self, e: &Property) -> Result<()> {
32552        // name=value
32553        self.generate_expression(&e.this)?;
32554        if let Some(value) = &e.value {
32555            self.write("=");
32556            self.generate_expression(value)?;
32557        }
32558        Ok(())
32559    }
32560
32561    fn generate_options_property(&mut self, e: &OptionsProperty) -> Result<()> {
32562        self.write_keyword("OPTIONS");
32563        if e.entries.is_empty() {
32564            self.write(" ()");
32565            return Ok(());
32566        }
32567
32568        if self.config.pretty {
32569            self.write(" (");
32570            self.write_newline();
32571            self.indent_level += 1;
32572            for (i, entry) in e.entries.iter().enumerate() {
32573                if i > 0 {
32574                    self.write(",");
32575                    self.write_newline();
32576                }
32577                self.write_indent();
32578                self.generate_identifier(&entry.key)?;
32579                self.write("=");
32580                self.generate_expression(&entry.value)?;
32581            }
32582            self.indent_level -= 1;
32583            self.write_newline();
32584            self.write(")");
32585        } else {
32586            self.write(" (");
32587            for (i, entry) in e.entries.iter().enumerate() {
32588                if i > 0 {
32589                    self.write(", ");
32590                }
32591                self.generate_identifier(&entry.key)?;
32592                self.write("=");
32593                self.generate_expression(&entry.value)?;
32594            }
32595            self.write(")");
32596        }
32597        Ok(())
32598    }
32599
32600    /// Generate BigQuery-style OPTIONS clause: OPTIONS (key=value, key=value, ...)
32601    fn generate_options_clause(&mut self, options: &[Expression]) -> Result<()> {
32602        self.write_keyword("OPTIONS");
32603        self.write(" (");
32604        for (i, opt) in options.iter().enumerate() {
32605            if i > 0 {
32606                self.write(", ");
32607            }
32608            self.generate_option_expression(opt)?;
32609        }
32610        self.write(")");
32611        Ok(())
32612    }
32613
32614    /// Generate Doris/StarRocks-style PROPERTIES clause: PROPERTIES ('key'='value', 'key'='value', ...)
32615    fn generate_properties_clause(&mut self, properties: &[Expression]) -> Result<()> {
32616        self.write_keyword("PROPERTIES");
32617        self.write(" (");
32618        for (i, prop) in properties.iter().enumerate() {
32619            if i > 0 {
32620                self.write(", ");
32621            }
32622            self.generate_option_expression(prop)?;
32623        }
32624        self.write(")");
32625        Ok(())
32626    }
32627
32628    /// Generate Databricks-style ENVIRONMENT clause: ENVIRONMENT (key = 'value', key = 'value', ...)
32629    fn generate_environment_clause(&mut self, environment: &[Expression]) -> Result<()> {
32630        self.write_keyword("ENVIRONMENT");
32631        self.write(" (");
32632        for (i, env_item) in environment.iter().enumerate() {
32633            if i > 0 {
32634                self.write(", ");
32635            }
32636            self.generate_environment_expression(env_item)?;
32637        }
32638        self.write(")");
32639        Ok(())
32640    }
32641
32642    /// Generate an environment expression with spaces around =
32643    fn generate_environment_expression(&mut self, expr: &Expression) -> Result<()> {
32644        match expr {
32645            Expression::Eq(eq) => {
32646                // Generate key = value with spaces (Databricks ENVIRONMENT style)
32647                self.generate_expression(&eq.left)?;
32648                self.write(" = ");
32649                self.generate_expression(&eq.right)?;
32650                Ok(())
32651            }
32652            _ => self.generate_expression(expr),
32653        }
32654    }
32655
32656    /// Generate Hive-style TBLPROPERTIES clause: TBLPROPERTIES ('key'='value', ...)
32657    fn generate_tblproperties_clause(&mut self, options: &[Expression]) -> Result<()> {
32658        self.write_keyword("TBLPROPERTIES");
32659        if self.config.pretty {
32660            self.write(" (");
32661            self.write_newline();
32662            self.indent_level += 1;
32663            for (i, opt) in options.iter().enumerate() {
32664                if i > 0 {
32665                    self.write(",");
32666                    self.write_newline();
32667                }
32668                self.write_indent();
32669                self.generate_option_expression(opt)?;
32670            }
32671            self.indent_level -= 1;
32672            self.write_newline();
32673            self.write(")");
32674        } else {
32675            self.write(" (");
32676            for (i, opt) in options.iter().enumerate() {
32677                if i > 0 {
32678                    self.write(", ");
32679                }
32680                self.generate_option_expression(opt)?;
32681            }
32682            self.write(")");
32683        }
32684        Ok(())
32685    }
32686
32687    /// Generate an option expression without spaces around =
32688    fn generate_option_expression(&mut self, expr: &Expression) -> Result<()> {
32689        match expr {
32690            Expression::Eq(eq) => {
32691                // Generate key=value without spaces
32692                self.generate_expression(&eq.left)?;
32693                self.write("=");
32694                self.generate_expression(&eq.right)?;
32695                Ok(())
32696            }
32697            _ => self.generate_expression(expr),
32698        }
32699    }
32700
32701    fn generate_pseudo_type(&mut self, e: &PseudoType) -> Result<()> {
32702        // Just output the name
32703        self.generate_expression(&e.this)?;
32704        Ok(())
32705    }
32706
32707    fn generate_put(&mut self, e: &PutStmt) -> Result<()> {
32708        // PUT source_file @stage [options]
32709        self.write_keyword("PUT");
32710        self.write_space();
32711
32712        // Source file path - preserve original quoting
32713        if e.source_quoted {
32714            self.write("'");
32715            self.write(&e.source);
32716            self.write("'");
32717        } else {
32718            self.write(&e.source);
32719        }
32720
32721        self.write_space();
32722
32723        // Target stage reference - output the string directly (includes @)
32724        if let Expression::Literal(lit) = &e.target {
32725            if let Literal::String(s) = lit.as_ref() {
32726                self.write(s);
32727            }
32728        } else {
32729            self.generate_expression(&e.target)?;
32730        }
32731
32732        // Optional parameters: KEY=VALUE
32733        for param in &e.params {
32734            self.write_space();
32735            self.write(&param.name);
32736            if let Some(ref value) = param.value {
32737                self.write("=");
32738                self.generate_expression(value)?;
32739            }
32740        }
32741
32742        Ok(())
32743    }
32744
32745    fn generate_quantile(&mut self, e: &Quantile) -> Result<()> {
32746        // QUANTILE(this, quantile)
32747        self.write_keyword("QUANTILE");
32748        self.write("(");
32749        self.generate_expression(&e.this)?;
32750        if let Some(quantile) = &e.quantile {
32751            self.write(", ");
32752            self.generate_expression(quantile)?;
32753        }
32754        self.write(")");
32755        Ok(())
32756    }
32757
32758    fn generate_query_band(&mut self, e: &QueryBand) -> Result<()> {
32759        // QUERY_BAND = this [UPDATE] [FOR scope]
32760        if matches!(
32761            self.config.dialect,
32762            Some(crate::dialects::DialectType::Teradata)
32763        ) {
32764            self.write_keyword("SET");
32765            self.write_space();
32766        }
32767        self.write_keyword("QUERY_BAND");
32768        self.write(" = ");
32769        self.generate_expression(&e.this)?;
32770        if e.update.is_some() {
32771            self.write_space();
32772            self.write_keyword("UPDATE");
32773        }
32774        if let Some(scope) = &e.scope {
32775            self.write_space();
32776            self.write_keyword("FOR");
32777            self.write_space();
32778            self.generate_expression(scope)?;
32779        }
32780        Ok(())
32781    }
32782
32783    fn generate_query_option(&mut self, e: &QueryOption) -> Result<()> {
32784        // this = expression
32785        self.generate_expression(&e.this)?;
32786        if let Some(expression) = &e.expression {
32787            self.write(" = ");
32788            self.generate_expression(expression)?;
32789        }
32790        Ok(())
32791    }
32792
32793    fn generate_query_transform(&mut self, e: &QueryTransform) -> Result<()> {
32794        // TRANSFORM (expressions) [row_format_before] [RECORDWRITER record_writer] USING command_script [AS schema] [row_format_after] [RECORDREADER record_reader]
32795        self.write_keyword("TRANSFORM");
32796        self.write("(");
32797        for (i, expr) in e.expressions.iter().enumerate() {
32798            if i > 0 {
32799                self.write(", ");
32800            }
32801            self.generate_expression(expr)?;
32802        }
32803        self.write(")");
32804        if let Some(row_format_before) = &e.row_format_before {
32805            self.write_space();
32806            self.generate_expression(row_format_before)?;
32807        }
32808        if let Some(record_writer) = &e.record_writer {
32809            self.write_space();
32810            self.write_keyword("RECORDWRITER");
32811            self.write_space();
32812            self.generate_expression(record_writer)?;
32813        }
32814        if let Some(command_script) = &e.command_script {
32815            self.write_space();
32816            self.write_keyword("USING");
32817            self.write_space();
32818            self.generate_expression(command_script)?;
32819        }
32820        if let Some(schema) = &e.schema {
32821            self.write_space();
32822            self.write_keyword("AS");
32823            self.write_space();
32824            self.generate_expression(schema)?;
32825        }
32826        if let Some(row_format_after) = &e.row_format_after {
32827            self.write_space();
32828            self.generate_expression(row_format_after)?;
32829        }
32830        if let Some(record_reader) = &e.record_reader {
32831            self.write_space();
32832            self.write_keyword("RECORDREADER");
32833            self.write_space();
32834            self.generate_expression(record_reader)?;
32835        }
32836        Ok(())
32837    }
32838
32839    fn generate_randn(&mut self, e: &Randn) -> Result<()> {
32840        // RANDN([seed])
32841        self.write_keyword("RANDN");
32842        self.write("(");
32843        if let Some(this) = &e.this {
32844            self.generate_expression(this)?;
32845        }
32846        self.write(")");
32847        Ok(())
32848    }
32849
32850    fn generate_randstr(&mut self, e: &Randstr) -> Result<()> {
32851        // RANDSTR(this, [generator])
32852        self.write_keyword("RANDSTR");
32853        self.write("(");
32854        self.generate_expression(&e.this)?;
32855        if let Some(generator) = &e.generator {
32856            self.write(", ");
32857            self.generate_expression(generator)?;
32858        }
32859        self.write(")");
32860        Ok(())
32861    }
32862
32863    fn generate_range_bucket(&mut self, e: &RangeBucket) -> Result<()> {
32864        // RANGE_BUCKET(this, expression)
32865        self.write_keyword("RANGE_BUCKET");
32866        self.write("(");
32867        self.generate_expression(&e.this)?;
32868        self.write(", ");
32869        self.generate_expression(&e.expression)?;
32870        self.write(")");
32871        Ok(())
32872    }
32873
32874    fn generate_range_n(&mut self, e: &RangeN) -> Result<()> {
32875        // RANGE_N(this BETWEEN expressions [EACH each])
32876        self.write_keyword("RANGE_N");
32877        self.write("(");
32878        self.generate_expression(&e.this)?;
32879        self.write_space();
32880        self.write_keyword("BETWEEN");
32881        self.write_space();
32882        for (i, expr) in e.expressions.iter().enumerate() {
32883            if i > 0 {
32884                self.write(", ");
32885            }
32886            self.generate_expression(expr)?;
32887        }
32888        if let Some(each) = &e.each {
32889            self.write_space();
32890            self.write_keyword("EACH");
32891            self.write_space();
32892            self.generate_expression(each)?;
32893        }
32894        self.write(")");
32895        Ok(())
32896    }
32897
32898    fn generate_read_csv(&mut self, e: &ReadCSV) -> Result<()> {
32899        // READ_CSV(this, expressions...)
32900        self.write_keyword("READ_CSV");
32901        self.write("(");
32902        self.generate_expression(&e.this)?;
32903        for expr in &e.expressions {
32904            self.write(", ");
32905            self.generate_expression(expr)?;
32906        }
32907        self.write(")");
32908        Ok(())
32909    }
32910
32911    fn generate_read_parquet(&mut self, e: &ReadParquet) -> Result<()> {
32912        // READ_PARQUET(expressions...)
32913        self.write_keyword("READ_PARQUET");
32914        self.write("(");
32915        for (i, expr) in e.expressions.iter().enumerate() {
32916            if i > 0 {
32917                self.write(", ");
32918            }
32919            self.generate_expression(expr)?;
32920        }
32921        self.write(")");
32922        Ok(())
32923    }
32924
32925    fn generate_recursive_with_search(&mut self, e: &RecursiveWithSearch) -> Result<()> {
32926        // SEARCH kind FIRST BY this SET expression [USING using]
32927        // or CYCLE this SET expression [USING using]
32928        if e.kind == "CYCLE" {
32929            self.write_keyword("CYCLE");
32930        } else {
32931            self.write_keyword("SEARCH");
32932            self.write_space();
32933            self.write(&e.kind);
32934            self.write_space();
32935            self.write_keyword("FIRST BY");
32936        }
32937        self.write_space();
32938        self.generate_expression(&e.this)?;
32939        self.write_space();
32940        self.write_keyword("SET");
32941        self.write_space();
32942        self.generate_expression(&e.expression)?;
32943        if let Some(using) = &e.using {
32944            self.write_space();
32945            self.write_keyword("USING");
32946            self.write_space();
32947            self.generate_expression(using)?;
32948        }
32949        Ok(())
32950    }
32951
32952    fn generate_reduce(&mut self, e: &Reduce) -> Result<()> {
32953        // REDUCE(this, initial, merge, [finish])
32954        self.write_keyword("REDUCE");
32955        self.write("(");
32956        self.generate_expression(&e.this)?;
32957        if let Some(initial) = &e.initial {
32958            self.write(", ");
32959            self.generate_expression(initial)?;
32960        }
32961        if let Some(merge) = &e.merge {
32962            self.write(", ");
32963            self.generate_expression(merge)?;
32964        }
32965        if let Some(finish) = &e.finish {
32966            self.write(", ");
32967            self.generate_expression(finish)?;
32968        }
32969        self.write(")");
32970        Ok(())
32971    }
32972
32973    fn generate_reference(&mut self, e: &Reference) -> Result<()> {
32974        // REFERENCES this (expressions) [options]
32975        self.write_keyword("REFERENCES");
32976        self.write_space();
32977        self.generate_expression(&e.this)?;
32978        if !e.expressions.is_empty() {
32979            self.write(" (");
32980            for (i, expr) in e.expressions.iter().enumerate() {
32981                if i > 0 {
32982                    self.write(", ");
32983                }
32984                self.generate_expression(expr)?;
32985            }
32986            self.write(")");
32987        }
32988        for opt in &e.options {
32989            self.write_space();
32990            self.generate_expression(opt)?;
32991        }
32992        Ok(())
32993    }
32994
32995    fn generate_refresh(&mut self, e: &Refresh) -> Result<()> {
32996        // REFRESH [kind] this
32997        self.write_keyword("REFRESH");
32998        if !e.kind.is_empty() {
32999            self.write_space();
33000            self.write_keyword(&e.kind);
33001        }
33002        self.write_space();
33003        self.generate_expression(&e.this)?;
33004        Ok(())
33005    }
33006
33007    fn generate_refresh_trigger_property(&mut self, e: &RefreshTriggerProperty) -> Result<()> {
33008        // Doris REFRESH clause: REFRESH method ON kind [EVERY n UNIT] [STARTS 'datetime']
33009        self.write_keyword("REFRESH");
33010        self.write_space();
33011        self.write_keyword(&e.method);
33012
33013        if let Some(ref kind) = e.kind {
33014            self.write_space();
33015            self.write_keyword("ON");
33016            self.write_space();
33017            self.write_keyword(kind);
33018
33019            // EVERY n UNIT
33020            if let Some(ref every) = e.every {
33021                self.write_space();
33022                self.write_keyword("EVERY");
33023                self.write_space();
33024                self.generate_expression(every)?;
33025                if let Some(ref unit) = e.unit {
33026                    self.write_space();
33027                    self.write_keyword(unit);
33028                }
33029            }
33030
33031            // STARTS 'datetime'
33032            if let Some(ref starts) = e.starts {
33033                self.write_space();
33034                self.write_keyword("STARTS");
33035                self.write_space();
33036                self.generate_expression(starts)?;
33037            }
33038        }
33039        Ok(())
33040    }
33041
33042    fn generate_regexp_count(&mut self, e: &RegexpCount) -> Result<()> {
33043        // REGEXP_COUNT(this, expression, position, parameters)
33044        self.write_keyword("REGEXP_COUNT");
33045        self.write("(");
33046        self.generate_expression(&e.this)?;
33047        self.write(", ");
33048        self.generate_expression(&e.expression)?;
33049        if let Some(position) = &e.position {
33050            self.write(", ");
33051            self.generate_expression(position)?;
33052        }
33053        if let Some(parameters) = &e.parameters {
33054            self.write(", ");
33055            self.generate_expression(parameters)?;
33056        }
33057        self.write(")");
33058        Ok(())
33059    }
33060
33061    fn generate_regexp_extract_all(&mut self, e: &RegexpExtractAll) -> Result<()> {
33062        // REGEXP_EXTRACT_ALL(this, expression, group, parameters, position, occurrence)
33063        self.write_keyword("REGEXP_EXTRACT_ALL");
33064        self.write("(");
33065        self.generate_expression(&e.this)?;
33066        self.write(", ");
33067        self.generate_expression(&e.expression)?;
33068        if let Some(group) = &e.group {
33069            self.write(", ");
33070            self.generate_expression(group)?;
33071        }
33072        self.write(")");
33073        Ok(())
33074    }
33075
33076    fn generate_regexp_full_match(&mut self, e: &RegexpFullMatch) -> Result<()> {
33077        // REGEXP_FULL_MATCH(this, expression)
33078        self.write_keyword("REGEXP_FULL_MATCH");
33079        self.write("(");
33080        self.generate_expression(&e.this)?;
33081        self.write(", ");
33082        self.generate_expression(&e.expression)?;
33083        self.write(")");
33084        Ok(())
33085    }
33086
33087    fn generate_regexp_i_like(&mut self, e: &RegexpILike) -> Result<()> {
33088        use crate::dialects::DialectType;
33089        // PostgreSQL/Redshift uses ~* operator for case-insensitive regex matching
33090        if matches!(
33091            self.config.dialect,
33092            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
33093        ) && e.flag.is_none()
33094        {
33095            self.generate_expression(&e.this)?;
33096            self.write(" ~* ");
33097            self.generate_expression(&e.expression)?;
33098        } else if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
33099            // Snowflake uses REGEXP_LIKE(x, pattern, 'i')
33100            self.write_keyword("REGEXP_LIKE");
33101            self.write("(");
33102            self.generate_expression(&e.this)?;
33103            self.write(", ");
33104            self.generate_expression(&e.expression)?;
33105            self.write(", ");
33106            if let Some(flag) = &e.flag {
33107                self.generate_expression(flag)?;
33108            } else {
33109                self.write("'i'");
33110            }
33111            self.write(")");
33112        } else {
33113            // this REGEXP_ILIKE expression or REGEXP_ILIKE(this, expression, flag)
33114            self.generate_expression(&e.this)?;
33115            self.write_space();
33116            self.write_keyword("REGEXP_ILIKE");
33117            self.write_space();
33118            self.generate_expression(&e.expression)?;
33119            if let Some(flag) = &e.flag {
33120                self.write(", ");
33121                self.generate_expression(flag)?;
33122            }
33123        }
33124        Ok(())
33125    }
33126
33127    fn generate_regexp_instr(&mut self, e: &RegexpInstr) -> Result<()> {
33128        // REGEXP_INSTR(this, expression, position, occurrence, option, parameters, group)
33129        self.write_keyword("REGEXP_INSTR");
33130        self.write("(");
33131        self.generate_expression(&e.this)?;
33132        self.write(", ");
33133        self.generate_expression(&e.expression)?;
33134        if let Some(position) = &e.position {
33135            self.write(", ");
33136            self.generate_expression(position)?;
33137        }
33138        if let Some(occurrence) = &e.occurrence {
33139            self.write(", ");
33140            self.generate_expression(occurrence)?;
33141        }
33142        if let Some(option) = &e.option {
33143            self.write(", ");
33144            self.generate_expression(option)?;
33145        }
33146        if let Some(parameters) = &e.parameters {
33147            self.write(", ");
33148            self.generate_expression(parameters)?;
33149        }
33150        if let Some(group) = &e.group {
33151            self.write(", ");
33152            self.generate_expression(group)?;
33153        }
33154        self.write(")");
33155        Ok(())
33156    }
33157
33158    fn generate_regexp_split(&mut self, e: &RegexpSplit) -> Result<()> {
33159        // REGEXP_SPLIT(this, expression, limit)
33160        self.write_keyword("REGEXP_SPLIT");
33161        self.write("(");
33162        self.generate_expression(&e.this)?;
33163        self.write(", ");
33164        self.generate_expression(&e.expression)?;
33165        if let Some(limit) = &e.limit {
33166            self.write(", ");
33167            self.generate_expression(limit)?;
33168        }
33169        self.write(")");
33170        Ok(())
33171    }
33172
33173    fn generate_regr_avgx(&mut self, e: &RegrAvgx) -> Result<()> {
33174        // REGR_AVGX(this, expression)
33175        self.write_keyword("REGR_AVGX");
33176        self.write("(");
33177        self.generate_expression(&e.this)?;
33178        self.write(", ");
33179        self.generate_expression(&e.expression)?;
33180        self.write(")");
33181        Ok(())
33182    }
33183
33184    fn generate_regr_avgy(&mut self, e: &RegrAvgy) -> Result<()> {
33185        // REGR_AVGY(this, expression)
33186        self.write_keyword("REGR_AVGY");
33187        self.write("(");
33188        self.generate_expression(&e.this)?;
33189        self.write(", ");
33190        self.generate_expression(&e.expression)?;
33191        self.write(")");
33192        Ok(())
33193    }
33194
33195    fn generate_regr_count(&mut self, e: &RegrCount) -> Result<()> {
33196        // REGR_COUNT(this, expression)
33197        self.write_keyword("REGR_COUNT");
33198        self.write("(");
33199        self.generate_expression(&e.this)?;
33200        self.write(", ");
33201        self.generate_expression(&e.expression)?;
33202        self.write(")");
33203        Ok(())
33204    }
33205
33206    fn generate_regr_intercept(&mut self, e: &RegrIntercept) -> Result<()> {
33207        // REGR_INTERCEPT(this, expression)
33208        self.write_keyword("REGR_INTERCEPT");
33209        self.write("(");
33210        self.generate_expression(&e.this)?;
33211        self.write(", ");
33212        self.generate_expression(&e.expression)?;
33213        self.write(")");
33214        Ok(())
33215    }
33216
33217    fn generate_regr_r2(&mut self, e: &RegrR2) -> Result<()> {
33218        // REGR_R2(this, expression)
33219        self.write_keyword("REGR_R2");
33220        self.write("(");
33221        self.generate_expression(&e.this)?;
33222        self.write(", ");
33223        self.generate_expression(&e.expression)?;
33224        self.write(")");
33225        Ok(())
33226    }
33227
33228    fn generate_regr_slope(&mut self, e: &RegrSlope) -> Result<()> {
33229        // REGR_SLOPE(this, expression)
33230        self.write_keyword("REGR_SLOPE");
33231        self.write("(");
33232        self.generate_expression(&e.this)?;
33233        self.write(", ");
33234        self.generate_expression(&e.expression)?;
33235        self.write(")");
33236        Ok(())
33237    }
33238
33239    fn generate_regr_sxx(&mut self, e: &RegrSxx) -> Result<()> {
33240        // REGR_SXX(this, expression)
33241        self.write_keyword("REGR_SXX");
33242        self.write("(");
33243        self.generate_expression(&e.this)?;
33244        self.write(", ");
33245        self.generate_expression(&e.expression)?;
33246        self.write(")");
33247        Ok(())
33248    }
33249
33250    fn generate_regr_sxy(&mut self, e: &RegrSxy) -> Result<()> {
33251        // REGR_SXY(this, expression)
33252        self.write_keyword("REGR_SXY");
33253        self.write("(");
33254        self.generate_expression(&e.this)?;
33255        self.write(", ");
33256        self.generate_expression(&e.expression)?;
33257        self.write(")");
33258        Ok(())
33259    }
33260
33261    fn generate_regr_syy(&mut self, e: &RegrSyy) -> Result<()> {
33262        // REGR_SYY(this, expression)
33263        self.write_keyword("REGR_SYY");
33264        self.write("(");
33265        self.generate_expression(&e.this)?;
33266        self.write(", ");
33267        self.generate_expression(&e.expression)?;
33268        self.write(")");
33269        Ok(())
33270    }
33271
33272    fn generate_regr_valx(&mut self, e: &RegrValx) -> Result<()> {
33273        // REGR_VALX(this, expression)
33274        self.write_keyword("REGR_VALX");
33275        self.write("(");
33276        self.generate_expression(&e.this)?;
33277        self.write(", ");
33278        self.generate_expression(&e.expression)?;
33279        self.write(")");
33280        Ok(())
33281    }
33282
33283    fn generate_regr_valy(&mut self, e: &RegrValy) -> Result<()> {
33284        // REGR_VALY(this, expression)
33285        self.write_keyword("REGR_VALY");
33286        self.write("(");
33287        self.generate_expression(&e.this)?;
33288        self.write(", ");
33289        self.generate_expression(&e.expression)?;
33290        self.write(")");
33291        Ok(())
33292    }
33293
33294    fn generate_remote_with_connection_model_property(
33295        &mut self,
33296        e: &RemoteWithConnectionModelProperty,
33297    ) -> Result<()> {
33298        // REMOTE WITH CONNECTION this
33299        self.write_keyword("REMOTE WITH CONNECTION");
33300        self.write_space();
33301        self.generate_expression(&e.this)?;
33302        Ok(())
33303    }
33304
33305    fn generate_rename_column(&mut self, e: &RenameColumn) -> Result<()> {
33306        // RENAME COLUMN [IF EXISTS] this TO new_name
33307        self.write_keyword("RENAME COLUMN");
33308        if e.exists {
33309            self.write_space();
33310            self.write_keyword("IF EXISTS");
33311        }
33312        self.write_space();
33313        self.generate_expression(&e.this)?;
33314        if let Some(to) = &e.to {
33315            self.write_space();
33316            self.write_keyword("TO");
33317            self.write_space();
33318            self.generate_expression(to)?;
33319        }
33320        Ok(())
33321    }
33322
33323    fn generate_replace_partition(&mut self, e: &ReplacePartition) -> Result<()> {
33324        // REPLACE PARTITION expression [FROM source]
33325        self.write_keyword("REPLACE PARTITION");
33326        self.write_space();
33327        self.generate_expression(&e.expression)?;
33328        if let Some(source) = &e.source {
33329            self.write_space();
33330            self.write_keyword("FROM");
33331            self.write_space();
33332            self.generate_expression(source)?;
33333        }
33334        Ok(())
33335    }
33336
33337    fn generate_returning(&mut self, e: &Returning) -> Result<()> {
33338        // RETURNING expressions [INTO into]
33339        // TSQL and Fabric use OUTPUT instead of RETURNING
33340        let keyword = match self.config.dialect {
33341            Some(DialectType::TSQL) | Some(DialectType::Fabric) => "OUTPUT",
33342            _ => "RETURNING",
33343        };
33344        self.write_keyword(keyword);
33345        self.write_space();
33346        for (i, expr) in e.expressions.iter().enumerate() {
33347            if i > 0 {
33348                self.write(", ");
33349            }
33350            self.generate_expression(expr)?;
33351        }
33352        if let Some(into) = &e.into {
33353            self.write_space();
33354            self.write_keyword("INTO");
33355            self.write_space();
33356            self.generate_expression(into)?;
33357        }
33358        Ok(())
33359    }
33360
33361    fn generate_output_clause(&mut self, output: &OutputClause) -> Result<()> {
33362        // OUTPUT expressions [INTO into_table]
33363        self.write_space();
33364        self.write_keyword("OUTPUT");
33365        self.write_space();
33366        for (i, expr) in output.columns.iter().enumerate() {
33367            if i > 0 {
33368                self.write(", ");
33369            }
33370            self.generate_expression(expr)?;
33371        }
33372        if let Some(into_table) = &output.into_table {
33373            self.write_space();
33374            self.write_keyword("INTO");
33375            self.write_space();
33376            self.generate_expression(into_table)?;
33377        }
33378        Ok(())
33379    }
33380
33381    fn generate_returns_property(&mut self, e: &ReturnsProperty) -> Result<()> {
33382        // RETURNS [TABLE] this [NULL ON NULL INPUT | CALLED ON NULL INPUT]
33383        self.write_keyword("RETURNS");
33384        if e.is_table.is_some() {
33385            self.write_space();
33386            self.write_keyword("TABLE");
33387        }
33388        if let Some(table) = &e.table {
33389            self.write_space();
33390            self.generate_expression(table)?;
33391        } else if let Some(this) = &e.this {
33392            self.write_space();
33393            self.generate_expression(this)?;
33394        }
33395        if e.null.is_some() {
33396            self.write_space();
33397            self.write_keyword("NULL ON NULL INPUT");
33398        }
33399        Ok(())
33400    }
33401
33402    fn generate_rollback(&mut self, e: &Rollback) -> Result<()> {
33403        // ROLLBACK [TRANSACTION [transaction_name]] [TO savepoint]
33404        self.write_keyword("ROLLBACK");
33405
33406        // TSQL always uses ROLLBACK TRANSACTION
33407        if e.this.is_none()
33408            && matches!(
33409                self.config.dialect,
33410                Some(DialectType::TSQL) | Some(DialectType::Fabric)
33411            )
33412        {
33413            self.write_space();
33414            self.write_keyword("TRANSACTION");
33415        }
33416
33417        // Check if this has TRANSACTION keyword or transaction name
33418        if let Some(this) = &e.this {
33419            // Check if it's just the "TRANSACTION" marker or an actual transaction name
33420            let is_transaction_marker = matches!(
33421                this.as_ref(),
33422                Expression::Identifier(id) if id.name == "TRANSACTION"
33423            );
33424
33425            self.write_space();
33426            self.write_keyword("TRANSACTION");
33427
33428            // If it's a real transaction name, output it
33429            if !is_transaction_marker {
33430                self.write_space();
33431                self.generate_expression(this)?;
33432            }
33433        }
33434
33435        // Output TO savepoint
33436        if let Some(savepoint) = &e.savepoint {
33437            self.write_space();
33438            self.write_keyword("TO");
33439            self.write_space();
33440            self.generate_expression(savepoint)?;
33441        }
33442        Ok(())
33443    }
33444
33445    fn generate_rollup(&mut self, e: &Rollup) -> Result<()> {
33446        // Python: return f"ROLLUP {self.wrap(expressions)}" if expressions else "WITH ROLLUP"
33447        if e.expressions.is_empty() {
33448            self.write_keyword("WITH ROLLUP");
33449        } else {
33450            self.write_keyword("ROLLUP");
33451            self.write("(");
33452            for (i, expr) in e.expressions.iter().enumerate() {
33453                if i > 0 {
33454                    self.write(", ");
33455                }
33456                self.generate_expression(expr)?;
33457            }
33458            self.write(")");
33459        }
33460        Ok(())
33461    }
33462
33463    fn generate_row_format_delimited_property(
33464        &mut self,
33465        e: &RowFormatDelimitedProperty,
33466    ) -> Result<()> {
33467        // ROW FORMAT DELIMITED [FIELDS TERMINATED BY ...] [ESCAPED BY ...] [COLLECTION ITEMS TERMINATED BY ...] [MAP KEYS TERMINATED BY ...] [LINES TERMINATED BY ...] [NULL DEFINED AS ...]
33468        self.write_keyword("ROW FORMAT DELIMITED");
33469        if let Some(fields) = &e.fields {
33470            self.write_space();
33471            self.write_keyword("FIELDS TERMINATED BY");
33472            self.write_space();
33473            self.generate_expression(fields)?;
33474        }
33475        if let Some(escaped) = &e.escaped {
33476            self.write_space();
33477            self.write_keyword("ESCAPED BY");
33478            self.write_space();
33479            self.generate_expression(escaped)?;
33480        }
33481        if let Some(items) = &e.collection_items {
33482            self.write_space();
33483            self.write_keyword("COLLECTION ITEMS TERMINATED BY");
33484            self.write_space();
33485            self.generate_expression(items)?;
33486        }
33487        if let Some(keys) = &e.map_keys {
33488            self.write_space();
33489            self.write_keyword("MAP KEYS TERMINATED BY");
33490            self.write_space();
33491            self.generate_expression(keys)?;
33492        }
33493        if let Some(lines) = &e.lines {
33494            self.write_space();
33495            self.write_keyword("LINES TERMINATED BY");
33496            self.write_space();
33497            self.generate_expression(lines)?;
33498        }
33499        if let Some(null) = &e.null {
33500            self.write_space();
33501            self.write_keyword("NULL DEFINED AS");
33502            self.write_space();
33503            self.generate_expression(null)?;
33504        }
33505        if let Some(serde) = &e.serde {
33506            self.write_space();
33507            self.generate_expression(serde)?;
33508        }
33509        Ok(())
33510    }
33511
33512    fn generate_row_format_property(&mut self, e: &RowFormatProperty) -> Result<()> {
33513        // ROW FORMAT this
33514        self.write_keyword("ROW FORMAT");
33515        self.write_space();
33516        self.generate_expression(&e.this)?;
33517        Ok(())
33518    }
33519
33520    fn generate_row_format_serde_property(&mut self, e: &RowFormatSerdeProperty) -> Result<()> {
33521        // ROW FORMAT SERDE this [WITH SERDEPROPERTIES (...)]
33522        self.write_keyword("ROW FORMAT SERDE");
33523        self.write_space();
33524        self.generate_expression(&e.this)?;
33525        if let Some(props) = &e.serde_properties {
33526            self.write_space();
33527            // SerdeProperties generates its own "[WITH] SERDEPROPERTIES (...)"
33528            self.generate_expression(props)?;
33529        }
33530        Ok(())
33531    }
33532
33533    fn generate_sha2(&mut self, e: &SHA2) -> Result<()> {
33534        // SHA2(this, length)
33535        self.write_keyword("SHA2");
33536        self.write("(");
33537        self.generate_expression(&e.this)?;
33538        if let Some(length) = e.length {
33539            self.write(", ");
33540            self.write(&length.to_string());
33541        }
33542        self.write(")");
33543        Ok(())
33544    }
33545
33546    fn generate_sha2_digest(&mut self, e: &SHA2Digest) -> Result<()> {
33547        // SHA2_DIGEST(this, length)
33548        self.write_keyword("SHA2_DIGEST");
33549        self.write("(");
33550        self.generate_expression(&e.this)?;
33551        if let Some(length) = e.length {
33552            self.write(", ");
33553            self.write(&length.to_string());
33554        }
33555        self.write(")");
33556        Ok(())
33557    }
33558
33559    fn generate_safe_add(&mut self, e: &SafeAdd) -> Result<()> {
33560        let name = if matches!(
33561            self.config.dialect,
33562            Some(crate::dialects::DialectType::Spark)
33563                | Some(crate::dialects::DialectType::Databricks)
33564        ) {
33565            "TRY_ADD"
33566        } else {
33567            "SAFE_ADD"
33568        };
33569        self.write_keyword(name);
33570        self.write("(");
33571        self.generate_expression(&e.this)?;
33572        self.write(", ");
33573        self.generate_expression(&e.expression)?;
33574        self.write(")");
33575        Ok(())
33576    }
33577
33578    fn generate_safe_divide(&mut self, e: &SafeDivide) -> Result<()> {
33579        // SAFE_DIVIDE(this, expression)
33580        self.write_keyword("SAFE_DIVIDE");
33581        self.write("(");
33582        self.generate_expression(&e.this)?;
33583        self.write(", ");
33584        self.generate_expression(&e.expression)?;
33585        self.write(")");
33586        Ok(())
33587    }
33588
33589    fn generate_safe_multiply(&mut self, e: &SafeMultiply) -> Result<()> {
33590        let name = if matches!(
33591            self.config.dialect,
33592            Some(crate::dialects::DialectType::Spark)
33593                | Some(crate::dialects::DialectType::Databricks)
33594        ) {
33595            "TRY_MULTIPLY"
33596        } else {
33597            "SAFE_MULTIPLY"
33598        };
33599        self.write_keyword(name);
33600        self.write("(");
33601        self.generate_expression(&e.this)?;
33602        self.write(", ");
33603        self.generate_expression(&e.expression)?;
33604        self.write(")");
33605        Ok(())
33606    }
33607
33608    fn generate_safe_subtract(&mut self, e: &SafeSubtract) -> Result<()> {
33609        let name = if matches!(
33610            self.config.dialect,
33611            Some(crate::dialects::DialectType::Spark)
33612                | Some(crate::dialects::DialectType::Databricks)
33613        ) {
33614            "TRY_SUBTRACT"
33615        } else {
33616            "SAFE_SUBTRACT"
33617        };
33618        self.write_keyword(name);
33619        self.write("(");
33620        self.generate_expression(&e.this)?;
33621        self.write(", ");
33622        self.generate_expression(&e.expression)?;
33623        self.write(")");
33624        Ok(())
33625    }
33626
33627    /// Generate the body of a USING SAMPLE or TABLESAMPLE clause:
33628    /// METHOD (size UNIT) [REPEATABLE (seed)]
33629    fn generate_sample_body(&mut self, sample: &Sample) -> Result<()> {
33630        // Handle BUCKET sampling: TABLESAMPLE (BUCKET n OUT OF m [ON col])
33631        if matches!(sample.method, SampleMethod::Bucket) {
33632            self.write(" (");
33633            self.write_keyword("BUCKET");
33634            self.write_space();
33635            if let Some(ref num) = sample.bucket_numerator {
33636                self.generate_expression(num)?;
33637            }
33638            self.write_space();
33639            self.write_keyword("OUT OF");
33640            self.write_space();
33641            if let Some(ref denom) = sample.bucket_denominator {
33642                self.generate_expression(denom)?;
33643            }
33644            if let Some(ref field) = sample.bucket_field {
33645                self.write_space();
33646                self.write_keyword("ON");
33647                self.write_space();
33648                self.generate_expression(field)?;
33649            }
33650            self.write(")");
33651            return Ok(());
33652        }
33653
33654        // Output method name if explicitly specified, or for dialects that always require it
33655        let is_snowflake = matches!(
33656            self.config.dialect,
33657            Some(crate::dialects::DialectType::Snowflake)
33658        );
33659        let is_postgres = matches!(
33660            self.config.dialect,
33661            Some(crate::dialects::DialectType::PostgreSQL)
33662                | Some(crate::dialects::DialectType::Redshift)
33663        );
33664        // Databricks and Spark don't output method names
33665        let is_databricks = matches!(
33666            self.config.dialect,
33667            Some(crate::dialects::DialectType::Databricks)
33668        );
33669        let is_spark = matches!(
33670            self.config.dialect,
33671            Some(crate::dialects::DialectType::Spark)
33672        );
33673        let suppress_method = is_databricks || is_spark || sample.suppress_method_output;
33674        // PostgreSQL always outputs BERNOULLI for BERNOULLI samples
33675        let force_method = is_postgres && matches!(sample.method, SampleMethod::Bernoulli);
33676        if !suppress_method && (sample.explicit_method || is_snowflake || force_method) {
33677            self.write_space();
33678            if !sample.explicit_method && (is_snowflake || force_method) {
33679                // Snowflake/PostgreSQL defaults to BERNOULLI when no method is specified
33680                self.write_keyword("BERNOULLI");
33681            } else {
33682                match sample.method {
33683                    SampleMethod::Bernoulli => self.write_keyword("BERNOULLI"),
33684                    SampleMethod::System => self.write_keyword("SYSTEM"),
33685                    SampleMethod::Block => self.write_keyword("BLOCK"),
33686                    SampleMethod::Row => self.write_keyword("ROW"),
33687                    SampleMethod::Reservoir => self.write_keyword("RESERVOIR"),
33688                    SampleMethod::Percent => self.write_keyword("SYSTEM"),
33689                    SampleMethod::Bucket => {} // handled above
33690                }
33691            }
33692        }
33693
33694        // Output size, with or without parentheses depending on dialect
33695        let emit_size_no_parens = !self.config.tablesample_requires_parens;
33696        if emit_size_no_parens {
33697            self.write_space();
33698            match &sample.size {
33699                Expression::Tuple(tuple) => {
33700                    for (i, expr) in tuple.expressions.iter().enumerate() {
33701                        if i > 0 {
33702                            self.write(", ");
33703                        }
33704                        self.generate_expression(expr)?;
33705                    }
33706                }
33707                expr => self.generate_expression(expr)?,
33708            }
33709        } else {
33710            self.write(" (");
33711            self.generate_expression(&sample.size)?;
33712        }
33713
33714        // Determine unit
33715        let is_rows_method = matches!(
33716            sample.method,
33717            SampleMethod::Reservoir | SampleMethod::Row | SampleMethod::Bucket
33718        );
33719        let is_percent = matches!(
33720            sample.method,
33721            SampleMethod::Percent
33722                | SampleMethod::System
33723                | SampleMethod::Bernoulli
33724                | SampleMethod::Block
33725        );
33726
33727        // For Snowflake, PostgreSQL, and Presto/Trino, only output ROWS/PERCENT when the user explicitly wrote it (unit_after_size).
33728        // These dialects use bare numbers for percentage by default in TABLESAMPLE METHOD(size) syntax.
33729        // For Databricks and Spark, always output PERCENT for percentage samples.
33730        let is_presto = matches!(
33731            self.config.dialect,
33732            Some(crate::dialects::DialectType::Presto)
33733                | Some(crate::dialects::DialectType::Trino)
33734                | Some(crate::dialects::DialectType::Athena)
33735        );
33736        let should_output_unit = if is_databricks || is_spark {
33737            // Always output PERCENT for percentage-based methods, or ROWS for row-based methods
33738            is_percent || is_rows_method || sample.unit_after_size
33739        } else if is_snowflake || is_postgres || is_presto {
33740            sample.unit_after_size
33741        } else {
33742            sample.unit_after_size || (sample.explicit_method && (is_rows_method || is_percent))
33743        };
33744
33745        if should_output_unit {
33746            self.write_space();
33747            if sample.is_percent {
33748                self.write_keyword("PERCENT");
33749            } else if is_rows_method && !sample.unit_after_size {
33750                self.write_keyword("ROWS");
33751            } else if sample.unit_after_size {
33752                match sample.method {
33753                    SampleMethod::Percent
33754                    | SampleMethod::System
33755                    | SampleMethod::Bernoulli
33756                    | SampleMethod::Block => {
33757                        self.write_keyword("PERCENT");
33758                    }
33759                    SampleMethod::Row | SampleMethod::Reservoir => {
33760                        self.write_keyword("ROWS");
33761                    }
33762                    _ => self.write_keyword("ROWS"),
33763                }
33764            } else {
33765                self.write_keyword("PERCENT");
33766            }
33767        }
33768
33769        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
33770            if let Some(ref offset) = sample.offset {
33771                self.write_space();
33772                self.write_keyword("OFFSET");
33773                self.write_space();
33774                self.generate_expression(offset)?;
33775            }
33776        }
33777        if !emit_size_no_parens {
33778            self.write(")");
33779        }
33780
33781        Ok(())
33782    }
33783
33784    fn generate_sample_property(&mut self, e: &SampleProperty) -> Result<()> {
33785        // SAMPLE this (ClickHouse uses SAMPLE BY)
33786        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
33787            self.write_keyword("SAMPLE BY");
33788        } else {
33789            self.write_keyword("SAMPLE");
33790        }
33791        self.write_space();
33792        self.generate_expression(&e.this)?;
33793        Ok(())
33794    }
33795
33796    fn generate_schema(&mut self, e: &Schema) -> Result<()> {
33797        // this (expressions...)
33798        if let Some(this) = &e.this {
33799            self.generate_expression(this)?;
33800        }
33801        if !e.expressions.is_empty() {
33802            // Add space before column list if there's a preceding expression
33803            if e.this.is_some() {
33804                self.write_space();
33805            }
33806            self.write("(");
33807            for (i, expr) in e.expressions.iter().enumerate() {
33808                if i > 0 {
33809                    self.write(", ");
33810                }
33811                self.generate_expression(expr)?;
33812            }
33813            self.write(")");
33814        }
33815        Ok(())
33816    }
33817
33818    fn generate_schema_comment_property(&mut self, e: &SchemaCommentProperty) -> Result<()> {
33819        // COMMENT this
33820        self.write_keyword("COMMENT");
33821        self.write_space();
33822        self.generate_expression(&e.this)?;
33823        Ok(())
33824    }
33825
33826    fn generate_scope_resolution(&mut self, e: &ScopeResolution) -> Result<()> {
33827        // [this::]expression
33828        if let Some(this) = &e.this {
33829            self.generate_expression(this)?;
33830            self.write("::");
33831        }
33832        self.generate_expression(&e.expression)?;
33833        Ok(())
33834    }
33835
33836    fn generate_search(&mut self, e: &Search) -> Result<()> {
33837        // SEARCH(this, expression, [json_scope], [analyzer], [analyzer_options], [search_mode])
33838        self.write_keyword("SEARCH");
33839        self.write("(");
33840        self.generate_expression(&e.this)?;
33841        self.write(", ");
33842        self.generate_expression(&e.expression)?;
33843        if let Some(json_scope) = &e.json_scope {
33844            self.write(", ");
33845            self.generate_expression(json_scope)?;
33846        }
33847        if let Some(analyzer) = &e.analyzer {
33848            self.write(", ");
33849            self.generate_expression(analyzer)?;
33850        }
33851        if let Some(analyzer_options) = &e.analyzer_options {
33852            self.write(", ");
33853            self.generate_expression(analyzer_options)?;
33854        }
33855        if let Some(search_mode) = &e.search_mode {
33856            self.write(", ");
33857            self.generate_expression(search_mode)?;
33858        }
33859        self.write(")");
33860        Ok(())
33861    }
33862
33863    fn generate_search_ip(&mut self, e: &SearchIp) -> Result<()> {
33864        // SEARCH_IP(this, expression)
33865        self.write_keyword("SEARCH_IP");
33866        self.write("(");
33867        self.generate_expression(&e.this)?;
33868        self.write(", ");
33869        self.generate_expression(&e.expression)?;
33870        self.write(")");
33871        Ok(())
33872    }
33873
33874    fn generate_security_property(&mut self, e: &SecurityProperty) -> Result<()> {
33875        // SECURITY this
33876        self.write_keyword("SECURITY");
33877        self.write_space();
33878        self.generate_expression(&e.this)?;
33879        Ok(())
33880    }
33881
33882    fn generate_semantic_view(&mut self, e: &SemanticView) -> Result<()> {
33883        // SEMANTIC_VIEW(this [METRICS ...] [DIMENSIONS ...] [FACTS ...] [WHERE ...])
33884        self.write("SEMANTIC_VIEW(");
33885
33886        if self.config.pretty {
33887            // Pretty print: each clause on its own line
33888            self.write_newline();
33889            self.indent_level += 1;
33890            self.write_indent();
33891            self.generate_expression(&e.this)?;
33892
33893            if let Some(metrics) = &e.metrics {
33894                self.write_newline();
33895                self.write_indent();
33896                self.write_keyword("METRICS");
33897                self.write_space();
33898                self.generate_semantic_view_tuple(metrics)?;
33899            }
33900            if let Some(dimensions) = &e.dimensions {
33901                self.write_newline();
33902                self.write_indent();
33903                self.write_keyword("DIMENSIONS");
33904                self.write_space();
33905                self.generate_semantic_view_tuple(dimensions)?;
33906            }
33907            if let Some(facts) = &e.facts {
33908                self.write_newline();
33909                self.write_indent();
33910                self.write_keyword("FACTS");
33911                self.write_space();
33912                self.generate_semantic_view_tuple(facts)?;
33913            }
33914            if let Some(where_) = &e.where_ {
33915                self.write_newline();
33916                self.write_indent();
33917                self.write_keyword("WHERE");
33918                self.write_space();
33919                self.generate_expression(where_)?;
33920            }
33921            self.write_newline();
33922            self.indent_level -= 1;
33923            self.write_indent();
33924        } else {
33925            // Compact: all on one line
33926            self.generate_expression(&e.this)?;
33927            if let Some(metrics) = &e.metrics {
33928                self.write_space();
33929                self.write_keyword("METRICS");
33930                self.write_space();
33931                self.generate_semantic_view_tuple(metrics)?;
33932            }
33933            if let Some(dimensions) = &e.dimensions {
33934                self.write_space();
33935                self.write_keyword("DIMENSIONS");
33936                self.write_space();
33937                self.generate_semantic_view_tuple(dimensions)?;
33938            }
33939            if let Some(facts) = &e.facts {
33940                self.write_space();
33941                self.write_keyword("FACTS");
33942                self.write_space();
33943                self.generate_semantic_view_tuple(facts)?;
33944            }
33945            if let Some(where_) = &e.where_ {
33946                self.write_space();
33947                self.write_keyword("WHERE");
33948                self.write_space();
33949                self.generate_expression(where_)?;
33950            }
33951        }
33952        self.write(")");
33953        Ok(())
33954    }
33955
33956    /// Helper for SEMANTIC_VIEW tuple contents (without parentheses)
33957    fn generate_semantic_view_tuple(&mut self, expr: &Expression) -> Result<()> {
33958        if let Expression::Tuple(t) = expr {
33959            for (i, e) in t.expressions.iter().enumerate() {
33960                if i > 0 {
33961                    self.write(", ");
33962                }
33963                self.generate_expression(e)?;
33964            }
33965        } else {
33966            self.generate_expression(expr)?;
33967        }
33968        Ok(())
33969    }
33970
33971    fn generate_sequence_properties(&mut self, e: &SequenceProperties) -> Result<()> {
33972        // [START WITH start] [INCREMENT BY increment] [MINVALUE minvalue] [MAXVALUE maxvalue] [CACHE cache] [OWNED BY owned]
33973        if let Some(start) = &e.start {
33974            self.write_keyword("START WITH");
33975            self.write_space();
33976            self.generate_expression(start)?;
33977        }
33978        if let Some(increment) = &e.increment {
33979            self.write_space();
33980            self.write_keyword("INCREMENT BY");
33981            self.write_space();
33982            self.generate_expression(increment)?;
33983        }
33984        if let Some(minvalue) = &e.minvalue {
33985            self.write_space();
33986            self.write_keyword("MINVALUE");
33987            self.write_space();
33988            self.generate_expression(minvalue)?;
33989        }
33990        if let Some(maxvalue) = &e.maxvalue {
33991            self.write_space();
33992            self.write_keyword("MAXVALUE");
33993            self.write_space();
33994            self.generate_expression(maxvalue)?;
33995        }
33996        if let Some(cache) = &e.cache {
33997            self.write_space();
33998            self.write_keyword("CACHE");
33999            self.write_space();
34000            self.generate_expression(cache)?;
34001        }
34002        if let Some(owned) = &e.owned {
34003            self.write_space();
34004            self.write_keyword("OWNED BY");
34005            self.write_space();
34006            self.generate_expression(owned)?;
34007        }
34008        for opt in &e.options {
34009            self.write_space();
34010            self.generate_expression(opt)?;
34011        }
34012        Ok(())
34013    }
34014
34015    fn generate_serde_properties(&mut self, e: &SerdeProperties) -> Result<()> {
34016        // [WITH] SERDEPROPERTIES (expressions)
34017        if e.with_.is_some() {
34018            self.write_keyword("WITH");
34019            self.write_space();
34020        }
34021        self.write_keyword("SERDEPROPERTIES");
34022        self.write(" (");
34023        for (i, expr) in e.expressions.iter().enumerate() {
34024            if i > 0 {
34025                self.write(", ");
34026            }
34027            // Generate key=value without spaces around =
34028            match expr {
34029                Expression::Eq(eq) => {
34030                    self.generate_expression(&eq.left)?;
34031                    self.write("=");
34032                    self.generate_expression(&eq.right)?;
34033                }
34034                _ => self.generate_expression(expr)?,
34035            }
34036        }
34037        self.write(")");
34038        Ok(())
34039    }
34040
34041    fn generate_session_parameter(&mut self, e: &SessionParameter) -> Result<()> {
34042        // @@[kind.]this
34043        self.write("@@");
34044        if let Some(kind) = &e.kind {
34045            self.write(kind);
34046            self.write(".");
34047        }
34048        self.generate_expression(&e.this)?;
34049        Ok(())
34050    }
34051
34052    fn generate_set(&mut self, e: &Set) -> Result<()> {
34053        // SET/UNSET [TAG] expressions
34054        if e.unset.is_some() {
34055            self.write_keyword("UNSET");
34056        } else {
34057            self.write_keyword("SET");
34058        }
34059        if e.tag.is_some() {
34060            self.write_space();
34061            self.write_keyword("TAG");
34062        }
34063        if !e.expressions.is_empty() {
34064            self.write_space();
34065            for (i, expr) in e.expressions.iter().enumerate() {
34066                if i > 0 {
34067                    self.write(", ");
34068                }
34069                self.generate_expression(expr)?;
34070            }
34071        }
34072        Ok(())
34073    }
34074
34075    fn generate_set_config_property(&mut self, e: &SetConfigProperty) -> Result<()> {
34076        // SET this or SETCONFIG this
34077        self.write_keyword("SET");
34078        self.write_space();
34079        self.generate_expression(&e.this)?;
34080        Ok(())
34081    }
34082
34083    fn generate_set_item(&mut self, e: &SetItem) -> Result<()> {
34084        // [kind] name = value
34085        if let Some(kind) = &e.kind {
34086            self.write_keyword(kind);
34087            self.write_space();
34088        }
34089        self.generate_expression(&e.name)?;
34090        self.write(" = ");
34091        self.generate_expression(&e.value)?;
34092        Ok(())
34093    }
34094
34095    fn generate_set_operation(&mut self, e: &SetOperation) -> Result<()> {
34096        // [WITH ...] this UNION|INTERSECT|EXCEPT [ALL|DISTINCT] [BY NAME] expression
34097        if let Some(with_) = &e.with_ {
34098            self.generate_expression(with_)?;
34099            self.write_space();
34100        }
34101        self.generate_expression(&e.this)?;
34102        self.write_space();
34103        // kind should be UNION, INTERSECT, EXCEPT, etc.
34104        if let Some(kind) = &e.kind {
34105            self.write_keyword(kind);
34106        }
34107        if e.distinct {
34108            self.write_space();
34109            self.write_keyword("DISTINCT");
34110        } else {
34111            self.write_space();
34112            self.write_keyword("ALL");
34113        }
34114        if e.by_name.is_some() {
34115            self.write_space();
34116            self.write_keyword("BY NAME");
34117        }
34118        self.write_space();
34119        self.generate_expression(&e.expression)?;
34120        Ok(())
34121    }
34122
34123    fn generate_set_property(&mut self, e: &SetProperty) -> Result<()> {
34124        // SET or MULTISET
34125        if e.multi.is_some() {
34126            self.write_keyword("MULTISET");
34127        } else {
34128            self.write_keyword("SET");
34129        }
34130        Ok(())
34131    }
34132
34133    fn generate_settings_property(&mut self, e: &SettingsProperty) -> Result<()> {
34134        // SETTINGS expressions
34135        self.write_keyword("SETTINGS");
34136        if self.config.pretty && e.expressions.len() > 1 {
34137            // Pretty print: each setting on its own line, indented
34138            self.indent_level += 1;
34139            for (i, expr) in e.expressions.iter().enumerate() {
34140                if i > 0 {
34141                    self.write(",");
34142                }
34143                self.write_newline();
34144                self.write_indent();
34145                self.generate_expression(expr)?;
34146            }
34147            self.indent_level -= 1;
34148        } else {
34149            self.write_space();
34150            for (i, expr) in e.expressions.iter().enumerate() {
34151                if i > 0 {
34152                    self.write(", ");
34153                }
34154                self.generate_expression(expr)?;
34155            }
34156        }
34157        Ok(())
34158    }
34159
34160    fn generate_sharing_property(&mut self, e: &SharingProperty) -> Result<()> {
34161        // SHARING = this
34162        self.write_keyword("SHARING");
34163        if let Some(this) = &e.this {
34164            self.write(" = ");
34165            self.generate_expression(this)?;
34166        }
34167        Ok(())
34168    }
34169
34170    fn generate_slice(&mut self, e: &Slice) -> Result<()> {
34171        // Python array slicing: begin:end:step
34172        if let Some(begin) = &e.this {
34173            self.generate_expression(begin)?;
34174        }
34175        self.write(":");
34176        if let Some(end) = &e.expression {
34177            self.generate_expression(end)?;
34178        }
34179        if let Some(step) = &e.step {
34180            self.write(":");
34181            self.generate_expression(step)?;
34182        }
34183        Ok(())
34184    }
34185
34186    fn generate_sort_array(&mut self, e: &SortArray) -> Result<()> {
34187        // SORT_ARRAY(this, asc)
34188        self.write_keyword("SORT_ARRAY");
34189        self.write("(");
34190        self.generate_expression(&e.this)?;
34191        if let Some(asc) = &e.asc {
34192            self.write(", ");
34193            self.generate_expression(asc)?;
34194        }
34195        self.write(")");
34196        Ok(())
34197    }
34198
34199    fn generate_sort_by(&mut self, e: &SortBy) -> Result<()> {
34200        // SORT BY expressions
34201        self.write_keyword("SORT BY");
34202        self.write_space();
34203        for (i, expr) in e.expressions.iter().enumerate() {
34204            if i > 0 {
34205                self.write(", ");
34206            }
34207            self.generate_ordered(expr)?;
34208        }
34209        Ok(())
34210    }
34211
34212    fn generate_sort_key_property(&mut self, e: &SortKeyProperty) -> Result<()> {
34213        // [COMPOUND] SORTKEY(col1, col2, ...) - no space before paren
34214        if e.compound.is_some() {
34215            self.write_keyword("COMPOUND");
34216            self.write_space();
34217        }
34218        self.write_keyword("SORTKEY");
34219        self.write("(");
34220        // If this is a Tuple, unwrap its contents to avoid double parentheses
34221        if let Expression::Tuple(t) = e.this.as_ref() {
34222            for (i, expr) in t.expressions.iter().enumerate() {
34223                if i > 0 {
34224                    self.write(", ");
34225                }
34226                self.generate_expression(expr)?;
34227            }
34228        } else {
34229            self.generate_expression(&e.this)?;
34230        }
34231        self.write(")");
34232        Ok(())
34233    }
34234
34235    fn generate_split_part(&mut self, e: &SplitPart) -> Result<()> {
34236        // SPLIT_PART(this, delimiter, part_index)
34237        self.write_keyword("SPLIT_PART");
34238        self.write("(");
34239        self.generate_expression(&e.this)?;
34240        if let Some(delimiter) = &e.delimiter {
34241            self.write(", ");
34242            self.generate_expression(delimiter)?;
34243        }
34244        if let Some(part_index) = &e.part_index {
34245            self.write(", ");
34246            self.generate_expression(part_index)?;
34247        }
34248        self.write(")");
34249        Ok(())
34250    }
34251
34252    fn generate_sql_read_write_property(&mut self, e: &SqlReadWriteProperty) -> Result<()> {
34253        // READS SQL DATA or MODIFIES SQL DATA, etc.
34254        self.generate_expression(&e.this)?;
34255        Ok(())
34256    }
34257
34258    fn generate_sql_security_property(&mut self, e: &SqlSecurityProperty) -> Result<()> {
34259        // SQL SECURITY DEFINER or SQL SECURITY INVOKER
34260        self.write_keyword("SQL SECURITY");
34261        self.write_space();
34262        self.generate_expression(&e.this)?;
34263        Ok(())
34264    }
34265
34266    fn generate_st_distance(&mut self, e: &StDistance) -> Result<()> {
34267        // ST_DISTANCE(this, expression, [use_spheroid])
34268        self.write_keyword("ST_DISTANCE");
34269        self.write("(");
34270        self.generate_expression(&e.this)?;
34271        self.write(", ");
34272        self.generate_expression(&e.expression)?;
34273        if let Some(use_spheroid) = &e.use_spheroid {
34274            self.write(", ");
34275            self.generate_expression(use_spheroid)?;
34276        }
34277        self.write(")");
34278        Ok(())
34279    }
34280
34281    fn generate_st_point(&mut self, e: &StPoint) -> Result<()> {
34282        // ST_POINT(this, expression)
34283        self.write_keyword("ST_POINT");
34284        self.write("(");
34285        self.generate_expression(&e.this)?;
34286        self.write(", ");
34287        self.generate_expression(&e.expression)?;
34288        self.write(")");
34289        Ok(())
34290    }
34291
34292    fn generate_stability_property(&mut self, e: &StabilityProperty) -> Result<()> {
34293        // IMMUTABLE, STABLE, VOLATILE
34294        self.generate_expression(&e.this)?;
34295        Ok(())
34296    }
34297
34298    fn generate_standard_hash(&mut self, e: &StandardHash) -> Result<()> {
34299        // STANDARD_HASH(this, [expression])
34300        self.write_keyword("STANDARD_HASH");
34301        self.write("(");
34302        self.generate_expression(&e.this)?;
34303        if let Some(expression) = &e.expression {
34304            self.write(", ");
34305            self.generate_expression(expression)?;
34306        }
34307        self.write(")");
34308        Ok(())
34309    }
34310
34311    fn generate_storage_handler_property(&mut self, e: &StorageHandlerProperty) -> Result<()> {
34312        // STORED BY this
34313        self.write_keyword("STORED BY");
34314        self.write_space();
34315        self.generate_expression(&e.this)?;
34316        Ok(())
34317    }
34318
34319    fn generate_str_position(&mut self, e: &StrPosition) -> Result<()> {
34320        // STRPOS(this, substr) or STRPOS(this, substr, position)
34321        // Different dialects have different function names
34322        use crate::dialects::DialectType;
34323        if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
34324            // Snowflake: CHARINDEX(substr, str[, position])
34325            self.write_keyword("CHARINDEX");
34326            self.write("(");
34327            if let Some(substr) = &e.substr {
34328                self.generate_expression(substr)?;
34329                self.write(", ");
34330            }
34331            self.generate_expression(&e.this)?;
34332            if let Some(position) = &e.position {
34333                self.write(", ");
34334                self.generate_expression(position)?;
34335            }
34336            self.write(")");
34337        } else if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
34338            self.write_keyword("POSITION");
34339            self.write("(");
34340            self.generate_expression(&e.this)?;
34341            if let Some(substr) = &e.substr {
34342                self.write(", ");
34343                self.generate_expression(substr)?;
34344            }
34345            if let Some(position) = &e.position {
34346                self.write(", ");
34347                self.generate_expression(position)?;
34348            }
34349            if let Some(occurrence) = &e.occurrence {
34350                self.write(", ");
34351                self.generate_expression(occurrence)?;
34352            }
34353            self.write(")");
34354        } else if matches!(
34355            self.config.dialect,
34356            Some(DialectType::SQLite)
34357                | Some(DialectType::Oracle)
34358                | Some(DialectType::BigQuery)
34359                | Some(DialectType::Teradata)
34360        ) {
34361            self.write_keyword("INSTR");
34362            self.write("(");
34363            self.generate_expression(&e.this)?;
34364            if let Some(substr) = &e.substr {
34365                self.write(", ");
34366                self.generate_expression(substr)?;
34367            }
34368            if let Some(position) = &e.position {
34369                self.write(", ");
34370                self.generate_expression(position)?;
34371            } else if e.occurrence.is_some() {
34372                // INSTR requires a position arg before occurrence: INSTR(str, substr, start, nth)
34373                // Default start position is 1
34374                self.write(", 1");
34375            }
34376            if let Some(occurrence) = &e.occurrence {
34377                self.write(", ");
34378                self.generate_expression(occurrence)?;
34379            }
34380            self.write(")");
34381        } else if matches!(
34382            self.config.dialect,
34383            Some(DialectType::MySQL)
34384                | Some(DialectType::SingleStore)
34385                | Some(DialectType::Doris)
34386                | Some(DialectType::StarRocks)
34387                | Some(DialectType::Hive)
34388                | Some(DialectType::Spark)
34389                | Some(DialectType::Databricks)
34390        ) {
34391            // LOCATE(substr, str[, position]) - substr first
34392            self.write_keyword("LOCATE");
34393            self.write("(");
34394            if let Some(substr) = &e.substr {
34395                self.generate_expression(substr)?;
34396                self.write(", ");
34397            }
34398            self.generate_expression(&e.this)?;
34399            if let Some(position) = &e.position {
34400                self.write(", ");
34401                self.generate_expression(position)?;
34402            }
34403            self.write(")");
34404        } else if matches!(self.config.dialect, Some(DialectType::TSQL)) {
34405            // CHARINDEX(substr, str[, position])
34406            self.write_keyword("CHARINDEX");
34407            self.write("(");
34408            if let Some(substr) = &e.substr {
34409                self.generate_expression(substr)?;
34410                self.write(", ");
34411            }
34412            self.generate_expression(&e.this)?;
34413            if let Some(position) = &e.position {
34414                self.write(", ");
34415                self.generate_expression(position)?;
34416            }
34417            self.write(")");
34418        } else if matches!(
34419            self.config.dialect,
34420            Some(DialectType::PostgreSQL)
34421                | Some(DialectType::Materialize)
34422                | Some(DialectType::RisingWave)
34423                | Some(DialectType::Redshift)
34424        ) {
34425            // POSITION(substr IN str) syntax
34426            self.write_keyword("POSITION");
34427            self.write("(");
34428            if let Some(substr) = &e.substr {
34429                self.generate_expression(substr)?;
34430                self.write(" IN ");
34431            }
34432            self.generate_expression(&e.this)?;
34433            self.write(")");
34434        } else {
34435            self.write_keyword("STRPOS");
34436            self.write("(");
34437            self.generate_expression(&e.this)?;
34438            if let Some(substr) = &e.substr {
34439                self.write(", ");
34440                self.generate_expression(substr)?;
34441            }
34442            if let Some(position) = &e.position {
34443                self.write(", ");
34444                self.generate_expression(position)?;
34445            }
34446            if let Some(occurrence) = &e.occurrence {
34447                self.write(", ");
34448                self.generate_expression(occurrence)?;
34449            }
34450            self.write(")");
34451        }
34452        Ok(())
34453    }
34454
34455    fn generate_str_to_date(&mut self, e: &StrToDate) -> Result<()> {
34456        match self.config.dialect {
34457            Some(DialectType::Spark) | Some(DialectType::Databricks) | Some(DialectType::Hive) => {
34458                // TO_DATE(this, java_format)
34459                self.write_keyword("TO_DATE");
34460                self.write("(");
34461                self.generate_expression(&e.this)?;
34462                if let Some(format) = &e.format {
34463                    self.write(", '");
34464                    self.write(&Self::strftime_to_java_format(format));
34465                    self.write("'");
34466                }
34467                self.write(")");
34468            }
34469            Some(DialectType::DuckDB) => {
34470                // CAST(STRPTIME(this, format) AS DATE)
34471                self.write_keyword("CAST");
34472                self.write("(");
34473                self.write_keyword("STRPTIME");
34474                self.write("(");
34475                self.generate_expression(&e.this)?;
34476                if let Some(format) = &e.format {
34477                    self.write(", '");
34478                    self.write(format);
34479                    self.write("'");
34480                }
34481                self.write(")");
34482                self.write_keyword(" AS ");
34483                self.write_keyword("DATE");
34484                self.write(")");
34485            }
34486            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift) => {
34487                // TO_DATE(this, pg_format)
34488                self.write_keyword("TO_DATE");
34489                self.write("(");
34490                self.generate_expression(&e.this)?;
34491                if let Some(format) = &e.format {
34492                    self.write(", '");
34493                    self.write(&Self::strftime_to_postgres_format(format));
34494                    self.write("'");
34495                }
34496                self.write(")");
34497            }
34498            Some(DialectType::BigQuery) => {
34499                // PARSE_DATE(format, this) - note: format comes first for BigQuery
34500                self.write_keyword("PARSE_DATE");
34501                self.write("(");
34502                if let Some(format) = &e.format {
34503                    self.write("'");
34504                    self.write(format);
34505                    self.write("'");
34506                    self.write(", ");
34507                }
34508                self.generate_expression(&e.this)?;
34509                self.write(")");
34510            }
34511            Some(DialectType::Teradata) => {
34512                // CAST(this AS DATE FORMAT 'teradata_fmt')
34513                self.write_keyword("CAST");
34514                self.write("(");
34515                self.generate_expression(&e.this)?;
34516                self.write_keyword(" AS ");
34517                self.write_keyword("DATE");
34518                if let Some(format) = &e.format {
34519                    self.write_keyword(" FORMAT ");
34520                    self.write("'");
34521                    self.write(&Self::strftime_to_teradata_format(format));
34522                    self.write("'");
34523                }
34524                self.write(")");
34525            }
34526            _ => {
34527                // STR_TO_DATE(this, format) - MySQL default
34528                self.write_keyword("STR_TO_DATE");
34529                self.write("(");
34530                self.generate_expression(&e.this)?;
34531                if let Some(format) = &e.format {
34532                    self.write(", '");
34533                    self.write(format);
34534                    self.write("'");
34535                }
34536                self.write(")");
34537            }
34538        }
34539        Ok(())
34540    }
34541
34542    /// Convert strftime format to Teradata date format (YYYY, DD, MM, etc.)
34543    fn strftime_to_teradata_format(fmt: &str) -> String {
34544        let mut result = String::with_capacity(fmt.len() * 2);
34545        let bytes = fmt.as_bytes();
34546        let len = bytes.len();
34547        let mut i = 0;
34548        while i < len {
34549            if bytes[i] == b'%' && i + 1 < len {
34550                let replacement = match bytes[i + 1] {
34551                    b'Y' => "YYYY",
34552                    b'y' => "YY",
34553                    b'm' => "MM",
34554                    b'B' => "MMMM",
34555                    b'b' => "MMM",
34556                    b'd' => "DD",
34557                    b'j' => "DDD",
34558                    b'H' => "HH",
34559                    b'M' => "MI",
34560                    b'S' => "SS",
34561                    b'f' => "SSSSSS",
34562                    b'A' => "EEEE",
34563                    b'a' => "EEE",
34564                    _ => {
34565                        result.push('%');
34566                        i += 1;
34567                        continue;
34568                    }
34569                };
34570                result.push_str(replacement);
34571                i += 2;
34572            } else {
34573                result.push(bytes[i] as char);
34574                i += 1;
34575            }
34576        }
34577        result
34578    }
34579
34580    /// Convert strftime format (%Y, %m, %d, etc.) to Java date format (yyyy, MM, dd, etc.)
34581    /// Public static version for use by other modules
34582    pub fn strftime_to_java_format_static(fmt: &str) -> String {
34583        Self::strftime_to_java_format(fmt)
34584    }
34585
34586    /// Convert strftime format (%Y, %m, %d, etc.) to Java date format (yyyy, MM, dd, etc.)
34587    fn strftime_to_java_format(fmt: &str) -> String {
34588        let mut result = String::with_capacity(fmt.len() * 2);
34589        let bytes = fmt.as_bytes();
34590        let len = bytes.len();
34591        let mut i = 0;
34592        while i < len {
34593            if bytes[i] == b'%' && i + 1 < len {
34594                // Check for non-padded variants (%-X)
34595                if bytes[i + 1] == b'-' && i + 2 < len {
34596                    let replacement = match bytes[i + 2] {
34597                        b'd' => "d",
34598                        b'm' => "M",
34599                        b'H' => "H",
34600                        b'M' => "m",
34601                        b'S' => "s",
34602                        _ => {
34603                            result.push('%');
34604                            i += 1;
34605                            continue;
34606                        }
34607                    };
34608                    result.push_str(replacement);
34609                    i += 3;
34610                } else {
34611                    let replacement = match bytes[i + 1] {
34612                        b'Y' => "yyyy",
34613                        b'y' => "yy",
34614                        b'm' => "MM",
34615                        b'B' => "MMMM",
34616                        b'b' => "MMM",
34617                        b'd' => "dd",
34618                        b'j' => "DDD",
34619                        b'H' => "HH",
34620                        b'M' => "mm",
34621                        b'S' => "ss",
34622                        b'f' => "SSSSSS",
34623                        b'A' => "EEEE",
34624                        b'a' => "EEE",
34625                        _ => {
34626                            result.push('%');
34627                            i += 1;
34628                            continue;
34629                        }
34630                    };
34631                    result.push_str(replacement);
34632                    i += 2;
34633                }
34634            } else {
34635                result.push(bytes[i] as char);
34636                i += 1;
34637            }
34638        }
34639        result
34640    }
34641
34642    /// Convert strftime format (%Y, %m, %d, etc.) to .NET date format for TSQL FORMAT()
34643    /// Similar to Java but uses ffffff for microseconds instead of SSSSSS
34644    fn strftime_to_tsql_format(fmt: &str) -> String {
34645        let mut result = String::with_capacity(fmt.len() * 2);
34646        let bytes = fmt.as_bytes();
34647        let len = bytes.len();
34648        let mut i = 0;
34649        while i < len {
34650            if bytes[i] == b'%' && i + 1 < len {
34651                // Check for non-padded variants (%-X)
34652                if bytes[i + 1] == b'-' && i + 2 < len {
34653                    let replacement = match bytes[i + 2] {
34654                        b'd' => "d",
34655                        b'm' => "M",
34656                        b'H' => "H",
34657                        b'M' => "m",
34658                        b'S' => "s",
34659                        _ => {
34660                            result.push('%');
34661                            i += 1;
34662                            continue;
34663                        }
34664                    };
34665                    result.push_str(replacement);
34666                    i += 3;
34667                } else {
34668                    let replacement = match bytes[i + 1] {
34669                        b'Y' => "yyyy",
34670                        b'y' => "yy",
34671                        b'm' => "MM",
34672                        b'B' => "MMMM",
34673                        b'b' => "MMM",
34674                        b'd' => "dd",
34675                        b'j' => "DDD",
34676                        b'H' => "HH",
34677                        b'M' => "mm",
34678                        b'S' => "ss",
34679                        b'f' => "ffffff",
34680                        b'A' => "dddd",
34681                        b'a' => "ddd",
34682                        _ => {
34683                            result.push('%');
34684                            i += 1;
34685                            continue;
34686                        }
34687                    };
34688                    result.push_str(replacement);
34689                    i += 2;
34690                }
34691            } else {
34692                result.push(bytes[i] as char);
34693                i += 1;
34694            }
34695        }
34696        result
34697    }
34698
34699    /// Decompose a JSON path string like "$.y[0].z" into individual parts: ["y", "0", "z"]
34700    /// This is used for PostgreSQL/Redshift JSON_EXTRACT_PATH / JSON_EXTRACT_PATH_TEXT
34701    fn decompose_json_path(path: &str) -> Vec<String> {
34702        let mut parts = Vec::new();
34703        // Strip leading $ and optional .
34704        let path = if path.starts_with("$.") {
34705            &path[2..]
34706        } else if path.starts_with('$') {
34707            &path[1..]
34708        } else {
34709            path
34710        };
34711        if path.is_empty() {
34712            return parts;
34713        }
34714        let mut current = String::new();
34715        let chars: Vec<char> = path.chars().collect();
34716        let mut i = 0;
34717        while i < chars.len() {
34718            match chars[i] {
34719                '.' => {
34720                    if !current.is_empty() {
34721                        parts.push(current.clone());
34722                        current.clear();
34723                    }
34724                    i += 1;
34725                }
34726                '[' => {
34727                    if !current.is_empty() {
34728                        parts.push(current.clone());
34729                        current.clear();
34730                    }
34731                    i += 1;
34732                    // Read the content inside brackets
34733                    let mut bracket_content = String::new();
34734                    while i < chars.len() && chars[i] != ']' {
34735                        // Skip quotes inside brackets
34736                        if chars[i] == '"' || chars[i] == '\'' {
34737                            let quote = chars[i];
34738                            i += 1;
34739                            while i < chars.len() && chars[i] != quote {
34740                                bracket_content.push(chars[i]);
34741                                i += 1;
34742                            }
34743                            if i < chars.len() {
34744                                i += 1;
34745                            } // skip closing quote
34746                        } else {
34747                            bracket_content.push(chars[i]);
34748                            i += 1;
34749                        }
34750                    }
34751                    if i < chars.len() {
34752                        i += 1;
34753                    } // skip ]
34754                      // Skip wildcard [*] - don't add as a part
34755                    if bracket_content != "*" {
34756                        parts.push(bracket_content);
34757                    }
34758                }
34759                _ => {
34760                    current.push(chars[i]);
34761                    i += 1;
34762                }
34763            }
34764        }
34765        if !current.is_empty() {
34766            parts.push(current);
34767        }
34768        parts
34769    }
34770
34771    /// Convert strftime format to PostgreSQL date format (YYYY, MM, DD, etc.)
34772    fn strftime_to_postgres_format(fmt: &str) -> String {
34773        let mut result = String::with_capacity(fmt.len() * 2);
34774        let bytes = fmt.as_bytes();
34775        let len = bytes.len();
34776        let mut i = 0;
34777        while i < len {
34778            if bytes[i] == b'%' && i + 1 < len {
34779                // Check for non-padded variants (%-X)
34780                if bytes[i + 1] == b'-' && i + 2 < len {
34781                    let replacement = match bytes[i + 2] {
34782                        b'd' => "FMDD",
34783                        b'm' => "FMMM",
34784                        b'H' => "FMHH24",
34785                        b'M' => "FMMI",
34786                        b'S' => "FMSS",
34787                        _ => {
34788                            result.push('%');
34789                            i += 1;
34790                            continue;
34791                        }
34792                    };
34793                    result.push_str(replacement);
34794                    i += 3;
34795                } else {
34796                    let replacement = match bytes[i + 1] {
34797                        b'Y' => "YYYY",
34798                        b'y' => "YY",
34799                        b'm' => "MM",
34800                        b'B' => "Month",
34801                        b'b' => "Mon",
34802                        b'd' => "DD",
34803                        b'j' => "DDD",
34804                        b'H' => "HH24",
34805                        b'M' => "MI",
34806                        b'S' => "SS",
34807                        b'f' => "US",
34808                        b'A' => "Day",
34809                        b'a' => "Dy",
34810                        _ => {
34811                            result.push('%');
34812                            i += 1;
34813                            continue;
34814                        }
34815                    };
34816                    result.push_str(replacement);
34817                    i += 2;
34818                }
34819            } else {
34820                result.push(bytes[i] as char);
34821                i += 1;
34822            }
34823        }
34824        result
34825    }
34826
34827    /// Convert strftime format to Snowflake date format (yyyy, mm, DD, etc.)
34828    fn strftime_to_snowflake_format(fmt: &str) -> String {
34829        let mut result = String::with_capacity(fmt.len() * 2);
34830        let bytes = fmt.as_bytes();
34831        let len = bytes.len();
34832        let mut i = 0;
34833        while i < len {
34834            if bytes[i] == b'%' && i + 1 < len {
34835                // Check for non-padded variants (%-X)
34836                if bytes[i + 1] == b'-' && i + 2 < len {
34837                    let replacement = match bytes[i + 2] {
34838                        b'd' => "dd",
34839                        b'm' => "mm",
34840                        _ => {
34841                            result.push('%');
34842                            i += 1;
34843                            continue;
34844                        }
34845                    };
34846                    result.push_str(replacement);
34847                    i += 3;
34848                } else {
34849                    let replacement = match bytes[i + 1] {
34850                        b'Y' => "yyyy",
34851                        b'y' => "yy",
34852                        b'm' => "mm",
34853                        b'd' => "DD",
34854                        b'H' => "hh24",
34855                        b'M' => "mi",
34856                        b'S' => "ss",
34857                        b'f' => "ff",
34858                        _ => {
34859                            result.push('%');
34860                            i += 1;
34861                            continue;
34862                        }
34863                    };
34864                    result.push_str(replacement);
34865                    i += 2;
34866                }
34867            } else {
34868                result.push(bytes[i] as char);
34869                i += 1;
34870            }
34871        }
34872        result
34873    }
34874
34875    fn generate_str_to_map(&mut self, e: &StrToMap) -> Result<()> {
34876        // STR_TO_MAP(this, pair_delim, key_value_delim)
34877        self.write_keyword("STR_TO_MAP");
34878        self.write("(");
34879        self.generate_expression(&e.this)?;
34880        // Spark/Hive: STR_TO_MAP needs explicit default delimiters
34881        let needs_defaults = matches!(
34882            self.config.dialect,
34883            Some(DialectType::Spark) | Some(DialectType::Hive) | Some(DialectType::Databricks)
34884        );
34885        if let Some(pair_delim) = &e.pair_delim {
34886            self.write(", ");
34887            self.generate_expression(pair_delim)?;
34888        } else if needs_defaults {
34889            self.write(", ','");
34890        }
34891        if let Some(key_value_delim) = &e.key_value_delim {
34892            self.write(", ");
34893            self.generate_expression(key_value_delim)?;
34894        } else if needs_defaults {
34895            self.write(", ':'");
34896        }
34897        self.write(")");
34898        Ok(())
34899    }
34900
34901    fn generate_str_to_time(&mut self, e: &StrToTime) -> Result<()> {
34902        // Detect format style: strftime (starts with %) vs Snowflake/Java
34903        let is_strftime = e.format.contains('%');
34904        // Helper: get strftime format from whatever style is stored
34905        let to_strftime = |f: &str| -> String {
34906            if is_strftime {
34907                f.to_string()
34908            } else {
34909                Self::snowflake_format_to_strftime(f)
34910            }
34911        };
34912        // Helper: get Java format
34913        let to_java = |f: &str| -> String {
34914            if is_strftime {
34915                Self::strftime_to_java_format(f)
34916            } else {
34917                Self::snowflake_format_to_spark(f)
34918            }
34919        };
34920        // Helper: get PG format
34921        let to_pg = |f: &str| -> String {
34922            if is_strftime {
34923                Self::strftime_to_postgres_format(f)
34924            } else {
34925                Self::convert_strptime_to_postgres_format(f)
34926            }
34927        };
34928
34929        match self.config.dialect {
34930            Some(DialectType::Exasol) => {
34931                self.write_keyword("TO_DATE");
34932                self.write("(");
34933                self.generate_expression(&e.this)?;
34934                self.write(", '");
34935                self.write(&Self::convert_strptime_to_exasol_format(&e.format));
34936                self.write("'");
34937                self.write(")");
34938            }
34939            Some(DialectType::BigQuery) => {
34940                // BigQuery: PARSE_TIMESTAMP(format, value) - note swapped args
34941                let fmt = to_strftime(&e.format);
34942                // BigQuery normalizes: %Y-%m-%d -> %F, %H:%M:%S -> %T
34943                let fmt = fmt.replace("%Y-%m-%d", "%F").replace("%H:%M:%S", "%T");
34944                self.write_keyword("PARSE_TIMESTAMP");
34945                self.write("('");
34946                self.write(&fmt);
34947                self.write("', ");
34948                self.generate_expression(&e.this)?;
34949                self.write(")");
34950            }
34951            Some(DialectType::Hive) => {
34952                // Hive: CAST(x AS TIMESTAMP) for simple date formats
34953                // Check both the raw format and the converted format (in case it's already Java)
34954                let java_fmt = to_java(&e.format);
34955                if java_fmt == "yyyy-MM-dd HH:mm:ss"
34956                    || java_fmt == "yyyy-MM-dd"
34957                    || e.format == "yyyy-MM-dd HH:mm:ss"
34958                    || e.format == "yyyy-MM-dd"
34959                {
34960                    self.write_keyword("CAST");
34961                    self.write("(");
34962                    self.generate_expression(&e.this)?;
34963                    self.write(" ");
34964                    self.write_keyword("AS TIMESTAMP");
34965                    self.write(")");
34966                } else {
34967                    // CAST(FROM_UNIXTIME(UNIX_TIMESTAMP(x, java_fmt)) AS TIMESTAMP)
34968                    self.write_keyword("CAST");
34969                    self.write("(");
34970                    self.write_keyword("FROM_UNIXTIME");
34971                    self.write("(");
34972                    self.write_keyword("UNIX_TIMESTAMP");
34973                    self.write("(");
34974                    self.generate_expression(&e.this)?;
34975                    self.write(", '");
34976                    self.write(&java_fmt);
34977                    self.write("')");
34978                    self.write(") ");
34979                    self.write_keyword("AS TIMESTAMP");
34980                    self.write(")");
34981                }
34982            }
34983            Some(DialectType::Spark) | Some(DialectType::Databricks) => {
34984                // Spark: TO_TIMESTAMP(value, java_format)
34985                let java_fmt = to_java(&e.format);
34986                self.write_keyword("TO_TIMESTAMP");
34987                self.write("(");
34988                self.generate_expression(&e.this)?;
34989                self.write(", '");
34990                self.write(&java_fmt);
34991                self.write("')");
34992            }
34993            Some(DialectType::MySQL) => {
34994                // MySQL: STR_TO_DATE(value, format)
34995                let mut fmt = to_strftime(&e.format);
34996                // MySQL uses %e for non-padded day, %T for %H:%M:%S
34997                fmt = fmt.replace("%-d", "%e");
34998                fmt = fmt.replace("%-m", "%c");
34999                fmt = fmt.replace("%H:%M:%S", "%T");
35000                self.write_keyword("STR_TO_DATE");
35001                self.write("(");
35002                self.generate_expression(&e.this)?;
35003                self.write(", '");
35004                self.write(&fmt);
35005                self.write("')");
35006            }
35007            Some(DialectType::Drill) => {
35008                // Drill: TO_TIMESTAMP(value, java_format) with T quoted in single quotes
35009                let java_fmt = to_java(&e.format);
35010                // Drill quotes literal T character: T -> ''T'' (double-quoted within SQL string literal)
35011                let java_fmt = java_fmt.replace('T', "''T''");
35012                self.write_keyword("TO_TIMESTAMP");
35013                self.write("(");
35014                self.generate_expression(&e.this)?;
35015                self.write(", '");
35016                self.write(&java_fmt);
35017                self.write("')");
35018            }
35019            Some(DialectType::Presto) | Some(DialectType::Trino) | Some(DialectType::Athena) => {
35020                // Presto: DATE_PARSE(value, strftime_format)
35021                let mut fmt = to_strftime(&e.format);
35022                // Presto uses %e for non-padded day, %T for %H:%M:%S
35023                fmt = fmt.replace("%-d", "%e");
35024                fmt = fmt.replace("%-m", "%c");
35025                fmt = fmt.replace("%H:%M:%S", "%T");
35026                self.write_keyword("DATE_PARSE");
35027                self.write("(");
35028                self.generate_expression(&e.this)?;
35029                self.write(", '");
35030                self.write(&fmt);
35031                self.write("')");
35032            }
35033            Some(DialectType::DuckDB) => {
35034                // DuckDB: STRPTIME(value, strftime_format)
35035                let fmt = to_strftime(&e.format);
35036                self.write_keyword("STRPTIME");
35037                self.write("(");
35038                self.generate_expression(&e.this)?;
35039                self.write(", '");
35040                self.write(&fmt);
35041                self.write("')");
35042            }
35043            Some(DialectType::PostgreSQL)
35044            | Some(DialectType::Redshift)
35045            | Some(DialectType::Materialize) => {
35046                // PostgreSQL/Redshift/Materialize: TO_TIMESTAMP(value, pg_format)
35047                let pg_fmt = to_pg(&e.format);
35048                self.write_keyword("TO_TIMESTAMP");
35049                self.write("(");
35050                self.generate_expression(&e.this)?;
35051                self.write(", '");
35052                self.write(&pg_fmt);
35053                self.write("')");
35054            }
35055            Some(DialectType::Oracle) => {
35056                // Oracle: TO_TIMESTAMP(value, pg_format)
35057                let pg_fmt = to_pg(&e.format);
35058                self.write_keyword("TO_TIMESTAMP");
35059                self.write("(");
35060                self.generate_expression(&e.this)?;
35061                self.write(", '");
35062                self.write(&pg_fmt);
35063                self.write("')");
35064            }
35065            Some(DialectType::Snowflake) => {
35066                // Snowflake: TO_TIMESTAMP(value, format) - native format
35067                self.write_keyword("TO_TIMESTAMP");
35068                self.write("(");
35069                self.generate_expression(&e.this)?;
35070                self.write(", '");
35071                self.write(&e.format);
35072                self.write("')");
35073            }
35074            _ => {
35075                // Default: STR_TO_TIME(this, format)
35076                self.write_keyword("STR_TO_TIME");
35077                self.write("(");
35078                self.generate_expression(&e.this)?;
35079                self.write(", '");
35080                self.write(&e.format);
35081                self.write("'");
35082                self.write(")");
35083            }
35084        }
35085        Ok(())
35086    }
35087
35088    /// Convert Snowflake normalized format to strftime-style (%Y, %m, etc.)
35089    fn snowflake_format_to_strftime(format: &str) -> String {
35090        let mut result = String::new();
35091        let chars: Vec<char> = format.chars().collect();
35092        let mut i = 0;
35093        while i < chars.len() {
35094            let remaining = &format[i..];
35095            if remaining.starts_with("yyyy") {
35096                result.push_str("%Y");
35097                i += 4;
35098            } else if remaining.starts_with("yy") {
35099                result.push_str("%y");
35100                i += 2;
35101            } else if remaining.starts_with("mmmm") {
35102                result.push_str("%B"); // full month name
35103                i += 4;
35104            } else if remaining.starts_with("mon") {
35105                result.push_str("%b"); // abbreviated month
35106                i += 3;
35107            } else if remaining.starts_with("mm") {
35108                result.push_str("%m");
35109                i += 2;
35110            } else if remaining.starts_with("DD") {
35111                result.push_str("%d");
35112                i += 2;
35113            } else if remaining.starts_with("dy") {
35114                result.push_str("%a"); // abbreviated day name
35115                i += 2;
35116            } else if remaining.starts_with("hh24") {
35117                result.push_str("%H");
35118                i += 4;
35119            } else if remaining.starts_with("hh12") {
35120                result.push_str("%I");
35121                i += 4;
35122            } else if remaining.starts_with("hh") {
35123                result.push_str("%H");
35124                i += 2;
35125            } else if remaining.starts_with("mi") {
35126                result.push_str("%M");
35127                i += 2;
35128            } else if remaining.starts_with("ss") {
35129                result.push_str("%S");
35130                i += 2;
35131            } else if remaining.starts_with("ff") {
35132                // Fractional seconds
35133                result.push_str("%f");
35134                i += 2;
35135                // Skip digits after ff (ff3, ff6, ff9)
35136                while i < chars.len() && chars[i].is_ascii_digit() {
35137                    i += 1;
35138                }
35139            } else if remaining.starts_with("am") || remaining.starts_with("pm") {
35140                result.push_str("%p");
35141                i += 2;
35142            } else if remaining.starts_with("tz") {
35143                result.push_str("%Z");
35144                i += 2;
35145            } else {
35146                result.push(chars[i]);
35147                i += 1;
35148            }
35149        }
35150        result
35151    }
35152
35153    /// Convert Snowflake normalized format to Spark format (Java-style)
35154    fn snowflake_format_to_spark(format: &str) -> String {
35155        let mut result = String::new();
35156        let chars: Vec<char> = format.chars().collect();
35157        let mut i = 0;
35158        while i < chars.len() {
35159            let remaining = &format[i..];
35160            if remaining.starts_with("yyyy") {
35161                result.push_str("yyyy");
35162                i += 4;
35163            } else if remaining.starts_with("yy") {
35164                result.push_str("yy");
35165                i += 2;
35166            } else if remaining.starts_with("mmmm") {
35167                result.push_str("MMMM"); // full month name
35168                i += 4;
35169            } else if remaining.starts_with("mon") {
35170                result.push_str("MMM"); // abbreviated month
35171                i += 3;
35172            } else if remaining.starts_with("mm") {
35173                result.push_str("MM");
35174                i += 2;
35175            } else if remaining.starts_with("DD") {
35176                result.push_str("dd");
35177                i += 2;
35178            } else if remaining.starts_with("dy") {
35179                result.push_str("EEE"); // abbreviated day name
35180                i += 2;
35181            } else if remaining.starts_with("hh24") {
35182                result.push_str("HH");
35183                i += 4;
35184            } else if remaining.starts_with("hh12") {
35185                result.push_str("hh");
35186                i += 4;
35187            } else if remaining.starts_with("hh") {
35188                result.push_str("HH");
35189                i += 2;
35190            } else if remaining.starts_with("mi") {
35191                result.push_str("mm");
35192                i += 2;
35193            } else if remaining.starts_with("ss") {
35194                result.push_str("ss");
35195                i += 2;
35196            } else if remaining.starts_with("ff") {
35197                result.push_str("SSS"); // milliseconds
35198                i += 2;
35199                // Skip digits after ff
35200                while i < chars.len() && chars[i].is_ascii_digit() {
35201                    i += 1;
35202                }
35203            } else if remaining.starts_with("am") || remaining.starts_with("pm") {
35204                result.push_str("a");
35205                i += 2;
35206            } else if remaining.starts_with("tz") {
35207                result.push_str("z");
35208                i += 2;
35209            } else {
35210                result.push(chars[i]);
35211                i += 1;
35212            }
35213        }
35214        result
35215    }
35216
35217    fn generate_str_to_unix(&mut self, e: &StrToUnix) -> Result<()> {
35218        match self.config.dialect {
35219            Some(DialectType::DuckDB) => {
35220                // DuckDB: EPOCH(STRPTIME(value, format))
35221                self.write_keyword("EPOCH");
35222                self.write("(");
35223                self.write_keyword("STRPTIME");
35224                self.write("(");
35225                if let Some(this) = &e.this {
35226                    self.generate_expression(this)?;
35227                }
35228                if let Some(format) = &e.format {
35229                    self.write(", '");
35230                    self.write(format);
35231                    self.write("'");
35232                }
35233                self.write("))");
35234            }
35235            Some(DialectType::Hive) => {
35236                // Hive: UNIX_TIMESTAMP(value, java_format) - convert C fmt to Java
35237                self.write_keyword("UNIX_TIMESTAMP");
35238                self.write("(");
35239                if let Some(this) = &e.this {
35240                    self.generate_expression(this)?;
35241                }
35242                if let Some(format) = &e.format {
35243                    let java_fmt = Self::strftime_to_java_format(format);
35244                    if java_fmt != "yyyy-MM-dd HH:mm:ss" {
35245                        self.write(", '");
35246                        self.write(&java_fmt);
35247                        self.write("'");
35248                    }
35249                }
35250                self.write(")");
35251            }
35252            Some(DialectType::Doris) | Some(DialectType::StarRocks) => {
35253                // Doris/StarRocks: UNIX_TIMESTAMP(value, format) - C format
35254                self.write_keyword("UNIX_TIMESTAMP");
35255                self.write("(");
35256                if let Some(this) = &e.this {
35257                    self.generate_expression(this)?;
35258                }
35259                if let Some(format) = &e.format {
35260                    self.write(", '");
35261                    self.write(format);
35262                    self.write("'");
35263                }
35264                self.write(")");
35265            }
35266            Some(DialectType::Presto) | Some(DialectType::Trino) => {
35267                // Presto: TO_UNIXTIME(COALESCE(TRY(DATE_PARSE(CAST(value AS VARCHAR), c_format)),
35268                //   PARSE_DATETIME(DATE_FORMAT(CAST(value AS TIMESTAMP), c_format), java_format)))
35269                let c_fmt = e.format.as_deref().unwrap_or("%Y-%m-%d %T");
35270                let java_fmt = Self::strftime_to_java_format(c_fmt);
35271                self.write_keyword("TO_UNIXTIME");
35272                self.write("(");
35273                self.write_keyword("COALESCE");
35274                self.write("(");
35275                self.write_keyword("TRY");
35276                self.write("(");
35277                self.write_keyword("DATE_PARSE");
35278                self.write("(");
35279                self.write_keyword("CAST");
35280                self.write("(");
35281                if let Some(this) = &e.this {
35282                    self.generate_expression(this)?;
35283                }
35284                self.write(" ");
35285                self.write_keyword("AS VARCHAR");
35286                self.write("), '");
35287                self.write(c_fmt);
35288                self.write("')), ");
35289                self.write_keyword("PARSE_DATETIME");
35290                self.write("(");
35291                self.write_keyword("DATE_FORMAT");
35292                self.write("(");
35293                self.write_keyword("CAST");
35294                self.write("(");
35295                if let Some(this) = &e.this {
35296                    self.generate_expression(this)?;
35297                }
35298                self.write(" ");
35299                self.write_keyword("AS TIMESTAMP");
35300                self.write("), '");
35301                self.write(c_fmt);
35302                self.write("'), '");
35303                self.write(&java_fmt);
35304                self.write("')))");
35305            }
35306            Some(DialectType::Spark) | Some(DialectType::Databricks) => {
35307                // Spark: UNIX_TIMESTAMP(value, java_format)
35308                self.write_keyword("UNIX_TIMESTAMP");
35309                self.write("(");
35310                if let Some(this) = &e.this {
35311                    self.generate_expression(this)?;
35312                }
35313                if let Some(format) = &e.format {
35314                    let java_fmt = Self::strftime_to_java_format(format);
35315                    self.write(", '");
35316                    self.write(&java_fmt);
35317                    self.write("'");
35318                }
35319                self.write(")");
35320            }
35321            _ => {
35322                // Default: STR_TO_UNIX(this, format)
35323                self.write_keyword("STR_TO_UNIX");
35324                self.write("(");
35325                if let Some(this) = &e.this {
35326                    self.generate_expression(this)?;
35327                }
35328                if let Some(format) = &e.format {
35329                    self.write(", '");
35330                    self.write(format);
35331                    self.write("'");
35332                }
35333                self.write(")");
35334            }
35335        }
35336        Ok(())
35337    }
35338
35339    fn generate_string_to_array(&mut self, e: &StringToArray) -> Result<()> {
35340        // STRING_TO_ARRAY(this, delimiter, null_string)
35341        self.write_keyword("STRING_TO_ARRAY");
35342        self.write("(");
35343        self.generate_expression(&e.this)?;
35344        if let Some(expression) = &e.expression {
35345            self.write(", ");
35346            self.generate_expression(expression)?;
35347        }
35348        if let Some(null_val) = &e.null {
35349            self.write(", ");
35350            self.generate_expression(null_val)?;
35351        }
35352        self.write(")");
35353        Ok(())
35354    }
35355
35356    fn generate_struct(&mut self, e: &Struct) -> Result<()> {
35357        if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
35358            // Snowflake: OBJECT_CONSTRUCT('key', value, 'key', value, ...)
35359            self.write_keyword("OBJECT_CONSTRUCT");
35360            self.write("(");
35361            for (i, (name, expr)) in e.fields.iter().enumerate() {
35362                if i > 0 {
35363                    self.write(", ");
35364                }
35365                if let Some(name) = name {
35366                    self.write("'");
35367                    self.write(name);
35368                    self.write("'");
35369                    self.write(", ");
35370                } else {
35371                    self.write("'_");
35372                    self.write(&i.to_string());
35373                    self.write("'");
35374                    self.write(", ");
35375                }
35376                self.generate_expression(expr)?;
35377            }
35378            self.write(")");
35379        } else if self.config.struct_curly_brace_notation {
35380            // DuckDB-style: {'key': value, ...}
35381            self.write("{");
35382            for (i, (name, expr)) in e.fields.iter().enumerate() {
35383                if i > 0 {
35384                    self.write(", ");
35385                }
35386                if let Some(name) = name {
35387                    // Quote the key as a string literal
35388                    self.write("'");
35389                    self.write(name);
35390                    self.write("'");
35391                    self.write(": ");
35392                } else {
35393                    // Unnamed field: use positional key
35394                    self.write("'_");
35395                    self.write(&i.to_string());
35396                    self.write("'");
35397                    self.write(": ");
35398                }
35399                self.generate_expression(expr)?;
35400            }
35401            self.write("}");
35402        } else {
35403            // Standard SQL struct notation
35404            // BigQuery/Spark/Databricks use: STRUCT(value AS name, ...)
35405            // Others (Presto etc.) use: STRUCT(name AS value, ...) or ROW(value, ...)
35406            let value_as_name = matches!(
35407                self.config.dialect,
35408                Some(DialectType::BigQuery)
35409                    | Some(DialectType::Spark)
35410                    | Some(DialectType::Databricks)
35411                    | Some(DialectType::Hive)
35412            );
35413            self.write_keyword("STRUCT");
35414            self.write("(");
35415            for (i, (name, expr)) in e.fields.iter().enumerate() {
35416                if i > 0 {
35417                    self.write(", ");
35418                }
35419                if let Some(name) = name {
35420                    if value_as_name {
35421                        // STRUCT(value AS name)
35422                        self.generate_expression(expr)?;
35423                        self.write_space();
35424                        self.write_keyword("AS");
35425                        self.write_space();
35426                        // Quote name if it contains spaces or special chars
35427                        let needs_quoting = name.contains(' ') || name.contains('-');
35428                        if needs_quoting {
35429                            if matches!(
35430                                self.config.dialect,
35431                                Some(DialectType::Spark)
35432                                    | Some(DialectType::Databricks)
35433                                    | Some(DialectType::Hive)
35434                            ) {
35435                                self.write("`");
35436                                self.write(name);
35437                                self.write("`");
35438                            } else {
35439                                self.write(name);
35440                            }
35441                        } else {
35442                            self.write(name);
35443                        }
35444                    } else {
35445                        // STRUCT(name AS value)
35446                        self.write(name);
35447                        self.write_space();
35448                        self.write_keyword("AS");
35449                        self.write_space();
35450                        self.generate_expression(expr)?;
35451                    }
35452                } else {
35453                    self.generate_expression(expr)?;
35454                }
35455            }
35456            self.write(")");
35457        }
35458        Ok(())
35459    }
35460
35461    fn generate_stuff(&mut self, e: &Stuff) -> Result<()> {
35462        // STUFF(this, start, length, expression)
35463        self.write_keyword("STUFF");
35464        self.write("(");
35465        self.generate_expression(&e.this)?;
35466        if let Some(start) = &e.start {
35467            self.write(", ");
35468            self.generate_expression(start)?;
35469        }
35470        if let Some(length) = e.length {
35471            self.write(", ");
35472            self.write(&length.to_string());
35473        }
35474        self.write(", ");
35475        self.generate_expression(&e.expression)?;
35476        self.write(")");
35477        Ok(())
35478    }
35479
35480    fn generate_substring_index(&mut self, e: &SubstringIndex) -> Result<()> {
35481        // SUBSTRING_INDEX(this, delimiter, count)
35482        self.write_keyword("SUBSTRING_INDEX");
35483        self.write("(");
35484        self.generate_expression(&e.this)?;
35485        if let Some(delimiter) = &e.delimiter {
35486            self.write(", ");
35487            self.generate_expression(delimiter)?;
35488        }
35489        if let Some(count) = &e.count {
35490            self.write(", ");
35491            self.generate_expression(count)?;
35492        }
35493        self.write(")");
35494        Ok(())
35495    }
35496
35497    fn generate_summarize(&mut self, e: &Summarize) -> Result<()> {
35498        // SUMMARIZE [TABLE] this
35499        self.write_keyword("SUMMARIZE");
35500        if e.table.is_some() {
35501            self.write_space();
35502            self.write_keyword("TABLE");
35503        }
35504        self.write_space();
35505        self.generate_expression(&e.this)?;
35506        Ok(())
35507    }
35508
35509    fn generate_systimestamp(&mut self, _e: &Systimestamp) -> Result<()> {
35510        // SYSTIMESTAMP
35511        self.write_keyword("SYSTIMESTAMP");
35512        Ok(())
35513    }
35514
35515    fn generate_table_alias(&mut self, e: &TableAlias) -> Result<()> {
35516        // alias (columns...)
35517        if let Some(this) = &e.this {
35518            self.generate_expression(this)?;
35519        }
35520        if !e.columns.is_empty() {
35521            self.write("(");
35522            for (i, col) in e.columns.iter().enumerate() {
35523                if i > 0 {
35524                    self.write(", ");
35525                }
35526                self.generate_expression(col)?;
35527            }
35528            self.write(")");
35529        }
35530        Ok(())
35531    }
35532
35533    fn generate_table_from_rows(&mut self, e: &TableFromRows) -> Result<()> {
35534        // TABLE(this) [AS alias]
35535        self.write_keyword("TABLE");
35536        self.write("(");
35537        self.generate_expression(&e.this)?;
35538        self.write(")");
35539        if let Some(alias) = &e.alias {
35540            self.write_space();
35541            self.write_keyword("AS");
35542            self.write_space();
35543            self.write(alias);
35544        }
35545        Ok(())
35546    }
35547
35548    fn generate_rows_from(&mut self, e: &RowsFrom) -> Result<()> {
35549        // ROWS FROM (func1(...) AS alias1(...), func2(...) AS alias2(...)) [WITH ORDINALITY] [AS alias(...)]
35550        self.write_keyword("ROWS FROM");
35551        self.write(" (");
35552        for (i, expr) in e.expressions.iter().enumerate() {
35553            if i > 0 {
35554                self.write(", ");
35555            }
35556            // Each expression is either:
35557            // - A plain function (no alias)
35558            // - A Tuple(function, TableAlias) for: FUNC() AS alias(col type, ...)
35559            match expr {
35560                Expression::Tuple(tuple) if tuple.expressions.len() == 2 => {
35561                    // First element is the function, second is the TableAlias
35562                    self.generate_expression(&tuple.expressions[0])?;
35563                    self.write_space();
35564                    self.write_keyword("AS");
35565                    self.write_space();
35566                    self.generate_expression(&tuple.expressions[1])?;
35567                }
35568                _ => {
35569                    self.generate_expression(expr)?;
35570                }
35571            }
35572        }
35573        self.write(")");
35574        if e.ordinality {
35575            self.write_space();
35576            self.write_keyword("WITH ORDINALITY");
35577        }
35578        if let Some(alias) = &e.alias {
35579            self.write_space();
35580            self.write_keyword("AS");
35581            self.write_space();
35582            self.generate_expression(alias)?;
35583        }
35584        Ok(())
35585    }
35586
35587    fn generate_table_sample(&mut self, e: &TableSample) -> Result<()> {
35588        use crate::dialects::DialectType;
35589
35590        // New wrapper pattern: expression + Sample struct
35591        if let (Some(this), Some(sample)) = (&e.this, &e.sample) {
35592            // For alias_post_tablesample dialects (Spark, Hive, Oracle): output base expr, TABLESAMPLE, then alias
35593            if self.config.alias_post_tablesample {
35594                // Handle Subquery with alias and Alias wrapper
35595                if let Expression::Subquery(ref s) = **this {
35596                    if let Some(ref alias) = s.alias {
35597                        // Create a clone without alias for output
35598                        let mut subquery_no_alias = (**s).clone();
35599                        subquery_no_alias.alias = None;
35600                        subquery_no_alias.column_aliases = Vec::new();
35601                        self.generate_expression(&Expression::Subquery(Box::new(
35602                            subquery_no_alias,
35603                        )))?;
35604                        self.write_space();
35605                        self.write_keyword("TABLESAMPLE");
35606                        self.generate_sample_body(sample)?;
35607                        if let Some(ref seed) = sample.seed {
35608                            self.write_space();
35609                            let use_seed = sample.use_seed_keyword
35610                                && !matches!(
35611                                    self.config.dialect,
35612                                    Some(crate::dialects::DialectType::Databricks)
35613                                        | Some(crate::dialects::DialectType::Spark)
35614                                );
35615                            if use_seed {
35616                                self.write_keyword("SEED");
35617                            } else {
35618                                self.write_keyword("REPEATABLE");
35619                            }
35620                            self.write(" (");
35621                            self.generate_expression(seed)?;
35622                            self.write(")");
35623                        }
35624                        self.write_space();
35625                        self.write_keyword("AS");
35626                        self.write_space();
35627                        self.generate_identifier(alias)?;
35628                        return Ok(());
35629                    }
35630                } else if let Expression::Alias(ref a) = **this {
35631                    // Output the base expression without alias
35632                    self.generate_expression(&a.this)?;
35633                    self.write_space();
35634                    self.write_keyword("TABLESAMPLE");
35635                    self.generate_sample_body(sample)?;
35636                    if let Some(ref seed) = sample.seed {
35637                        self.write_space();
35638                        let use_seed = sample.use_seed_keyword
35639                            && !matches!(
35640                                self.config.dialect,
35641                                Some(crate::dialects::DialectType::Databricks)
35642                                    | Some(crate::dialects::DialectType::Spark)
35643                            );
35644                        if use_seed {
35645                            self.write_keyword("SEED");
35646                        } else {
35647                            self.write_keyword("REPEATABLE");
35648                        }
35649                        self.write(" (");
35650                        self.generate_expression(seed)?;
35651                        self.write(")");
35652                    }
35653                    // Output alias after TABLESAMPLE
35654                    self.write_space();
35655                    self.write_keyword("AS");
35656                    self.write_space();
35657                    self.generate_identifier(&a.alias)?;
35658                    return Ok(());
35659                }
35660            }
35661            // Default: generate wrapped expression first, then TABLESAMPLE
35662            self.generate_expression(this)?;
35663            self.write_space();
35664            self.write_keyword("TABLESAMPLE");
35665            self.generate_sample_body(sample)?;
35666            // Seed for table-level sample
35667            if let Some(ref seed) = sample.seed {
35668                self.write_space();
35669                // Databricks uses REPEATABLE, not SEED
35670                let use_seed = sample.use_seed_keyword
35671                    && !matches!(
35672                        self.config.dialect,
35673                        Some(crate::dialects::DialectType::Databricks)
35674                            | Some(crate::dialects::DialectType::Spark)
35675                    );
35676                if use_seed {
35677                    self.write_keyword("SEED");
35678                } else {
35679                    self.write_keyword("REPEATABLE");
35680                }
35681                self.write(" (");
35682                self.generate_expression(seed)?;
35683                self.write(")");
35684            }
35685            return Ok(());
35686        }
35687
35688        // Legacy pattern: TABLESAMPLE [method] (expressions) or TABLESAMPLE method BUCKET numerator OUT OF denominator
35689        self.write_keyword("TABLESAMPLE");
35690        if let Some(method) = &e.method {
35691            self.write_space();
35692            self.write_keyword(method);
35693        } else if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
35694            // Snowflake defaults to BERNOULLI when no method is specified
35695            self.write_space();
35696            self.write_keyword("BERNOULLI");
35697        }
35698        if let (Some(numerator), Some(denominator)) = (&e.bucket_numerator, &e.bucket_denominator) {
35699            self.write_space();
35700            self.write_keyword("BUCKET");
35701            self.write_space();
35702            self.generate_expression(numerator)?;
35703            self.write_space();
35704            self.write_keyword("OUT OF");
35705            self.write_space();
35706            self.generate_expression(denominator)?;
35707            if let Some(field) = &e.bucket_field {
35708                self.write_space();
35709                self.write_keyword("ON");
35710                self.write_space();
35711                self.generate_expression(field)?;
35712            }
35713        } else if !e.expressions.is_empty() {
35714            self.write(" (");
35715            for (i, expr) in e.expressions.iter().enumerate() {
35716                if i > 0 {
35717                    self.write(", ");
35718                }
35719                self.generate_expression(expr)?;
35720            }
35721            self.write(")");
35722        } else if let Some(percent) = &e.percent {
35723            self.write(" (");
35724            self.generate_expression(percent)?;
35725            self.write_space();
35726            self.write_keyword("PERCENT");
35727            self.write(")");
35728        }
35729        Ok(())
35730    }
35731
35732    fn generate_tag(&mut self, e: &Tag) -> Result<()> {
35733        // [prefix]this[postfix]
35734        if let Some(prefix) = &e.prefix {
35735            self.generate_expression(prefix)?;
35736        }
35737        if let Some(this) = &e.this {
35738            self.generate_expression(this)?;
35739        }
35740        if let Some(postfix) = &e.postfix {
35741            self.generate_expression(postfix)?;
35742        }
35743        Ok(())
35744    }
35745
35746    fn generate_tags(&mut self, e: &Tags) -> Result<()> {
35747        // TAG (expressions)
35748        self.write_keyword("TAG");
35749        self.write(" (");
35750        for (i, expr) in e.expressions.iter().enumerate() {
35751            if i > 0 {
35752                self.write(", ");
35753            }
35754            self.generate_expression(expr)?;
35755        }
35756        self.write(")");
35757        Ok(())
35758    }
35759
35760    fn generate_temporary_property(&mut self, e: &TemporaryProperty) -> Result<()> {
35761        // TEMPORARY or TEMP or [this] TEMPORARY
35762        if let Some(this) = &e.this {
35763            self.generate_expression(this)?;
35764            self.write_space();
35765        }
35766        self.write_keyword("TEMPORARY");
35767        Ok(())
35768    }
35769
35770    /// Generate a Time function expression
35771    /// For most dialects: TIME('value')
35772    fn generate_time_func(&mut self, e: &UnaryFunc) -> Result<()> {
35773        // Standard: TIME(value)
35774        self.write_keyword("TIME");
35775        self.write("(");
35776        self.generate_expression(&e.this)?;
35777        self.write(")");
35778        Ok(())
35779    }
35780
35781    fn generate_time_add(&mut self, e: &TimeAdd) -> Result<()> {
35782        // TIME_ADD(this, expression, unit)
35783        self.write_keyword("TIME_ADD");
35784        self.write("(");
35785        self.generate_expression(&e.this)?;
35786        self.write(", ");
35787        self.generate_expression(&e.expression)?;
35788        if let Some(unit) = &e.unit {
35789            self.write(", ");
35790            self.write_keyword(unit);
35791        }
35792        self.write(")");
35793        Ok(())
35794    }
35795
35796    fn generate_time_diff(&mut self, e: &TimeDiff) -> Result<()> {
35797        // TIME_DIFF(this, expression, unit)
35798        self.write_keyword("TIME_DIFF");
35799        self.write("(");
35800        self.generate_expression(&e.this)?;
35801        self.write(", ");
35802        self.generate_expression(&e.expression)?;
35803        if let Some(unit) = &e.unit {
35804            self.write(", ");
35805            self.write_keyword(unit);
35806        }
35807        self.write(")");
35808        Ok(())
35809    }
35810
35811    fn generate_time_from_parts(&mut self, e: &TimeFromParts) -> Result<()> {
35812        // TIME_FROM_PARTS(hour, minute, second, nanosecond)
35813        self.write_keyword("TIME_FROM_PARTS");
35814        self.write("(");
35815        let mut first = true;
35816        if let Some(hour) = &e.hour {
35817            self.generate_expression(hour)?;
35818            first = false;
35819        }
35820        if let Some(minute) = &e.min {
35821            if !first {
35822                self.write(", ");
35823            }
35824            self.generate_expression(minute)?;
35825            first = false;
35826        }
35827        if let Some(second) = &e.sec {
35828            if !first {
35829                self.write(", ");
35830            }
35831            self.generate_expression(second)?;
35832            first = false;
35833        }
35834        if let Some(ns) = &e.nano {
35835            if !first {
35836                self.write(", ");
35837            }
35838            self.generate_expression(ns)?;
35839        }
35840        self.write(")");
35841        Ok(())
35842    }
35843
35844    fn generate_time_slice(&mut self, e: &TimeSlice) -> Result<()> {
35845        // TIME_SLICE(this, expression, unit)
35846        self.write_keyword("TIME_SLICE");
35847        self.write("(");
35848        self.generate_expression(&e.this)?;
35849        self.write(", ");
35850        self.generate_expression(&e.expression)?;
35851        self.write(", ");
35852        self.write_keyword(&e.unit);
35853        self.write(")");
35854        Ok(())
35855    }
35856
35857    fn generate_time_str_to_time(&mut self, e: &TimeStrToTime) -> Result<()> {
35858        // TIME_STR_TO_TIME(this)
35859        self.write_keyword("TIME_STR_TO_TIME");
35860        self.write("(");
35861        self.generate_expression(&e.this)?;
35862        self.write(")");
35863        Ok(())
35864    }
35865
35866    fn generate_time_sub(&mut self, e: &TimeSub) -> Result<()> {
35867        // TIME_SUB(this, expression, unit)
35868        self.write_keyword("TIME_SUB");
35869        self.write("(");
35870        self.generate_expression(&e.this)?;
35871        self.write(", ");
35872        self.generate_expression(&e.expression)?;
35873        if let Some(unit) = &e.unit {
35874            self.write(", ");
35875            self.write_keyword(unit);
35876        }
35877        self.write(")");
35878        Ok(())
35879    }
35880
35881    fn generate_time_to_str(&mut self, e: &TimeToStr) -> Result<()> {
35882        match self.config.dialect {
35883            Some(DialectType::Exasol) => {
35884                // Exasol uses TO_CHAR with Exasol-specific format
35885                self.write_keyword("TO_CHAR");
35886                self.write("(");
35887                self.generate_expression(&e.this)?;
35888                self.write(", '");
35889                self.write(&Self::convert_strptime_to_exasol_format(&e.format));
35890                self.write("'");
35891                self.write(")");
35892            }
35893            Some(DialectType::PostgreSQL)
35894            | Some(DialectType::Redshift)
35895            | Some(DialectType::Materialize) => {
35896                // PostgreSQL/Redshift/Materialize uses TO_CHAR with PG-specific format
35897                self.write_keyword("TO_CHAR");
35898                self.write("(");
35899                self.generate_expression(&e.this)?;
35900                self.write(", '");
35901                self.write(&Self::convert_strptime_to_postgres_format(&e.format));
35902                self.write("'");
35903                self.write(")");
35904            }
35905            Some(DialectType::Oracle) => {
35906                // Oracle uses TO_CHAR with PG-like format
35907                self.write_keyword("TO_CHAR");
35908                self.write("(");
35909                self.generate_expression(&e.this)?;
35910                self.write(", '");
35911                self.write(&Self::convert_strptime_to_postgres_format(&e.format));
35912                self.write("'");
35913                self.write(")");
35914            }
35915            Some(DialectType::Drill) => {
35916                // Drill: TO_CHAR with Java format
35917                self.write_keyword("TO_CHAR");
35918                self.write("(");
35919                self.generate_expression(&e.this)?;
35920                self.write(", '");
35921                self.write(&Self::strftime_to_java_format(&e.format));
35922                self.write("'");
35923                self.write(")");
35924            }
35925            Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
35926                // TSQL: FORMAT(value, format) with .NET-style format
35927                self.write_keyword("FORMAT");
35928                self.write("(");
35929                self.generate_expression(&e.this)?;
35930                self.write(", '");
35931                self.write(&Self::strftime_to_tsql_format(&e.format));
35932                self.write("'");
35933                self.write(")");
35934            }
35935            Some(DialectType::DuckDB) => {
35936                // DuckDB: STRFTIME(value, format) - keeps C format
35937                self.write_keyword("STRFTIME");
35938                self.write("(");
35939                self.generate_expression(&e.this)?;
35940                self.write(", '");
35941                self.write(&e.format);
35942                self.write("'");
35943                self.write(")");
35944            }
35945            Some(DialectType::BigQuery) => {
35946                // BigQuery: FORMAT_DATE(format, value) - note swapped arg order
35947                // Normalize: %Y-%m-%d -> %F, %H:%M:%S -> %T
35948                let fmt = e.format.replace("%Y-%m-%d", "%F").replace("%H:%M:%S", "%T");
35949                self.write_keyword("FORMAT_DATE");
35950                self.write("('");
35951                self.write(&fmt);
35952                self.write("', ");
35953                self.generate_expression(&e.this)?;
35954                self.write(")");
35955            }
35956            Some(DialectType::Hive) | Some(DialectType::Spark) | Some(DialectType::Databricks) => {
35957                // Hive/Spark: DATE_FORMAT(value, java_format)
35958                self.write_keyword("DATE_FORMAT");
35959                self.write("(");
35960                self.generate_expression(&e.this)?;
35961                self.write(", '");
35962                self.write(&Self::strftime_to_java_format(&e.format));
35963                self.write("'");
35964                self.write(")");
35965            }
35966            Some(DialectType::Presto) | Some(DialectType::Trino) | Some(DialectType::Athena) => {
35967                // Presto/Trino: DATE_FORMAT(value, format) - keeps C format
35968                self.write_keyword("DATE_FORMAT");
35969                self.write("(");
35970                self.generate_expression(&e.this)?;
35971                self.write(", '");
35972                self.write(&e.format);
35973                self.write("'");
35974                self.write(")");
35975            }
35976            Some(DialectType::Doris) | Some(DialectType::StarRocks) => {
35977                // Doris/StarRocks: DATE_FORMAT(value, format) - keeps C format
35978                self.write_keyword("DATE_FORMAT");
35979                self.write("(");
35980                self.generate_expression(&e.this)?;
35981                self.write(", '");
35982                self.write(&e.format);
35983                self.write("'");
35984                self.write(")");
35985            }
35986            _ => {
35987                // Default: TIME_TO_STR(this, format)
35988                self.write_keyword("TIME_TO_STR");
35989                self.write("(");
35990                self.generate_expression(&e.this)?;
35991                self.write(", '");
35992                self.write(&e.format);
35993                self.write("'");
35994                self.write(")");
35995            }
35996        }
35997        Ok(())
35998    }
35999
36000    fn generate_time_to_unix(&mut self, e: &crate::expressions::UnaryFunc) -> Result<()> {
36001        match self.config.dialect {
36002            Some(DialectType::DuckDB) => {
36003                // DuckDB: EPOCH(x)
36004                self.write_keyword("EPOCH");
36005                self.write("(");
36006                self.generate_expression(&e.this)?;
36007                self.write(")");
36008            }
36009            Some(DialectType::Hive)
36010            | Some(DialectType::Spark)
36011            | Some(DialectType::Databricks)
36012            | Some(DialectType::Doris)
36013            | Some(DialectType::StarRocks)
36014            | Some(DialectType::Drill) => {
36015                // Hive/Spark/Doris/StarRocks/Drill: UNIX_TIMESTAMP(x)
36016                self.write_keyword("UNIX_TIMESTAMP");
36017                self.write("(");
36018                self.generate_expression(&e.this)?;
36019                self.write(")");
36020            }
36021            Some(DialectType::Presto) | Some(DialectType::Trino) => {
36022                // Presto: TO_UNIXTIME(x)
36023                self.write_keyword("TO_UNIXTIME");
36024                self.write("(");
36025                self.generate_expression(&e.this)?;
36026                self.write(")");
36027            }
36028            _ => {
36029                // Default: TIME_TO_UNIX(x)
36030                self.write_keyword("TIME_TO_UNIX");
36031                self.write("(");
36032                self.generate_expression(&e.this)?;
36033                self.write(")");
36034            }
36035        }
36036        Ok(())
36037    }
36038
36039    fn generate_time_str_to_date(&mut self, e: &crate::expressions::UnaryFunc) -> Result<()> {
36040        match self.config.dialect {
36041            Some(DialectType::Hive) => {
36042                // Hive: TO_DATE(x)
36043                self.write_keyword("TO_DATE");
36044                self.write("(");
36045                self.generate_expression(&e.this)?;
36046                self.write(")");
36047            }
36048            _ => {
36049                // Default: TIME_STR_TO_DATE(x)
36050                self.write_keyword("TIME_STR_TO_DATE");
36051                self.write("(");
36052                self.generate_expression(&e.this)?;
36053                self.write(")");
36054            }
36055        }
36056        Ok(())
36057    }
36058
36059    fn generate_time_trunc(&mut self, e: &TimeTrunc) -> Result<()> {
36060        // TIME_TRUNC(this, unit)
36061        self.write_keyword("TIME_TRUNC");
36062        self.write("(");
36063        self.generate_expression(&e.this)?;
36064        self.write(", ");
36065        self.write_keyword(&e.unit);
36066        self.write(")");
36067        Ok(())
36068    }
36069
36070    fn generate_time_unit(&mut self, e: &TimeUnit) -> Result<()> {
36071        // Just output the unit name
36072        if let Some(unit) = &e.unit {
36073            self.write_keyword(unit);
36074        }
36075        Ok(())
36076    }
36077
36078    /// Generate a Timestamp function expression
36079    /// For Exasol: {ts'value'} -> TO_TIMESTAMP('value')
36080    /// For other dialects: TIMESTAMP('value')
36081    fn generate_timestamp_func(&mut self, e: &TimestampFunc) -> Result<()> {
36082        use crate::dialects::DialectType;
36083        use crate::expressions::Literal;
36084
36085        match self.config.dialect {
36086            // Exasol uses TO_TIMESTAMP for Timestamp expressions
36087            Some(DialectType::Exasol) => {
36088                self.write_keyword("TO_TIMESTAMP");
36089                self.write("(");
36090                // Extract the string value from the expression if it's a string literal
36091                if let Some(this) = &e.this {
36092                    match this.as_ref() {
36093                        Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
36094                            let Literal::String(s) = lit.as_ref() else {
36095                                unreachable!()
36096                            };
36097                            self.write("'");
36098                            self.write(s);
36099                            self.write("'");
36100                        }
36101                        _ => {
36102                            self.generate_expression(this)?;
36103                        }
36104                    }
36105                }
36106                self.write(")");
36107            }
36108            // Standard: TIMESTAMP(value) or TIMESTAMP(value, zone)
36109            _ => {
36110                self.write_keyword("TIMESTAMP");
36111                self.write("(");
36112                if let Some(this) = &e.this {
36113                    self.generate_expression(this)?;
36114                }
36115                if let Some(zone) = &e.zone {
36116                    self.write(", ");
36117                    self.generate_expression(zone)?;
36118                }
36119                self.write(")");
36120            }
36121        }
36122        Ok(())
36123    }
36124
36125    fn generate_timestamp_add(&mut self, e: &TimestampAdd) -> Result<()> {
36126        // TIMESTAMP_ADD(this, expression, unit)
36127        self.write_keyword("TIMESTAMP_ADD");
36128        self.write("(");
36129        self.generate_expression(&e.this)?;
36130        self.write(", ");
36131        self.generate_expression(&e.expression)?;
36132        if let Some(unit) = &e.unit {
36133            self.write(", ");
36134            self.write_keyword(unit);
36135        }
36136        self.write(")");
36137        Ok(())
36138    }
36139
36140    fn generate_timestamp_diff(&mut self, e: &TimestampDiff) -> Result<()> {
36141        // TIMESTAMP_DIFF(this, expression, unit)
36142        self.write_keyword("TIMESTAMP_DIFF");
36143        self.write("(");
36144        self.generate_expression(&e.this)?;
36145        self.write(", ");
36146        self.generate_expression(&e.expression)?;
36147        if let Some(unit) = &e.unit {
36148            self.write(", ");
36149            self.write_keyword(unit);
36150        }
36151        self.write(")");
36152        Ok(())
36153    }
36154
36155    fn generate_timestamp_from_parts(&mut self, e: &TimestampFromParts) -> Result<()> {
36156        // TIMESTAMP_FROM_PARTS(this, expression)
36157        self.write_keyword("TIMESTAMP_FROM_PARTS");
36158        self.write("(");
36159        if let Some(this) = &e.this {
36160            self.generate_expression(this)?;
36161        }
36162        if let Some(expression) = &e.expression {
36163            self.write(", ");
36164            self.generate_expression(expression)?;
36165        }
36166        if let Some(zone) = &e.zone {
36167            self.write(", ");
36168            self.generate_expression(zone)?;
36169        }
36170        if let Some(milli) = &e.milli {
36171            self.write(", ");
36172            self.generate_expression(milli)?;
36173        }
36174        self.write(")");
36175        Ok(())
36176    }
36177
36178    fn generate_timestamp_sub(&mut self, e: &TimestampSub) -> Result<()> {
36179        // TIMESTAMP_SUB(this, INTERVAL expression unit)
36180        self.write_keyword("TIMESTAMP_SUB");
36181        self.write("(");
36182        self.generate_expression(&e.this)?;
36183        self.write(", ");
36184        self.write_keyword("INTERVAL");
36185        self.write_space();
36186        self.generate_expression(&e.expression)?;
36187        if let Some(unit) = &e.unit {
36188            self.write_space();
36189            self.write_keyword(unit);
36190        }
36191        self.write(")");
36192        Ok(())
36193    }
36194
36195    fn generate_timestamp_tz_from_parts(&mut self, e: &TimestampTzFromParts) -> Result<()> {
36196        // TIMESTAMP_TZ_FROM_PARTS(...)
36197        self.write_keyword("TIMESTAMP_TZ_FROM_PARTS");
36198        self.write("(");
36199        if let Some(zone) = &e.zone {
36200            self.generate_expression(zone)?;
36201        }
36202        self.write(")");
36203        Ok(())
36204    }
36205
36206    fn generate_to_binary(&mut self, e: &ToBinary) -> Result<()> {
36207        // TO_BINARY(this, [format])
36208        self.write_keyword("TO_BINARY");
36209        self.write("(");
36210        self.generate_expression(&e.this)?;
36211        if let Some(format) = &e.format {
36212            self.write(", '");
36213            self.write(format);
36214            self.write("'");
36215        }
36216        self.write(")");
36217        Ok(())
36218    }
36219
36220    fn generate_to_boolean(&mut self, e: &ToBoolean) -> Result<()> {
36221        // TO_BOOLEAN(this)
36222        self.write_keyword("TO_BOOLEAN");
36223        self.write("(");
36224        self.generate_expression(&e.this)?;
36225        self.write(")");
36226        Ok(())
36227    }
36228
36229    fn generate_to_char(&mut self, e: &ToChar) -> Result<()> {
36230        // TO_CHAR(this, [format], [nlsparam])
36231        self.write_keyword("TO_CHAR");
36232        self.write("(");
36233        self.generate_expression(&e.this)?;
36234        if let Some(format) = &e.format {
36235            self.write(", '");
36236            self.write(format);
36237            self.write("'");
36238        }
36239        if let Some(nlsparam) = &e.nlsparam {
36240            self.write(", ");
36241            self.generate_expression(nlsparam)?;
36242        }
36243        self.write(")");
36244        Ok(())
36245    }
36246
36247    fn generate_to_decfloat(&mut self, e: &ToDecfloat) -> Result<()> {
36248        // TO_DECFLOAT(this, [format])
36249        self.write_keyword("TO_DECFLOAT");
36250        self.write("(");
36251        self.generate_expression(&e.this)?;
36252        if let Some(format) = &e.format {
36253            self.write(", '");
36254            self.write(format);
36255            self.write("'");
36256        }
36257        self.write(")");
36258        Ok(())
36259    }
36260
36261    fn generate_to_double(&mut self, e: &ToDouble) -> Result<()> {
36262        // TO_DOUBLE(this, [format])
36263        self.write_keyword("TO_DOUBLE");
36264        self.write("(");
36265        self.generate_expression(&e.this)?;
36266        if let Some(format) = &e.format {
36267            self.write(", '");
36268            self.write(format);
36269            self.write("'");
36270        }
36271        self.write(")");
36272        Ok(())
36273    }
36274
36275    fn generate_to_file(&mut self, e: &ToFile) -> Result<()> {
36276        // TO_FILE(this, path)
36277        self.write_keyword("TO_FILE");
36278        self.write("(");
36279        self.generate_expression(&e.this)?;
36280        if let Some(path) = &e.path {
36281            self.write(", ");
36282            self.generate_expression(path)?;
36283        }
36284        self.write(")");
36285        Ok(())
36286    }
36287
36288    fn generate_to_number(&mut self, e: &ToNumber) -> Result<()> {
36289        // TO_NUMBER or TRY_TO_NUMBER (this, [format], [precision], [scale])
36290        // If safe flag is set, output TRY_TO_NUMBER
36291        let is_safe = e.safe.is_some();
36292        if is_safe {
36293            self.write_keyword("TRY_TO_NUMBER");
36294        } else {
36295            self.write_keyword("TO_NUMBER");
36296        }
36297        self.write("(");
36298        self.generate_expression(&e.this)?;
36299        let precision_is_snowflake_default = e.precision.is_none()
36300            || matches!(
36301                e.precision.as_deref(),
36302                Some(Expression::Literal(lit))
36303                    if matches!(lit.as_ref(), Literal::Number(n) if n == "0")
36304            );
36305        let is_snowflake_default_precision =
36306            matches!(self.config.dialect, Some(DialectType::Snowflake))
36307                && e.nlsparam.is_none()
36308                && e.scale.is_none()
36309                && matches!(
36310                    e.format.as_deref(),
36311                    Some(Expression::Literal(lit))
36312                        if matches!(lit.as_ref(), Literal::Number(n) if n == "38")
36313                )
36314                && precision_is_snowflake_default;
36315
36316        if !is_snowflake_default_precision {
36317            if let Some(format) = &e.format {
36318                self.write(", ");
36319                self.generate_expression(format)?;
36320            }
36321            if let Some(nlsparam) = &e.nlsparam {
36322                self.write(", ");
36323                self.generate_expression(nlsparam)?;
36324            }
36325            if let Some(precision) = &e.precision {
36326                self.write(", ");
36327                self.generate_expression(precision)?;
36328            }
36329            if let Some(scale) = &e.scale {
36330                self.write(", ");
36331                self.generate_expression(scale)?;
36332            }
36333        }
36334        self.write(")");
36335        Ok(())
36336    }
36337
36338    fn generate_to_table_property(&mut self, e: &ToTableProperty) -> Result<()> {
36339        // TO_TABLE this
36340        self.write_keyword("TO_TABLE");
36341        self.write_space();
36342        self.generate_expression(&e.this)?;
36343        Ok(())
36344    }
36345
36346    fn generate_transaction(&mut self, e: &Transaction) -> Result<()> {
36347        // Check mark to determine the format
36348        let mark_text = e.mark.as_ref().map(|m| match m.as_ref() {
36349            Expression::Identifier(id) => id.name.clone(),
36350            Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
36351                let Literal::String(s) = lit.as_ref() else {
36352                    unreachable!()
36353                };
36354                s.clone()
36355            }
36356            _ => String::new(),
36357        });
36358
36359        let is_start = mark_text.as_ref().map_or(false, |s| s == "START");
36360        let has_transaction_keyword = mark_text.as_ref().map_or(false, |s| s == "TRANSACTION");
36361        let has_with_mark = e.mark.as_ref().map_or(false, |m| {
36362            matches!(m.as_ref(), Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)))
36363        });
36364
36365        // For Presto/Trino: always use START TRANSACTION
36366        let use_start_transaction = matches!(
36367            self.config.dialect,
36368            Some(DialectType::Presto) | Some(DialectType::Trino) | Some(DialectType::Athena)
36369        );
36370        // For most dialects: strip TRANSACTION keyword
36371        let strip_transaction = matches!(
36372            self.config.dialect,
36373            Some(DialectType::Snowflake)
36374                | Some(DialectType::PostgreSQL)
36375                | Some(DialectType::Redshift)
36376                | Some(DialectType::MySQL)
36377                | Some(DialectType::Hive)
36378                | Some(DialectType::Spark)
36379                | Some(DialectType::Databricks)
36380                | Some(DialectType::DuckDB)
36381                | Some(DialectType::Oracle)
36382                | Some(DialectType::Doris)
36383                | Some(DialectType::StarRocks)
36384                | Some(DialectType::Materialize)
36385                | Some(DialectType::ClickHouse)
36386        );
36387
36388        if is_start || use_start_transaction {
36389            // START TRANSACTION [modes]
36390            self.write_keyword("START TRANSACTION");
36391            if let Some(modes) = &e.modes {
36392                self.write_space();
36393                self.generate_expression(modes)?;
36394            }
36395        } else {
36396            // BEGIN [DEFERRED|IMMEDIATE|EXCLUSIVE] [TRANSACTION] [transaction_name] [WITH MARK 'desc']
36397            self.write_keyword("BEGIN");
36398
36399            // Check if `this` is a transaction kind (DEFERRED/IMMEDIATE/EXCLUSIVE)
36400            let is_kind = e.this.as_ref().map_or(false, |t| {
36401                if let Expression::Identifier(id) = t.as_ref() {
36402                    id.name.eq_ignore_ascii_case("DEFERRED")
36403                        || id.name.eq_ignore_ascii_case("IMMEDIATE")
36404                        || id.name.eq_ignore_ascii_case("EXCLUSIVE")
36405                } else {
36406                    false
36407                }
36408            });
36409
36410            // Output kind before TRANSACTION keyword
36411            if is_kind {
36412                if let Some(this) = &e.this {
36413                    self.write_space();
36414                    if let Expression::Identifier(id) = this.as_ref() {
36415                        self.write_keyword(&id.name);
36416                    }
36417                }
36418            }
36419
36420            // Output TRANSACTION keyword if it was present and target supports it
36421            if (has_transaction_keyword || has_with_mark) && !strip_transaction {
36422                self.write_space();
36423                self.write_keyword("TRANSACTION");
36424            }
36425
36426            // Output transaction name (not kind)
36427            if !is_kind {
36428                if let Some(this) = &e.this {
36429                    self.write_space();
36430                    self.generate_expression(this)?;
36431                }
36432            }
36433
36434            // Output WITH MARK 'description' for TSQL
36435            if has_with_mark {
36436                self.write_space();
36437                self.write_keyword("WITH MARK");
36438                if let Some(Expression::Literal(lit)) = e.mark.as_deref() {
36439                    if let Literal::String(desc) = lit.as_ref() {
36440                        if !desc.is_empty() {
36441                            self.write_space();
36442                            self.write(&format!("'{}'", desc));
36443                        }
36444                    }
36445                }
36446            }
36447
36448            // Output modes (isolation levels, etc.)
36449            if let Some(modes) = &e.modes {
36450                self.write_space();
36451                self.generate_expression(modes)?;
36452            }
36453        }
36454        Ok(())
36455    }
36456
36457    fn generate_transform(&mut self, e: &Transform) -> Result<()> {
36458        // TRANSFORM(this, expression)
36459        self.write_keyword("TRANSFORM");
36460        self.write("(");
36461        self.generate_expression(&e.this)?;
36462        self.write(", ");
36463        self.generate_expression(&e.expression)?;
36464        self.write(")");
36465        Ok(())
36466    }
36467
36468    fn generate_transform_model_property(&mut self, e: &TransformModelProperty) -> Result<()> {
36469        // TRANSFORM(expressions)
36470        self.write_keyword("TRANSFORM");
36471        self.write("(");
36472        if self.config.pretty && !e.expressions.is_empty() {
36473            self.indent_level += 1;
36474            for (i, expr) in e.expressions.iter().enumerate() {
36475                if i > 0 {
36476                    self.write(",");
36477                }
36478                self.write_newline();
36479                self.write_indent();
36480                self.generate_expression(expr)?;
36481            }
36482            self.indent_level -= 1;
36483            self.write_newline();
36484            self.write(")");
36485        } else {
36486            for (i, expr) in e.expressions.iter().enumerate() {
36487                if i > 0 {
36488                    self.write(", ");
36489                }
36490                self.generate_expression(expr)?;
36491            }
36492            self.write(")");
36493        }
36494        Ok(())
36495    }
36496
36497    fn generate_transient_property(&mut self, e: &TransientProperty) -> Result<()> {
36498        use crate::dialects::DialectType;
36499        // TRANSIENT is Snowflake-specific; skip for other dialects
36500        if let Some(this) = &e.this {
36501            self.generate_expression(this)?;
36502            if matches!(self.config.dialect, Some(DialectType::Snowflake) | None) {
36503                self.write_space();
36504            }
36505        }
36506        if matches!(self.config.dialect, Some(DialectType::Snowflake) | None) {
36507            self.write_keyword("TRANSIENT");
36508        }
36509        Ok(())
36510    }
36511
36512    fn generate_translate(&mut self, e: &Translate) -> Result<()> {
36513        // TRANSLATE(this, from_, to)
36514        self.write_keyword("TRANSLATE");
36515        self.write("(");
36516        self.generate_expression(&e.this)?;
36517        if let Some(from) = &e.from_ {
36518            self.write(", ");
36519            self.generate_expression(from)?;
36520        }
36521        if let Some(to) = &e.to {
36522            self.write(", ");
36523            self.generate_expression(to)?;
36524        }
36525        self.write(")");
36526        Ok(())
36527    }
36528
36529    fn generate_translate_characters(&mut self, e: &TranslateCharacters) -> Result<()> {
36530        // TRANSLATE(this USING expression)
36531        self.write_keyword("TRANSLATE");
36532        self.write("(");
36533        self.generate_expression(&e.this)?;
36534        self.write_space();
36535        self.write_keyword("USING");
36536        self.write_space();
36537        self.generate_expression(&e.expression)?;
36538        if e.with_error.is_some() {
36539            self.write_space();
36540            self.write_keyword("WITH ERROR");
36541        }
36542        self.write(")");
36543        Ok(())
36544    }
36545
36546    fn generate_truncate_table(&mut self, e: &TruncateTable) -> Result<()> {
36547        // TRUNCATE TABLE table1, table2, ...
36548        self.write_keyword("TRUNCATE TABLE");
36549        self.write_space();
36550        for (i, expr) in e.expressions.iter().enumerate() {
36551            if i > 0 {
36552                self.write(", ");
36553            }
36554            self.generate_expression(expr)?;
36555        }
36556        Ok(())
36557    }
36558
36559    fn generate_try_base64_decode_binary(&mut self, e: &TryBase64DecodeBinary) -> Result<()> {
36560        // TRY_BASE64_DECODE_BINARY(this, [alphabet])
36561        self.write_keyword("TRY_BASE64_DECODE_BINARY");
36562        self.write("(");
36563        self.generate_expression(&e.this)?;
36564        if let Some(alphabet) = &e.alphabet {
36565            self.write(", ");
36566            self.generate_expression(alphabet)?;
36567        }
36568        self.write(")");
36569        Ok(())
36570    }
36571
36572    fn generate_try_base64_decode_string(&mut self, e: &TryBase64DecodeString) -> Result<()> {
36573        // TRY_BASE64_DECODE_STRING(this, [alphabet])
36574        self.write_keyword("TRY_BASE64_DECODE_STRING");
36575        self.write("(");
36576        self.generate_expression(&e.this)?;
36577        if let Some(alphabet) = &e.alphabet {
36578            self.write(", ");
36579            self.generate_expression(alphabet)?;
36580        }
36581        self.write(")");
36582        Ok(())
36583    }
36584
36585    fn generate_try_to_decfloat(&mut self, e: &TryToDecfloat) -> Result<()> {
36586        // TRY_TO_DECFLOAT(this, [format])
36587        self.write_keyword("TRY_TO_DECFLOAT");
36588        self.write("(");
36589        self.generate_expression(&e.this)?;
36590        if let Some(format) = &e.format {
36591            self.write(", '");
36592            self.write(format);
36593            self.write("'");
36594        }
36595        self.write(")");
36596        Ok(())
36597    }
36598
36599    fn generate_ts_or_ds_add(&mut self, e: &TsOrDsAdd) -> Result<()> {
36600        // TS_OR_DS_ADD(this, expression, [unit], [return_type])
36601        self.write_keyword("TS_OR_DS_ADD");
36602        self.write("(");
36603        self.generate_expression(&e.this)?;
36604        self.write(", ");
36605        self.generate_expression(&e.expression)?;
36606        if let Some(unit) = &e.unit {
36607            self.write(", ");
36608            self.write_keyword(unit);
36609        }
36610        if let Some(return_type) = &e.return_type {
36611            self.write(", ");
36612            self.generate_expression(return_type)?;
36613        }
36614        self.write(")");
36615        Ok(())
36616    }
36617
36618    fn generate_ts_or_ds_diff(&mut self, e: &TsOrDsDiff) -> Result<()> {
36619        // TS_OR_DS_DIFF(this, expression, [unit])
36620        self.write_keyword("TS_OR_DS_DIFF");
36621        self.write("(");
36622        self.generate_expression(&e.this)?;
36623        self.write(", ");
36624        self.generate_expression(&e.expression)?;
36625        if let Some(unit) = &e.unit {
36626            self.write(", ");
36627            self.write_keyword(unit);
36628        }
36629        self.write(")");
36630        Ok(())
36631    }
36632
36633    fn generate_ts_or_ds_to_date(&mut self, e: &TsOrDsToDate) -> Result<()> {
36634        let default_time_format = "%Y-%m-%d %H:%M:%S";
36635        let default_date_format = "%Y-%m-%d";
36636        let has_non_default_format = e.format.as_ref().map_or(false, |f| {
36637            f != default_time_format && f != default_date_format
36638        });
36639
36640        if has_non_default_format {
36641            // With non-default format: dialect-specific handling
36642            let fmt = e.format.as_ref().unwrap();
36643            match self.config.dialect {
36644                Some(DialectType::MySQL) | Some(DialectType::StarRocks) => {
36645                    // MySQL/StarRocks: STR_TO_DATE(x, fmt) - no CAST wrapper
36646                    // STR_TO_DATE is the MySQL-native form of StrToTime
36647                    let str_to_time = crate::expressions::StrToTime {
36648                        this: Box::new((*e.this).clone()),
36649                        format: fmt.clone(),
36650                        zone: None,
36651                        safe: None,
36652                        target_type: None,
36653                    };
36654                    self.generate_str_to_time(&str_to_time)?;
36655                }
36656                Some(DialectType::Hive)
36657                | Some(DialectType::Spark)
36658                | Some(DialectType::Databricks) => {
36659                    // Hive/Spark: TO_DATE(x, java_fmt)
36660                    self.write_keyword("TO_DATE");
36661                    self.write("(");
36662                    self.generate_expression(&e.this)?;
36663                    self.write(", '");
36664                    self.write(&Self::strftime_to_java_format(fmt));
36665                    self.write("')");
36666                }
36667                Some(DialectType::Snowflake) => {
36668                    // Snowflake: TO_DATE(x, snowflake_fmt)
36669                    self.write_keyword("TO_DATE");
36670                    self.write("(");
36671                    self.generate_expression(&e.this)?;
36672                    self.write(", '");
36673                    self.write(&Self::strftime_to_snowflake_format(fmt));
36674                    self.write("')");
36675                }
36676                Some(DialectType::Doris) => {
36677                    // Doris: TO_DATE(x) - ignores format
36678                    self.write_keyword("TO_DATE");
36679                    self.write("(");
36680                    self.generate_expression(&e.this)?;
36681                    self.write(")");
36682                }
36683                _ => {
36684                    // Default: CAST(STR_TO_TIME(x, fmt) AS DATE)
36685                    self.write_keyword("CAST");
36686                    self.write("(");
36687                    let str_to_time = crate::expressions::StrToTime {
36688                        this: Box::new((*e.this).clone()),
36689                        format: fmt.clone(),
36690                        zone: None,
36691                        safe: None,
36692                        target_type: None,
36693                    };
36694                    self.generate_str_to_time(&str_to_time)?;
36695                    self.write_keyword(" AS ");
36696                    self.write_keyword("DATE");
36697                    self.write(")");
36698                }
36699            }
36700        } else {
36701            // Without format (or default format): simple date conversion
36702            match self.config.dialect {
36703                Some(DialectType::MySQL)
36704                | Some(DialectType::SQLite)
36705                | Some(DialectType::StarRocks) => {
36706                    // MySQL/SQLite/StarRocks: DATE(x)
36707                    self.write_keyword("DATE");
36708                    self.write("(");
36709                    self.generate_expression(&e.this)?;
36710                    self.write(")");
36711                }
36712                Some(DialectType::Hive)
36713                | Some(DialectType::Spark)
36714                | Some(DialectType::Databricks)
36715                | Some(DialectType::Snowflake)
36716                | Some(DialectType::Doris) => {
36717                    // Hive/Spark/Databricks/Snowflake/Doris: TO_DATE(x)
36718                    self.write_keyword("TO_DATE");
36719                    self.write("(");
36720                    self.generate_expression(&e.this)?;
36721                    self.write(")");
36722                }
36723                Some(DialectType::Presto)
36724                | Some(DialectType::Trino)
36725                | Some(DialectType::Athena) => {
36726                    // Presto/Trino: CAST(CAST(x AS TIMESTAMP) AS DATE)
36727                    self.write_keyword("CAST");
36728                    self.write("(");
36729                    self.write_keyword("CAST");
36730                    self.write("(");
36731                    self.generate_expression(&e.this)?;
36732                    self.write_keyword(" AS ");
36733                    self.write_keyword("TIMESTAMP");
36734                    self.write(")");
36735                    self.write_keyword(" AS ");
36736                    self.write_keyword("DATE");
36737                    self.write(")");
36738                }
36739                Some(DialectType::ClickHouse) => {
36740                    // ClickHouse: CAST(x AS Nullable(DATE))
36741                    self.write_keyword("CAST");
36742                    self.write("(");
36743                    self.generate_expression(&e.this)?;
36744                    self.write_keyword(" AS ");
36745                    self.write("Nullable(DATE)");
36746                    self.write(")");
36747                }
36748                _ => {
36749                    // Default: CAST(x AS DATE)
36750                    self.write_keyword("CAST");
36751                    self.write("(");
36752                    self.generate_expression(&e.this)?;
36753                    self.write_keyword(" AS ");
36754                    self.write_keyword("DATE");
36755                    self.write(")");
36756                }
36757            }
36758        }
36759        Ok(())
36760    }
36761
36762    fn generate_ts_or_ds_to_time(&mut self, e: &TsOrDsToTime) -> Result<()> {
36763        // TS_OR_DS_TO_TIME(this, [format])
36764        self.write_keyword("TS_OR_DS_TO_TIME");
36765        self.write("(");
36766        self.generate_expression(&e.this)?;
36767        if let Some(format) = &e.format {
36768            self.write(", '");
36769            self.write(format);
36770            self.write("'");
36771        }
36772        self.write(")");
36773        Ok(())
36774    }
36775
36776    fn generate_unhex(&mut self, e: &Unhex) -> Result<()> {
36777        // UNHEX(this, [expression])
36778        self.write_keyword("UNHEX");
36779        self.write("(");
36780        self.generate_expression(&e.this)?;
36781        if let Some(expression) = &e.expression {
36782            self.write(", ");
36783            self.generate_expression(expression)?;
36784        }
36785        self.write(")");
36786        Ok(())
36787    }
36788
36789    fn generate_unicode_string(&mut self, e: &UnicodeString) -> Result<()> {
36790        // U&this [UESCAPE escape]
36791        self.write("U&");
36792        self.generate_expression(&e.this)?;
36793        if let Some(escape) = &e.escape {
36794            self.write_space();
36795            self.write_keyword("UESCAPE");
36796            self.write_space();
36797            self.generate_expression(escape)?;
36798        }
36799        Ok(())
36800    }
36801
36802    fn generate_uniform(&mut self, e: &Uniform) -> Result<()> {
36803        // UNIFORM(this, expression, [gen], [seed])
36804        self.write_keyword("UNIFORM");
36805        self.write("(");
36806        self.generate_expression(&e.this)?;
36807        self.write(", ");
36808        self.generate_expression(&e.expression)?;
36809        if let Some(gen) = &e.gen {
36810            self.write(", ");
36811            self.generate_expression(gen)?;
36812        }
36813        if let Some(seed) = &e.seed {
36814            self.write(", ");
36815            self.generate_expression(seed)?;
36816        }
36817        self.write(")");
36818        Ok(())
36819    }
36820
36821    fn generate_unique_column_constraint(&mut self, e: &UniqueColumnConstraint) -> Result<()> {
36822        // UNIQUE [NULLS NOT DISTINCT] [this] [index_type] [on_conflict] [options]
36823        self.write_keyword("UNIQUE");
36824        // Output NULLS NOT DISTINCT if nulls is set (PostgreSQL 15+ feature)
36825        if e.nulls.is_some() {
36826            self.write(" NULLS NOT DISTINCT");
36827        }
36828        if let Some(this) = &e.this {
36829            self.write_space();
36830            self.generate_expression(this)?;
36831        }
36832        if let Some(index_type) = &e.index_type {
36833            self.write(" USING ");
36834            self.generate_expression(index_type)?;
36835        }
36836        if let Some(on_conflict) = &e.on_conflict {
36837            self.write_space();
36838            self.generate_expression(on_conflict)?;
36839        }
36840        for opt in &e.options {
36841            self.write_space();
36842            self.generate_expression(opt)?;
36843        }
36844        Ok(())
36845    }
36846
36847    fn generate_unique_key_property(&mut self, e: &UniqueKeyProperty) -> Result<()> {
36848        // UNIQUE KEY (expressions)
36849        self.write_keyword("UNIQUE KEY");
36850        self.write(" (");
36851        for (i, expr) in e.expressions.iter().enumerate() {
36852            if i > 0 {
36853                self.write(", ");
36854            }
36855            self.generate_expression(expr)?;
36856        }
36857        self.write(")");
36858        Ok(())
36859    }
36860
36861    fn generate_rollup_property(&mut self, e: &RollupProperty) -> Result<()> {
36862        // ROLLUP (r1(col1, col2), r2(col1))
36863        self.write_keyword("ROLLUP");
36864        self.write(" (");
36865        for (i, index) in e.expressions.iter().enumerate() {
36866            if i > 0 {
36867                self.write(", ");
36868            }
36869            self.generate_identifier(&index.name)?;
36870            self.write("(");
36871            for (j, col) in index.expressions.iter().enumerate() {
36872                if j > 0 {
36873                    self.write(", ");
36874                }
36875                self.generate_identifier(col)?;
36876            }
36877            self.write(")");
36878        }
36879        self.write(")");
36880        Ok(())
36881    }
36882
36883    fn generate_unix_to_str(&mut self, e: &UnixToStr) -> Result<()> {
36884        match self.config.dialect {
36885            Some(DialectType::DuckDB) => {
36886                // DuckDB: STRFTIME(TO_TIMESTAMP(value), format)
36887                self.write_keyword("STRFTIME");
36888                self.write("(");
36889                self.write_keyword("TO_TIMESTAMP");
36890                self.write("(");
36891                self.generate_expression(&e.this)?;
36892                self.write("), '");
36893                if let Some(format) = &e.format {
36894                    self.write(format);
36895                }
36896                self.write("')");
36897            }
36898            Some(DialectType::Hive) => {
36899                // Hive: FROM_UNIXTIME(value, format) - elide format when it's the default
36900                self.write_keyword("FROM_UNIXTIME");
36901                self.write("(");
36902                self.generate_expression(&e.this)?;
36903                if let Some(format) = &e.format {
36904                    if format != "yyyy-MM-dd HH:mm:ss" {
36905                        self.write(", '");
36906                        self.write(format);
36907                        self.write("'");
36908                    }
36909                }
36910                self.write(")");
36911            }
36912            Some(DialectType::Presto) | Some(DialectType::Trino) => {
36913                // Presto: DATE_FORMAT(FROM_UNIXTIME(value), format)
36914                self.write_keyword("DATE_FORMAT");
36915                self.write("(");
36916                self.write_keyword("FROM_UNIXTIME");
36917                self.write("(");
36918                self.generate_expression(&e.this)?;
36919                self.write("), '");
36920                if let Some(format) = &e.format {
36921                    self.write(format);
36922                }
36923                self.write("')");
36924            }
36925            Some(DialectType::Spark) | Some(DialectType::Databricks) => {
36926                // Spark: FROM_UNIXTIME(value, format)
36927                self.write_keyword("FROM_UNIXTIME");
36928                self.write("(");
36929                self.generate_expression(&e.this)?;
36930                if let Some(format) = &e.format {
36931                    self.write(", '");
36932                    self.write(format);
36933                    self.write("'");
36934                }
36935                self.write(")");
36936            }
36937            _ => {
36938                // Default: UNIX_TO_STR(this, [format])
36939                self.write_keyword("UNIX_TO_STR");
36940                self.write("(");
36941                self.generate_expression(&e.this)?;
36942                if let Some(format) = &e.format {
36943                    self.write(", '");
36944                    self.write(format);
36945                    self.write("'");
36946                }
36947                self.write(")");
36948            }
36949        }
36950        Ok(())
36951    }
36952
36953    fn generate_unix_to_time(&mut self, e: &UnixToTime) -> Result<()> {
36954        use crate::dialects::DialectType;
36955        let scale = e.scale.unwrap_or(0); // 0 = seconds
36956
36957        match self.config.dialect {
36958            Some(DialectType::Snowflake) => {
36959                // Snowflake: TO_TIMESTAMP(value[, scale]) - skip scale for seconds (0)
36960                self.write_keyword("TO_TIMESTAMP");
36961                self.write("(");
36962                self.generate_expression(&e.this)?;
36963                if let Some(s) = e.scale {
36964                    if s > 0 {
36965                        self.write(", ");
36966                        self.write(&s.to_string());
36967                    }
36968                }
36969                self.write(")");
36970            }
36971            Some(DialectType::BigQuery) => {
36972                // BigQuery: TIMESTAMP_SECONDS(value) / TIMESTAMP_MILLIS(value)
36973                // or TIMESTAMP_SECONDS(CAST(value / POWER(10, scale) AS INT64)) for other scales
36974                match scale {
36975                    0 => {
36976                        self.write_keyword("TIMESTAMP_SECONDS");
36977                        self.write("(");
36978                        self.generate_expression(&e.this)?;
36979                        self.write(")");
36980                    }
36981                    3 => {
36982                        self.write_keyword("TIMESTAMP_MILLIS");
36983                        self.write("(");
36984                        self.generate_expression(&e.this)?;
36985                        self.write(")");
36986                    }
36987                    6 => {
36988                        self.write_keyword("TIMESTAMP_MICROS");
36989                        self.write("(");
36990                        self.generate_expression(&e.this)?;
36991                        self.write(")");
36992                    }
36993                    _ => {
36994                        // TIMESTAMP_SECONDS(CAST(value / POWER(10, scale) AS INT64))
36995                        self.write_keyword("TIMESTAMP_SECONDS");
36996                        self.write("(CAST(");
36997                        self.generate_expression(&e.this)?;
36998                        self.write(&format!(" / POWER(10, {}) AS INT64))", scale));
36999                    }
37000                }
37001            }
37002            Some(DialectType::Spark) => {
37003                // Spark: CAST(FROM_UNIXTIME(value) AS TIMESTAMP) for scale=0
37004                // TIMESTAMP_MILLIS(value) for scale=3
37005                // TIMESTAMP_MICROS(value) for scale=6
37006                // TIMESTAMP_SECONDS(value / POWER(10, scale)) for other scales
37007                match scale {
37008                    0 => {
37009                        self.write_keyword("CAST");
37010                        self.write("(");
37011                        self.write_keyword("FROM_UNIXTIME");
37012                        self.write("(");
37013                        self.generate_expression(&e.this)?;
37014                        self.write(") ");
37015                        self.write_keyword("AS TIMESTAMP");
37016                        self.write(")");
37017                    }
37018                    3 => {
37019                        self.write_keyword("TIMESTAMP_MILLIS");
37020                        self.write("(");
37021                        self.generate_expression(&e.this)?;
37022                        self.write(")");
37023                    }
37024                    6 => {
37025                        self.write_keyword("TIMESTAMP_MICROS");
37026                        self.write("(");
37027                        self.generate_expression(&e.this)?;
37028                        self.write(")");
37029                    }
37030                    _ => {
37031                        self.write_keyword("TIMESTAMP_SECONDS");
37032                        self.write("(");
37033                        self.generate_expression(&e.this)?;
37034                        self.write(&format!(" / POWER(10, {}))", scale));
37035                    }
37036                }
37037            }
37038            Some(DialectType::Databricks) => {
37039                // Databricks: CAST(FROM_UNIXTIME(value) AS TIMESTAMP) for scale=0
37040                // TIMESTAMP_MILLIS(value) for scale=3
37041                // TIMESTAMP_MICROS(value) for scale=6
37042                match scale {
37043                    0 => {
37044                        self.write_keyword("CAST");
37045                        self.write("(");
37046                        self.write_keyword("FROM_UNIXTIME");
37047                        self.write("(");
37048                        self.generate_expression(&e.this)?;
37049                        self.write(") ");
37050                        self.write_keyword("AS TIMESTAMP");
37051                        self.write(")");
37052                    }
37053                    3 => {
37054                        self.write_keyword("TIMESTAMP_MILLIS");
37055                        self.write("(");
37056                        self.generate_expression(&e.this)?;
37057                        self.write(")");
37058                    }
37059                    6 => {
37060                        self.write_keyword("TIMESTAMP_MICROS");
37061                        self.write("(");
37062                        self.generate_expression(&e.this)?;
37063                        self.write(")");
37064                    }
37065                    _ => {
37066                        self.write_keyword("TIMESTAMP_SECONDS");
37067                        self.write("(");
37068                        self.generate_expression(&e.this)?;
37069                        self.write(&format!(" / POWER(10, {}))", scale));
37070                    }
37071                }
37072            }
37073            Some(DialectType::Hive) => {
37074                // Hive: FROM_UNIXTIME(value)
37075                if scale == 0 {
37076                    self.write_keyword("FROM_UNIXTIME");
37077                    self.write("(");
37078                    self.generate_expression(&e.this)?;
37079                    self.write(")");
37080                } else {
37081                    self.write_keyword("FROM_UNIXTIME");
37082                    self.write("(");
37083                    self.generate_expression(&e.this)?;
37084                    self.write(&format!(" / POWER(10, {})", scale));
37085                    self.write(")");
37086                }
37087            }
37088            Some(DialectType::Presto) | Some(DialectType::Trino) => {
37089                // Presto: FROM_UNIXTIME(CAST(value AS DOUBLE) / POW(10, scale)) for scale > 0
37090                // FROM_UNIXTIME(value) for scale=0
37091                if scale == 0 {
37092                    self.write_keyword("FROM_UNIXTIME");
37093                    self.write("(");
37094                    self.generate_expression(&e.this)?;
37095                    self.write(")");
37096                } else {
37097                    self.write_keyword("FROM_UNIXTIME");
37098                    self.write("(CAST(");
37099                    self.generate_expression(&e.this)?;
37100                    self.write(&format!(" AS DOUBLE) / POW(10, {}))", scale));
37101                }
37102            }
37103            Some(DialectType::DuckDB) => {
37104                // DuckDB: TO_TIMESTAMP(value) for scale=0
37105                // EPOCH_MS(value) for scale=3
37106                // MAKE_TIMESTAMP(value) for scale=6
37107                match scale {
37108                    0 => {
37109                        self.write_keyword("TO_TIMESTAMP");
37110                        self.write("(");
37111                        self.generate_expression(&e.this)?;
37112                        self.write(")");
37113                    }
37114                    3 => {
37115                        self.write_keyword("EPOCH_MS");
37116                        self.write("(");
37117                        self.generate_expression(&e.this)?;
37118                        self.write(")");
37119                    }
37120                    6 => {
37121                        self.write_keyword("MAKE_TIMESTAMP");
37122                        self.write("(");
37123                        self.generate_expression(&e.this)?;
37124                        self.write(")");
37125                    }
37126                    _ => {
37127                        self.write_keyword("TO_TIMESTAMP");
37128                        self.write("(");
37129                        self.generate_expression(&e.this)?;
37130                        self.write(&format!(" / POWER(10, {}))", scale));
37131                        self.write_keyword(" AT TIME ZONE");
37132                        self.write(" 'UTC'");
37133                    }
37134                }
37135            }
37136            Some(DialectType::Doris) | Some(DialectType::StarRocks) => {
37137                // Doris/StarRocks: FROM_UNIXTIME(value)
37138                self.write_keyword("FROM_UNIXTIME");
37139                self.write("(");
37140                self.generate_expression(&e.this)?;
37141                self.write(")");
37142            }
37143            Some(DialectType::Oracle) => {
37144                // Oracle: TO_DATE('1970-01-01', 'YYYY-MM-DD') + (x / 86400)
37145                self.write("TO_DATE('1970-01-01', 'YYYY-MM-DD') + (");
37146                self.generate_expression(&e.this)?;
37147                self.write(" / 86400)");
37148            }
37149            Some(DialectType::Redshift) => {
37150                // Redshift: (TIMESTAMP 'epoch' + value * INTERVAL '1 SECOND') for scale=0
37151                // (TIMESTAMP 'epoch' + (value / POWER(10, scale)) * INTERVAL '1 SECOND') for scale > 0
37152                self.write("(TIMESTAMP 'epoch' + ");
37153                if scale == 0 {
37154                    self.generate_expression(&e.this)?;
37155                } else {
37156                    self.write("(");
37157                    self.generate_expression(&e.this)?;
37158                    self.write(&format!(" / POWER(10, {}))", scale));
37159                }
37160                self.write(" * INTERVAL '1 SECOND')");
37161            }
37162            Some(DialectType::Exasol) => {
37163                // Exasol: FROM_POSIX_TIME(value)
37164                self.write_keyword("FROM_POSIX_TIME");
37165                self.write("(");
37166                self.generate_expression(&e.this)?;
37167                self.write(")");
37168            }
37169            _ => {
37170                // Default: TO_TIMESTAMP(value[, scale])
37171                self.write_keyword("TO_TIMESTAMP");
37172                self.write("(");
37173                self.generate_expression(&e.this)?;
37174                if let Some(s) = e.scale {
37175                    self.write(", ");
37176                    self.write(&s.to_string());
37177                }
37178                self.write(")");
37179            }
37180        }
37181        Ok(())
37182    }
37183
37184    fn generate_unpivot_columns(&mut self, e: &UnpivotColumns) -> Result<()> {
37185        // NAME col VALUE col1, col2, ...
37186        if !matches!(&*e.this, Expression::Null(_)) {
37187            self.write_keyword("NAME");
37188            self.write_space();
37189            self.generate_expression(&e.this)?;
37190        }
37191        if !e.expressions.is_empty() {
37192            self.write_space();
37193            self.write_keyword("VALUE");
37194            self.write_space();
37195            for (i, expr) in e.expressions.iter().enumerate() {
37196                if i > 0 {
37197                    self.write(", ");
37198                }
37199                self.generate_expression(expr)?;
37200            }
37201        }
37202        Ok(())
37203    }
37204
37205    fn generate_user_defined_function(&mut self, e: &UserDefinedFunction) -> Result<()> {
37206        // this(expressions) or (this)(expressions)
37207        if e.wrapped.is_some() {
37208            self.write("(");
37209        }
37210        self.generate_expression(&e.this)?;
37211        if e.wrapped.is_some() {
37212            self.write(")");
37213        }
37214        self.write("(");
37215        for (i, expr) in e.expressions.iter().enumerate() {
37216            if i > 0 {
37217                self.write(", ");
37218            }
37219            self.generate_expression(expr)?;
37220        }
37221        self.write(")");
37222        Ok(())
37223    }
37224
37225    fn generate_using_template_property(&mut self, e: &UsingTemplateProperty) -> Result<()> {
37226        // USING TEMPLATE this
37227        self.write_keyword("USING TEMPLATE");
37228        self.write_space();
37229        self.generate_expression(&e.this)?;
37230        Ok(())
37231    }
37232
37233    fn generate_utc_time(&mut self, _e: &UtcTime) -> Result<()> {
37234        // UTC_TIME
37235        self.write_keyword("UTC_TIME");
37236        Ok(())
37237    }
37238
37239    fn generate_utc_timestamp(&mut self, _e: &UtcTimestamp) -> Result<()> {
37240        if matches!(
37241            self.config.dialect,
37242            Some(crate::dialects::DialectType::ClickHouse)
37243        ) {
37244            self.write_keyword("CURRENT_TIMESTAMP");
37245            self.write("('UTC')");
37246        } else {
37247            self.write_keyword("UTC_TIMESTAMP");
37248        }
37249        Ok(())
37250    }
37251
37252    fn generate_uuid(&mut self, e: &Uuid) -> Result<()> {
37253        use crate::dialects::DialectType;
37254        // Choose UUID function name based on target dialect
37255        let func_name = match self.config.dialect {
37256            Some(DialectType::Snowflake) => "UUID_STRING",
37257            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift) => "GEN_RANDOM_UUID",
37258            Some(DialectType::BigQuery) => "GENERATE_UUID",
37259            _ => {
37260                if let Some(name) = &e.name {
37261                    name.as_str()
37262                } else {
37263                    "UUID"
37264                }
37265            }
37266        };
37267        self.write_keyword(func_name);
37268        self.write("(");
37269        if let Some(this) = &e.this {
37270            self.generate_expression(this)?;
37271        }
37272        self.write(")");
37273        Ok(())
37274    }
37275
37276    fn generate_var_map(&mut self, e: &VarMap) -> Result<()> {
37277        // MAP(key1, value1, key2, value2, ...)
37278        self.write_keyword("MAP");
37279        self.write("(");
37280        let mut first = true;
37281        for (k, v) in e.keys.iter().zip(e.values.iter()) {
37282            if !first {
37283                self.write(", ");
37284            }
37285            self.generate_expression(k)?;
37286            self.write(", ");
37287            self.generate_expression(v)?;
37288            first = false;
37289        }
37290        self.write(")");
37291        Ok(())
37292    }
37293
37294    fn generate_vector_search(&mut self, e: &VectorSearch) -> Result<()> {
37295        // VECTOR_SEARCH(this, column_to_search, query_table, query_column_to_search, top_k, distance_type, ...)
37296        self.write_keyword("VECTOR_SEARCH");
37297        self.write("(");
37298        self.generate_expression(&e.this)?;
37299        if let Some(col) = &e.column_to_search {
37300            self.write(", ");
37301            self.generate_expression(col)?;
37302        }
37303        if let Some(query_table) = &e.query_table {
37304            self.write(", ");
37305            self.generate_expression(query_table)?;
37306        }
37307        if let Some(query_col) = &e.query_column_to_search {
37308            self.write(", ");
37309            self.generate_expression(query_col)?;
37310        }
37311        if let Some(top_k) = &e.top_k {
37312            self.write(", ");
37313            self.generate_expression(top_k)?;
37314        }
37315        if let Some(dist_type) = &e.distance_type {
37316            self.write(", ");
37317            self.generate_expression(dist_type)?;
37318        }
37319        self.write(")");
37320        Ok(())
37321    }
37322
37323    fn generate_version(&mut self, e: &Version) -> Result<()> {
37324        // Python: f"FOR {expression.name} {kind} {expr}"
37325        // e.this = Identifier("TIMESTAMP" or "VERSION")
37326        // e.kind = "AS OF" (or "BETWEEN", etc.)
37327        // e.expression = the value expression
37328        // Hive does NOT use the FOR prefix for time travel
37329        use crate::dialects::DialectType;
37330        let skip_for = matches!(
37331            self.config.dialect,
37332            Some(DialectType::Hive) | Some(DialectType::Spark) | Some(DialectType::Databricks)
37333        );
37334        if !skip_for {
37335            self.write_keyword("FOR");
37336            self.write_space();
37337        }
37338        // Extract the name from this (which is an Identifier expression)
37339        match e.this.as_ref() {
37340            Expression::Identifier(ident) => {
37341                self.write_keyword(&ident.name);
37342            }
37343            _ => {
37344                self.generate_expression(&e.this)?;
37345            }
37346        }
37347        self.write_space();
37348        self.write_keyword(&e.kind);
37349        if let Some(expression) = &e.expression {
37350            self.write_space();
37351            self.generate_expression(expression)?;
37352        }
37353        Ok(())
37354    }
37355
37356    fn generate_view_attribute_property(&mut self, e: &ViewAttributeProperty) -> Result<()> {
37357        // Python: return self.sql(expression, "this")
37358        self.generate_expression(&e.this)?;
37359        Ok(())
37360    }
37361
37362    fn generate_volatile_property(&mut self, e: &VolatileProperty) -> Result<()> {
37363        // Python: return "VOLATILE" if expression.args.get("this") is None else "NOT VOLATILE"
37364        if e.this.is_some() {
37365            self.write_keyword("NOT VOLATILE");
37366        } else {
37367            self.write_keyword("VOLATILE");
37368        }
37369        Ok(())
37370    }
37371
37372    fn generate_watermark_column_constraint(
37373        &mut self,
37374        e: &WatermarkColumnConstraint,
37375    ) -> Result<()> {
37376        // Python: f"WATERMARK FOR {self.sql(expression, 'this')} AS {self.sql(expression, 'expression')}"
37377        self.write_keyword("WATERMARK FOR");
37378        self.write_space();
37379        self.generate_expression(&e.this)?;
37380        self.write_space();
37381        self.write_keyword("AS");
37382        self.write_space();
37383        self.generate_expression(&e.expression)?;
37384        Ok(())
37385    }
37386
37387    fn generate_week(&mut self, e: &Week) -> Result<()> {
37388        // Python: return self.func("WEEK", expression.this, expression.args.get("mode"))
37389        self.write_keyword("WEEK");
37390        self.write("(");
37391        self.generate_expression(&e.this)?;
37392        if let Some(mode) = &e.mode {
37393            self.write(", ");
37394            self.generate_expression(mode)?;
37395        }
37396        self.write(")");
37397        Ok(())
37398    }
37399
37400    fn generate_when(&mut self, e: &When) -> Result<()> {
37401        // Python: WHEN {matched}{source}{condition} THEN {then}
37402        // matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED"
37403        // source = " BY SOURCE" if MATCHED_BY_SOURCE and expression.args.get("source") else ""
37404        self.write_keyword("WHEN");
37405        self.write_space();
37406
37407        // Check if matched
37408        if let Some(matched) = &e.matched {
37409            // Check the expression - if it's a boolean true, use MATCHED, otherwise NOT MATCHED
37410            match matched.as_ref() {
37411                Expression::Boolean(b) if b.value => {
37412                    self.write_keyword("MATCHED");
37413                }
37414                _ => {
37415                    self.write_keyword("NOT MATCHED");
37416                }
37417            }
37418        } else {
37419            self.write_keyword("NOT MATCHED");
37420        }
37421
37422        // BY SOURCE / BY TARGET
37423        // source = Boolean(true) means BY SOURCE, Boolean(false) means BY TARGET
37424        // BY TARGET is the default and typically omitted in output
37425        // Only emit if the dialect supports BY SOURCE syntax
37426        if self.config.matched_by_source {
37427            if let Some(source) = &e.source {
37428                if let Expression::Boolean(b) = source.as_ref() {
37429                    if b.value {
37430                        // BY SOURCE
37431                        self.write_space();
37432                        self.write_keyword("BY SOURCE");
37433                    }
37434                    // BY TARGET (b.value == false) is omitted as it's the default
37435                } else {
37436                    // For non-boolean source, output as BY SOURCE (legacy behavior)
37437                    self.write_space();
37438                    self.write_keyword("BY SOURCE");
37439                }
37440            }
37441        }
37442
37443        // Condition
37444        if let Some(condition) = &e.condition {
37445            self.write_space();
37446            self.write_keyword("AND");
37447            self.write_space();
37448            self.generate_expression(condition)?;
37449        }
37450
37451        self.write_space();
37452        self.write_keyword("THEN");
37453        self.write_space();
37454
37455        // Generate the then expression (could be INSERT, UPDATE, DELETE)
37456        // MERGE actions are stored as Tuples with the action keyword as first element
37457        self.generate_merge_action(&e.then)?;
37458
37459        Ok(())
37460    }
37461
37462    fn generate_merge_action(&mut self, action: &Expression) -> Result<()> {
37463        match action {
37464            Expression::Tuple(tuple) => {
37465                let elements = &tuple.expressions;
37466                if elements.is_empty() {
37467                    return self.generate_expression(action);
37468                }
37469                // Check if first element is a Var (INSERT, UPDATE, DELETE, etc.)
37470                match &elements[0] {
37471                    Expression::Var(v) if v.this == "INSERT" => {
37472                        self.write_keyword("INSERT");
37473                        // Spark: INSERT * (insert all columns)
37474                        if elements.len() > 1 && matches!(&elements[1], Expression::Star(_)) {
37475                            self.write(" *");
37476                            if let Some(Expression::Where(w)) = elements.get(2) {
37477                                self.write_space();
37478                                self.generate_where(w)?;
37479                            }
37480                        } else {
37481                            let mut values_idx = 1;
37482                            // Check if second element is column list (Tuple)
37483                            if elements.len() > 1 {
37484                                if let Expression::Tuple(cols) = &elements[1] {
37485                                    // Could be columns or values - if there's a third element, second is columns
37486                                    if elements.len() > 2 {
37487                                        // Second is columns, third is values
37488                                        self.write(" (");
37489                                        for (i, col) in cols.expressions.iter().enumerate() {
37490                                            if i > 0 {
37491                                                self.write(", ");
37492                                            }
37493                                            // Strip MERGE target qualifiers from INSERT column list
37494                                            if !self.merge_strip_qualifiers.is_empty() {
37495                                                let stripped = self.strip_merge_qualifier(col);
37496                                                self.generate_expression(&stripped)?;
37497                                            } else {
37498                                                self.generate_expression(col)?;
37499                                            }
37500                                        }
37501                                        self.write(")");
37502                                        values_idx = 2;
37503                                    } else {
37504                                        // Only two elements: INSERT + values (no explicit columns)
37505                                        values_idx = 1;
37506                                    }
37507                                }
37508                            }
37509                            let mut next_idx = values_idx;
37510                            // Generate VALUES clause
37511                            if values_idx < elements.len()
37512                                && !matches!(&elements[values_idx], Expression::Where(_))
37513                            {
37514                                // Check if it's INSERT ROW (BigQuery) — no VALUES keyword needed
37515                                let is_row = matches!(&elements[values_idx], Expression::Var(v) if v.this == "ROW");
37516                                if !is_row {
37517                                    self.write_space();
37518                                    self.write_keyword("VALUES");
37519                                }
37520                                self.write(" ");
37521                                if let Expression::Tuple(vals) = &elements[values_idx] {
37522                                    self.write("(");
37523                                    for (i, val) in vals.expressions.iter().enumerate() {
37524                                        if i > 0 {
37525                                            self.write(", ");
37526                                        }
37527                                        self.generate_expression(val)?;
37528                                    }
37529                                    self.write(")");
37530                                } else {
37531                                    self.generate_expression(&elements[values_idx])?;
37532                                }
37533                                next_idx += 1;
37534                            }
37535                            if let Some(Expression::Where(w)) = elements.get(next_idx) {
37536                                self.write_space();
37537                                self.generate_where(w)?;
37538                            }
37539                        } // close else for INSERT * check
37540                    }
37541                    Expression::Var(v) if v.this == "UPDATE" => {
37542                        self.write_keyword("UPDATE");
37543                        // Spark: UPDATE * (update all columns)
37544                        if elements.len() > 1 && matches!(&elements[1], Expression::Star(_)) {
37545                            self.write(" *");
37546                            if let Some(Expression::Where(w)) = elements.get(2) {
37547                                self.write_space();
37548                                self.generate_where(w)?;
37549                            }
37550                        } else if elements.len() > 1 {
37551                            self.write_space();
37552                            self.write_keyword("SET");
37553                            // In pretty mode, put assignments on next line with extra indent
37554                            if self.config.pretty {
37555                                self.write_newline();
37556                                self.indent_level += 1;
37557                                self.write_indent();
37558                            } else {
37559                                self.write_space();
37560                            }
37561                            if let Expression::Tuple(assignments) = &elements[1] {
37562                                for (i, assignment) in assignments.expressions.iter().enumerate() {
37563                                    if i > 0 {
37564                                        if self.config.pretty {
37565                                            self.write(",");
37566                                            self.write_newline();
37567                                            self.write_indent();
37568                                        } else {
37569                                            self.write(", ");
37570                                        }
37571                                    }
37572                                    // Strip MERGE target qualifiers from left side of UPDATE SET
37573                                    if !self.merge_strip_qualifiers.is_empty() {
37574                                        self.generate_merge_set_assignment(assignment)?;
37575                                    } else {
37576                                        self.generate_expression(assignment)?;
37577                                    }
37578                                }
37579                            } else {
37580                                self.generate_expression(&elements[1])?;
37581                            }
37582                            if self.config.pretty {
37583                                self.indent_level -= 1;
37584                            }
37585                            if let Some(Expression::Where(w)) = elements.get(2) {
37586                                self.write_space();
37587                                self.generate_where(w)?;
37588                            }
37589                        }
37590                    }
37591                    Expression::Var(v) if v.this == "DELETE" => {
37592                        self.write_keyword("DELETE");
37593                        if let Some(Expression::Where(w)) = elements.get(1) {
37594                            self.write_space();
37595                            self.generate_where(w)?;
37596                        }
37597                    }
37598                    _ => {
37599                        // Fallback: generic tuple generation
37600                        self.generate_expression(action)?;
37601                    }
37602                }
37603            }
37604            Expression::Var(v)
37605                if v.this == "INSERT"
37606                    || v.this == "UPDATE"
37607                    || v.this == "DELETE"
37608                    || v.this == "DO NOTHING" =>
37609            {
37610                self.write_keyword(&v.this);
37611            }
37612            _ => {
37613                self.generate_expression(action)?;
37614            }
37615        }
37616        Ok(())
37617    }
37618
37619    /// Generate a MERGE UPDATE SET assignment, stripping target table qualifier from left side
37620    fn generate_merge_set_assignment(&mut self, assignment: &Expression) -> Result<()> {
37621        match assignment {
37622            Expression::Eq(eq) => {
37623                // Strip qualifier from the left side if it matches a MERGE target name
37624                let stripped_left = self.strip_merge_qualifier(&eq.left);
37625                self.generate_expression(&stripped_left)?;
37626                self.write(" = ");
37627                self.generate_expression(&eq.right)?;
37628                Ok(())
37629            }
37630            other => self.generate_expression(other),
37631        }
37632    }
37633
37634    /// Strip table qualifier from a column reference if it matches a MERGE target name
37635    fn strip_merge_qualifier(&self, expr: &Expression) -> Expression {
37636        match expr {
37637            Expression::Column(col) => {
37638                if let Some(ref table_ident) = col.table {
37639                    if self
37640                        .merge_strip_qualifiers
37641                        .iter()
37642                        .any(|n| n.eq_ignore_ascii_case(&table_ident.name))
37643                    {
37644                        // Strip the table qualifier
37645                        let mut col = col.clone();
37646                        col.table = None;
37647                        return Expression::Column(col);
37648                    }
37649                }
37650                expr.clone()
37651            }
37652            Expression::Dot(dot) => {
37653                // table.column -> column (strip qualifier)
37654                if let Expression::Identifier(id) = &dot.this {
37655                    if self
37656                        .merge_strip_qualifiers
37657                        .iter()
37658                        .any(|n| n.eq_ignore_ascii_case(&id.name))
37659                    {
37660                        return Expression::Identifier(dot.field.clone());
37661                    }
37662                }
37663                expr.clone()
37664            }
37665            _ => expr.clone(),
37666        }
37667    }
37668
37669    fn generate_whens(&mut self, e: &Whens) -> Result<()> {
37670        // Python: return self.expressions(expression, sep=" ", indent=False)
37671        for (i, expr) in e.expressions.iter().enumerate() {
37672            if i > 0 {
37673                // In pretty mode, each WHEN clause on its own line
37674                if self.config.pretty {
37675                    self.write_newline();
37676                    self.write_indent();
37677                } else {
37678                    self.write_space();
37679                }
37680            }
37681            self.generate_expression(expr)?;
37682        }
37683        Ok(())
37684    }
37685
37686    fn generate_where(&mut self, e: &Where) -> Result<()> {
37687        // Python: return f"{self.seg('WHERE')}{self.sep()}{this}"
37688        self.write_keyword("WHERE");
37689        self.write_space();
37690        self.generate_expression(&e.this)?;
37691        Ok(())
37692    }
37693
37694    fn generate_width_bucket(&mut self, e: &WidthBucket) -> Result<()> {
37695        // Python: return self.func("WIDTH_BUCKET", expression.this, ...)
37696        self.write_keyword("WIDTH_BUCKET");
37697        self.write("(");
37698        self.generate_expression(&e.this)?;
37699        if let Some(min_value) = &e.min_value {
37700            self.write(", ");
37701            self.generate_expression(min_value)?;
37702        }
37703        if let Some(max_value) = &e.max_value {
37704            self.write(", ");
37705            self.generate_expression(max_value)?;
37706        }
37707        if let Some(num_buckets) = &e.num_buckets {
37708            self.write(", ");
37709            self.generate_expression(num_buckets)?;
37710        }
37711        self.write(")");
37712        Ok(())
37713    }
37714
37715    fn generate_window(&mut self, e: &WindowSpec) -> Result<()> {
37716        // Window specification: PARTITION BY ... ORDER BY ... frame
37717        self.generate_window_spec(e)
37718    }
37719
37720    fn generate_window_spec(&mut self, e: &WindowSpec) -> Result<()> {
37721        // Window specification: PARTITION BY ... ORDER BY ... frame
37722        let mut has_content = false;
37723
37724        // PARTITION BY
37725        if !e.partition_by.is_empty() {
37726            self.write_keyword("PARTITION BY");
37727            self.write_space();
37728            for (i, expr) in e.partition_by.iter().enumerate() {
37729                if i > 0 {
37730                    self.write(", ");
37731                }
37732                self.generate_expression(expr)?;
37733            }
37734            has_content = true;
37735        }
37736
37737        // ORDER BY
37738        if !e.order_by.is_empty() {
37739            if has_content {
37740                self.write_space();
37741            }
37742            self.write_keyword("ORDER BY");
37743            self.write_space();
37744            for (i, ordered) in e.order_by.iter().enumerate() {
37745                if i > 0 {
37746                    self.write(", ");
37747                }
37748                self.generate_expression(&ordered.this)?;
37749                if ordered.desc {
37750                    self.write_space();
37751                    self.write_keyword("DESC");
37752                } else if ordered.explicit_asc {
37753                    self.write_space();
37754                    self.write_keyword("ASC");
37755                }
37756                if let Some(nulls_first) = ordered.nulls_first {
37757                    self.write_space();
37758                    self.write_keyword("NULLS");
37759                    self.write_space();
37760                    if nulls_first {
37761                        self.write_keyword("FIRST");
37762                    } else {
37763                        self.write_keyword("LAST");
37764                    }
37765                }
37766            }
37767            has_content = true;
37768        }
37769
37770        // Frame specification
37771        if let Some(frame) = &e.frame {
37772            if has_content {
37773                self.write_space();
37774            }
37775            self.generate_window_frame(frame)?;
37776        }
37777
37778        Ok(())
37779    }
37780
37781    fn generate_with_data_property(&mut self, e: &WithDataProperty) -> Result<()> {
37782        // Python: f"WITH {'NO ' if expression.args.get('no') else ''}DATA"
37783        self.write_keyword("WITH");
37784        self.write_space();
37785        if e.no.is_some() {
37786            self.write_keyword("NO");
37787            self.write_space();
37788        }
37789        self.write_keyword("DATA");
37790
37791        // statistics
37792        if let Some(statistics) = &e.statistics {
37793            self.write_space();
37794            self.write_keyword("AND");
37795            self.write_space();
37796            // Check if statistics is true or false
37797            match statistics.as_ref() {
37798                Expression::Boolean(b) if !b.value => {
37799                    self.write_keyword("NO");
37800                    self.write_space();
37801                }
37802                _ => {}
37803            }
37804            self.write_keyword("STATISTICS");
37805        }
37806        Ok(())
37807    }
37808
37809    fn generate_with_fill(&mut self, e: &WithFill) -> Result<()> {
37810        // Python: f"WITH FILL{from_sql}{to_sql}{step_sql}{interpolate}"
37811        self.write_keyword("WITH FILL");
37812
37813        if let Some(from_) = &e.from_ {
37814            self.write_space();
37815            self.write_keyword("FROM");
37816            self.write_space();
37817            self.generate_expression(from_)?;
37818        }
37819
37820        if let Some(to) = &e.to {
37821            self.write_space();
37822            self.write_keyword("TO");
37823            self.write_space();
37824            self.generate_expression(to)?;
37825        }
37826
37827        if let Some(step) = &e.step {
37828            self.write_space();
37829            self.write_keyword("STEP");
37830            self.write_space();
37831            self.generate_expression(step)?;
37832        }
37833
37834        if let Some(staleness) = &e.staleness {
37835            self.write_space();
37836            self.write_keyword("STALENESS");
37837            self.write_space();
37838            self.generate_expression(staleness)?;
37839        }
37840
37841        if let Some(interpolate) = &e.interpolate {
37842            self.write_space();
37843            self.write_keyword("INTERPOLATE");
37844            self.write(" (");
37845            // INTERPOLATE items use reversed alias format: name AS expression
37846            self.generate_interpolate_item(interpolate)?;
37847            self.write(")");
37848        }
37849
37850        Ok(())
37851    }
37852
37853    /// Generate INTERPOLATE items with reversed alias format (name AS expression)
37854    fn generate_interpolate_item(&mut self, expr: &Expression) -> Result<()> {
37855        match expr {
37856            Expression::Alias(alias) => {
37857                // Output as: alias_name AS expression
37858                self.generate_identifier(&alias.alias)?;
37859                self.write_space();
37860                self.write_keyword("AS");
37861                self.write_space();
37862                self.generate_expression(&alias.this)?;
37863            }
37864            Expression::Tuple(tuple) => {
37865                for (i, item) in tuple.expressions.iter().enumerate() {
37866                    if i > 0 {
37867                        self.write(", ");
37868                    }
37869                    self.generate_interpolate_item(item)?;
37870                }
37871            }
37872            other => {
37873                self.generate_expression(other)?;
37874            }
37875        }
37876        Ok(())
37877    }
37878
37879    fn generate_with_journal_table_property(&mut self, e: &WithJournalTableProperty) -> Result<()> {
37880        // Python: return f"WITH JOURNAL TABLE={self.sql(expression, 'this')}"
37881        self.write_keyword("WITH JOURNAL TABLE");
37882        self.write("=");
37883        self.generate_expression(&e.this)?;
37884        Ok(())
37885    }
37886
37887    fn generate_with_operator(&mut self, e: &WithOperator) -> Result<()> {
37888        // Python: return f"{self.sql(expression, 'this')} WITH {self.sql(expression, 'op')}"
37889        self.generate_expression(&e.this)?;
37890        self.write_space();
37891        self.write_keyword("WITH");
37892        self.write_space();
37893        self.write_keyword(&e.op);
37894        Ok(())
37895    }
37896
37897    fn generate_with_procedure_options(&mut self, e: &WithProcedureOptions) -> Result<()> {
37898        // Python: return f"WITH {self.expressions(expression, flat=True)}"
37899        self.write_keyword("WITH");
37900        self.write_space();
37901        for (i, expr) in e.expressions.iter().enumerate() {
37902            if i > 0 {
37903                self.write(", ");
37904            }
37905            self.generate_expression(expr)?;
37906        }
37907        Ok(())
37908    }
37909
37910    fn generate_with_schema_binding_property(
37911        &mut self,
37912        e: &WithSchemaBindingProperty,
37913    ) -> Result<()> {
37914        // Python: return f"WITH {self.sql(expression, 'this')}"
37915        self.write_keyword("WITH");
37916        self.write_space();
37917        self.generate_expression(&e.this)?;
37918        Ok(())
37919    }
37920
37921    fn generate_with_system_versioning_property(
37922        &mut self,
37923        e: &WithSystemVersioningProperty,
37924    ) -> Result<()> {
37925        // Python: complex logic for SYSTEM_VERSIONING with options
37926        // SYSTEM_VERSIONING=ON(HISTORY_TABLE=..., DATA_CONSISTENCY_CHECK=..., HISTORY_RETENTION_PERIOD=...)
37927        // or SYSTEM_VERSIONING=ON/OFF
37928        // with WITH(...) wrapper if with_ is set
37929
37930        let mut parts = Vec::new();
37931
37932        if let Some(this) = &e.this {
37933            // HISTORY_TABLE=...
37934            let mut s = String::from("HISTORY_TABLE=");
37935            let mut gen = Generator::new();
37936            gen.generate_expression(this)?;
37937            s.push_str(&gen.output);
37938            parts.push(s);
37939        }
37940
37941        if let Some(data_consistency) = &e.data_consistency {
37942            let mut s = String::from("DATA_CONSISTENCY_CHECK=");
37943            let mut gen = Generator::new();
37944            gen.generate_expression(data_consistency)?;
37945            s.push_str(&gen.output);
37946            parts.push(s);
37947        }
37948
37949        if let Some(retention_period) = &e.retention_period {
37950            let mut s = String::from("HISTORY_RETENTION_PERIOD=");
37951            let mut gen = Generator::new();
37952            gen.generate_expression(retention_period)?;
37953            s.push_str(&gen.output);
37954            parts.push(s);
37955        }
37956
37957        self.write_keyword("SYSTEM_VERSIONING");
37958        self.write("=");
37959
37960        if !parts.is_empty() {
37961            self.write_keyword("ON");
37962            self.write("(");
37963            self.write(&parts.join(", "));
37964            self.write(")");
37965        } else if e.on.is_some() {
37966            self.write_keyword("ON");
37967        } else {
37968            self.write_keyword("OFF");
37969        }
37970
37971        // Wrap in WITH(...) if with_ is set
37972        if e.with_.is_some() {
37973            let inner = self.output.clone();
37974            self.output.clear();
37975            self.write("WITH(");
37976            self.write(&inner);
37977            self.write(")");
37978        }
37979
37980        Ok(())
37981    }
37982
37983    fn generate_with_table_hint(&mut self, e: &WithTableHint) -> Result<()> {
37984        // Python: f"WITH ({self.expressions(expression, flat=True)})"
37985        self.write_keyword("WITH");
37986        self.write(" (");
37987        for (i, expr) in e.expressions.iter().enumerate() {
37988            if i > 0 {
37989                self.write(", ");
37990            }
37991            self.generate_expression(expr)?;
37992        }
37993        self.write(")");
37994        Ok(())
37995    }
37996
37997    fn generate_xml_element(&mut self, e: &XMLElement) -> Result<()> {
37998        // Python: prefix = "EVALNAME" if expression.args.get("evalname") else "NAME"
37999        // return self.func("XMLELEMENT", name, *expression.expressions)
38000        self.write_keyword("XMLELEMENT");
38001        self.write("(");
38002
38003        if e.evalname.is_some() {
38004            self.write_keyword("EVALNAME");
38005        } else {
38006            self.write_keyword("NAME");
38007        }
38008        self.write_space();
38009        self.generate_expression(&e.this)?;
38010
38011        for expr in &e.expressions {
38012            self.write(", ");
38013            self.generate_expression(expr)?;
38014        }
38015        self.write(")");
38016        Ok(())
38017    }
38018
38019    fn generate_xml_get(&mut self, e: &XMLGet) -> Result<()> {
38020        // XMLGET(this, expression [, instance])
38021        self.write_keyword("XMLGET");
38022        self.write("(");
38023        self.generate_expression(&e.this)?;
38024        self.write(", ");
38025        self.generate_expression(&e.expression)?;
38026        if let Some(instance) = &e.instance {
38027            self.write(", ");
38028            self.generate_expression(instance)?;
38029        }
38030        self.write(")");
38031        Ok(())
38032    }
38033
38034    fn generate_xml_key_value_option(&mut self, e: &XMLKeyValueOption) -> Result<()> {
38035        // Python: this + optional (expr)
38036        self.generate_expression(&e.this)?;
38037        if let Some(expression) = &e.expression {
38038            self.write("(");
38039            self.generate_expression(expression)?;
38040            self.write(")");
38041        }
38042        Ok(())
38043    }
38044
38045    fn generate_xml_table(&mut self, e: &XMLTable) -> Result<()> {
38046        // Python: XMLTABLE(namespaces + this + passing + by_ref + columns)
38047        self.write_keyword("XMLTABLE");
38048        self.write("(");
38049
38050        if self.config.pretty {
38051            self.indent_level += 1;
38052            self.write_newline();
38053            self.write_indent();
38054            self.generate_expression(&e.this)?;
38055
38056            if let Some(passing) = &e.passing {
38057                self.write_newline();
38058                self.write_indent();
38059                self.write_keyword("PASSING");
38060                if let Expression::Tuple(tuple) = passing.as_ref() {
38061                    for expr in &tuple.expressions {
38062                        self.write_newline();
38063                        self.indent_level += 1;
38064                        self.write_indent();
38065                        self.generate_expression(expr)?;
38066                        self.indent_level -= 1;
38067                    }
38068                } else {
38069                    self.write_newline();
38070                    self.indent_level += 1;
38071                    self.write_indent();
38072                    self.generate_expression(passing)?;
38073                    self.indent_level -= 1;
38074                }
38075            }
38076
38077            if e.by_ref.is_some() {
38078                self.write_newline();
38079                self.write_indent();
38080                self.write_keyword("RETURNING SEQUENCE BY REF");
38081            }
38082
38083            if !e.columns.is_empty() {
38084                self.write_newline();
38085                self.write_indent();
38086                self.write_keyword("COLUMNS");
38087                for (i, col) in e.columns.iter().enumerate() {
38088                    self.write_newline();
38089                    self.indent_level += 1;
38090                    self.write_indent();
38091                    self.generate_expression(col)?;
38092                    self.indent_level -= 1;
38093                    if i < e.columns.len() - 1 {
38094                        self.write(",");
38095                    }
38096                }
38097            }
38098
38099            self.indent_level -= 1;
38100            self.write_newline();
38101            self.write_indent();
38102            self.write(")");
38103            return Ok(());
38104        }
38105
38106        // Namespaces - unwrap Tuple to generate comma-separated list without parentheses
38107        if let Some(namespaces) = &e.namespaces {
38108            self.write_keyword("XMLNAMESPACES");
38109            self.write("(");
38110            // Unwrap Tuple if present to avoid extra parentheses
38111            if let Expression::Tuple(tuple) = namespaces.as_ref() {
38112                for (i, expr) in tuple.expressions.iter().enumerate() {
38113                    if i > 0 {
38114                        self.write(", ");
38115                    }
38116                    // Python pattern: if it's an Alias, output as-is; otherwise prepend DEFAULT
38117                    // See xmlnamespace_sql in generator.py
38118                    if !matches!(expr, Expression::Alias(_)) {
38119                        self.write_keyword("DEFAULT");
38120                        self.write_space();
38121                    }
38122                    self.generate_expression(expr)?;
38123                }
38124            } else {
38125                // Single namespace - check if DEFAULT
38126                if !matches!(namespaces.as_ref(), Expression::Alias(_)) {
38127                    self.write_keyword("DEFAULT");
38128                    self.write_space();
38129                }
38130                self.generate_expression(namespaces)?;
38131            }
38132            self.write("), ");
38133        }
38134
38135        // XPath expression
38136        self.generate_expression(&e.this)?;
38137
38138        // PASSING clause - unwrap Tuple to generate comma-separated list without parentheses
38139        if let Some(passing) = &e.passing {
38140            self.write_space();
38141            self.write_keyword("PASSING");
38142            self.write_space();
38143            // Unwrap Tuple if present to avoid extra parentheses
38144            if let Expression::Tuple(tuple) = passing.as_ref() {
38145                for (i, expr) in tuple.expressions.iter().enumerate() {
38146                    if i > 0 {
38147                        self.write(", ");
38148                    }
38149                    self.generate_expression(expr)?;
38150                }
38151            } else {
38152                self.generate_expression(passing)?;
38153            }
38154        }
38155
38156        // RETURNING SEQUENCE BY REF
38157        if e.by_ref.is_some() {
38158            self.write_space();
38159            self.write_keyword("RETURNING SEQUENCE BY REF");
38160        }
38161
38162        // COLUMNS clause
38163        if !e.columns.is_empty() {
38164            self.write_space();
38165            self.write_keyword("COLUMNS");
38166            self.write_space();
38167            for (i, col) in e.columns.iter().enumerate() {
38168                if i > 0 {
38169                    self.write(", ");
38170                }
38171                self.generate_expression(col)?;
38172            }
38173        }
38174
38175        self.write(")");
38176        Ok(())
38177    }
38178
38179    fn generate_xor(&mut self, e: &Xor) -> Result<()> {
38180        // Python: return self.connector_sql(expression, "XOR", stack)
38181        // Handles: this XOR expression or expressions joined by XOR
38182        if let Some(this) = &e.this {
38183            self.generate_expression(this)?;
38184            if let Some(expression) = &e.expression {
38185                self.write_space();
38186                self.write_keyword("XOR");
38187                self.write_space();
38188                self.generate_expression(expression)?;
38189            }
38190        }
38191
38192        // Handle multiple expressions
38193        for (i, expr) in e.expressions.iter().enumerate() {
38194            if i > 0 || e.this.is_some() {
38195                self.write_space();
38196                self.write_keyword("XOR");
38197                self.write_space();
38198            }
38199            self.generate_expression(expr)?;
38200        }
38201        Ok(())
38202    }
38203
38204    fn generate_zipf(&mut self, e: &Zipf) -> Result<()> {
38205        // ZIPF(this, elementcount [, gen])
38206        self.write_keyword("ZIPF");
38207        self.write("(");
38208        self.generate_expression(&e.this)?;
38209        if let Some(elementcount) = &e.elementcount {
38210            self.write(", ");
38211            self.generate_expression(elementcount)?;
38212        }
38213        if let Some(gen) = &e.gen {
38214            self.write(", ");
38215            self.generate_expression(gen)?;
38216        }
38217        self.write(")");
38218        Ok(())
38219    }
38220}
38221
38222impl Default for Generator {
38223    fn default() -> Self {
38224        Self::new()
38225    }
38226}
38227
38228#[cfg(test)]
38229mod tests {
38230    use super::*;
38231    use crate::parser::Parser;
38232
38233    fn roundtrip(sql: &str) -> String {
38234        let ast = Parser::parse_sql(sql).unwrap();
38235        Generator::sql(&ast[0]).unwrap()
38236    }
38237
38238    #[test]
38239    fn test_simple_select() {
38240        let result = roundtrip("SELECT 1");
38241        assert_eq!(result, "SELECT 1");
38242    }
38243
38244    #[test]
38245    fn test_select_from() {
38246        let result = roundtrip("SELECT a, b FROM t");
38247        assert_eq!(result, "SELECT a, b FROM t");
38248    }
38249
38250    #[test]
38251    fn test_select_where() {
38252        let result = roundtrip("SELECT * FROM t WHERE x = 1");
38253        assert_eq!(result, "SELECT * FROM t WHERE x = 1");
38254    }
38255
38256    #[test]
38257    fn test_select_join() {
38258        let result = roundtrip("SELECT * FROM a JOIN b ON a.id = b.id");
38259        assert_eq!(result, "SELECT * FROM a JOIN b ON a.id = b.id");
38260    }
38261
38262    #[test]
38263    fn test_insert() {
38264        let result = roundtrip("INSERT INTO t (a, b) VALUES (1, 2)");
38265        assert_eq!(result, "INSERT INTO t (a, b) VALUES (1, 2)");
38266    }
38267
38268    #[test]
38269    fn test_pretty_print() {
38270        let ast = Parser::parse_sql("SELECT a, b FROM t WHERE x = 1").unwrap();
38271        let result = Generator::pretty_sql(&ast[0]).unwrap();
38272        assert!(result.contains('\n'));
38273    }
38274
38275    #[test]
38276    fn test_window_function() {
38277        let result = roundtrip("SELECT ROW_NUMBER() OVER (PARTITION BY category ORDER BY id)");
38278        assert_eq!(
38279            result,
38280            "SELECT ROW_NUMBER() OVER (PARTITION BY category ORDER BY id)"
38281        );
38282    }
38283
38284    #[test]
38285    fn test_window_function_with_frame() {
38286        let result = roundtrip("SELECT SUM(amount) OVER (ORDER BY order_date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)");
38287        assert_eq!(result, "SELECT SUM(amount) OVER (ORDER BY order_date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)");
38288    }
38289
38290    #[test]
38291    fn test_aggregate_with_filter() {
38292        let result = roundtrip("SELECT COUNT(*) FILTER (WHERE status = 1) FROM orders");
38293        assert_eq!(
38294            result,
38295            "SELECT COUNT(*) FILTER(WHERE status = 1) FROM orders"
38296        );
38297    }
38298
38299    #[test]
38300    fn test_subscript() {
38301        let result = roundtrip("SELECT arr[0]");
38302        assert_eq!(result, "SELECT arr[0]");
38303    }
38304
38305    // DDL tests
38306    #[test]
38307    fn test_create_table() {
38308        let result = roundtrip("CREATE TABLE users (id INT, name VARCHAR(100))");
38309        assert_eq!(result, "CREATE TABLE users (id INT, name VARCHAR(100))");
38310    }
38311
38312    #[test]
38313    fn test_create_table_with_constraints() {
38314        let result = roundtrip(
38315            "CREATE TABLE users (id INT PRIMARY KEY, email VARCHAR(255) UNIQUE NOT NULL)",
38316        );
38317        assert_eq!(
38318            result,
38319            "CREATE TABLE users (id INT PRIMARY KEY, email VARCHAR(255) UNIQUE NOT NULL)"
38320        );
38321    }
38322
38323    #[test]
38324    fn test_create_table_if_not_exists() {
38325        let result = roundtrip("CREATE TABLE IF NOT EXISTS t (id INT)");
38326        assert_eq!(result, "CREATE TABLE IF NOT EXISTS t (id INT)");
38327    }
38328
38329    #[test]
38330    fn test_drop_table() {
38331        let result = roundtrip("DROP TABLE users");
38332        assert_eq!(result, "DROP TABLE users");
38333    }
38334
38335    #[test]
38336    fn test_drop_table_if_exists_cascade() {
38337        let result = roundtrip("DROP TABLE IF EXISTS users CASCADE");
38338        assert_eq!(result, "DROP TABLE IF EXISTS users CASCADE");
38339    }
38340
38341    #[test]
38342    fn test_alter_table_add_column() {
38343        let result = roundtrip("ALTER TABLE users ADD COLUMN email VARCHAR(255)");
38344        assert_eq!(result, "ALTER TABLE users ADD COLUMN email VARCHAR(255)");
38345    }
38346
38347    #[test]
38348    fn test_alter_table_drop_column() {
38349        let result = roundtrip("ALTER TABLE users DROP COLUMN email");
38350        assert_eq!(result, "ALTER TABLE users DROP COLUMN email");
38351    }
38352
38353    #[test]
38354    fn test_create_index() {
38355        let result = roundtrip("CREATE INDEX idx_name ON users(name)");
38356        assert_eq!(result, "CREATE INDEX idx_name ON users(name)");
38357    }
38358
38359    #[test]
38360    fn test_create_unique_index() {
38361        let result = roundtrip("CREATE UNIQUE INDEX idx_email ON users(email)");
38362        assert_eq!(result, "CREATE UNIQUE INDEX idx_email ON users(email)");
38363    }
38364
38365    #[test]
38366    fn test_drop_index() {
38367        let result = roundtrip("DROP INDEX idx_name");
38368        assert_eq!(result, "DROP INDEX idx_name");
38369    }
38370
38371    #[test]
38372    fn test_create_view() {
38373        let result = roundtrip("CREATE VIEW active_users AS SELECT * FROM users WHERE active = 1");
38374        assert_eq!(
38375            result,
38376            "CREATE VIEW active_users AS SELECT * FROM users WHERE active = 1"
38377        );
38378    }
38379
38380    #[test]
38381    fn test_drop_view() {
38382        let result = roundtrip("DROP VIEW active_users");
38383        assert_eq!(result, "DROP VIEW active_users");
38384    }
38385
38386    #[test]
38387    fn test_truncate() {
38388        let result = roundtrip("TRUNCATE TABLE users");
38389        assert_eq!(result, "TRUNCATE TABLE users");
38390    }
38391
38392    #[test]
38393    fn test_string_literal_escaping_default() {
38394        // Default: double single quotes
38395        let result = roundtrip("SELECT 'hello'");
38396        assert_eq!(result, "SELECT 'hello'");
38397
38398        // Single quotes are doubled
38399        let result = roundtrip("SELECT 'it''s a test'");
38400        assert_eq!(result, "SELECT 'it''s a test'");
38401    }
38402
38403    #[test]
38404    fn test_not_in_style_prefix_default_generic() {
38405        let result = roundtrip("SELECT id FROM users WHERE status NOT IN ('deleted', 'banned')");
38406        assert_eq!(
38407            result,
38408            "SELECT id FROM users WHERE NOT status IN ('deleted', 'banned')"
38409        );
38410    }
38411
38412    #[test]
38413    fn test_not_in_style_infix_generic_override() {
38414        let ast =
38415            Parser::parse_sql("SELECT id FROM users WHERE status NOT IN ('deleted', 'banned')")
38416                .unwrap();
38417        let config = GeneratorConfig {
38418            not_in_style: NotInStyle::Infix,
38419            ..Default::default()
38420        };
38421        let mut gen = Generator::with_config(config);
38422        let result = gen.generate(&ast[0]).unwrap();
38423        assert_eq!(
38424            result,
38425            "SELECT id FROM users WHERE status NOT IN ('deleted', 'banned')"
38426        );
38427    }
38428
38429    #[test]
38430    fn test_string_literal_escaping_mysql() {
38431        use crate::dialects::DialectType;
38432
38433        let config = GeneratorConfig {
38434            dialect: Some(DialectType::MySQL),
38435            ..Default::default()
38436        };
38437
38438        let ast = Parser::parse_sql("SELECT 'hello'").unwrap();
38439        let mut gen = Generator::with_config(config.clone());
38440        let result = gen.generate(&ast[0]).unwrap();
38441        assert_eq!(result, "SELECT 'hello'");
38442
38443        // MySQL uses SQL standard quote doubling for escaping (matches Python sqlglot)
38444        let ast = Parser::parse_sql("SELECT 'it''s'").unwrap();
38445        let mut gen = Generator::with_config(config.clone());
38446        let result = gen.generate(&ast[0]).unwrap();
38447        assert_eq!(result, "SELECT 'it''s'");
38448    }
38449
38450    #[test]
38451    fn test_string_literal_escaping_postgres() {
38452        use crate::dialects::DialectType;
38453
38454        let config = GeneratorConfig {
38455            dialect: Some(DialectType::PostgreSQL),
38456            ..Default::default()
38457        };
38458
38459        let ast = Parser::parse_sql("SELECT 'hello'").unwrap();
38460        let mut gen = Generator::with_config(config.clone());
38461        let result = gen.generate(&ast[0]).unwrap();
38462        assert_eq!(result, "SELECT 'hello'");
38463
38464        // PostgreSQL uses doubled quotes for regular strings
38465        let ast = Parser::parse_sql("SELECT 'it''s'").unwrap();
38466        let mut gen = Generator::with_config(config.clone());
38467        let result = gen.generate(&ast[0]).unwrap();
38468        assert_eq!(result, "SELECT 'it''s'");
38469    }
38470
38471    #[test]
38472    fn test_string_literal_escaping_bigquery() {
38473        use crate::dialects::DialectType;
38474
38475        let config = GeneratorConfig {
38476            dialect: Some(DialectType::BigQuery),
38477            ..Default::default()
38478        };
38479
38480        let ast = Parser::parse_sql("SELECT 'hello'").unwrap();
38481        let mut gen = Generator::with_config(config.clone());
38482        let result = gen.generate(&ast[0]).unwrap();
38483        assert_eq!(result, "SELECT 'hello'");
38484
38485        // BigQuery escapes single quotes with backslash
38486        let ast = Parser::parse_sql("SELECT 'it''s'").unwrap();
38487        let mut gen = Generator::with_config(config.clone());
38488        let result = gen.generate(&ast[0]).unwrap();
38489        assert_eq!(result, "SELECT 'it\\'s'");
38490    }
38491
38492    #[test]
38493    fn test_generate_deep_and_chain_without_stack_growth() {
38494        let mut expr = Expression::Eq(Box::new(BinaryOp::new(
38495            Expression::column("c0"),
38496            Expression::number(0),
38497        )));
38498
38499        for i in 1..2500 {
38500            let predicate = Expression::Eq(Box::new(BinaryOp::new(
38501                Expression::column(format!("c{i}")),
38502                Expression::number(i as i64),
38503            )));
38504            expr = Expression::And(Box::new(BinaryOp::new(expr, predicate)));
38505        }
38506
38507        let sql = Generator::sql(&expr).expect("deep AND chain should generate");
38508        assert!(sql.contains("c2499 = 2499"), "{}", sql);
38509    }
38510
38511    #[test]
38512    fn test_generate_deep_or_chain_without_stack_growth() {
38513        let mut expr = Expression::Eq(Box::new(BinaryOp::new(
38514            Expression::column("c0"),
38515            Expression::number(0),
38516        )));
38517
38518        for i in 1..2500 {
38519            let predicate = Expression::Eq(Box::new(BinaryOp::new(
38520                Expression::column(format!("c{i}")),
38521                Expression::number(i as i64),
38522            )));
38523            expr = Expression::Or(Box::new(BinaryOp::new(expr, predicate)));
38524        }
38525
38526        let sql = Generator::sql(&expr).expect("deep OR chain should generate");
38527        assert!(sql.contains("c2499 = 2499"), "{}", sql);
38528    }
38529}