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        let is_mysql = matches!(self.config.dialect, Some(DialectType::MySQL));
14202        let mysql_tables_scope_as_from = is_mysql
14203            && matches!(s.this.as_str(), "TABLES" | "FULL TABLES")
14204            && s.scope_kind.as_deref() == Some("SCHEMA")
14205            && s.scope.is_some()
14206            && s.from.is_none();
14207
14208        if !is_snowflake && s.from.is_some() {
14209            // MySQL ordering: IN, FROM, LIKE
14210
14211            // IN scope_kind [scope]
14212            if let Some(ref scope_kind) = s.scope_kind {
14213                self.write_space();
14214                self.write_keyword("IN");
14215                self.write_space();
14216                self.write_keyword(scope_kind);
14217                if let Some(ref scope) = s.scope {
14218                    self.write_space();
14219                    self.generate_expression(scope)?;
14220                }
14221            } else if let Some(ref scope) = s.scope {
14222                self.write_space();
14223                self.write_keyword("IN");
14224                self.write_space();
14225                self.generate_expression(scope)?;
14226            }
14227
14228            // FROM clause
14229            if let Some(ref from) = s.from {
14230                self.write_space();
14231                self.write_keyword("FROM");
14232                self.write_space();
14233                self.generate_expression(from)?;
14234            }
14235
14236            // Second FROM clause (db name)
14237            if let Some(ref db) = s.db {
14238                self.write_space();
14239                self.write_keyword("FROM");
14240                self.write_space();
14241                self.generate_expression(db)?;
14242            }
14243
14244            // LIKE pattern
14245            if let Some(ref like) = s.like {
14246                self.write_space();
14247                self.write_keyword("LIKE");
14248                self.write_space();
14249                self.generate_expression(like)?;
14250            }
14251        } else {
14252            // Snowflake ordering: LIKE, IN, STARTS WITH, LIMIT, FROM
14253
14254            // LIKE pattern
14255            if let Some(ref like) = s.like {
14256                self.write_space();
14257                self.write_keyword("LIKE");
14258                self.write_space();
14259                self.generate_expression(like)?;
14260            }
14261
14262            // IN scope_kind [scope]
14263            if mysql_tables_scope_as_from {
14264                self.write_space();
14265                self.write_keyword("FROM");
14266                self.write_space();
14267                self.generate_expression(s.scope.as_ref().unwrap())?;
14268            } else if let Some(ref scope_kind) = s.scope_kind {
14269                self.write_space();
14270                self.write_keyword("IN");
14271                self.write_space();
14272                self.write_keyword(scope_kind);
14273                if let Some(ref scope) = s.scope {
14274                    self.write_space();
14275                    self.generate_expression(scope)?;
14276                }
14277            } else if let Some(ref scope) = s.scope {
14278                self.write_space();
14279                self.write_keyword("IN");
14280                self.write_space();
14281                self.generate_expression(scope)?;
14282            }
14283        }
14284
14285        // STARTS WITH pattern
14286        if let Some(ref starts_with) = s.starts_with {
14287            self.write_space();
14288            self.write_keyword("STARTS WITH");
14289            self.write_space();
14290            self.generate_expression(starts_with)?;
14291        }
14292
14293        // LIMIT clause
14294        if let Some(ref limit) = s.limit {
14295            self.write_space();
14296            self.generate_limit(limit)?;
14297        }
14298
14299        // FROM clause (for Snowflake, FROM comes after STARTS WITH and LIMIT)
14300        if is_snowflake {
14301            if let Some(ref from) = s.from {
14302                self.write_space();
14303                self.write_keyword("FROM");
14304                self.write_space();
14305                self.generate_expression(from)?;
14306            }
14307        }
14308
14309        // WHERE clause (MySQL: SHOW STATUS WHERE condition)
14310        if let Some(ref where_clause) = s.where_clause {
14311            self.write_space();
14312            self.write_keyword("WHERE");
14313            self.write_space();
14314            self.generate_expression(where_clause)?;
14315        }
14316
14317        // MUTEX/STATUS suffix (MySQL: SHOW ENGINE foo STATUS/MUTEX)
14318        if let Some(is_mutex) = s.mutex {
14319            self.write_space();
14320            if is_mutex {
14321                self.write_keyword("MUTEX");
14322            } else {
14323                self.write_keyword("STATUS");
14324            }
14325        }
14326
14327        // WITH PRIVILEGES clause (Snowflake: SHOW ... WITH PRIVILEGES USAGE, MODIFY)
14328        if !s.privileges.is_empty() {
14329            self.write_space();
14330            self.write_keyword("WITH PRIVILEGES");
14331            self.write_space();
14332            for (i, priv_name) in s.privileges.iter().enumerate() {
14333                if i > 0 {
14334                    self.write(", ");
14335                }
14336                self.write_keyword(priv_name);
14337            }
14338        }
14339
14340        Ok(())
14341    }
14342
14343    // ==================== End DDL Generation ====================
14344
14345    fn generate_literal(&mut self, lit: &Literal) -> Result<()> {
14346        use crate::dialects::DialectType;
14347        match lit {
14348            Literal::String(s) => {
14349                self.generate_string_literal(s)?;
14350            }
14351            Literal::Number(n) => {
14352                if matches!(self.config.dialect, Some(DialectType::MySQL))
14353                    && n.len() > 2
14354                    && (n.starts_with("0x") || n.starts_with("0X"))
14355                    && !n[2..].chars().all(|c| c.is_ascii_hexdigit())
14356                {
14357                    return self.generate_identifier(&Identifier {
14358                        name: n.clone(),
14359                        quoted: true,
14360                        trailing_comments: Vec::new(),
14361                        span: None,
14362                    });
14363                }
14364                // Strip underscore digit separators (e.g., 1_000_000 -> 1000000)
14365                // for dialects that don't support them (MySQL interprets as identifier).
14366                // ClickHouse, DuckDB, PostgreSQL, and Hive/Spark/Databricks support them.
14367                let n = if n.contains('_')
14368                    && !matches!(
14369                        self.config.dialect,
14370                        Some(DialectType::ClickHouse)
14371                            | Some(DialectType::DuckDB)
14372                            | Some(DialectType::PostgreSQL)
14373                            | Some(DialectType::Hive)
14374                            | Some(DialectType::Spark)
14375                            | Some(DialectType::Databricks)
14376                    ) {
14377                    std::borrow::Cow::Owned(n.replace('_', ""))
14378                } else {
14379                    std::borrow::Cow::Borrowed(n.as_str())
14380                };
14381                // Normalize numbers starting with decimal point to have leading zero
14382                // e.g., .25 -> 0.25 (matches sqlglot behavior)
14383                if n.starts_with('.') {
14384                    self.write("0");
14385                    self.write(&n);
14386                } else if n.starts_with("-.") {
14387                    // Handle negative numbers like -.25 -> -0.25
14388                    self.write("-0");
14389                    self.write(&n[1..]);
14390                } else {
14391                    self.write(&n);
14392                }
14393            }
14394            Literal::HexString(h) => {
14395                // Most dialects use lowercase x'...' for hex literals; Spark/Databricks/Teradata use uppercase X'...'
14396                match self.config.dialect {
14397                    Some(DialectType::Spark)
14398                    | Some(DialectType::Databricks)
14399                    | Some(DialectType::Teradata) => self.write("X'"),
14400                    _ => self.write("x'"),
14401                }
14402                self.write(h);
14403                self.write("'");
14404            }
14405            Literal::HexNumber(h) => {
14406                // Hex number (0xA) - integer in hex notation (from BigQuery)
14407                // For BigQuery, TSQL, Fabric output as 0xHEX (native hex notation)
14408                // For other dialects, convert to decimal integer
14409                match self.config.dialect {
14410                    Some(DialectType::BigQuery)
14411                    | Some(DialectType::TSQL)
14412                    | Some(DialectType::Fabric) => {
14413                        self.write("0x");
14414                        self.write(h);
14415                    }
14416                    _ => {
14417                        // Convert hex to decimal
14418                        if let Ok(val) = u64::from_str_radix(h, 16) {
14419                            self.write(&val.to_string());
14420                        } else {
14421                            // Fallback: keep as 0x notation
14422                            self.write("0x");
14423                            self.write(h);
14424                        }
14425                    }
14426                }
14427            }
14428            Literal::BitString(b) => {
14429                // Bit string B'0101...'
14430                self.write("B'");
14431                self.write(b);
14432                self.write("'");
14433            }
14434            Literal::ByteString(b) => {
14435                // Byte string b'...' (BigQuery style)
14436                self.write("b'");
14437                // Escape special characters for output
14438                self.write_escaped_byte_string(b);
14439                self.write("'");
14440            }
14441            Literal::NationalString(s) => {
14442                // N'string' is supported by TSQL, Oracle, MySQL, and generic SQL
14443                // Other dialects strip the N prefix and output as regular string
14444                let keep_n_prefix = matches!(
14445                    self.config.dialect,
14446                    Some(DialectType::TSQL)
14447                        | Some(DialectType::Oracle)
14448                        | Some(DialectType::MySQL)
14449                        | None
14450                );
14451                if keep_n_prefix {
14452                    self.write("N'");
14453                } else {
14454                    self.write("'");
14455                }
14456                self.write(s);
14457                self.write("'");
14458            }
14459            Literal::Date(d) => {
14460                self.generate_date_literal(d)?;
14461            }
14462            Literal::Time(t) => {
14463                self.generate_time_literal(t)?;
14464            }
14465            Literal::Timestamp(ts) => {
14466                self.generate_timestamp_literal(ts)?;
14467            }
14468            Literal::Datetime(dt) => {
14469                self.generate_datetime_literal(dt)?;
14470            }
14471            Literal::TripleQuotedString(s, _quote_char) => {
14472                // For BigQuery and other dialects that don't support triple-quote, normalize to regular strings
14473                if matches!(
14474                    self.config.dialect,
14475                    Some(crate::dialects::DialectType::BigQuery)
14476                        | Some(crate::dialects::DialectType::DuckDB)
14477                        | Some(crate::dialects::DialectType::Snowflake)
14478                        | Some(crate::dialects::DialectType::Spark)
14479                        | Some(crate::dialects::DialectType::Hive)
14480                        | Some(crate::dialects::DialectType::Presto)
14481                        | Some(crate::dialects::DialectType::Trino)
14482                        | Some(crate::dialects::DialectType::PostgreSQL)
14483                        | Some(crate::dialects::DialectType::MySQL)
14484                        | Some(crate::dialects::DialectType::Redshift)
14485                        | Some(crate::dialects::DialectType::TSQL)
14486                        | Some(crate::dialects::DialectType::Oracle)
14487                        | Some(crate::dialects::DialectType::ClickHouse)
14488                        | Some(crate::dialects::DialectType::Databricks)
14489                        | Some(crate::dialects::DialectType::SQLite)
14490                ) {
14491                    self.generate_string_literal(s)?;
14492                } else {
14493                    // Preserve triple-quoted string syntax for generic/unknown dialects
14494                    let quotes = format!("{0}{0}{0}", _quote_char);
14495                    self.write(&quotes);
14496                    self.write(s);
14497                    self.write(&quotes);
14498                }
14499            }
14500            Literal::EscapeString(s) => {
14501                // PostgreSQL escape string: e'...' or E'...'
14502                // Token text format is "e:content" or "E:content"
14503                // Normalize escape sequences: \' -> '' (standard SQL doubled quote)
14504                use crate::dialects::DialectType;
14505                let content = if let Some(c) = s.strip_prefix("e:") {
14506                    c
14507                } else if let Some(c) = s.strip_prefix("E:") {
14508                    c
14509                } else {
14510                    s.as_str()
14511                };
14512
14513                // MySQL: output the content without quotes or prefix
14514                if matches!(
14515                    self.config.dialect,
14516                    Some(DialectType::MySQL) | Some(DialectType::TiDB)
14517                ) {
14518                    self.write(content);
14519                } else {
14520                    // Some dialects use lowercase e' prefix
14521                    let prefix = if matches!(
14522                        self.config.dialect,
14523                        Some(DialectType::SingleStore)
14524                            | Some(DialectType::DuckDB)
14525                            | Some(DialectType::PostgreSQL)
14526                            | Some(DialectType::CockroachDB)
14527                            | Some(DialectType::Materialize)
14528                            | Some(DialectType::RisingWave)
14529                    ) {
14530                        "e'"
14531                    } else {
14532                        "E'"
14533                    };
14534
14535                    // Normalize \' to '' for output
14536                    let normalized = content.replace("\\'", "''");
14537                    self.write(prefix);
14538                    self.write(&normalized);
14539                    self.write("'");
14540                }
14541            }
14542            Literal::DollarString(s) => {
14543                // Convert dollar-quoted strings to single-quoted strings
14544                // (like Python sqlglot's rawstring_sql)
14545                use crate::dialects::DialectType;
14546                // Extract content from tag\x00content format
14547                let (_tag, content) = crate::tokens::parse_dollar_string_token(s);
14548                // Step 1: Escape backslashes if the dialect uses backslash as a string escape
14549                let escape_backslash = matches!(self.config.dialect, Some(DialectType::Snowflake));
14550                // Step 2: Determine quote escaping style
14551                // Snowflake: ' -> \' (backslash escape)
14552                // PostgreSQL, DuckDB, others: ' -> '' (doubled quote)
14553                let use_backslash_quote =
14554                    matches!(self.config.dialect, Some(DialectType::Snowflake));
14555
14556                let mut escaped = String::with_capacity(content.len() + 4);
14557                for ch in content.chars() {
14558                    if escape_backslash && ch == '\\' {
14559                        // Escape backslash first (before quote escaping)
14560                        escaped.push('\\');
14561                        escaped.push('\\');
14562                    } else if ch == '\'' {
14563                        if use_backslash_quote {
14564                            escaped.push('\\');
14565                            escaped.push('\'');
14566                        } else {
14567                            escaped.push('\'');
14568                            escaped.push('\'');
14569                        }
14570                    } else {
14571                        escaped.push(ch);
14572                    }
14573                }
14574                self.write("'");
14575                self.write(&escaped);
14576                self.write("'");
14577            }
14578            Literal::RawString(s) => {
14579                // Raw strings (r"..." or r'...') contain literal backslashes.
14580                // When converting to a regular string, this follows Python sqlglot's rawstring_sql:
14581                // 1. If \\ is in STRING_ESCAPES, double all backslashes
14582                // 2. Apply ESCAPED_SEQUENCES for special chars (but NOT for backslash itself)
14583                // 3. Escape quotes using STRING_ESCAPES[0] + quote_char
14584                use crate::dialects::DialectType;
14585
14586                // Dialects where \\ is in STRING_ESCAPES (backslashes need doubling)
14587                let escape_backslash = matches!(
14588                    self.config.dialect,
14589                    Some(DialectType::BigQuery)
14590                        | Some(DialectType::MySQL)
14591                        | Some(DialectType::SingleStore)
14592                        | Some(DialectType::TiDB)
14593                        | Some(DialectType::Hive)
14594                        | Some(DialectType::Spark)
14595                        | Some(DialectType::Databricks)
14596                        | Some(DialectType::Drill)
14597                        | Some(DialectType::Snowflake)
14598                        | Some(DialectType::Redshift)
14599                        | Some(DialectType::ClickHouse)
14600                );
14601
14602                // Dialects where backslash is the PRIMARY string escape (STRING_ESCAPES[0] = "\\")
14603                // These escape quotes as \' instead of ''
14604                let backslash_escapes_quote = matches!(
14605                    self.config.dialect,
14606                    Some(DialectType::BigQuery)
14607                        | Some(DialectType::Hive)
14608                        | Some(DialectType::Spark)
14609                        | Some(DialectType::Databricks)
14610                        | Some(DialectType::Drill)
14611                        | Some(DialectType::Snowflake)
14612                        | Some(DialectType::Redshift)
14613                );
14614
14615                // Whether this dialect supports escaped sequences (ESCAPED_SEQUENCES mapping)
14616                // This is True when \\ is in STRING_ESCAPES (same as escape_backslash)
14617                let supports_escape_sequences = escape_backslash;
14618
14619                let mut escaped = String::with_capacity(s.len() + 4);
14620                for ch in s.chars() {
14621                    if escape_backslash && ch == '\\' {
14622                        // Double the backslash for the target dialect
14623                        escaped.push('\\');
14624                        escaped.push('\\');
14625                    } else if ch == '\'' {
14626                        if backslash_escapes_quote {
14627                            // Use backslash to escape the quote: \'
14628                            escaped.push('\\');
14629                            escaped.push('\'');
14630                        } else {
14631                            // Use SQL standard quote doubling: ''
14632                            escaped.push('\'');
14633                            escaped.push('\'');
14634                        }
14635                    } else if supports_escape_sequences {
14636                        // Apply ESCAPED_SEQUENCES mapping for special chars
14637                        // (escape_backslash=False in rawstring_sql, so \\ is NOT escaped here)
14638                        match ch {
14639                            '\n' => {
14640                                escaped.push('\\');
14641                                escaped.push('n');
14642                            }
14643                            '\r' => {
14644                                escaped.push('\\');
14645                                escaped.push('r');
14646                            }
14647                            '\t' => {
14648                                escaped.push('\\');
14649                                escaped.push('t');
14650                            }
14651                            '\x07' => {
14652                                escaped.push('\\');
14653                                escaped.push('a');
14654                            }
14655                            '\x08' => {
14656                                escaped.push('\\');
14657                                escaped.push('b');
14658                            }
14659                            '\x0C' => {
14660                                escaped.push('\\');
14661                                escaped.push('f');
14662                            }
14663                            '\x0B' => {
14664                                escaped.push('\\');
14665                                escaped.push('v');
14666                            }
14667                            _ => escaped.push(ch),
14668                        }
14669                    } else {
14670                        escaped.push(ch);
14671                    }
14672                }
14673                self.write("'");
14674                self.write(&escaped);
14675                self.write("'");
14676            }
14677        }
14678        Ok(())
14679    }
14680
14681    /// Generate a DATE literal with dialect-specific formatting
14682    fn generate_date_literal(&mut self, d: &str) -> Result<()> {
14683        use crate::dialects::DialectType;
14684
14685        match self.config.dialect {
14686            // SQL Server / Fabric use CONVERT or CAST
14687            Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
14688                self.write("CAST('");
14689                self.write(d);
14690                self.write("' AS DATE)");
14691            }
14692            // BigQuery uses CAST syntax for type literals
14693            // DATE 'value' -> CAST('value' AS DATE)
14694            Some(DialectType::BigQuery) => {
14695                self.write("CAST('");
14696                self.write(d);
14697                self.write("' AS DATE)");
14698            }
14699            // Exasol uses CAST syntax for DATE literals
14700            // DATE 'value' -> CAST('value' AS DATE)
14701            Some(DialectType::Exasol) => {
14702                self.write("CAST('");
14703                self.write(d);
14704                self.write("' AS DATE)");
14705            }
14706            // Snowflake uses CAST syntax for DATE literals
14707            // DATE 'value' -> CAST('value' AS DATE)
14708            Some(DialectType::Snowflake) => {
14709                self.write("CAST('");
14710                self.write(d);
14711                self.write("' AS DATE)");
14712            }
14713            // PostgreSQL, MySQL, Redshift: DATE 'value' -> CAST('value' AS DATE)
14714            Some(DialectType::PostgreSQL)
14715            | Some(DialectType::MySQL)
14716            | Some(DialectType::SingleStore)
14717            | Some(DialectType::TiDB)
14718            | Some(DialectType::Redshift) => {
14719                self.write("CAST('");
14720                self.write(d);
14721                self.write("' AS DATE)");
14722            }
14723            // DuckDB, Presto, Trino, Spark: DATE 'value' -> CAST('value' AS DATE)
14724            Some(DialectType::DuckDB)
14725            | Some(DialectType::Presto)
14726            | Some(DialectType::Trino)
14727            | Some(DialectType::Athena)
14728            | Some(DialectType::Spark)
14729            | Some(DialectType::Databricks)
14730            | Some(DialectType::Hive) => {
14731                self.write("CAST('");
14732                self.write(d);
14733                self.write("' AS DATE)");
14734            }
14735            // Oracle: DATE 'value' -> TO_DATE('value', 'YYYY-MM-DD')
14736            Some(DialectType::Oracle) => {
14737                self.write("TO_DATE('");
14738                self.write(d);
14739                self.write("', 'YYYY-MM-DD')");
14740            }
14741            // Standard SQL: DATE '...'
14742            _ => {
14743                self.write_keyword("DATE");
14744                self.write(" '");
14745                self.write(d);
14746                self.write("'");
14747            }
14748        }
14749        Ok(())
14750    }
14751
14752    /// Generate a TIME literal with dialect-specific formatting
14753    fn generate_time_literal(&mut self, t: &str) -> Result<()> {
14754        use crate::dialects::DialectType;
14755
14756        match self.config.dialect {
14757            // SQL Server uses CONVERT or CAST
14758            Some(DialectType::TSQL) => {
14759                self.write("CAST('");
14760                self.write(t);
14761                self.write("' AS TIME)");
14762            }
14763            // Standard SQL: TIME '...'
14764            _ => {
14765                self.write_keyword("TIME");
14766                self.write(" '");
14767                self.write(t);
14768                self.write("'");
14769            }
14770        }
14771        Ok(())
14772    }
14773
14774    /// Generate a date expression for Dremio, converting DATE literals to CAST
14775    fn generate_dremio_date_expression(&mut self, expr: &Expression) -> Result<()> {
14776        use crate::expressions::Literal;
14777
14778        match expr {
14779            Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Date(_)) => {
14780                let Literal::Date(d) = lit.as_ref() else {
14781                    unreachable!()
14782                };
14783                // DATE 'value' -> CAST('value' AS DATE)
14784                self.write("CAST('");
14785                self.write(d);
14786                self.write("' AS DATE)");
14787            }
14788            _ => {
14789                // For all other expressions, generate normally
14790                self.generate_expression(expr)?;
14791            }
14792        }
14793        Ok(())
14794    }
14795
14796    /// Generate a TIMESTAMP literal with dialect-specific formatting
14797    fn generate_timestamp_literal(&mut self, ts: &str) -> Result<()> {
14798        use crate::dialects::DialectType;
14799
14800        match self.config.dialect {
14801            // SQL Server uses CONVERT or CAST
14802            Some(DialectType::TSQL) => {
14803                self.write("CAST('");
14804                self.write(ts);
14805                self.write("' AS DATETIME2)");
14806            }
14807            // BigQuery uses CAST syntax for type literals
14808            // TIMESTAMP 'value' -> CAST('value' AS TIMESTAMP)
14809            Some(DialectType::BigQuery) => {
14810                self.write("CAST('");
14811                self.write(ts);
14812                self.write("' AS TIMESTAMP)");
14813            }
14814            // Snowflake uses CAST syntax for TIMESTAMP literals
14815            // TIMESTAMP 'value' -> CAST('value' AS TIMESTAMP)
14816            Some(DialectType::Snowflake) => {
14817                self.write("CAST('");
14818                self.write(ts);
14819                self.write("' AS TIMESTAMP)");
14820            }
14821            // Dremio uses CAST syntax for TIMESTAMP literals
14822            // TIMESTAMP 'value' -> CAST('value' AS TIMESTAMP)
14823            Some(DialectType::Dremio) => {
14824                self.write("CAST('");
14825                self.write(ts);
14826                self.write("' AS TIMESTAMP)");
14827            }
14828            // Exasol uses CAST syntax for TIMESTAMP literals
14829            // TIMESTAMP 'value' -> CAST('value' AS TIMESTAMP)
14830            Some(DialectType::Exasol) => {
14831                self.write("CAST('");
14832                self.write(ts);
14833                self.write("' AS TIMESTAMP)");
14834            }
14835            // Oracle prefers TO_TIMESTAMP function call
14836            // TIMESTAMP 'value' -> TO_TIMESTAMP('value', 'YYYY-MM-DD HH24:MI:SS.FF6')
14837            Some(DialectType::Oracle) => {
14838                self.write("TO_TIMESTAMP('");
14839                self.write(ts);
14840                self.write("', 'YYYY-MM-DD HH24:MI:SS.FF6')");
14841            }
14842            // Presto/Trino: always use CAST for TIMESTAMP literals
14843            Some(DialectType::Presto) | Some(DialectType::Trino) => {
14844                if Self::timestamp_has_timezone(ts) {
14845                    self.write("CAST('");
14846                    self.write(ts);
14847                    self.write("' AS TIMESTAMP WITH TIME ZONE)");
14848                } else {
14849                    self.write("CAST('");
14850                    self.write(ts);
14851                    self.write("' AS TIMESTAMP)");
14852                }
14853            }
14854            // ClickHouse: CAST('...' AS Nullable(DateTime))
14855            Some(DialectType::ClickHouse) => {
14856                self.write("CAST('");
14857                self.write(ts);
14858                self.write("' AS Nullable(DateTime))");
14859            }
14860            // Spark: CAST('...' AS TIMESTAMP)
14861            Some(DialectType::Spark) => {
14862                self.write("CAST('");
14863                self.write(ts);
14864                self.write("' AS TIMESTAMP)");
14865            }
14866            // Redshift: CAST('...' AS TIMESTAMP) for regular timestamps,
14867            // but TIMESTAMP '...' for special values like 'epoch'
14868            Some(DialectType::Redshift) => {
14869                if ts == "epoch" {
14870                    self.write_keyword("TIMESTAMP");
14871                    self.write(" '");
14872                    self.write(ts);
14873                    self.write("'");
14874                } else {
14875                    self.write("CAST('");
14876                    self.write(ts);
14877                    self.write("' AS TIMESTAMP)");
14878                }
14879            }
14880            // PostgreSQL, Hive, DuckDB, etc.: CAST('...' AS TIMESTAMP)
14881            Some(DialectType::PostgreSQL)
14882            | Some(DialectType::Hive)
14883            | Some(DialectType::SQLite)
14884            | Some(DialectType::DuckDB)
14885            | Some(DialectType::Athena)
14886            | Some(DialectType::Drill)
14887            | Some(DialectType::Teradata) => {
14888                self.write("CAST('");
14889                self.write(ts);
14890                self.write("' AS TIMESTAMP)");
14891            }
14892            // MySQL/StarRocks: CAST('...' AS DATETIME)
14893            Some(DialectType::MySQL) | Some(DialectType::StarRocks) | Some(DialectType::Doris) => {
14894                self.write("CAST('");
14895                self.write(ts);
14896                self.write("' AS DATETIME)");
14897            }
14898            // Databricks: CAST('...' AS TIMESTAMP_NTZ)
14899            Some(DialectType::Databricks) => {
14900                self.write("CAST('");
14901                self.write(ts);
14902                self.write("' AS TIMESTAMP_NTZ)");
14903            }
14904            // Standard SQL: TIMESTAMP '...'
14905            _ => {
14906                self.write_keyword("TIMESTAMP");
14907                self.write(" '");
14908                self.write(ts);
14909                self.write("'");
14910            }
14911        }
14912        Ok(())
14913    }
14914
14915    /// Check if a timestamp string contains a timezone identifier
14916    /// This detects IANA timezone names like Europe/Prague, America/New_York, etc.
14917    fn timestamp_has_timezone(ts: &str) -> bool {
14918        // Check for common IANA timezone patterns: Continent/City format
14919        // Examples: Europe/Prague, America/New_York, Asia/Tokyo, etc.
14920        // Also handles: UTC, GMT, Etc/GMT+0, etc.
14921        let ts_lower = ts.to_ascii_lowercase();
14922
14923        // Check for Continent/City pattern (most common)
14924        let continent_prefixes = [
14925            "africa/",
14926            "america/",
14927            "antarctica/",
14928            "arctic/",
14929            "asia/",
14930            "atlantic/",
14931            "australia/",
14932            "europe/",
14933            "indian/",
14934            "pacific/",
14935            "etc/",
14936            "brazil/",
14937            "canada/",
14938            "chile/",
14939            "mexico/",
14940            "us/",
14941        ];
14942
14943        for prefix in &continent_prefixes {
14944            if ts_lower.contains(prefix) {
14945                return true;
14946            }
14947        }
14948
14949        // Check for standalone timezone abbreviations at the end
14950        // These typically appear after the time portion
14951        let tz_abbrevs = [
14952            " utc", " gmt", " cet", " cest", " eet", " eest", " wet", " west", " est", " edt",
14953            " cst", " cdt", " mst", " mdt", " pst", " pdt", " ist", " bst", " jst", " kst", " hkt",
14954            " sgt", " aest", " aedt", " acst", " acdt", " awst",
14955        ];
14956
14957        for abbrev in &tz_abbrevs {
14958            if ts_lower.ends_with(abbrev) {
14959                return true;
14960            }
14961        }
14962
14963        // Check for numeric timezone offsets: +N, -N, +NN:NN, -NN:NN
14964        // Examples: "2012-10-31 01:00 -2", "2012-10-31 01:00 +02:00"
14965        // Look for pattern: space followed by + or - and digits (optionally with :)
14966        let trimmed = ts.trim();
14967        if let Some(last_space) = trimmed.rfind(' ') {
14968            let suffix = &trimmed[last_space + 1..];
14969            if (suffix.starts_with('+') || suffix.starts_with('-')) && suffix.len() > 1 {
14970                // Check if rest is numeric (possibly with : for hh:mm format)
14971                let rest = &suffix[1..];
14972                if rest.chars().all(|c| c.is_ascii_digit() || c == ':') {
14973                    return true;
14974                }
14975            }
14976        }
14977
14978        false
14979    }
14980
14981    /// Generate a DATETIME literal with dialect-specific formatting
14982    fn generate_datetime_literal(&mut self, dt: &str) -> Result<()> {
14983        use crate::dialects::DialectType;
14984
14985        match self.config.dialect {
14986            // BigQuery uses CAST syntax for type literals
14987            // DATETIME 'value' -> CAST('value' AS DATETIME)
14988            Some(DialectType::BigQuery) => {
14989                self.write("CAST('");
14990                self.write(dt);
14991                self.write("' AS DATETIME)");
14992            }
14993            // DuckDB: DATETIME -> CAST('value' AS TIMESTAMP)
14994            Some(DialectType::DuckDB) => {
14995                self.write("CAST('");
14996                self.write(dt);
14997                self.write("' AS TIMESTAMP)");
14998            }
14999            // DATETIME is primarily a BigQuery type
15000            // Output as DATETIME '...' for dialects that support it
15001            _ => {
15002                self.write_keyword("DATETIME");
15003                self.write(" '");
15004                self.write(dt);
15005                self.write("'");
15006            }
15007        }
15008        Ok(())
15009    }
15010
15011    /// Generate a string literal with dialect-specific escaping
15012    fn generate_string_literal(&mut self, s: &str) -> Result<()> {
15013        use crate::dialects::DialectType;
15014
15015        match self.config.dialect {
15016            // MySQL/Hive: Uses SQL standard quote escaping ('') for quotes,
15017            // and backslash escaping for special characters like newlines
15018            // Hive STRING_ESCAPES = ["\\"] - uses backslash escapes
15019            Some(DialectType::Hive) | Some(DialectType::Spark) | Some(DialectType::Databricks) => {
15020                // Hive/Spark use backslash escaping for quotes (\') and special chars
15021                self.write("'");
15022                for c in s.chars() {
15023                    match c {
15024                        '\'' => self.write("\\'"),
15025                        '\\' => self.write("\\\\"),
15026                        '\n' => self.write("\\n"),
15027                        '\r' => self.write("\\r"),
15028                        '\t' => self.write("\\t"),
15029                        '\0' => self.write("\\0"),
15030                        _ => self.output.push(c),
15031                    }
15032                }
15033                self.write("'");
15034            }
15035            Some(DialectType::Drill) => {
15036                // Drill uses SQL-standard quote doubling ('') for quotes,
15037                // but backslash escaping for special characters
15038                self.write("'");
15039                for c in s.chars() {
15040                    match c {
15041                        '\'' => self.write("''"),
15042                        '\\' => self.write("\\\\"),
15043                        '\n' => self.write("\\n"),
15044                        '\r' => self.write("\\r"),
15045                        '\t' => self.write("\\t"),
15046                        '\0' => self.write("\\0"),
15047                        _ => self.output.push(c),
15048                    }
15049                }
15050                self.write("'");
15051            }
15052            Some(DialectType::MySQL) | Some(DialectType::SingleStore) | Some(DialectType::TiDB) => {
15053                self.write("'");
15054                for c in s.chars() {
15055                    match c {
15056                        // MySQL uses SQL standard quote doubling
15057                        '\'' => self.write("''"),
15058                        '\\' => self.write("\\\\"),
15059                        '\n' => self.write("\\n"),
15060                        '\r' => self.write("\\r"),
15061                        '\t' => self.write("\\t"),
15062                        // sqlglot writes a literal NUL for this case
15063                        '\0' => self.output.push('\0'),
15064                        _ => self.output.push(c),
15065                    }
15066                }
15067                self.write("'");
15068            }
15069            // BigQuery: Uses backslash escaping
15070            Some(DialectType::BigQuery) => {
15071                self.write("'");
15072                for c in s.chars() {
15073                    match c {
15074                        '\'' => self.write("\\'"),
15075                        '\\' => self.write("\\\\"),
15076                        '\n' => self.write("\\n"),
15077                        '\r' => self.write("\\r"),
15078                        '\t' => self.write("\\t"),
15079                        '\0' => self.write("\\0"),
15080                        '\x07' => self.write("\\a"),
15081                        '\x08' => self.write("\\b"),
15082                        '\x0C' => self.write("\\f"),
15083                        '\x0B' => self.write("\\v"),
15084                        _ => self.output.push(c),
15085                    }
15086                }
15087                self.write("'");
15088            }
15089            // Athena: Uses different escaping for DDL (Hive) vs DML (Trino)
15090            // In Hive context (DDL): backslash escaping for single quotes (\') and backslashes (\\)
15091            // In Trino context (DML): SQL-standard escaping ('') and literal backslashes
15092            Some(DialectType::Athena) => {
15093                if self.athena_hive_context {
15094                    // Hive-style: backslash escaping
15095                    self.write("'");
15096                    for c in s.chars() {
15097                        match c {
15098                            '\'' => self.write("\\'"),
15099                            '\\' => self.write("\\\\"),
15100                            '\n' => self.write("\\n"),
15101                            '\r' => self.write("\\r"),
15102                            '\t' => self.write("\\t"),
15103                            '\0' => self.write("\\0"),
15104                            _ => self.output.push(c),
15105                        }
15106                    }
15107                    self.write("'");
15108                } else {
15109                    // Trino-style: SQL-standard escaping, preserve backslashes
15110                    self.write("'");
15111                    for c in s.chars() {
15112                        match c {
15113                            '\'' => self.write("''"),
15114                            // Preserve backslashes literally (no re-escaping)
15115                            _ => self.output.push(c),
15116                        }
15117                    }
15118                    self.write("'");
15119                }
15120            }
15121            // Snowflake: Uses backslash escaping (STRING_ESCAPES = ["\\", "'"])
15122            // The tokenizer preserves backslash escape sequences literally (e.g., input '\\'
15123            // becomes string value '\\'), so we should NOT re-escape backslashes.
15124            // We only need to escape single quotes.
15125            Some(DialectType::Snowflake) => {
15126                self.write("'");
15127                for c in s.chars() {
15128                    match c {
15129                        '\'' => self.write("\\'"),
15130                        // Backslashes are already escaped in the tokenized string, don't re-escape
15131                        // Only escape special characters that might not have been escaped
15132                        '\n' => self.write("\\n"),
15133                        '\r' => self.write("\\r"),
15134                        '\t' => self.write("\\t"),
15135                        _ => self.output.push(c),
15136                    }
15137                }
15138                self.write("'");
15139            }
15140            // PostgreSQL: Output special characters as literal chars in strings (no E-string prefix)
15141            Some(DialectType::PostgreSQL) => {
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            // Redshift: Uses backslash escaping for single quotes
15152            Some(DialectType::Redshift) => {
15153                self.write("'");
15154                for c in s.chars() {
15155                    match c {
15156                        '\'' => self.write("\\'"),
15157                        _ => self.output.push(c),
15158                    }
15159                }
15160                self.write("'");
15161            }
15162            // Oracle: Uses standard double single-quote escaping
15163            Some(DialectType::Oracle) => {
15164                self.write("'");
15165                for ch in s.chars() {
15166                    if ch == '\'' {
15167                        self.output.push_str("''");
15168                    } else {
15169                        self.output.push(ch);
15170                    }
15171                }
15172                self.write("'");
15173            }
15174            // ClickHouse: Uses SQL-standard quote doubling ('') for quotes,
15175            // backslash escaping for backslashes and special characters
15176            Some(DialectType::ClickHouse) => {
15177                self.write("'");
15178                for c in s.chars() {
15179                    match c {
15180                        '\'' => self.write("''"),
15181                        '\\' => self.write("\\\\"),
15182                        '\n' => self.write("\\n"),
15183                        '\r' => self.write("\\r"),
15184                        '\t' => self.write("\\t"),
15185                        '\0' => self.write("\\0"),
15186                        '\x07' => self.write("\\a"),
15187                        '\x08' => self.write("\\b"),
15188                        '\x0C' => self.write("\\f"),
15189                        '\x0B' => self.write("\\v"),
15190                        // Non-printable characters: emit as \xNN hex escapes
15191                        c if c.is_control() || (c as u32) < 0x20 => {
15192                            let byte = c as u32;
15193                            if byte < 256 {
15194                                self.write(&format!("\\x{:02X}", byte));
15195                            } else {
15196                                self.output.push(c);
15197                            }
15198                        }
15199                        _ => self.output.push(c),
15200                    }
15201                }
15202                self.write("'");
15203            }
15204            // Default: SQL standard double single quotes (works for most dialects)
15205            // PostgreSQL, Snowflake, DuckDB, TSQL, etc.
15206            _ => {
15207                self.write("'");
15208                for ch in s.chars() {
15209                    if ch == '\'' {
15210                        self.output.push_str("''");
15211                    } else {
15212                        self.output.push(ch);
15213                    }
15214                }
15215                self.write("'");
15216            }
15217        }
15218        Ok(())
15219    }
15220
15221    /// Write a byte string with proper escaping for BigQuery-style byte literals
15222    /// Escapes characters as \xNN hex escapes where needed
15223    fn write_escaped_byte_string(&mut self, s: &str) {
15224        for c in s.chars() {
15225            match c {
15226                // Escape single quotes
15227                '\'' => self.write("\\'"),
15228                // Escape backslashes
15229                '\\' => self.write("\\\\"),
15230                // Keep all printable characters (including non-ASCII) as-is
15231                _ if !c.is_control() => self.output.push(c),
15232                // Escape control characters as hex
15233                _ => {
15234                    let byte = c as u32;
15235                    if byte < 256 {
15236                        self.write(&format!("\\x{:02x}", byte));
15237                    } else {
15238                        // For unicode characters, write each UTF-8 byte
15239                        for b in c.to_string().as_bytes() {
15240                            self.write(&format!("\\x{:02x}", b));
15241                        }
15242                    }
15243                }
15244            }
15245        }
15246    }
15247
15248    fn generate_boolean(&mut self, b: &BooleanLiteral) -> Result<()> {
15249        use crate::dialects::DialectType;
15250
15251        // Different dialects have different boolean literal formats
15252        match self.config.dialect {
15253            // SQL Server typically uses 1/0 for boolean literals in many contexts
15254            // However, TRUE/FALSE also works in modern versions
15255            Some(DialectType::TSQL) => {
15256                self.write(if b.value { "1" } else { "0" });
15257            }
15258            // Oracle traditionally uses 1/0 (no native boolean until recent versions)
15259            Some(DialectType::Oracle) => {
15260                self.write(if b.value { "1" } else { "0" });
15261            }
15262            // MySQL accepts TRUE/FALSE as aliases for 1/0
15263            Some(DialectType::MySQL) => {
15264                self.write_keyword(if b.value { "TRUE" } else { "FALSE" });
15265            }
15266            // Most other dialects support TRUE/FALSE
15267            _ => {
15268                self.write_keyword(if b.value { "TRUE" } else { "FALSE" });
15269            }
15270        }
15271        Ok(())
15272    }
15273
15274    /// Generate an identifier that's used as an alias name
15275    /// This quotes reserved keywords in addition to already-quoted identifiers
15276    fn generate_alias_identifier(&mut self, id: &Identifier) -> Result<()> {
15277        let name = &id.name;
15278        let quote_style = &self.config.identifier_quote_style;
15279
15280        // For aliases, quote if:
15281        // 1. The identifier was explicitly quoted in the source
15282        // 2. The identifier is a reserved keyword for the current dialect
15283        let needs_quoting = id.quoted || self.is_reserved_keyword(name);
15284
15285        // Normalize identifier if configured
15286        let output_name = if self.config.normalize_identifiers && !id.quoted {
15287            name.to_ascii_lowercase()
15288        } else {
15289            name.to_string()
15290        };
15291
15292        if needs_quoting {
15293            // Escape any quote characters within the identifier
15294            let escaped_name = if quote_style.start == quote_style.end {
15295                output_name.replace(
15296                    quote_style.end,
15297                    &format!("{}{}", quote_style.end, quote_style.end),
15298                )
15299            } else {
15300                output_name.replace(
15301                    quote_style.end,
15302                    &format!("{}{}", quote_style.end, quote_style.end),
15303                )
15304            };
15305            self.write(&format!(
15306                "{}{}{}",
15307                quote_style.start, escaped_name, quote_style.end
15308            ));
15309        } else {
15310            self.write(&output_name);
15311        }
15312
15313        // Output trailing comments
15314        for comment in &id.trailing_comments {
15315            self.write(" ");
15316            self.write_formatted_comment(comment);
15317        }
15318        Ok(())
15319    }
15320
15321    fn generate_identifier(&mut self, id: &Identifier) -> Result<()> {
15322        use crate::dialects::DialectType;
15323
15324        let name = &id.name;
15325
15326        // For Athena, use backticks in Hive context, double quotes in Trino context
15327        let quote_style = if matches!(self.config.dialect, Some(DialectType::Athena))
15328            && self.athena_hive_context
15329        {
15330            &IdentifierQuoteStyle::BACKTICK
15331        } else {
15332            &self.config.identifier_quote_style
15333        };
15334
15335        // Quote if:
15336        // 1. The identifier was explicitly quoted in the source
15337        // 2. The identifier is a reserved keyword for the current dialect
15338        // 3. The config says to always quote identifiers (e.g., Athena/Presto)
15339        // This matches Python sqlglot's identifier_sql behavior
15340        // Also quote identifiers starting with digits if the target dialect doesn't support them
15341        let starts_with_digit = name.chars().next().map_or(false, |c| c.is_ascii_digit());
15342        let needs_digit_quoting = starts_with_digit
15343            && !self.config.identifiers_can_start_with_digit
15344            && self.config.dialect.is_some();
15345        let mysql_invalid_hex_identifier = matches!(self.config.dialect, Some(DialectType::MySQL))
15346            && name.len() > 2
15347            && (name.starts_with("0x") || name.starts_with("0X"))
15348            && !name[2..].chars().all(|c| c.is_ascii_hexdigit());
15349        let needs_quoting = id.quoted
15350            || self.is_reserved_keyword(name)
15351            || self.config.always_quote_identifiers
15352            || needs_digit_quoting
15353            || mysql_invalid_hex_identifier;
15354
15355        // Check for MySQL index column prefix length: name(16) or name(16) ASC/DESC
15356        // When quoted, we need to output `name`(16) not `name(16)`
15357        let (base_name, suffix) = if needs_quoting {
15358            // Try to extract prefix length from identifier: name(number) or name(number) ASC/DESC
15359            if let Some(paren_pos) = name.find('(') {
15360                let base = &name[..paren_pos];
15361                let rest = &name[paren_pos..];
15362                // Verify it looks like (digits) or (digits) ASC/DESC
15363                if rest.starts_with('(')
15364                    && (rest.ends_with(')') || rest.ends_with(") ASC") || rest.ends_with(") DESC"))
15365                {
15366                    // Check if content between parens is all digits
15367                    let close_paren = rest.find(')').unwrap_or(rest.len());
15368                    let inside = &rest[1..close_paren];
15369                    if inside.chars().all(|c| c.is_ascii_digit()) {
15370                        (base.to_string(), rest.to_string())
15371                    } else {
15372                        (name.to_string(), String::new())
15373                    }
15374                } else {
15375                    (name.to_string(), String::new())
15376                }
15377            } else if name.ends_with(" ASC") {
15378                let base = &name[..name.len() - 4];
15379                (base.to_string(), " ASC".to_string())
15380            } else if name.ends_with(" DESC") {
15381                let base = &name[..name.len() - 5];
15382                (base.to_string(), " DESC".to_string())
15383            } else {
15384                (name.to_string(), String::new())
15385            }
15386        } else {
15387            (name.to_string(), String::new())
15388        };
15389
15390        // Normalize identifier if configured, with special handling for Exasol
15391        // Exasol uses UPPERCASE normalization strategy, so reserved keywords that need quoting
15392        // should be uppercased when not already quoted (to match Python sqlglot behavior)
15393        let output_name = if self.config.normalize_identifiers && !id.quoted {
15394            base_name.to_ascii_lowercase()
15395        } else if matches!(self.config.dialect, Some(DialectType::Exasol))
15396            && !id.quoted
15397            && self.is_reserved_keyword(name)
15398        {
15399            // Exasol: uppercase reserved keywords when quoting them
15400            // This matches Python sqlglot's behavior with NORMALIZATION_STRATEGY = UPPERCASE
15401            base_name.to_ascii_uppercase()
15402        } else {
15403            base_name
15404        };
15405
15406        if needs_quoting {
15407            // Escape any quote characters within the identifier
15408            let escaped_name = if quote_style.start == quote_style.end {
15409                // Same start/end char (e.g., " or `) - double the quote char
15410                output_name.replace(
15411                    quote_style.end,
15412                    &format!("{}{}", quote_style.end, quote_style.end),
15413                )
15414            } else {
15415                // Different start/end (e.g., [ and ]) - escape only the end char
15416                output_name.replace(
15417                    quote_style.end,
15418                    &format!("{}{}", quote_style.end, quote_style.end),
15419                )
15420            };
15421            self.write(&format!(
15422                "{}{}{}{}",
15423                quote_style.start, escaped_name, quote_style.end, suffix
15424            ));
15425        } else {
15426            self.write(&output_name);
15427        }
15428
15429        // Output trailing comments
15430        for comment in &id.trailing_comments {
15431            self.write(" ");
15432            self.write_formatted_comment(comment);
15433        }
15434        Ok(())
15435    }
15436
15437    fn generate_column(&mut self, col: &Column) -> Result<()> {
15438        use crate::dialects::DialectType;
15439
15440        if let Some(table) = &col.table {
15441            // Exasol special case: LOCAL as column table prefix should NOT be quoted
15442            // LOCAL is a special keyword in Exasol for referencing aliases from the current scope
15443            // Only applies when: dialect is Exasol, name is "LOCAL" (case-insensitive), and not already quoted
15444            let is_exasol_local_prefix = matches!(self.config.dialect, Some(DialectType::Exasol))
15445                && !table.quoted
15446                && table.name.eq_ignore_ascii_case("LOCAL");
15447
15448            if is_exasol_local_prefix {
15449                // Write LOCAL unquoted (this is special Exasol syntax, not a table reference)
15450                self.write("LOCAL");
15451            } else {
15452                self.generate_identifier(table)?;
15453            }
15454            self.write(".");
15455        }
15456        self.generate_identifier(&col.name)?;
15457        // Oracle-style join marker (+)
15458        // Only output if dialect supports it (Oracle, Exasol)
15459        if col.join_mark && self.config.supports_column_join_marks {
15460            self.write(" (+)");
15461        }
15462        // Output trailing comments
15463        for comment in &col.trailing_comments {
15464            self.write_space();
15465            self.write_formatted_comment(comment);
15466        }
15467        Ok(())
15468    }
15469
15470    /// Generate a pseudocolumn (Oracle ROWNUM, ROWID, LEVEL, etc.)
15471    /// Pseudocolumns should NEVER be quoted, as quoting breaks them in Oracle
15472    fn generate_pseudocolumn(&mut self, pc: &Pseudocolumn) -> Result<()> {
15473        use crate::dialects::DialectType;
15474        use crate::expressions::PseudocolumnType;
15475
15476        // SYSDATE -> CURRENT_TIMESTAMP for non-Oracle/Redshift dialects
15477        if pc.kind == PseudocolumnType::Sysdate
15478            && !matches!(
15479                self.config.dialect,
15480                Some(DialectType::Oracle) | Some(DialectType::Redshift) | None
15481            )
15482        {
15483            self.write_keyword("CURRENT_TIMESTAMP");
15484            // Add () for dialects that expect it
15485            if matches!(
15486                self.config.dialect,
15487                Some(DialectType::MySQL)
15488                    | Some(DialectType::ClickHouse)
15489                    | Some(DialectType::Spark)
15490                    | Some(DialectType::Databricks)
15491                    | Some(DialectType::Hive)
15492            ) {
15493                self.write("()");
15494            }
15495        } else {
15496            self.write(pc.kind.as_str());
15497        }
15498        Ok(())
15499    }
15500
15501    /// Generate CONNECT BY clause (Oracle hierarchical queries)
15502    fn generate_connect(&mut self, connect: &Connect) -> Result<()> {
15503        use crate::dialects::DialectType;
15504
15505        // Generate native CONNECT BY for Oracle and Snowflake
15506        // For other dialects, add a comment noting manual conversion needed
15507        let supports_connect_by = matches!(
15508            self.config.dialect,
15509            Some(DialectType::Oracle) | Some(DialectType::Snowflake)
15510        );
15511
15512        if !supports_connect_by && self.config.dialect.is_some() {
15513            // Add comment for unsupported dialects
15514            if self.config.pretty {
15515                self.write_newline();
15516            } else {
15517                self.write_space();
15518            }
15519            self.write_unsupported_comment(
15520                "CONNECT BY requires manual conversion to recursive CTE",
15521            )?;
15522        }
15523
15524        // Generate START WITH if present (before CONNECT BY)
15525        if let Some(start) = &connect.start {
15526            if self.config.pretty {
15527                self.write_newline();
15528            } else {
15529                self.write_space();
15530            }
15531            self.write_keyword("START WITH");
15532            self.write_space();
15533            self.generate_expression(start)?;
15534        }
15535
15536        // Generate CONNECT BY
15537        if self.config.pretty {
15538            self.write_newline();
15539        } else {
15540            self.write_space();
15541        }
15542        self.write_keyword("CONNECT BY");
15543        if connect.nocycle {
15544            self.write_space();
15545            self.write_keyword("NOCYCLE");
15546        }
15547        self.write_space();
15548        self.generate_expression(&connect.connect)?;
15549
15550        Ok(())
15551    }
15552
15553    /// Generate Connect expression (for Expression::Connect variant)
15554    fn generate_connect_expr(&mut self, connect: &Connect) -> Result<()> {
15555        self.generate_connect(connect)
15556    }
15557
15558    /// Generate PRIOR expression
15559    fn generate_prior(&mut self, prior: &Prior) -> Result<()> {
15560        self.write_keyword("PRIOR");
15561        self.write_space();
15562        self.generate_expression(&prior.this)?;
15563        Ok(())
15564    }
15565
15566    /// Generate CONNECT_BY_ROOT function
15567    /// Syntax: CONNECT_BY_ROOT column (no parentheses)
15568    fn generate_connect_by_root(&mut self, cbr: &ConnectByRoot) -> Result<()> {
15569        self.write_keyword("CONNECT_BY_ROOT");
15570        self.write_space();
15571        self.generate_expression(&cbr.this)?;
15572        Ok(())
15573    }
15574
15575    /// Generate MATCH_RECOGNIZE clause
15576    fn generate_match_recognize(&mut self, mr: &MatchRecognize) -> Result<()> {
15577        use crate::dialects::DialectType;
15578
15579        // MATCH_RECOGNIZE is supported in Oracle, Snowflake, Presto, and Trino
15580        let supports_match_recognize = matches!(
15581            self.config.dialect,
15582            Some(DialectType::Oracle)
15583                | Some(DialectType::Snowflake)
15584                | Some(DialectType::Presto)
15585                | Some(DialectType::Trino)
15586        );
15587
15588        // Generate the source table first
15589        if let Some(source) = &mr.this {
15590            self.generate_expression(source)?;
15591        }
15592
15593        if !supports_match_recognize {
15594            self.write_unsupported_comment("MATCH_RECOGNIZE not supported in this dialect")?;
15595            return Ok(());
15596        }
15597
15598        // In pretty mode, MATCH_RECOGNIZE should be on a new line
15599        if self.config.pretty {
15600            self.write_newline();
15601        } else {
15602            self.write_space();
15603        }
15604
15605        self.write_keyword("MATCH_RECOGNIZE");
15606        self.write(" (");
15607
15608        if self.config.pretty {
15609            self.indent_level += 1;
15610        }
15611
15612        let mut needs_separator = false;
15613
15614        // PARTITION BY
15615        if let Some(partition_by) = &mr.partition_by {
15616            if !partition_by.is_empty() {
15617                if self.config.pretty {
15618                    self.write_newline();
15619                    self.write_indent();
15620                }
15621                self.write_keyword("PARTITION BY");
15622                self.write_space();
15623                for (i, expr) in partition_by.iter().enumerate() {
15624                    if i > 0 {
15625                        self.write(", ");
15626                    }
15627                    self.generate_expression(expr)?;
15628                }
15629                needs_separator = true;
15630            }
15631        }
15632
15633        // ORDER BY
15634        if let Some(order_by) = &mr.order_by {
15635            if !order_by.is_empty() {
15636                if needs_separator {
15637                    if self.config.pretty {
15638                        self.write_newline();
15639                        self.write_indent();
15640                    } else {
15641                        self.write_space();
15642                    }
15643                } else if self.config.pretty {
15644                    self.write_newline();
15645                    self.write_indent();
15646                }
15647                self.write_keyword("ORDER BY");
15648                // In pretty mode, put each ORDER BY column on a new indented line
15649                if self.config.pretty {
15650                    self.indent_level += 1;
15651                    for (i, ordered) in order_by.iter().enumerate() {
15652                        if i > 0 {
15653                            self.write(",");
15654                        }
15655                        self.write_newline();
15656                        self.write_indent();
15657                        self.generate_ordered(ordered)?;
15658                    }
15659                    self.indent_level -= 1;
15660                } else {
15661                    self.write_space();
15662                    for (i, ordered) in order_by.iter().enumerate() {
15663                        if i > 0 {
15664                            self.write(", ");
15665                        }
15666                        self.generate_ordered(ordered)?;
15667                    }
15668                }
15669                needs_separator = true;
15670            }
15671        }
15672
15673        // MEASURES
15674        if let Some(measures) = &mr.measures {
15675            if !measures.is_empty() {
15676                if needs_separator {
15677                    if self.config.pretty {
15678                        self.write_newline();
15679                        self.write_indent();
15680                    } else {
15681                        self.write_space();
15682                    }
15683                } else if self.config.pretty {
15684                    self.write_newline();
15685                    self.write_indent();
15686                }
15687                self.write_keyword("MEASURES");
15688                // In pretty mode, put each MEASURE on a new indented line
15689                if self.config.pretty {
15690                    self.indent_level += 1;
15691                    for (i, measure) in measures.iter().enumerate() {
15692                        if i > 0 {
15693                            self.write(",");
15694                        }
15695                        self.write_newline();
15696                        self.write_indent();
15697                        // Handle RUNNING/FINAL prefix
15698                        if let Some(semantics) = &measure.window_frame {
15699                            match semantics {
15700                                MatchRecognizeSemantics::Running => {
15701                                    self.write_keyword("RUNNING");
15702                                    self.write_space();
15703                                }
15704                                MatchRecognizeSemantics::Final => {
15705                                    self.write_keyword("FINAL");
15706                                    self.write_space();
15707                                }
15708                            }
15709                        }
15710                        self.generate_expression(&measure.this)?;
15711                    }
15712                    self.indent_level -= 1;
15713                } else {
15714                    self.write_space();
15715                    for (i, measure) in measures.iter().enumerate() {
15716                        if i > 0 {
15717                            self.write(", ");
15718                        }
15719                        // Handle RUNNING/FINAL prefix
15720                        if let Some(semantics) = &measure.window_frame {
15721                            match semantics {
15722                                MatchRecognizeSemantics::Running => {
15723                                    self.write_keyword("RUNNING");
15724                                    self.write_space();
15725                                }
15726                                MatchRecognizeSemantics::Final => {
15727                                    self.write_keyword("FINAL");
15728                                    self.write_space();
15729                                }
15730                            }
15731                        }
15732                        self.generate_expression(&measure.this)?;
15733                    }
15734                }
15735                needs_separator = true;
15736            }
15737        }
15738
15739        // Row semantics (ONE ROW PER MATCH, ALL ROWS PER MATCH, etc.)
15740        if let Some(rows) = &mr.rows {
15741            if needs_separator {
15742                if self.config.pretty {
15743                    self.write_newline();
15744                    self.write_indent();
15745                } else {
15746                    self.write_space();
15747                }
15748            } else if self.config.pretty {
15749                self.write_newline();
15750                self.write_indent();
15751            }
15752            match rows {
15753                MatchRecognizeRows::OneRowPerMatch => {
15754                    self.write_keyword("ONE ROW PER MATCH");
15755                }
15756                MatchRecognizeRows::AllRowsPerMatch => {
15757                    self.write_keyword("ALL ROWS PER MATCH");
15758                }
15759                MatchRecognizeRows::AllRowsPerMatchShowEmptyMatches => {
15760                    self.write_keyword("ALL ROWS PER MATCH SHOW EMPTY MATCHES");
15761                }
15762                MatchRecognizeRows::AllRowsPerMatchOmitEmptyMatches => {
15763                    self.write_keyword("ALL ROWS PER MATCH OMIT EMPTY MATCHES");
15764                }
15765                MatchRecognizeRows::AllRowsPerMatchWithUnmatchedRows => {
15766                    self.write_keyword("ALL ROWS PER MATCH WITH UNMATCHED ROWS");
15767                }
15768            }
15769            needs_separator = true;
15770        }
15771
15772        // AFTER MATCH SKIP
15773        if let Some(after) = &mr.after {
15774            if needs_separator {
15775                if self.config.pretty {
15776                    self.write_newline();
15777                    self.write_indent();
15778                } else {
15779                    self.write_space();
15780                }
15781            } else if self.config.pretty {
15782                self.write_newline();
15783                self.write_indent();
15784            }
15785            match after {
15786                MatchRecognizeAfter::PastLastRow => {
15787                    self.write_keyword("AFTER MATCH SKIP PAST LAST ROW");
15788                }
15789                MatchRecognizeAfter::ToNextRow => {
15790                    self.write_keyword("AFTER MATCH SKIP TO NEXT ROW");
15791                }
15792                MatchRecognizeAfter::ToFirst(ident) => {
15793                    self.write_keyword("AFTER MATCH SKIP TO FIRST");
15794                    self.write_space();
15795                    self.generate_identifier(ident)?;
15796                }
15797                MatchRecognizeAfter::ToLast(ident) => {
15798                    self.write_keyword("AFTER MATCH SKIP TO LAST");
15799                    self.write_space();
15800                    self.generate_identifier(ident)?;
15801                }
15802            }
15803            needs_separator = true;
15804        }
15805
15806        // PATTERN
15807        if let Some(pattern) = &mr.pattern {
15808            if needs_separator {
15809                if self.config.pretty {
15810                    self.write_newline();
15811                    self.write_indent();
15812                } else {
15813                    self.write_space();
15814                }
15815            } else if self.config.pretty {
15816                self.write_newline();
15817                self.write_indent();
15818            }
15819            self.write_keyword("PATTERN");
15820            self.write_space();
15821            self.write("(");
15822            self.write(pattern);
15823            self.write(")");
15824            needs_separator = true;
15825        }
15826
15827        // DEFINE
15828        if let Some(define) = &mr.define {
15829            if !define.is_empty() {
15830                if needs_separator {
15831                    if self.config.pretty {
15832                        self.write_newline();
15833                        self.write_indent();
15834                    } else {
15835                        self.write_space();
15836                    }
15837                } else if self.config.pretty {
15838                    self.write_newline();
15839                    self.write_indent();
15840                }
15841                self.write_keyword("DEFINE");
15842                // In pretty mode, put each DEFINE on a new indented line
15843                if self.config.pretty {
15844                    self.indent_level += 1;
15845                    for (i, (name, expr)) in define.iter().enumerate() {
15846                        if i > 0 {
15847                            self.write(",");
15848                        }
15849                        self.write_newline();
15850                        self.write_indent();
15851                        self.generate_identifier(name)?;
15852                        self.write(" AS ");
15853                        self.generate_expression(expr)?;
15854                    }
15855                    self.indent_level -= 1;
15856                } else {
15857                    self.write_space();
15858                    for (i, (name, expr)) in define.iter().enumerate() {
15859                        if i > 0 {
15860                            self.write(", ");
15861                        }
15862                        self.generate_identifier(name)?;
15863                        self.write(" AS ");
15864                        self.generate_expression(expr)?;
15865                    }
15866                }
15867            }
15868        }
15869
15870        if self.config.pretty {
15871            self.indent_level -= 1;
15872            self.write_newline();
15873        }
15874        self.write(")");
15875
15876        // Alias - only include AS if it was explicitly present in the input
15877        if let Some(alias) = &mr.alias {
15878            self.write(" ");
15879            if mr.alias_explicit_as {
15880                self.write_keyword("AS");
15881                self.write(" ");
15882            }
15883            self.generate_identifier(alias)?;
15884        }
15885
15886        Ok(())
15887    }
15888
15889    /// Generate a query hint /*+ ... */
15890    fn generate_hint(&mut self, hint: &Hint) -> Result<()> {
15891        use crate::dialects::DialectType;
15892
15893        // Output hints for dialects that support them, or when no dialect is specified (identity tests)
15894        let supports_hints = matches!(
15895            self.config.dialect,
15896            None |  // No dialect = preserve everything
15897            Some(DialectType::Oracle) | Some(DialectType::MySQL) |
15898            Some(DialectType::Spark) | Some(DialectType::Hive) |
15899            Some(DialectType::Databricks) | Some(DialectType::PostgreSQL)
15900        );
15901
15902        if !supports_hints || hint.expressions.is_empty() {
15903            return Ok(());
15904        }
15905
15906        // First, expand raw hint text into individual hint strings
15907        // This handles the case where the parser stored multiple hints as a single raw string
15908        let mut hint_strings: Vec<String> = Vec::new();
15909        for expr in &hint.expressions {
15910            match expr {
15911                HintExpression::Raw(text) => {
15912                    // Parse raw hint text into individual hint function calls
15913                    let parsed = self.parse_raw_hint_text(text);
15914                    hint_strings.extend(parsed);
15915                }
15916                _ => {
15917                    hint_strings.push(self.hint_expression_to_string(expr)?);
15918                }
15919            }
15920        }
15921
15922        // In pretty mode with multiple hints, always use multiline format
15923        // This matches Python sqlglot's behavior where expressions() with default dynamic=False
15924        // always joins with newlines in pretty mode
15925        let use_multiline = self.config.pretty && hint_strings.len() > 1;
15926
15927        if use_multiline {
15928            // Pretty print with each hint on its own line
15929            self.write(" /*+ ");
15930            for (i, hint_str) in hint_strings.iter().enumerate() {
15931                if i > 0 {
15932                    self.write_newline();
15933                    self.write("  "); // 2-space indent within hint block
15934                }
15935                self.write(hint_str);
15936            }
15937            self.write(" */");
15938        } else {
15939            // Single line format
15940            self.write(" /*+ ");
15941            let sep = match self.config.dialect {
15942                Some(DialectType::Spark) | Some(DialectType::Databricks) => ", ",
15943                _ => " ",
15944            };
15945            for (i, hint_str) in hint_strings.iter().enumerate() {
15946                if i > 0 {
15947                    self.write(sep);
15948                }
15949                self.write(hint_str);
15950            }
15951            self.write(" */");
15952        }
15953
15954        Ok(())
15955    }
15956
15957    /// Parse raw hint text into individual hint function calls
15958    /// e.g., "LEADING(a b) USE_NL(c)" -> ["LEADING(a b)", "USE_NL(c)"]
15959    /// If the hint contains unparseable content (like SQL keywords), return as single raw string
15960    fn parse_raw_hint_text(&self, text: &str) -> Vec<String> {
15961        let mut results = Vec::new();
15962        let mut chars = text.chars().peekable();
15963        let mut current = String::new();
15964        let mut paren_depth = 0;
15965        let mut has_unparseable_content = false;
15966        let mut position_after_last_function = 0;
15967        let mut char_position = 0;
15968
15969        while let Some(c) = chars.next() {
15970            char_position += c.len_utf8();
15971            match c {
15972                '(' => {
15973                    paren_depth += 1;
15974                    current.push(c);
15975                }
15976                ')' => {
15977                    paren_depth -= 1;
15978                    current.push(c);
15979                    // When we close the outer parenthesis, we've completed a hint function
15980                    if paren_depth == 0 {
15981                        let trimmed = current.trim().to_string();
15982                        if !trimmed.is_empty() {
15983                            // Format this hint for pretty printing if needed
15984                            let formatted = self.format_hint_function(&trimmed);
15985                            results.push(formatted);
15986                        }
15987                        current.clear();
15988                        position_after_last_function = char_position;
15989                    }
15990                }
15991                ' ' | '\t' | '\n' | ',' if paren_depth == 0 => {
15992                    // Space/comma/whitespace outside parentheses - skip
15993                }
15994                _ if paren_depth == 0 => {
15995                    // Character outside parentheses - accumulate for potential hint name
15996                    current.push(c);
15997                }
15998                _ => {
15999                    current.push(c);
16000                }
16001            }
16002        }
16003
16004        // Check if there's remaining text after the last function call
16005        let remaining_text = text[position_after_last_function..].trim();
16006        if !remaining_text.is_empty() {
16007            // Check if it looks like valid hint function names
16008            // Valid hint identifiers typically are uppercase alphanumeric with underscores
16009            // If we see multiple words without parens, it's likely unparseable
16010            let words: Vec<&str> = remaining_text.split_whitespace().collect();
16011            let looks_like_hint_functions = words.iter().all(|word| {
16012                // A valid hint name followed by opening paren, or a standalone uppercase identifier
16013                word.contains('(') || (word.chars().all(|c| c.is_ascii_uppercase() || c == '_'))
16014            });
16015
16016            if !looks_like_hint_functions && words.len() > 1 {
16017                has_unparseable_content = true;
16018            }
16019        }
16020
16021        // If we detected unparseable content (like SQL keywords), return the whole hint as-is
16022        if has_unparseable_content {
16023            return vec![text.trim().to_string()];
16024        }
16025
16026        // If we couldn't parse anything, return the original text as a single hint
16027        if results.is_empty() {
16028            results.push(text.trim().to_string());
16029        }
16030
16031        results
16032    }
16033
16034    /// Format a hint function for pretty printing
16035    /// e.g., "LEADING(aaa bbb ccc ddd)" -> multiline if args are too wide
16036    fn format_hint_function(&self, hint: &str) -> String {
16037        if !self.config.pretty {
16038            return hint.to_string();
16039        }
16040
16041        // Try to parse NAME(args) pattern
16042        if let Some(paren_pos) = hint.find('(') {
16043            if hint.ends_with(')') {
16044                let name = &hint[..paren_pos];
16045                let args_str = &hint[paren_pos + 1..hint.len() - 1];
16046
16047                // Parse arguments (space-separated for Oracle hints)
16048                let args: Vec<&str> = args_str.split_whitespace().collect();
16049
16050                // Calculate total width of arguments
16051                let total_args_width: usize =
16052                    args.iter().map(|s| s.len()).sum::<usize>() + args.len().saturating_sub(1); // spaces between args
16053
16054                // If too wide, format on multiple lines
16055                if total_args_width > self.config.max_text_width && !args.is_empty() {
16056                    let mut result = format!("{}(\n", name);
16057                    for arg in &args {
16058                        result.push_str("    "); // 4-space indent for args
16059                        result.push_str(arg);
16060                        result.push('\n');
16061                    }
16062                    result.push_str("  )"); // 2-space indent for closing paren
16063                    return result;
16064                }
16065            }
16066        }
16067
16068        hint.to_string()
16069    }
16070
16071    /// Convert a hint expression to a string, handling multiline formatting for long arguments
16072    fn hint_expression_to_string(&mut self, expr: &HintExpression) -> Result<String> {
16073        match expr {
16074            HintExpression::Function { name, args } => {
16075                // Generate each argument to a string
16076                let arg_strings: Vec<String> = args
16077                    .iter()
16078                    .map(|arg| {
16079                        let mut gen = Generator::with_arc_config(self.config.clone());
16080                        gen.generate_expression(arg)?;
16081                        Ok(gen.output)
16082                    })
16083                    .collect::<Result<Vec<_>>>()?;
16084
16085                // Oracle hints use space-separated arguments, not comma-separated
16086                let total_args_width: usize = arg_strings.iter().map(|s| s.len()).sum::<usize>()
16087                    + arg_strings.len().saturating_sub(1); // spaces between args
16088
16089                // Check if function args need multiline formatting
16090                // Use too_wide check for argument formatting
16091                let args_multiline =
16092                    self.config.pretty && total_args_width > self.config.max_text_width;
16093
16094                if args_multiline && !arg_strings.is_empty() {
16095                    // Multiline format for long argument lists
16096                    let mut result = format!("{}(\n", name);
16097                    for arg_str in &arg_strings {
16098                        result.push_str("    "); // 4-space indent for args
16099                        result.push_str(arg_str);
16100                        result.push('\n');
16101                    }
16102                    result.push_str("  )"); // 2-space indent for closing paren
16103                    Ok(result)
16104                } else {
16105                    // Single line format with space-separated args (Oracle style)
16106                    let args_str = arg_strings.join(" ");
16107                    Ok(format!("{}({})", name, args_str))
16108                }
16109            }
16110            HintExpression::Identifier(name) => Ok(name.clone()),
16111            HintExpression::Raw(text) => {
16112                // For pretty printing, try to format the raw text
16113                if self.config.pretty {
16114                    Ok(self.format_hint_function(text))
16115                } else {
16116                    Ok(text.clone())
16117                }
16118            }
16119        }
16120    }
16121
16122    fn generate_table(&mut self, table: &TableRef) -> Result<()> {
16123        // PostgreSQL ONLY modifier: prevents scanning child tables
16124        if table.only {
16125            self.write_keyword("ONLY");
16126            self.write_space();
16127        }
16128
16129        // Check for IDENTIFIER() (Snowflake) or OPENDATASOURCE(...).db.schema.table (TSQL)
16130        if let Some(ref identifier_func) = table.identifier_func {
16131            self.generate_expression(identifier_func)?;
16132            // If table name parts are present, emit .catalog.schema.name after the function
16133            if !table.name.name.is_empty() {
16134                if let Some(catalog) = &table.catalog {
16135                    self.write(".");
16136                    self.generate_identifier(catalog)?;
16137                }
16138                if let Some(schema) = &table.schema {
16139                    self.write(".");
16140                    self.generate_identifier(schema)?;
16141                }
16142                self.write(".");
16143                self.generate_identifier(&table.name)?;
16144            }
16145        } else {
16146            if let Some(catalog) = &table.catalog {
16147                self.generate_identifier(catalog)?;
16148                self.write(".");
16149            }
16150            if let Some(schema) = &table.schema {
16151                self.generate_identifier(schema)?;
16152                self.write(".");
16153            }
16154            self.generate_identifier(&table.name)?;
16155        }
16156
16157        // Output Snowflake CHANGES clause (before partition, includes its own AT/BEFORE/END)
16158        if let Some(changes) = &table.changes {
16159            self.write(" ");
16160            self.generate_changes(changes)?;
16161        }
16162
16163        // Output MySQL PARTITION clause: t1 PARTITION(p0, p1)
16164        if !table.partitions.is_empty() {
16165            self.write_space();
16166            self.write_keyword("PARTITION");
16167            self.write("(");
16168            for (i, partition) in table.partitions.iter().enumerate() {
16169                if i > 0 {
16170                    self.write(", ");
16171                }
16172                self.generate_identifier(partition)?;
16173            }
16174            self.write(")");
16175        }
16176
16177        // Output time travel clause: BEFORE (STATEMENT => ...) or AT (TIMESTAMP => ...)
16178        // Skip if CHANGES clause is present (CHANGES includes its own time travel)
16179        if table.changes.is_none() {
16180            if let Some(when) = &table.when {
16181                self.write_space();
16182                self.generate_historical_data(when)?;
16183            }
16184        }
16185
16186        // Output TSQL FOR SYSTEM_TIME temporal clause (before alias, except BigQuery)
16187        let system_time_post_alias = matches!(self.config.dialect, Some(DialectType::BigQuery));
16188        if !system_time_post_alias {
16189            if let Some(ref system_time) = table.system_time {
16190                self.write_space();
16191                self.write(system_time);
16192            }
16193        }
16194
16195        // Output Presto/Trino time travel: FOR VERSION AS OF / FOR TIMESTAMP AS OF
16196        if let Some(ref version) = table.version {
16197            self.write_space();
16198            self.generate_version(version)?;
16199        }
16200
16201        // When alias_post_tablesample is true, the order is: table TABLESAMPLE (...) alias
16202        // When alias_post_tablesample is false (default), the order is: table alias TABLESAMPLE (...)
16203        // Oracle, Hive, Spark use ALIAS_POST_TABLESAMPLE = true (alias comes after sample)
16204        let alias_post_tablesample = self.config.alias_post_tablesample;
16205
16206        if alias_post_tablesample {
16207            // TABLESAMPLE before alias (Oracle, Hive, Spark)
16208            self.generate_table_sample_clause(table)?;
16209        }
16210
16211        // Output table hints (TSQL: WITH (TABLOCK, INDEX(myindex), ...))
16212        // For SQLite, INDEXED BY hints come after the alias, so skip here
16213        let is_sqlite_hint = matches!(self.config.dialect, Some(DialectType::SQLite))
16214            && table.hints.iter().any(|h| {
16215                if let Expression::Identifier(id) = h {
16216                    id.name.starts_with("INDEXED BY") || id.name == "NOT INDEXED"
16217                } else {
16218                    false
16219                }
16220            });
16221        if !table.hints.is_empty() && !is_sqlite_hint {
16222            for hint in &table.hints {
16223                self.write_space();
16224                self.generate_expression(hint)?;
16225            }
16226        }
16227
16228        if let Some(alias) = &table.alias {
16229            self.write_space();
16230            // Output AS if it was explicitly present in the input, OR for certain dialects/cases
16231            // Generic mode and most dialects always use AS for table aliases
16232            let always_use_as = self.config.dialect.is_none()
16233                || matches!(
16234                    self.config.dialect,
16235                    Some(DialectType::Generic)
16236                        | Some(DialectType::PostgreSQL)
16237                        | Some(DialectType::Redshift)
16238                        | Some(DialectType::Snowflake)
16239                        | Some(DialectType::BigQuery)
16240                        | Some(DialectType::DuckDB)
16241                        | Some(DialectType::Presto)
16242                        | Some(DialectType::Trino)
16243                        | Some(DialectType::TSQL)
16244                        | Some(DialectType::Fabric)
16245                        | Some(DialectType::MySQL)
16246                        | Some(DialectType::Spark)
16247                        | Some(DialectType::Hive)
16248                        | Some(DialectType::SQLite)
16249                        | Some(DialectType::Drill)
16250                );
16251            let is_stage_ref = table.name.name.starts_with('@');
16252            // Oracle never uses AS for table aliases
16253            let suppress_as = matches!(self.config.dialect, Some(DialectType::Oracle));
16254            if !suppress_as && (table.alias_explicit_as || always_use_as || is_stage_ref) {
16255                self.write_keyword("AS");
16256                self.write_space();
16257            }
16258            self.generate_identifier(alias)?;
16259
16260            // Output column aliases if present: AS t(c1, c2)
16261            // Skip for dialects that don't support table alias columns (BigQuery, SQLite)
16262            if !table.column_aliases.is_empty() && self.config.supports_table_alias_columns {
16263                self.write("(");
16264                for (i, col_alias) in table.column_aliases.iter().enumerate() {
16265                    if i > 0 {
16266                        self.write(", ");
16267                    }
16268                    self.generate_identifier(col_alias)?;
16269                }
16270                self.write(")");
16271            }
16272        }
16273
16274        // BigQuery: FOR SYSTEM_TIME AS OF after alias
16275        if system_time_post_alias {
16276            if let Some(ref system_time) = table.system_time {
16277                self.write_space();
16278                self.write(system_time);
16279            }
16280        }
16281
16282        // For default behavior (alias_post_tablesample = false), output TABLESAMPLE after alias
16283        if !alias_post_tablesample {
16284            self.generate_table_sample_clause(table)?;
16285        }
16286
16287        // Output SQLite INDEXED BY / NOT INDEXED hints after alias
16288        if is_sqlite_hint {
16289            for hint in &table.hints {
16290                self.write_space();
16291                self.generate_expression(hint)?;
16292            }
16293        }
16294
16295        // ClickHouse FINAL modifier
16296        if table.final_ && matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
16297            self.write_space();
16298            self.write_keyword("FINAL");
16299        }
16300
16301        // Output trailing comments
16302        for comment in &table.trailing_comments {
16303            self.write_space();
16304            self.write_formatted_comment(comment);
16305        }
16306        // Note: leading_comments (from before table in FROM clause) are intentionally NOT
16307        // output here - they are output by the FROM/PIVOT generator after the full expression
16308
16309        Ok(())
16310    }
16311
16312    /// Helper to output TABLESAMPLE clause for a table reference
16313    fn generate_table_sample_clause(&mut self, table: &TableRef) -> Result<()> {
16314        if let Some(ref ts) = table.table_sample {
16315            self.write_space();
16316            if ts.is_using_sample {
16317                self.write_keyword("USING SAMPLE");
16318            } else {
16319                // Use the configured tablesample keyword (e.g., "TABLESAMPLE" or "SAMPLE")
16320                self.write_keyword(self.config.tablesample_keywords);
16321            }
16322            self.generate_sample_body(ts)?;
16323            // Seed for table-level sample - use dialect's configured keyword
16324            if let Some(ref seed) = ts.seed {
16325                self.write_space();
16326                self.write_keyword(self.config.tablesample_seed_keyword);
16327                self.write(" (");
16328                self.generate_expression(seed)?;
16329                self.write(")");
16330            }
16331        }
16332        Ok(())
16333    }
16334
16335    fn generate_stage_reference(&mut self, sr: &StageReference) -> Result<()> {
16336        // Output: '@stage_name/path' if quoted, or @stage_name/path otherwise
16337        // Optionally followed by (FILE_FORMAT => 'fmt', PATTERN => '*.csv')
16338
16339        if sr.quoted {
16340            self.write("'");
16341        }
16342
16343        self.write(&sr.name);
16344        if let Some(path) = &sr.path {
16345            self.write(path);
16346        }
16347
16348        if sr.quoted {
16349            self.write("'");
16350        }
16351
16352        // Output FILE_FORMAT and PATTERN if present
16353        let has_options = sr.file_format.is_some() || sr.pattern.is_some();
16354        if has_options {
16355            self.write(" (");
16356            let mut first = true;
16357
16358            if let Some(file_format) = &sr.file_format {
16359                if !first {
16360                    self.write(", ");
16361                }
16362                self.write_keyword("FILE_FORMAT");
16363                self.write(" => ");
16364                self.generate_expression(file_format)?;
16365                first = false;
16366            }
16367
16368            if let Some(pattern) = &sr.pattern {
16369                if !first {
16370                    self.write(", ");
16371                }
16372                self.write_keyword("PATTERN");
16373                self.write(" => '");
16374                self.write(pattern);
16375                self.write("'");
16376            }
16377
16378            self.write(")");
16379        }
16380        Ok(())
16381    }
16382
16383    fn generate_star(&mut self, star: &Star) -> Result<()> {
16384        use crate::dialects::DialectType;
16385
16386        if let Some(table) = &star.table {
16387            self.generate_identifier(table)?;
16388            self.write(".");
16389        }
16390        self.write("*");
16391
16392        // Generate EXCLUDE/EXCEPT clause based on dialect
16393        if let Some(except) = &star.except {
16394            if !except.is_empty() {
16395                self.write_space();
16396                // Use dialect-appropriate keyword
16397                match self.config.dialect {
16398                    Some(DialectType::BigQuery) => self.write_keyword("EXCEPT"),
16399                    Some(DialectType::DuckDB) | Some(DialectType::Snowflake) => {
16400                        self.write_keyword("EXCLUDE")
16401                    }
16402                    _ => self.write_keyword("EXCEPT"), // Default to EXCEPT
16403                }
16404                self.write(" (");
16405                for (i, col) in except.iter().enumerate() {
16406                    if i > 0 {
16407                        self.write(", ");
16408                    }
16409                    self.generate_identifier(col)?;
16410                }
16411                self.write(")");
16412            }
16413        }
16414
16415        // Generate REPLACE clause
16416        if let Some(replace) = &star.replace {
16417            if !replace.is_empty() {
16418                self.write_space();
16419                self.write_keyword("REPLACE");
16420                self.write(" (");
16421                for (i, alias) in replace.iter().enumerate() {
16422                    if i > 0 {
16423                        self.write(", ");
16424                    }
16425                    self.generate_expression(&alias.this)?;
16426                    self.write_space();
16427                    self.write_keyword("AS");
16428                    self.write_space();
16429                    self.generate_identifier(&alias.alias)?;
16430                }
16431                self.write(")");
16432            }
16433        }
16434
16435        // Generate RENAME clause (Snowflake specific)
16436        if let Some(rename) = &star.rename {
16437            if !rename.is_empty() {
16438                self.write_space();
16439                self.write_keyword("RENAME");
16440                self.write(" (");
16441                for (i, (old_name, new_name)) in rename.iter().enumerate() {
16442                    if i > 0 {
16443                        self.write(", ");
16444                    }
16445                    self.generate_identifier(old_name)?;
16446                    self.write_space();
16447                    self.write_keyword("AS");
16448                    self.write_space();
16449                    self.generate_identifier(new_name)?;
16450                }
16451                self.write(")");
16452            }
16453        }
16454
16455        // Output trailing comments
16456        for comment in &star.trailing_comments {
16457            self.write_space();
16458            self.write_formatted_comment(comment);
16459        }
16460
16461        Ok(())
16462    }
16463
16464    /// Generate Snowflake braced wildcard syntax: {*}, {tbl.*}, {* EXCLUDE (...)}, {* ILIKE '...'}
16465    fn generate_braced_wildcard(&mut self, expr: &Expression) -> Result<()> {
16466        self.write("{");
16467        match expr {
16468            Expression::Star(star) => {
16469                // Generate the star (table.* or just * with optional EXCLUDE)
16470                self.generate_star(star)?;
16471            }
16472            Expression::ILike(ilike) => {
16473                // {* ILIKE 'pattern'} syntax
16474                self.generate_expression(&ilike.left)?;
16475                self.write_space();
16476                self.write_keyword("ILIKE");
16477                self.write_space();
16478                self.generate_expression(&ilike.right)?;
16479            }
16480            _ => {
16481                self.generate_expression(expr)?;
16482            }
16483        }
16484        self.write("}");
16485        Ok(())
16486    }
16487
16488    fn generate_alias(&mut self, alias: &Alias) -> Result<()> {
16489        // Generate inner expression, but skip trailing comments if they're in pre_alias_comments
16490        // to avoid duplication (comments are captured as both Column.trailing_comments
16491        // and Alias.pre_alias_comments during parsing)
16492        match &alias.this {
16493            Expression::Column(col) => {
16494                // Generate column without trailing comments - they're in pre_alias_comments
16495                if let Some(table) = &col.table {
16496                    self.generate_identifier(table)?;
16497                    self.write(".");
16498                }
16499                self.generate_identifier(&col.name)?;
16500            }
16501            _ => {
16502                self.generate_expression(&alias.this)?;
16503            }
16504        }
16505
16506        // Handle pre-alias comments: when there are no trailing_comments, sqlglot
16507        // moves pre-alias comments to after the alias. When there are also trailing_comments,
16508        // keep pre-alias comments in their original position (between expression and AS).
16509        if !alias.pre_alias_comments.is_empty() && !alias.trailing_comments.is_empty() {
16510            for comment in &alias.pre_alias_comments {
16511                self.write_space();
16512                self.write_formatted_comment(comment);
16513            }
16514        }
16515
16516        use crate::dialects::DialectType;
16517
16518        // Determine if we should skip AS keyword for table-valued function aliases
16519        // Oracle and some other dialects don't use AS for table aliases
16520        // Note: We specifically use TableFromRows here, NOT Function, because Function
16521        // matches regular functions like MATCH_NUMBER() which should include the AS keyword.
16522        // TableFromRows represents TABLE(expr) constructs which are actual table-valued functions.
16523        let is_table_source = matches!(
16524            &alias.this,
16525            Expression::JSONTable(_)
16526                | Expression::XMLTable(_)
16527                | Expression::TableFromRows(_)
16528                | Expression::Unnest(_)
16529                | Expression::MatchRecognize(_)
16530                | Expression::Select(_)
16531                | Expression::Subquery(_)
16532                | Expression::Paren(_)
16533        );
16534        let dialect_skips_table_alias_as = matches!(self.config.dialect, Some(DialectType::Oracle));
16535        let skip_as = is_table_source && dialect_skips_table_alias_as;
16536
16537        self.write_space();
16538        if !skip_as {
16539            self.write_keyword("AS");
16540            self.write_space();
16541        }
16542
16543        // BigQuery doesn't support column aliases in table aliases: AS t(c1, c2)
16544        let skip_column_aliases = matches!(self.config.dialect, Some(DialectType::BigQuery));
16545
16546        // Check if we have column aliases only (no table alias name)
16547        if alias.alias.is_empty() && !alias.column_aliases.is_empty() && !skip_column_aliases {
16548            // Generate AS (col1, col2, ...)
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 if !alias.column_aliases.is_empty() && !skip_column_aliases {
16558            // Generate AS alias(col1, col2, ...)
16559            self.generate_alias_identifier(&alias.alias)?;
16560            self.write("(");
16561            for (i, col_alias) in alias.column_aliases.iter().enumerate() {
16562                if i > 0 {
16563                    self.write(", ");
16564                }
16565                self.generate_alias_identifier(col_alias)?;
16566            }
16567            self.write(")");
16568        } else {
16569            // Simple alias (or BigQuery without column aliases)
16570            self.generate_alias_identifier(&alias.alias)?;
16571        }
16572
16573        // Output trailing comments (comments after the alias)
16574        for comment in &alias.trailing_comments {
16575            self.write_space();
16576            self.write_formatted_comment(comment);
16577        }
16578
16579        // Output pre-alias comments: when there are no trailing_comments, sqlglot
16580        // moves pre-alias comments to after the alias. When there are trailing_comments,
16581        // the pre-alias comments were already lost (consumed as column trailing comments
16582        // that were then used as pre_alias_comments). We always emit them after alias.
16583        if alias.trailing_comments.is_empty() {
16584            for comment in &alias.pre_alias_comments {
16585                self.write_space();
16586                self.write_formatted_comment(comment);
16587            }
16588        }
16589
16590        Ok(())
16591    }
16592
16593    fn generate_cast(&mut self, cast: &Cast) -> Result<()> {
16594        use crate::dialects::DialectType;
16595
16596        // SingleStore uses :> syntax
16597        if matches!(self.config.dialect, Some(DialectType::SingleStore)) {
16598            self.generate_expression(&cast.this)?;
16599            self.write(" :> ");
16600            self.generate_data_type(&cast.to)?;
16601            return Ok(());
16602        }
16603
16604        // Teradata: CAST(x AS FORMAT 'fmt') (no data type)
16605        if matches!(self.config.dialect, Some(DialectType::Teradata)) {
16606            let is_unknown_type = matches!(cast.to, DataType::Unknown)
16607                || matches!(cast.to, DataType::Custom { ref name } if name.is_empty());
16608            if is_unknown_type {
16609                if let Some(format) = &cast.format {
16610                    self.write_keyword("CAST");
16611                    self.write("(");
16612                    self.generate_expression(&cast.this)?;
16613                    self.write_space();
16614                    self.write_keyword("AS");
16615                    self.write_space();
16616                    self.write_keyword("FORMAT");
16617                    self.write_space();
16618                    self.generate_expression(format)?;
16619                    self.write(")");
16620                    return Ok(());
16621                }
16622            }
16623        }
16624
16625        // Oracle: CAST(x AS DATE/TIMESTAMP ..., 'format') -> TO_DATE/TO_TIMESTAMP(x, 'format')
16626        // This follows Python sqlglot's behavior of transforming CAST with format to native functions
16627        if matches!(self.config.dialect, Some(DialectType::Oracle)) {
16628            if let Some(format) = &cast.format {
16629                // Check if target type is DATE or TIMESTAMP
16630                let is_date = matches!(cast.to, DataType::Date);
16631                let is_timestamp = matches!(cast.to, DataType::Timestamp { .. });
16632
16633                if is_date || is_timestamp {
16634                    let func_name = if is_date { "TO_DATE" } else { "TO_TIMESTAMP" };
16635                    self.write_keyword(func_name);
16636                    self.write("(");
16637                    self.generate_expression(&cast.this)?;
16638                    self.write(", ");
16639
16640                    // Normalize format string for Oracle (HH -> HH12)
16641                    // Oracle HH is 12-hour format, same as HH12. For clarity, Python sqlglot uses HH12.
16642                    if let Expression::Literal(lit) = format.as_ref() {
16643                        if let Literal::String(fmt_str) = lit.as_ref() {
16644                            let normalized = self.normalize_oracle_format(fmt_str);
16645                            self.write("'");
16646                            self.write(&normalized);
16647                            self.write("'");
16648                        }
16649                    } else {
16650                        self.generate_expression(format)?;
16651                    }
16652
16653                    self.write(")");
16654                    return Ok(());
16655                }
16656            }
16657        }
16658
16659        // BigQuery: CAST(ARRAY[...] AS ARRAY<T>) -> ARRAY<T>[...]
16660        // This preserves sqlglot's typed inline array literal output.
16661        if matches!(self.config.dialect, Some(DialectType::BigQuery)) {
16662            if let Expression::Array(arr) = &cast.this {
16663                self.generate_data_type(&cast.to)?;
16664                // Output just the bracket content [values] without the ARRAY prefix
16665                self.write("[");
16666                for (i, expr) in arr.expressions.iter().enumerate() {
16667                    if i > 0 {
16668                        self.write(", ");
16669                    }
16670                    self.generate_expression(expr)?;
16671                }
16672                self.write("]");
16673                return Ok(());
16674            }
16675            if matches!(&cast.this, Expression::ArrayFunc(_)) {
16676                self.generate_data_type(&cast.to)?;
16677                self.generate_expression(&cast.this)?;
16678                return Ok(());
16679            }
16680        }
16681
16682        // DuckDB/Presto/Trino: When CAST(Struct([unnamed]) AS STRUCT(...)),
16683        // convert the inner Struct to ROW(values...) format
16684        if matches!(
16685            self.config.dialect,
16686            Some(DialectType::DuckDB) | Some(DialectType::Presto) | Some(DialectType::Trino)
16687        ) {
16688            if let Expression::Struct(ref s) = cast.this {
16689                let all_unnamed = s.fields.iter().all(|(name, _)| name.is_none());
16690                if all_unnamed && matches!(cast.to, DataType::Struct { .. }) {
16691                    self.write_keyword("CAST");
16692                    self.write("(");
16693                    self.generate_struct_as_row(s)?;
16694                    self.write_space();
16695                    self.write_keyword("AS");
16696                    self.write_space();
16697                    self.generate_data_type(&cast.to)?;
16698                    self.write(")");
16699                    return Ok(());
16700                }
16701            }
16702        }
16703
16704        // Determine if we should use :: syntax based on dialect
16705        // PostgreSQL prefers :: for identity, most others prefer CAST()
16706        let use_double_colon = cast.double_colon_syntax && self.dialect_prefers_double_colon();
16707
16708        if use_double_colon {
16709            // PostgreSQL :: syntax: expr::type
16710            self.generate_expression(&cast.this)?;
16711            self.write("::");
16712            self.generate_data_type(&cast.to)?;
16713        } else {
16714            // Standard CAST() syntax
16715            self.write_keyword("CAST");
16716            self.write("(");
16717            self.generate_expression(&cast.this)?;
16718            self.write_space();
16719            self.write_keyword("AS");
16720            self.write_space();
16721            // For MySQL/SingleStore/TiDB, map text/blob variant types to CHAR in CAST
16722            // This matches Python sqlglot's CAST_MAPPING behavior
16723            if matches!(
16724                self.config.dialect,
16725                Some(DialectType::MySQL) | Some(DialectType::SingleStore) | Some(DialectType::TiDB)
16726            ) {
16727                match &cast.to {
16728                    DataType::Custom { ref name } => {
16729                        if name.eq_ignore_ascii_case("LONGTEXT")
16730                            || name.eq_ignore_ascii_case("MEDIUMTEXT")
16731                            || name.eq_ignore_ascii_case("TINYTEXT")
16732                            || name.eq_ignore_ascii_case("LONGBLOB")
16733                            || name.eq_ignore_ascii_case("MEDIUMBLOB")
16734                            || name.eq_ignore_ascii_case("TINYBLOB")
16735                        {
16736                            self.write_keyword("CHAR");
16737                        } else {
16738                            self.generate_data_type(&cast.to)?;
16739                        }
16740                    }
16741                    DataType::VarChar { length, .. } => {
16742                        // MySQL CAST: VARCHAR -> CHAR
16743                        self.write_keyword("CHAR");
16744                        if let Some(n) = length {
16745                            self.write(&format!("({})", n));
16746                        }
16747                    }
16748                    DataType::Text => {
16749                        // MySQL CAST: TEXT -> CHAR
16750                        self.write_keyword("CHAR");
16751                    }
16752                    DataType::Timestamp {
16753                        precision,
16754                        timezone: false,
16755                    } => {
16756                        // MySQL CAST: TIMESTAMP -> DATETIME
16757                        self.write_keyword("DATETIME");
16758                        if let Some(p) = precision {
16759                            self.write(&format!("({})", p));
16760                        }
16761                    }
16762                    _ => {
16763                        self.generate_data_type(&cast.to)?;
16764                    }
16765                }
16766            } else if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
16767                // Snowflake CAST: STRING -> VARCHAR
16768                match &cast.to {
16769                    DataType::String { length } => {
16770                        self.write_keyword("VARCHAR");
16771                        if let Some(n) = length {
16772                            self.write(&format!("({})", n));
16773                        }
16774                    }
16775                    _ => {
16776                        self.generate_data_type(&cast.to)?;
16777                    }
16778                }
16779            } else {
16780                self.generate_data_type(&cast.to)?;
16781            }
16782
16783            // Output DEFAULT ... ON CONVERSION ERROR clause if present (Oracle)
16784            if let Some(default) = &cast.default {
16785                self.write_space();
16786                self.write_keyword("DEFAULT");
16787                self.write_space();
16788                self.generate_expression(default)?;
16789                self.write_space();
16790                self.write_keyword("ON");
16791                self.write_space();
16792                self.write_keyword("CONVERSION");
16793                self.write_space();
16794                self.write_keyword("ERROR");
16795            }
16796
16797            // Output FORMAT clause if present (BigQuery: CAST(x AS STRING FORMAT 'format'))
16798            // For Oracle with comma-separated format: CAST(x AS DATE DEFAULT NULL ON CONVERSION ERROR, 'format')
16799            if let Some(format) = &cast.format {
16800                // Check if Oracle dialect - use comma syntax
16801                if matches!(
16802                    self.config.dialect,
16803                    Some(crate::dialects::DialectType::Oracle)
16804                ) {
16805                    self.write(", ");
16806                } else {
16807                    self.write_space();
16808                    self.write_keyword("FORMAT");
16809                    self.write_space();
16810                }
16811                self.generate_expression(format)?;
16812            }
16813
16814            self.write(")");
16815            // Output trailing comments
16816            for comment in &cast.trailing_comments {
16817                self.write_space();
16818                self.write_formatted_comment(comment);
16819            }
16820        }
16821        Ok(())
16822    }
16823
16824    /// Generate a Struct as ROW(values...) format, recursively converting inner Struct to ROW too.
16825    /// Used for DuckDB/Presto/Trino CAST(Struct AS STRUCT(...)) context.
16826    fn generate_struct_as_row(&mut self, s: &crate::expressions::Struct) -> Result<()> {
16827        self.write_keyword("ROW");
16828        self.write("(");
16829        for (i, (_, expr)) in s.fields.iter().enumerate() {
16830            if i > 0 {
16831                self.write(", ");
16832            }
16833            // Recursively convert inner Struct to ROW format
16834            if let Expression::Struct(ref inner_s) = expr {
16835                self.generate_struct_as_row(inner_s)?;
16836            } else {
16837                self.generate_expression(expr)?;
16838            }
16839        }
16840        self.write(")");
16841        Ok(())
16842    }
16843
16844    /// Normalize Oracle date/time format strings
16845    /// HH -> HH12 (both are 12-hour format, but Python sqlglot prefers explicit HH12)
16846    fn normalize_oracle_format(&self, format: &str) -> String {
16847        // Replace standalone HH with HH12 (but not HH12 or HH24)
16848        // We need to be careful not to replace HH12 -> HH1212 or HH24 -> HH1224
16849        let mut result = String::new();
16850        let chars: Vec<char> = format.chars().collect();
16851        let mut i = 0;
16852
16853        while i < chars.len() {
16854            if i + 1 < chars.len() && chars[i] == 'H' && chars[i + 1] == 'H' {
16855                // Check what follows HH
16856                if i + 2 < chars.len() {
16857                    let next = chars[i + 2];
16858                    if next == '1' || next == '2' {
16859                        // This is HH12 or HH24, keep as is
16860                        result.push('H');
16861                        result.push('H');
16862                        i += 2;
16863                        continue;
16864                    }
16865                }
16866                // Standalone HH -> HH12
16867                result.push_str("HH12");
16868                i += 2;
16869            } else {
16870                result.push(chars[i]);
16871                i += 1;
16872            }
16873        }
16874
16875        result
16876    }
16877
16878    /// Check if the current dialect prefers :: cast syntax
16879    /// Note: Python sqlglot normalizes all :: to CAST() for output, even for PostgreSQL
16880    /// So we return false for all dialects to match Python sqlglot's behavior
16881    fn dialect_prefers_double_colon(&self) -> bool {
16882        // Python sqlglot normalizes :: syntax to CAST() for all dialects
16883        // Even PostgreSQL outputs CAST() not ::
16884        false
16885    }
16886
16887    /// Generate MOD function - uses % operator for Snowflake/MySQL/Presto/Trino, MOD() for others
16888    fn generate_mod_func(&mut self, f: &crate::expressions::BinaryFunc) -> Result<()> {
16889        use crate::dialects::DialectType;
16890
16891        // Snowflake, MySQL, Presto, Trino, PostgreSQL, and DuckDB prefer x % y instead of MOD(x, y)
16892        let use_percent_operator = matches!(
16893            self.config.dialect,
16894            Some(DialectType::Snowflake)
16895                | Some(DialectType::MySQL)
16896                | Some(DialectType::Presto)
16897                | Some(DialectType::Trino)
16898                | Some(DialectType::PostgreSQL)
16899                | Some(DialectType::DuckDB)
16900                | Some(DialectType::Hive)
16901                | Some(DialectType::Spark)
16902                | Some(DialectType::Databricks)
16903                | Some(DialectType::Athena)
16904        );
16905
16906        if use_percent_operator {
16907            // Wrap complex expressions in parens to preserve precedence
16908            // Since % has higher precedence than +/-, we need parens for Add/Sub on either side
16909            let needs_paren = |e: &Expression| matches!(e, Expression::Add(_) | Expression::Sub(_));
16910            if needs_paren(&f.this) {
16911                self.write("(");
16912                self.generate_expression(&f.this)?;
16913                self.write(")");
16914            } else {
16915                self.generate_expression(&f.this)?;
16916            }
16917            self.write(" % ");
16918            if needs_paren(&f.expression) {
16919                self.write("(");
16920                self.generate_expression(&f.expression)?;
16921                self.write(")");
16922            } else {
16923                self.generate_expression(&f.expression)?;
16924            }
16925            Ok(())
16926        } else {
16927            self.generate_binary_func("MOD", &f.this, &f.expression)
16928        }
16929    }
16930
16931    /// Generate IFNULL - uses COALESCE for Snowflake, IFNULL for others
16932    fn generate_ifnull(&mut self, f: &crate::expressions::BinaryFunc) -> Result<()> {
16933        use crate::dialects::DialectType;
16934
16935        // Snowflake normalizes IFNULL to COALESCE
16936        let func_name = match self.config.dialect {
16937            Some(DialectType::Snowflake) => "COALESCE",
16938            _ => "IFNULL",
16939        };
16940
16941        self.generate_binary_func(func_name, &f.this, &f.expression)
16942    }
16943
16944    /// Generate NVL - preserves original name if available, otherwise uses dialect-specific output
16945    fn generate_nvl(&mut self, f: &crate::expressions::BinaryFunc) -> Result<()> {
16946        // Use original function name if preserved (for identity tests)
16947        if let Some(ref original_name) = f.original_name {
16948            return self.generate_binary_func(original_name, &f.this, &f.expression);
16949        }
16950
16951        // Otherwise, use dialect-specific function names
16952        use crate::dialects::DialectType;
16953        let func_name = match self.config.dialect {
16954            Some(DialectType::Snowflake)
16955            | Some(DialectType::ClickHouse)
16956            | Some(DialectType::PostgreSQL)
16957            | Some(DialectType::Presto)
16958            | Some(DialectType::Trino)
16959            | Some(DialectType::Athena)
16960            | Some(DialectType::DuckDB)
16961            | Some(DialectType::BigQuery)
16962            | Some(DialectType::Spark)
16963            | Some(DialectType::Databricks)
16964            | Some(DialectType::Hive) => "COALESCE",
16965            Some(DialectType::MySQL)
16966            | Some(DialectType::Doris)
16967            | Some(DialectType::StarRocks)
16968            | Some(DialectType::SingleStore)
16969            | Some(DialectType::TiDB) => "IFNULL",
16970            _ => "NVL",
16971        };
16972
16973        self.generate_binary_func(func_name, &f.this, &f.expression)
16974    }
16975
16976    /// Generate STDDEV_SAMP - uses STDDEV for Snowflake, STDDEV_SAMP for others
16977    fn generate_stddev_samp(&mut self, f: &crate::expressions::AggFunc) -> Result<()> {
16978        use crate::dialects::DialectType;
16979
16980        // Snowflake normalizes STDDEV_SAMP to STDDEV
16981        let func_name = match self.config.dialect {
16982            Some(DialectType::Snowflake) => "STDDEV",
16983            _ => "STDDEV_SAMP",
16984        };
16985
16986        self.generate_agg_func(func_name, f)
16987    }
16988
16989    fn generate_collation(&mut self, coll: &CollationExpr) -> Result<()> {
16990        self.generate_expression(&coll.this)?;
16991        self.write_space();
16992        self.write_keyword("COLLATE");
16993        self.write_space();
16994        if coll.quoted {
16995            // Single-quoted string: COLLATE 'de_DE'
16996            self.write("'");
16997            self.write(&coll.collation);
16998            self.write("'");
16999        } else if coll.double_quoted {
17000            // Double-quoted identifier: COLLATE "de_DE"
17001            self.write("\"");
17002            self.write(&coll.collation);
17003            self.write("\"");
17004        } else {
17005            // Unquoted identifier: COLLATE de_DE
17006            self.write(&coll.collation);
17007        }
17008        Ok(())
17009    }
17010
17011    fn generate_case(&mut self, case: &Case) -> Result<()> {
17012        // In pretty mode, decide whether to expand based on total text width
17013        let multiline_case = if self.config.pretty {
17014            // Build the flat representation to check width
17015            let mut statements: Vec<String> = Vec::new();
17016            let operand_str = if let Some(operand) = &case.operand {
17017                let s = self.generate_to_string(operand)?;
17018                statements.push(format!("CASE {}", s));
17019                s
17020            } else {
17021                statements.push("CASE".to_string());
17022                String::new()
17023            };
17024            let _ = operand_str;
17025            for (condition, result) in &case.whens {
17026                statements.push(format!("WHEN {}", self.generate_to_string(condition)?));
17027                statements.push(format!("THEN {}", self.generate_to_string(result)?));
17028            }
17029            if let Some(else_) = &case.else_ {
17030                statements.push(format!("ELSE {}", self.generate_to_string(else_)?));
17031            }
17032            statements.push("END".to_string());
17033            self.too_wide(&statements)
17034        } else {
17035            false
17036        };
17037
17038        self.write_keyword("CASE");
17039        if let Some(operand) = &case.operand {
17040            self.write_space();
17041            self.generate_expression(operand)?;
17042        }
17043        if multiline_case {
17044            self.indent_level += 1;
17045        }
17046        for (condition, result) in &case.whens {
17047            if multiline_case {
17048                self.write_newline();
17049                self.write_indent();
17050            } else {
17051                self.write_space();
17052            }
17053            self.write_keyword("WHEN");
17054            self.write_space();
17055            self.generate_expression(condition)?;
17056            if multiline_case {
17057                self.write_newline();
17058                self.write_indent();
17059            } else {
17060                self.write_space();
17061            }
17062            self.write_keyword("THEN");
17063            self.write_space();
17064            self.generate_expression(result)?;
17065        }
17066        if let Some(else_) = &case.else_ {
17067            if multiline_case {
17068                self.write_newline();
17069                self.write_indent();
17070            } else {
17071                self.write_space();
17072            }
17073            self.write_keyword("ELSE");
17074            self.write_space();
17075            self.generate_expression(else_)?;
17076        }
17077        if multiline_case {
17078            self.indent_level -= 1;
17079            self.write_newline();
17080            self.write_indent();
17081        } else {
17082            self.write_space();
17083        }
17084        self.write_keyword("END");
17085        // Emit any comments that were attached to the CASE keyword
17086        for comment in &case.comments {
17087            self.write(" ");
17088            self.write_formatted_comment(comment);
17089        }
17090        Ok(())
17091    }
17092
17093    fn generate_function(&mut self, func: &Function) -> Result<()> {
17094        // Normalize function name based on dialect settings
17095        let normalized_name = self.normalize_func_name(&func.name);
17096
17097        // DuckDB: ARRAY_CONSTRUCT_COMPACT(a, b, c) -> LIST_FILTER([a, b, c], _u -> NOT _u IS NULL)
17098        if matches!(self.config.dialect, Some(DialectType::DuckDB))
17099            && func.name.eq_ignore_ascii_case("ARRAY_CONSTRUCT_COMPACT")
17100        {
17101            self.write("LIST_FILTER(");
17102            self.write("[");
17103            for (i, arg) in func.args.iter().enumerate() {
17104                if i > 0 {
17105                    self.write(", ");
17106                }
17107                self.generate_expression(arg)?;
17108            }
17109            self.write("], _u -> NOT _u IS NULL)");
17110            return Ok(());
17111        }
17112
17113        // Snowflake fixtures expect TO_VARIANT applied to arrays to keep ARRAY_CONSTRUCT(...)
17114        // rather than bracket-array syntax.
17115        if matches!(self.config.dialect, Some(DialectType::Snowflake))
17116            && func.name.eq_ignore_ascii_case("TO_VARIANT")
17117            && func.args.len() == 1
17118        {
17119            let array_expressions = match &func.args[0] {
17120                Expression::ArrayFunc(arr) => Some(&arr.expressions),
17121                Expression::Array(arr) => Some(&arr.expressions),
17122                _ => None,
17123            };
17124            if let Some(expressions) = array_expressions {
17125                self.write_keyword("TO_VARIANT");
17126                self.write("(");
17127                self.write_keyword("ARRAY_CONSTRUCT");
17128                self.write("(");
17129                for (i, arg) in expressions.iter().enumerate() {
17130                    if i > 0 {
17131                        self.write(", ");
17132                    }
17133                    self.generate_expression(arg)?;
17134                }
17135                self.write(")");
17136                self.write(")");
17137                return Ok(());
17138            }
17139        }
17140
17141        // STRUCT function: BigQuery STRUCT('Alice' AS name, 85 AS score) -> dialect-specific
17142        if func.name.eq_ignore_ascii_case("STRUCT")
17143            && !matches!(
17144                self.config.dialect,
17145                Some(DialectType::BigQuery)
17146                    | Some(DialectType::Spark)
17147                    | Some(DialectType::Databricks)
17148                    | Some(DialectType::Hive)
17149                    | None
17150            )
17151        {
17152            return self.generate_struct_function_cross_dialect(func);
17153        }
17154
17155        // SingleStore: __SS_JSON_PATH_QMARK__(expr, key) -> expr::?key
17156        // This is an internal marker function for ::? JSON path syntax
17157        if func.name.eq_ignore_ascii_case("__SS_JSON_PATH_QMARK__") && func.args.len() == 2 {
17158            self.generate_expression(&func.args[0])?;
17159            self.write("::?");
17160            // Extract the key from the string literal
17161            if let Expression::Literal(lit) = &func.args[1] {
17162                if let crate::expressions::Literal::String(key) = lit.as_ref() {
17163                    self.write(key);
17164                }
17165            } else {
17166                self.generate_expression(&func.args[1])?;
17167            }
17168            return Ok(());
17169        }
17170
17171        // PostgreSQL: __PG_BITWISE_XOR__(a, b) -> a # b
17172        if func.name.eq_ignore_ascii_case("__PG_BITWISE_XOR__") && func.args.len() == 2 {
17173            self.generate_expression(&func.args[0])?;
17174            self.write(" # ");
17175            self.generate_expression(&func.args[1])?;
17176            return Ok(());
17177        }
17178
17179        // Spark/Hive family: unwrap TRY(expr) since these dialects don't emit TRY as a scalar wrapper.
17180        if matches!(
17181            self.config.dialect,
17182            Some(DialectType::Spark | DialectType::Databricks | DialectType::Hive)
17183        ) && func.name.eq_ignore_ascii_case("TRY")
17184            && func.args.len() == 1
17185        {
17186            self.generate_expression(&func.args[0])?;
17187            return Ok(());
17188        }
17189
17190        // ClickHouse normalization: toStartOfDay(x) -> dateTrunc('DAY', x)
17191        if self.config.dialect == Some(DialectType::ClickHouse)
17192            && func.name.eq_ignore_ascii_case("TOSTARTOFDAY")
17193            && func.args.len() == 1
17194        {
17195            self.write("dateTrunc('DAY', ");
17196            self.generate_expression(&func.args[0])?;
17197            self.write(")");
17198            return Ok(());
17199        }
17200
17201        // ClickHouse uses dateTrunc casing.
17202        if self.config.dialect == Some(DialectType::ClickHouse)
17203            && func.name.eq_ignore_ascii_case("DATE_TRUNC")
17204            && func.args.len() == 2
17205        {
17206            self.write("dateTrunc(");
17207            self.generate_expression(&func.args[0])?;
17208            self.write(", ");
17209            self.generate_expression(&func.args[1])?;
17210            self.write(")");
17211            return Ok(());
17212        }
17213
17214        // Presto-family dialects spell SUBSTRING as SUBSTR in SQLGlot outputs.
17215        if matches!(
17216            self.config.dialect,
17217            Some(DialectType::Presto | DialectType::Trino | DialectType::Athena)
17218        ) && func.name.eq_ignore_ascii_case("SUBSTRING")
17219        {
17220            self.write_keyword("SUBSTR");
17221            self.write("(");
17222            for (i, arg) in func.args.iter().enumerate() {
17223                if i > 0 {
17224                    self.write(", ");
17225                }
17226                self.generate_expression(arg)?;
17227            }
17228            self.write(")");
17229            return Ok(());
17230        }
17231
17232        if self.config.dialect == Some(DialectType::Snowflake)
17233            && func.name.eq_ignore_ascii_case("LIST_DISTINCT")
17234            && func.args.len() == 1
17235        {
17236            self.write_keyword("ARRAY_DISTINCT");
17237            self.write("(");
17238            self.write_keyword("ARRAY_COMPACT");
17239            self.write("(");
17240            self.generate_expression(&func.args[0])?;
17241            self.write("))");
17242            return Ok(());
17243        }
17244
17245        if self.config.dialect == Some(DialectType::Snowflake)
17246            && func.name.eq_ignore_ascii_case("LIST")
17247            && func.args.len() == 1
17248            && !matches!(func.args.first(), Some(Expression::Select(_)))
17249        {
17250            self.write_keyword("ARRAY_AGG");
17251            self.write("(");
17252            self.generate_expression(&func.args[0])?;
17253            self.write(")");
17254            return Ok(());
17255        }
17256
17257        // Redshift: CONCAT(a, b, ...) -> a || b || ...
17258        if self.config.dialect == Some(DialectType::Redshift)
17259            && func.name.eq_ignore_ascii_case("CONCAT")
17260            && func.args.len() >= 2
17261        {
17262            for (i, arg) in func.args.iter().enumerate() {
17263                if i > 0 {
17264                    self.write(" || ");
17265                }
17266                self.generate_expression(arg)?;
17267            }
17268            return Ok(());
17269        }
17270
17271        // Redshift: CONCAT_WS(delim, a, b, c) -> a || delim || b || delim || c
17272        if self.config.dialect == Some(DialectType::Redshift)
17273            && func.name.eq_ignore_ascii_case("CONCAT_WS")
17274            && func.args.len() >= 2
17275        {
17276            let sep = &func.args[0];
17277            for (i, arg) in func.args.iter().skip(1).enumerate() {
17278                if i > 0 {
17279                    self.write(" || ");
17280                    self.generate_expression(sep)?;
17281                    self.write(" || ");
17282                }
17283                self.generate_expression(arg)?;
17284            }
17285            return Ok(());
17286        }
17287
17288        // Redshift: DATEDIFF/DATE_DIFF(unit, start, end) -> DATEDIFF(UNIT, start, end)
17289        // Unit should be unquoted uppercase identifier
17290        if self.config.dialect == Some(DialectType::Redshift)
17291            && (func.name.eq_ignore_ascii_case("DATEDIFF")
17292                || func.name.eq_ignore_ascii_case("DATE_DIFF"))
17293            && func.args.len() == 3
17294        {
17295            self.write_keyword("DATEDIFF");
17296            self.write("(");
17297            // First arg is unit - normalize to unquoted uppercase
17298            self.write_redshift_date_part(&func.args[0]);
17299            self.write(", ");
17300            self.generate_expression(&func.args[1])?;
17301            self.write(", ");
17302            self.generate_expression(&func.args[2])?;
17303            self.write(")");
17304            return Ok(());
17305        }
17306
17307        // Redshift: DATEADD/DATE_ADD(unit, interval, date) -> DATEADD(UNIT, interval, date)
17308        // Unit should be unquoted uppercase identifier
17309        if self.config.dialect == Some(DialectType::Redshift)
17310            && (func.name.eq_ignore_ascii_case("DATEADD")
17311                || func.name.eq_ignore_ascii_case("DATE_ADD"))
17312            && func.args.len() == 3
17313        {
17314            self.write_keyword("DATEADD");
17315            self.write("(");
17316            // First arg is unit - normalize to unquoted uppercase
17317            self.write_redshift_date_part(&func.args[0]);
17318            self.write(", ");
17319            self.generate_expression(&func.args[1])?;
17320            self.write(", ");
17321            self.generate_expression(&func.args[2])?;
17322            self.write(")");
17323            return Ok(());
17324        }
17325
17326        // UUID_STRING(args) from Snowflake -> dialect-specific UUID function.
17327        if func.name.eq_ignore_ascii_case("UUID_STRING")
17328            && !matches!(self.config.dialect, Some(DialectType::Snowflake) | None)
17329        {
17330            if matches!(
17331                self.config.dialect,
17332                Some(DialectType::Hive | DialectType::Spark | DialectType::Databricks)
17333            ) {
17334                self.write_keyword("CAST");
17335                self.write("(");
17336                self.write_keyword("UUID");
17337                self.write("() ");
17338                self.write_keyword("AS");
17339                self.write(" ");
17340                self.write_keyword("STRING");
17341                self.write(")");
17342                return Ok(());
17343            }
17344
17345            if matches!(
17346                self.config.dialect,
17347                Some(DialectType::Presto | DialectType::Trino)
17348            ) {
17349                self.write_keyword("CAST");
17350                self.write("(");
17351                self.write_keyword("UUID");
17352                self.write("() ");
17353                self.write_keyword("AS");
17354                self.write(" ");
17355                self.write_keyword("VARCHAR");
17356                self.write(")");
17357                return Ok(());
17358            }
17359
17360            if self.config.dialect == Some(DialectType::DuckDB) && func.args.len() == 2 {
17361                self.write("(SELECT LOWER(SUBSTRING(h, 1, 8) || '-' || SUBSTRING(h, 9, 4) || '-' || '5' || SUBSTRING(h, 14, 3) || '-' || FORMAT('{:02x}', CAST('0x' || SUBSTRING(h, 17, 2) AS INT) & 63 | 128) || SUBSTRING(h, 19, 2) || '-' || SUBSTRING(h, 21, 12)) FROM (SELECT SUBSTRING(SHA1(UNHEX(REPLACE(");
17362                self.generate_expression(&func.args[0])?;
17363                self.write(", '-', '')) || ENCODE(");
17364                self.generate_expression(&func.args[1])?;
17365                self.write(")), 1, 32) AS h))");
17366                return Ok(());
17367            }
17368
17369            let func_name = match self.config.dialect {
17370                Some(DialectType::PostgreSQL) | Some(DialectType::Redshift) => "GEN_RANDOM_UUID",
17371                Some(DialectType::BigQuery) => "GENERATE_UUID",
17372                _ => "UUID",
17373            };
17374            self.write_keyword(func_name);
17375            self.write("()");
17376            return Ok(());
17377        }
17378
17379        // Snowflake: GENERATOR(val) -> GENERATOR(ROWCOUNT => val)
17380        // GENERATOR(val1, val2) -> GENERATOR(ROWCOUNT => val1, TIMELIMIT => val2)
17381        // Positional args are mapped to named parameters.
17382        if matches!(self.config.dialect, Some(DialectType::Snowflake))
17383            && func.name.eq_ignore_ascii_case("GENERATOR")
17384        {
17385            let has_positional_args =
17386                !func.args.is_empty() && !matches!(&func.args[0], Expression::NamedArgument(_));
17387            if has_positional_args {
17388                let param_names = ["ROWCOUNT", "TIMELIMIT"];
17389                self.write_keyword("GENERATOR");
17390                self.write("(");
17391                for (i, arg) in func.args.iter().enumerate() {
17392                    if i > 0 {
17393                        self.write(", ");
17394                    }
17395                    if i < param_names.len() {
17396                        self.write_keyword(param_names[i]);
17397                        self.write(" => ");
17398                        self.generate_expression(arg)?;
17399                    } else {
17400                        self.generate_expression(arg)?;
17401                    }
17402                }
17403                self.write(")");
17404                return Ok(());
17405            }
17406        }
17407
17408        // Redshift: DATE_TRUNC('unit', date) -> DATE_TRUNC('UNIT', date)
17409        // Unit should be quoted uppercase string
17410        if self.config.dialect == Some(DialectType::Redshift)
17411            && func.name.eq_ignore_ascii_case("DATE_TRUNC")
17412            && func.args.len() == 2
17413        {
17414            self.write_keyword("DATE_TRUNC");
17415            self.write("(");
17416            // First arg is unit - normalize to quoted uppercase
17417            self.write_redshift_date_part_quoted(&func.args[0]);
17418            self.write(", ");
17419            self.generate_expression(&func.args[1])?;
17420            self.write(")");
17421            return Ok(());
17422        }
17423
17424        // TSQL/Fabric: DATE_PART -> DATEPART (no underscore)
17425        if matches!(
17426            self.config.dialect,
17427            Some(DialectType::TSQL) | Some(DialectType::Fabric)
17428        ) && (func.name.eq_ignore_ascii_case("DATE_PART")
17429            || func.name.eq_ignore_ascii_case("DATEPART"))
17430            && func.args.len() == 2
17431        {
17432            self.write_keyword("DATEPART");
17433            self.write("(");
17434            self.generate_expression(&func.args[0])?;
17435            self.write(", ");
17436            self.generate_expression(&func.args[1])?;
17437            self.write(")");
17438            return Ok(());
17439        }
17440
17441        // PostgreSQL/Redshift: DATE_PART(part, value) -> EXTRACT(part FROM value)
17442        if matches!(
17443            self.config.dialect,
17444            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
17445        ) && (func.name.eq_ignore_ascii_case("DATE_PART")
17446            || func.name.eq_ignore_ascii_case("DATEPART"))
17447            && func.args.len() == 2
17448        {
17449            self.write_keyword("EXTRACT");
17450            self.write("(");
17451            // Extract the datetime field - if it's a string literal, strip quotes to make it a keyword
17452            match &func.args[0] {
17453                Expression::Literal(lit)
17454                    if matches!(lit.as_ref(), crate::expressions::Literal::String(_)) =>
17455                {
17456                    let crate::expressions::Literal::String(s) = lit.as_ref() else {
17457                        unreachable!()
17458                    };
17459                    self.write(&s.to_ascii_lowercase());
17460                }
17461                _ => self.generate_expression(&func.args[0])?,
17462            }
17463            self.write_space();
17464            self.write_keyword("FROM");
17465            self.write_space();
17466            self.generate_expression(&func.args[1])?;
17467            self.write(")");
17468            return Ok(());
17469        }
17470
17471        // PostgreSQL: DATE_ADD(date, INTERVAL '...') / DATE_SUB(...) -> infix interval arithmetic.
17472        if self.config.dialect == Some(DialectType::PostgreSQL)
17473            && matches!(
17474                func.name.to_ascii_uppercase().as_str(),
17475                "DATE_ADD" | "DATE_SUB"
17476            )
17477            && func.args.len() == 2
17478            && matches!(func.args[1], Expression::Interval(_))
17479        {
17480            self.generate_expression(&func.args[0])?;
17481            self.write_space();
17482            if func.name.eq_ignore_ascii_case("DATE_SUB") {
17483                self.write("-");
17484            } else {
17485                self.write("+");
17486            }
17487            self.write_space();
17488            self.generate_expression(&func.args[1])?;
17489            return Ok(());
17490        }
17491
17492        // Dremio: DATE_PART(part, value) -> EXTRACT(part FROM value)
17493        // Also DATE literals in Dremio should be CAST(...AS DATE)
17494        if self.config.dialect == Some(DialectType::Dremio)
17495            && (func.name.eq_ignore_ascii_case("DATE_PART")
17496                || func.name.eq_ignore_ascii_case("DATEPART"))
17497            && func.args.len() == 2
17498        {
17499            self.write_keyword("EXTRACT");
17500            self.write("(");
17501            self.generate_expression(&func.args[0])?;
17502            self.write_space();
17503            self.write_keyword("FROM");
17504            self.write_space();
17505            // For Dremio, DATE literals should become CAST('value' AS DATE)
17506            self.generate_dremio_date_expression(&func.args[1])?;
17507            self.write(")");
17508            return Ok(());
17509        }
17510
17511        // Dremio: CURRENT_DATE_UTC() -> CURRENT_DATE_UTC (no parentheses)
17512        if self.config.dialect == Some(DialectType::Dremio)
17513            && func.name.eq_ignore_ascii_case("CURRENT_DATE_UTC")
17514            && func.args.is_empty()
17515        {
17516            self.write_keyword("CURRENT_DATE_UTC");
17517            return Ok(());
17518        }
17519
17520        // Dremio: DATETYPE(year, month, day) transformation
17521        // - If all args are integer literals: DATE('YYYY-MM-DD')
17522        // - If args are expressions: CAST(CONCAT(x, '-', y, '-', z) AS DATE)
17523        if self.config.dialect == Some(DialectType::Dremio)
17524            && func.name.eq_ignore_ascii_case("DATETYPE")
17525            && func.args.len() == 3
17526        {
17527            // Helper function to extract integer from number literal
17528            fn get_int_literal(expr: &Expression) -> Option<i64> {
17529                if let Expression::Literal(lit) = expr {
17530                    if let crate::expressions::Literal::Number(s) = lit.as_ref() {
17531                        s.parse::<i64>().ok()
17532                    } else {
17533                        None
17534                    }
17535                } else {
17536                    None
17537                }
17538            }
17539
17540            // Check if all arguments are integer literals
17541            if let (Some(year), Some(month), Some(day)) = (
17542                get_int_literal(&func.args[0]),
17543                get_int_literal(&func.args[1]),
17544                get_int_literal(&func.args[2]),
17545            ) {
17546                // All are integer literals: DATE('YYYY-MM-DD')
17547                self.write_keyword("DATE");
17548                self.write(&format!("('{:04}-{:02}-{:02}')", year, month, day));
17549                return Ok(());
17550            }
17551
17552            // For expressions: CAST(CONCAT(x, '-', y, '-', z) AS DATE)
17553            self.write_keyword("CAST");
17554            self.write("(");
17555            self.write_keyword("CONCAT");
17556            self.write("(");
17557            self.generate_expression(&func.args[0])?;
17558            self.write(", '-', ");
17559            self.generate_expression(&func.args[1])?;
17560            self.write(", '-', ");
17561            self.generate_expression(&func.args[2])?;
17562            self.write(")");
17563            self.write_space();
17564            self.write_keyword("AS");
17565            self.write_space();
17566            self.write_keyword("DATE");
17567            self.write(")");
17568            return Ok(());
17569        }
17570
17571        // Presto/Trino: DATE_ADD('unit', interval, date) - wrap interval in CAST(...AS BIGINT)
17572        // when it's not an integer literal
17573        let is_presto_like = matches!(
17574            self.config.dialect,
17575            Some(DialectType::Presto) | Some(DialectType::Trino)
17576        );
17577        if is_presto_like && func.name.eq_ignore_ascii_case("DATE_ADD") && func.args.len() == 3 {
17578            self.write_keyword("DATE_ADD");
17579            self.write("(");
17580            // First arg: unit (pass through as-is, e.g., 'DAY')
17581            self.generate_expression(&func.args[0])?;
17582            self.write(", ");
17583            // Second arg: interval - wrap in CAST(...AS BIGINT) if it doesn't return integer type
17584            let interval = &func.args[1];
17585            let needs_cast = !self.returns_integer_type(interval);
17586            if needs_cast {
17587                self.write_keyword("CAST");
17588                self.write("(");
17589            }
17590            self.generate_expression(interval)?;
17591            if needs_cast {
17592                self.write_space();
17593                self.write_keyword("AS");
17594                self.write_space();
17595                self.write_keyword("BIGINT");
17596                self.write(")");
17597            }
17598            self.write(", ");
17599            // Third arg: date
17600            self.generate_expression(&func.args[2])?;
17601            self.write(")");
17602            return Ok(());
17603        }
17604
17605        // Use bracket syntax if the function was parsed with brackets (e.g., MAP[keys, values])
17606        let use_brackets = func.use_bracket_syntax;
17607
17608        // Special case: functions WITH ORDINALITY need special output order
17609        // Input: FUNC(args) WITH ORDINALITY
17610        // Stored as: name="FUNC WITH ORDINALITY", args=[...]
17611        // Output must be: FUNC(args) WITH ORDINALITY
17612        let has_ordinality = func.name.len() >= 16
17613            && func.name[func.name.len() - 16..].eq_ignore_ascii_case(" WITH ORDINALITY");
17614        let output_name = if has_ordinality {
17615            let base_name = &func.name[..func.name.len() - " WITH ORDINALITY".len()];
17616            self.normalize_func_name(base_name)
17617        } else {
17618            normalized_name.clone()
17619        };
17620
17621        // For qualified names (schema.function or object.method), preserve original case
17622        // because they can be case-sensitive (e.g., TSQL XML methods like .nodes(), .value())
17623        if func.name.contains('.') && !has_ordinality {
17624            // Don't normalize qualified functions - preserve original case
17625            // If the function was quoted (e.g., BigQuery `p.d.UdF`), wrap it in backticks
17626            if func.quoted {
17627                self.write("`");
17628                self.write(&func.name);
17629                self.write("`");
17630            } else {
17631                self.write(&func.name);
17632            }
17633        } else {
17634            self.write(&output_name);
17635        }
17636
17637        // If no_parens is true and there are no args, output just the function name
17638        // Unless the target dialect requires parens for this function
17639        let force_parens = func.no_parens && func.args.is_empty() && !func.distinct && {
17640            let needs_parens = if func.name.eq_ignore_ascii_case("CURRENT_USER")
17641                || func.name.eq_ignore_ascii_case("SESSION_USER")
17642                || func.name.eq_ignore_ascii_case("SYSTEM_USER")
17643            {
17644                matches!(
17645                    self.config.dialect,
17646                    Some(DialectType::Snowflake)
17647                        | Some(DialectType::Spark)
17648                        | Some(DialectType::Databricks)
17649                        | Some(DialectType::Hive)
17650                )
17651            } else {
17652                false
17653            };
17654            !needs_parens
17655        };
17656        if force_parens {
17657            // Output trailing comments
17658            for comment in &func.trailing_comments {
17659                self.write_space();
17660                self.write_formatted_comment(comment);
17661            }
17662            return Ok(());
17663        }
17664
17665        // CUBE, ROLLUP, GROUPING SETS need a space before the parenthesis
17666        if func.name.eq_ignore_ascii_case("CUBE")
17667            || func.name.eq_ignore_ascii_case("ROLLUP")
17668            || func.name.eq_ignore_ascii_case("GROUPING SETS")
17669        {
17670            self.write(" (");
17671        } else if use_brackets {
17672            self.write("[");
17673        } else {
17674            self.write("(");
17675        }
17676        if func.distinct {
17677            self.write_keyword("DISTINCT");
17678            self.write_space();
17679        }
17680
17681        // Check if arguments should be split onto multiple lines (pretty + too wide)
17682        let compact_pretty_func = matches!(self.config.dialect, Some(DialectType::Snowflake))
17683            && (func.name.eq_ignore_ascii_case("TABLE")
17684                || func.name.eq_ignore_ascii_case("FLATTEN"));
17685        // GROUPING SETS, CUBE, ROLLUP always expand in pretty mode
17686        let is_grouping_func = func.name.eq_ignore_ascii_case("GROUPING SETS")
17687            || func.name.eq_ignore_ascii_case("CUBE")
17688            || func.name.eq_ignore_ascii_case("ROLLUP");
17689        let should_split = if self.config.pretty && !func.args.is_empty() && !compact_pretty_func {
17690            if is_grouping_func {
17691                true
17692            } else {
17693                // Pre-render arguments to check total width
17694                let mut expr_strings: Vec<String> = Vec::with_capacity(func.args.len());
17695                for arg in &func.args {
17696                    let mut temp_gen = Generator::with_arc_config(self.config.clone());
17697                    Arc::make_mut(&mut temp_gen.config).pretty = false; // Don't recurse into pretty
17698                    temp_gen.generate_expression(arg)?;
17699                    expr_strings.push(temp_gen.output);
17700                }
17701                self.too_wide(&expr_strings)
17702            }
17703        } else {
17704            false
17705        };
17706
17707        if should_split {
17708            // Split onto multiple lines
17709            self.write_newline();
17710            self.indent_level += 1;
17711            for (i, arg) in func.args.iter().enumerate() {
17712                self.write_indent();
17713                self.generate_expression(arg)?;
17714                if i + 1 < func.args.len() {
17715                    self.write(",");
17716                }
17717                self.write_newline();
17718            }
17719            self.indent_level -= 1;
17720            self.write_indent();
17721        } else {
17722            // All on one line
17723            for (i, arg) in func.args.iter().enumerate() {
17724                if i > 0 {
17725                    self.write(", ");
17726                }
17727                self.generate_expression(arg)?;
17728            }
17729        }
17730
17731        if use_brackets {
17732            self.write("]");
17733        } else {
17734            self.write(")");
17735        }
17736        // Append WITH ORDINALITY after closing paren for table-valued functions
17737        if has_ordinality {
17738            self.write_space();
17739            self.write_keyword("WITH ORDINALITY");
17740        }
17741        // Output trailing comments
17742        for comment in &func.trailing_comments {
17743            self.write_space();
17744            self.write_formatted_comment(comment);
17745        }
17746        Ok(())
17747    }
17748
17749    fn generate_function_emits(&mut self, fe: &FunctionEmits) -> Result<()> {
17750        self.generate_expression(&fe.this)?;
17751        self.write_keyword(" EMITS ");
17752        self.generate_expression(&fe.emits)?;
17753        Ok(())
17754    }
17755
17756    fn generate_aggregate_function(&mut self, func: &AggregateFunction) -> Result<()> {
17757        // Normalize function name based on dialect settings
17758        let mut normalized_name = self.normalize_func_name(&func.name);
17759
17760        // Dialect-specific name mappings for aggregate functions
17761        if func.name.eq_ignore_ascii_case("MAX_BY") || func.name.eq_ignore_ascii_case("MIN_BY") {
17762            let is_max = func.name.eq_ignore_ascii_case("MAX_BY");
17763            match self.config.dialect {
17764                Some(DialectType::ClickHouse) => {
17765                    normalized_name = if is_max {
17766                        Cow::Borrowed("argMax")
17767                    } else {
17768                        Cow::Borrowed("argMin")
17769                    };
17770                }
17771                Some(DialectType::DuckDB) => {
17772                    normalized_name = if is_max {
17773                        Cow::Borrowed("ARG_MAX")
17774                    } else {
17775                        Cow::Borrowed("ARG_MIN")
17776                    };
17777                }
17778                _ => {}
17779            }
17780        }
17781        self.write(normalized_name.as_ref());
17782        self.write("(");
17783        if func.distinct {
17784            self.write_keyword("DISTINCT");
17785            self.write_space();
17786        }
17787
17788        // Check if we need to transform multi-arg COUNT DISTINCT
17789        // When dialect doesn't support multi_arg_distinct, transform:
17790        // COUNT(DISTINCT a, b) -> COUNT(DISTINCT CASE WHEN a IS NULL THEN NULL WHEN b IS NULL THEN NULL ELSE (a, b) END)
17791        let is_count = normalized_name.eq_ignore_ascii_case("COUNT");
17792        let needs_multi_arg_transform =
17793            func.distinct && is_count && func.args.len() > 1 && !self.config.multi_arg_distinct;
17794
17795        if needs_multi_arg_transform {
17796            // Generate: CASE WHEN a IS NULL THEN NULL WHEN b IS NULL THEN NULL ELSE (a, b) END
17797            self.write_keyword("CASE");
17798            for arg in &func.args {
17799                self.write_space();
17800                self.write_keyword("WHEN");
17801                self.write_space();
17802                self.generate_expression(arg)?;
17803                self.write_space();
17804                self.write_keyword("IS NULL THEN NULL");
17805            }
17806            self.write_space();
17807            self.write_keyword("ELSE");
17808            self.write(" (");
17809            for (i, arg) in func.args.iter().enumerate() {
17810                if i > 0 {
17811                    self.write(", ");
17812                }
17813                self.generate_expression(arg)?;
17814            }
17815            self.write(")");
17816            self.write_space();
17817            self.write_keyword("END");
17818        } else {
17819            for (i, arg) in func.args.iter().enumerate() {
17820                if i > 0 {
17821                    self.write(", ");
17822                }
17823                self.generate_expression(arg)?;
17824            }
17825        }
17826
17827        // IGNORE NULLS / RESPECT NULLS inside parens (for BigQuery style or when config says in_func)
17828        if self.config.ignore_nulls_in_func
17829            && !matches!(self.config.dialect, Some(DialectType::DuckDB))
17830        {
17831            if let Some(ignore) = func.ignore_nulls {
17832                self.write_space();
17833                if ignore {
17834                    self.write_keyword("IGNORE NULLS");
17835                } else {
17836                    self.write_keyword("RESPECT NULLS");
17837                }
17838            }
17839        }
17840
17841        // ORDER BY inside aggregate
17842        if !func.order_by.is_empty() {
17843            self.write_space();
17844            self.write_keyword("ORDER BY");
17845            self.write_space();
17846            for (i, ord) in func.order_by.iter().enumerate() {
17847                if i > 0 {
17848                    self.write(", ");
17849                }
17850                self.generate_ordered(ord)?;
17851            }
17852        }
17853
17854        // LIMIT inside aggregate
17855        if let Some(limit) = &func.limit {
17856            self.write_space();
17857            self.write_keyword("LIMIT");
17858            self.write_space();
17859            // Check if this is a Tuple representing LIMIT offset, count
17860            if let Expression::Tuple(t) = limit.as_ref() {
17861                if t.expressions.len() == 2 {
17862                    self.generate_expression(&t.expressions[0])?;
17863                    self.write(", ");
17864                    self.generate_expression(&t.expressions[1])?;
17865                } else {
17866                    self.generate_expression(limit)?;
17867                }
17868            } else {
17869                self.generate_expression(limit)?;
17870            }
17871        }
17872
17873        self.write(")");
17874
17875        // IGNORE NULLS / RESPECT NULLS outside parens (standard style)
17876        if !self.config.ignore_nulls_in_func
17877            && !matches!(self.config.dialect, Some(DialectType::DuckDB))
17878        {
17879            if let Some(ignore) = func.ignore_nulls {
17880                self.write_space();
17881                if ignore {
17882                    self.write_keyword("IGNORE NULLS");
17883                } else {
17884                    self.write_keyword("RESPECT NULLS");
17885                }
17886            }
17887        }
17888
17889        if let Some(filter) = &func.filter {
17890            self.write_space();
17891            self.write_keyword("FILTER");
17892            self.write("(");
17893            self.write_keyword("WHERE");
17894            self.write_space();
17895            self.generate_expression(filter)?;
17896            self.write(")");
17897        }
17898
17899        Ok(())
17900    }
17901
17902    fn generate_window_function(&mut self, wf: &WindowFunction) -> Result<()> {
17903        self.generate_expression(&wf.this)?;
17904
17905        // Generate KEEP clause if present (Oracle KEEP (DENSE_RANK FIRST|LAST ORDER BY ...))
17906        if let Some(keep) = &wf.keep {
17907            self.write_space();
17908            self.write_keyword("KEEP");
17909            self.write(" (");
17910            self.write_keyword("DENSE_RANK");
17911            self.write_space();
17912            if keep.first {
17913                self.write_keyword("FIRST");
17914            } else {
17915                self.write_keyword("LAST");
17916            }
17917            self.write_space();
17918            self.write_keyword("ORDER BY");
17919            self.write_space();
17920            for (i, ord) in keep.order_by.iter().enumerate() {
17921                if i > 0 {
17922                    self.write(", ");
17923                }
17924                self.generate_ordered(ord)?;
17925            }
17926            self.write(")");
17927        }
17928
17929        // Check if there's any OVER clause content
17930        let has_over = !wf.over.partition_by.is_empty()
17931            || !wf.over.order_by.is_empty()
17932            || wf.over.frame.is_some()
17933            || wf.over.window_name.is_some();
17934
17935        // Only output OVER if there's actual window specification (not just KEEP alone)
17936        if has_over {
17937            self.write_space();
17938            self.write_keyword("OVER");
17939
17940            // Check if this is just a bare named window reference (no parens needed)
17941            let has_specs = !wf.over.partition_by.is_empty()
17942                || !wf.over.order_by.is_empty()
17943                || wf.over.frame.is_some();
17944
17945            if wf.over.window_name.is_some() && !has_specs {
17946                // OVER window_name (without parentheses)
17947                self.write_space();
17948                self.write(&wf.over.window_name.as_ref().unwrap().name);
17949            } else {
17950                // OVER (...) or OVER (window_name ...)
17951                self.write(" (");
17952                self.generate_over(&wf.over)?;
17953                self.write(")");
17954            }
17955        } else if wf.keep.is_none() {
17956            // No KEEP and no OVER content, but still a WindowFunction - output empty OVER ()
17957            self.write_space();
17958            self.write_keyword("OVER");
17959            self.write(" ()");
17960        }
17961
17962        Ok(())
17963    }
17964
17965    /// Generate WITHIN GROUP clause (for ordered-set aggregate functions)
17966    fn generate_within_group(&mut self, wg: &WithinGroup) -> Result<()> {
17967        self.generate_expression(&wg.this)?;
17968        self.write_space();
17969        self.write_keyword("WITHIN GROUP");
17970        self.write(" (");
17971        self.write_keyword("ORDER BY");
17972        self.write_space();
17973        for (i, ord) in wg.order_by.iter().enumerate() {
17974            if i > 0 {
17975                self.write(", ");
17976            }
17977            self.generate_ordered(ord)?;
17978        }
17979        self.write(")");
17980        Ok(())
17981    }
17982
17983    /// Generate the contents of an OVER clause (without parentheses)
17984    fn generate_over(&mut self, over: &Over) -> Result<()> {
17985        let mut has_content = false;
17986
17987        // Named window reference
17988        if let Some(name) = &over.window_name {
17989            self.write(&name.name);
17990            has_content = true;
17991        }
17992
17993        // PARTITION BY
17994        if !over.partition_by.is_empty() {
17995            if has_content {
17996                self.write_space();
17997            }
17998            self.write_keyword("PARTITION BY");
17999            self.write_space();
18000            for (i, expr) in over.partition_by.iter().enumerate() {
18001                if i > 0 {
18002                    self.write(", ");
18003                }
18004                self.generate_expression(expr)?;
18005            }
18006            has_content = true;
18007        }
18008
18009        // ORDER BY
18010        if !over.order_by.is_empty() {
18011            if has_content {
18012                self.write_space();
18013            }
18014            self.write_keyword("ORDER BY");
18015            self.write_space();
18016            for (i, ordered) in over.order_by.iter().enumerate() {
18017                if i > 0 {
18018                    self.write(", ");
18019                }
18020                self.generate_ordered(ordered)?;
18021            }
18022            has_content = true;
18023        }
18024
18025        // Window frame
18026        if let Some(frame) = &over.frame {
18027            if has_content {
18028                self.write_space();
18029            }
18030            self.generate_window_frame(frame)?;
18031        }
18032
18033        Ok(())
18034    }
18035
18036    fn generate_window_frame(&mut self, frame: &WindowFrame) -> Result<()> {
18037        // Exasol uses lowercase for frame kind (rows/range/groups)
18038        let lowercase_frame = self.config.lowercase_window_frame_keywords;
18039
18040        // Use preserved kind_text if available (for case preservation), unless lowercase override is active
18041        if !lowercase_frame {
18042            if let Some(kind_text) = &frame.kind_text {
18043                self.write(kind_text);
18044            } else {
18045                match frame.kind {
18046                    WindowFrameKind::Rows => self.write_keyword("ROWS"),
18047                    WindowFrameKind::Range => self.write_keyword("RANGE"),
18048                    WindowFrameKind::Groups => self.write_keyword("GROUPS"),
18049                }
18050            }
18051        } else {
18052            match frame.kind {
18053                WindowFrameKind::Rows => self.write("rows"),
18054                WindowFrameKind::Range => self.write("range"),
18055                WindowFrameKind::Groups => self.write("groups"),
18056            }
18057        }
18058
18059        // Use BETWEEN format only when there's an explicit end bound,
18060        // or when normalize_window_frame_between is enabled and the start is a directional bound
18061        self.write_space();
18062        let should_normalize = self.config.normalize_window_frame_between
18063            && frame.end.is_none()
18064            && matches!(
18065                frame.start,
18066                WindowFrameBound::Preceding(_)
18067                    | WindowFrameBound::Following(_)
18068                    | WindowFrameBound::UnboundedPreceding
18069                    | WindowFrameBound::UnboundedFollowing
18070            );
18071
18072        if let Some(end) = &frame.end {
18073            // BETWEEN format: RANGE BETWEEN start AND end
18074            self.write_keyword("BETWEEN");
18075            self.write_space();
18076            self.generate_window_frame_bound(&frame.start, frame.start_side_text.as_deref())?;
18077            self.write_space();
18078            self.write_keyword("AND");
18079            self.write_space();
18080            self.generate_window_frame_bound(end, frame.end_side_text.as_deref())?;
18081        } else if should_normalize {
18082            // Normalize single-bound to BETWEEN form: ROWS 1 PRECEDING → ROWS BETWEEN 1 PRECEDING AND CURRENT ROW
18083            self.write_keyword("BETWEEN");
18084            self.write_space();
18085            self.generate_window_frame_bound(&frame.start, frame.start_side_text.as_deref())?;
18086            self.write_space();
18087            self.write_keyword("AND");
18088            self.write_space();
18089            self.write_keyword("CURRENT ROW");
18090        } else {
18091            // Single bound format: RANGE CURRENT ROW
18092            self.generate_window_frame_bound(&frame.start, frame.start_side_text.as_deref())?;
18093        }
18094
18095        // EXCLUDE clause
18096        if let Some(exclude) = &frame.exclude {
18097            self.write_space();
18098            self.write_keyword("EXCLUDE");
18099            self.write_space();
18100            match exclude {
18101                WindowFrameExclude::CurrentRow => self.write_keyword("CURRENT ROW"),
18102                WindowFrameExclude::Group => self.write_keyword("GROUP"),
18103                WindowFrameExclude::Ties => self.write_keyword("TIES"),
18104                WindowFrameExclude::NoOthers => self.write_keyword("NO OTHERS"),
18105            }
18106        }
18107
18108        Ok(())
18109    }
18110
18111    fn generate_window_frame_bound(
18112        &mut self,
18113        bound: &WindowFrameBound,
18114        side_text: Option<&str>,
18115    ) -> Result<()> {
18116        // Exasol uses lowercase for preceding/following
18117        let lowercase_frame = self.config.lowercase_window_frame_keywords;
18118
18119        match bound {
18120            WindowFrameBound::CurrentRow => {
18121                self.write_keyword("CURRENT ROW");
18122            }
18123            WindowFrameBound::UnboundedPreceding => {
18124                self.write_keyword("UNBOUNDED");
18125                self.write_space();
18126                if lowercase_frame {
18127                    self.write("preceding");
18128                } else if let Some(text) = side_text {
18129                    self.write(text);
18130                } else {
18131                    self.write_keyword("PRECEDING");
18132                }
18133            }
18134            WindowFrameBound::UnboundedFollowing => {
18135                self.write_keyword("UNBOUNDED");
18136                self.write_space();
18137                if lowercase_frame {
18138                    self.write("following");
18139                } else if let Some(text) = side_text {
18140                    self.write(text);
18141                } else {
18142                    self.write_keyword("FOLLOWING");
18143                }
18144            }
18145            WindowFrameBound::Preceding(expr) => {
18146                self.generate_expression(expr)?;
18147                self.write_space();
18148                if lowercase_frame {
18149                    self.write("preceding");
18150                } else if let Some(text) = side_text {
18151                    self.write(text);
18152                } else {
18153                    self.write_keyword("PRECEDING");
18154                }
18155            }
18156            WindowFrameBound::Following(expr) => {
18157                self.generate_expression(expr)?;
18158                self.write_space();
18159                if lowercase_frame {
18160                    self.write("following");
18161                } else if let Some(text) = side_text {
18162                    self.write(text);
18163                } else {
18164                    self.write_keyword("FOLLOWING");
18165                }
18166            }
18167            WindowFrameBound::BarePreceding => {
18168                if lowercase_frame {
18169                    self.write("preceding");
18170                } else if let Some(text) = side_text {
18171                    self.write(text);
18172                } else {
18173                    self.write_keyword("PRECEDING");
18174                }
18175            }
18176            WindowFrameBound::BareFollowing => {
18177                if lowercase_frame {
18178                    self.write("following");
18179                } else if let Some(text) = side_text {
18180                    self.write(text);
18181                } else {
18182                    self.write_keyword("FOLLOWING");
18183                }
18184            }
18185            WindowFrameBound::Value(expr) => {
18186                // Bare numeric bound without PRECEDING/FOLLOWING
18187                self.generate_expression(expr)?;
18188            }
18189        }
18190        Ok(())
18191    }
18192
18193    fn generate_interval(&mut self, interval: &Interval) -> Result<()> {
18194        // For Oracle with ExprSpan: only output INTERVAL if `this` is a literal
18195        // (e.g., `(expr) DAY(9) TO SECOND(3)` should NOT have INTERVAL prefix)
18196        let skip_interval_keyword = matches!(self.config.dialect, Some(DialectType::Oracle))
18197            && matches!(&interval.unit, Some(IntervalUnitSpec::ExprSpan(_)))
18198            && !matches!(&interval.this, Some(Expression::Literal(_)));
18199
18200        // SINGLE_STRING_INTERVAL: combine value and unit into a single quoted string
18201        // e.g., INTERVAL '1' DAY -> INTERVAL '1 DAY'
18202        if self.config.single_string_interval {
18203            if let (
18204                Some(Expression::Literal(lit)),
18205                Some(IntervalUnitSpec::Simple {
18206                    ref unit,
18207                    ref use_plural,
18208                }),
18209            ) = (&interval.this, &interval.unit)
18210            {
18211                if let Literal::String(ref val) = lit.as_ref() {
18212                    self.write_keyword("INTERVAL");
18213                    self.write_space();
18214                    let effective_plural = *use_plural && self.config.interval_allows_plural_form;
18215                    let unit_str = self.interval_unit_str(unit, effective_plural);
18216                    self.write("'");
18217                    self.write(val);
18218                    self.write(" ");
18219                    self.write(&unit_str);
18220                    self.write("'");
18221                    return Ok(());
18222                }
18223            }
18224        }
18225
18226        if !skip_interval_keyword {
18227            self.write_keyword("INTERVAL");
18228        }
18229
18230        // Generate value if present
18231        if let Some(ref value) = interval.this {
18232            if !skip_interval_keyword {
18233                self.write_space();
18234            }
18235            // If the value is a complex expression (not a literal/column/function call)
18236            // and there's a unit, wrap it in parentheses
18237            // e.g., INTERVAL (2 * 2) MONTH, INTERVAL (DAYOFMONTH(dt) - 1) DAY
18238            let needs_parens = interval.unit.is_some()
18239                && matches!(
18240                    value,
18241                    Expression::Add(_)
18242                        | Expression::Sub(_)
18243                        | Expression::Mul(_)
18244                        | Expression::Div(_)
18245                        | Expression::Mod(_)
18246                        | Expression::BitwiseAnd(_)
18247                        | Expression::BitwiseOr(_)
18248                        | Expression::BitwiseXor(_)
18249                );
18250            if needs_parens {
18251                self.write("(");
18252            }
18253            self.generate_expression(value)?;
18254            if needs_parens {
18255                self.write(")");
18256            }
18257        }
18258
18259        // Generate unit if present
18260        if let Some(ref unit_spec) = interval.unit {
18261            self.write_space();
18262            self.write_interval_unit_spec(unit_spec)?;
18263        }
18264
18265        Ok(())
18266    }
18267
18268    /// Return the string representation of an interval unit
18269    fn interval_unit_str(&self, unit: &IntervalUnit, use_plural: bool) -> &'static str {
18270        match (unit, use_plural) {
18271            (IntervalUnit::Year, false) => "YEAR",
18272            (IntervalUnit::Year, true) => "YEARS",
18273            (IntervalUnit::Quarter, false) => "QUARTER",
18274            (IntervalUnit::Quarter, true) => "QUARTERS",
18275            (IntervalUnit::Month, false) => "MONTH",
18276            (IntervalUnit::Month, true) => "MONTHS",
18277            (IntervalUnit::Week, false) => "WEEK",
18278            (IntervalUnit::Week, true) => "WEEKS",
18279            (IntervalUnit::Day, false) => "DAY",
18280            (IntervalUnit::Day, true) => "DAYS",
18281            (IntervalUnit::Hour, false) => "HOUR",
18282            (IntervalUnit::Hour, true) => "HOURS",
18283            (IntervalUnit::Minute, false) => "MINUTE",
18284            (IntervalUnit::Minute, true) => "MINUTES",
18285            (IntervalUnit::Second, false) => "SECOND",
18286            (IntervalUnit::Second, true) => "SECONDS",
18287            (IntervalUnit::Millisecond, false) => "MILLISECOND",
18288            (IntervalUnit::Millisecond, true) => "MILLISECONDS",
18289            (IntervalUnit::Microsecond, false) => "MICROSECOND",
18290            (IntervalUnit::Microsecond, true) => "MICROSECONDS",
18291            (IntervalUnit::Nanosecond, false) => "NANOSECOND",
18292            (IntervalUnit::Nanosecond, true) => "NANOSECONDS",
18293        }
18294    }
18295
18296    fn write_interval_unit_spec(&mut self, unit_spec: &IntervalUnitSpec) -> Result<()> {
18297        match unit_spec {
18298            IntervalUnitSpec::Simple { unit, use_plural } => {
18299                // If dialect doesn't allow plural forms, force singular
18300                let effective_plural = *use_plural && self.config.interval_allows_plural_form;
18301                self.write_simple_interval_unit(unit, effective_plural);
18302            }
18303            IntervalUnitSpec::Span(span) => {
18304                self.write_simple_interval_unit(&span.this, false);
18305                self.write_space();
18306                self.write_keyword("TO");
18307                self.write_space();
18308                self.write_simple_interval_unit(&span.expression, false);
18309            }
18310            IntervalUnitSpec::ExprSpan(span) => {
18311                // Expression-based interval span (e.g., DAY(9) TO SECOND(3))
18312                self.generate_expression(&span.this)?;
18313                self.write_space();
18314                self.write_keyword("TO");
18315                self.write_space();
18316                self.generate_expression(&span.expression)?;
18317            }
18318            IntervalUnitSpec::Expr(expr) => {
18319                self.generate_expression(expr)?;
18320            }
18321        }
18322        Ok(())
18323    }
18324
18325    fn write_simple_interval_unit(&mut self, unit: &IntervalUnit, use_plural: bool) {
18326        // Output interval unit, respecting plural preference
18327        match (unit, use_plural) {
18328            (IntervalUnit::Year, false) => self.write_keyword("YEAR"),
18329            (IntervalUnit::Year, true) => self.write_keyword("YEARS"),
18330            (IntervalUnit::Quarter, false) => self.write_keyword("QUARTER"),
18331            (IntervalUnit::Quarter, true) => self.write_keyword("QUARTERS"),
18332            (IntervalUnit::Month, false) => self.write_keyword("MONTH"),
18333            (IntervalUnit::Month, true) => self.write_keyword("MONTHS"),
18334            (IntervalUnit::Week, false) => self.write_keyword("WEEK"),
18335            (IntervalUnit::Week, true) => self.write_keyword("WEEKS"),
18336            (IntervalUnit::Day, false) => self.write_keyword("DAY"),
18337            (IntervalUnit::Day, true) => self.write_keyword("DAYS"),
18338            (IntervalUnit::Hour, false) => self.write_keyword("HOUR"),
18339            (IntervalUnit::Hour, true) => self.write_keyword("HOURS"),
18340            (IntervalUnit::Minute, false) => self.write_keyword("MINUTE"),
18341            (IntervalUnit::Minute, true) => self.write_keyword("MINUTES"),
18342            (IntervalUnit::Second, false) => self.write_keyword("SECOND"),
18343            (IntervalUnit::Second, true) => self.write_keyword("SECONDS"),
18344            (IntervalUnit::Millisecond, false) => self.write_keyword("MILLISECOND"),
18345            (IntervalUnit::Millisecond, true) => self.write_keyword("MILLISECONDS"),
18346            (IntervalUnit::Microsecond, false) => self.write_keyword("MICROSECOND"),
18347            (IntervalUnit::Microsecond, true) => self.write_keyword("MICROSECONDS"),
18348            (IntervalUnit::Nanosecond, false) => self.write_keyword("NANOSECOND"),
18349            (IntervalUnit::Nanosecond, true) => self.write_keyword("NANOSECONDS"),
18350        }
18351    }
18352
18353    /// Normalize a date part expression to unquoted uppercase for Redshift DATEDIFF/DATEADD
18354    /// Converts: 'day', 'days', day, days, DAY -> DAY (unquoted)
18355    fn write_redshift_date_part(&mut self, expr: &Expression) {
18356        let part_str = self.extract_date_part_string(expr);
18357        if let Some(part) = part_str {
18358            let normalized = self.normalize_date_part(&part);
18359            self.write_keyword(&normalized);
18360        } else {
18361            // If we can't extract a date part string, fall back to generating the expression
18362            let _ = self.generate_expression(expr);
18363        }
18364    }
18365
18366    /// Normalize a date part expression to quoted uppercase for Redshift DATE_TRUNC
18367    /// Converts: 'day', day, DAY -> 'DAY' (quoted)
18368    fn write_redshift_date_part_quoted(&mut self, expr: &Expression) {
18369        let part_str = self.extract_date_part_string(expr);
18370        if let Some(part) = part_str {
18371            let normalized = self.normalize_date_part(&part);
18372            self.write("'");
18373            self.write(&normalized);
18374            self.write("'");
18375        } else {
18376            // If we can't extract a date part string, fall back to generating the expression
18377            let _ = self.generate_expression(expr);
18378        }
18379    }
18380
18381    /// Extract date part string from expression (handles string literals and identifiers)
18382    fn extract_date_part_string(&self, expr: &Expression) -> Option<String> {
18383        match expr {
18384            Expression::Literal(lit)
18385                if matches!(lit.as_ref(), crate::expressions::Literal::String(_)) =>
18386            {
18387                let crate::expressions::Literal::String(s) = lit.as_ref() else {
18388                    unreachable!()
18389                };
18390                Some(s.clone())
18391            }
18392            Expression::Identifier(id) => Some(id.name.clone()),
18393            Expression::Var(v) => Some(v.this.clone()),
18394            Expression::Column(col) if col.table.is_none() => {
18395                // Simple column reference without table prefix, treat as identifier
18396                Some(col.name.name.clone())
18397            }
18398            _ => None,
18399        }
18400    }
18401
18402    /// Normalize date part to uppercase singular form
18403    /// days -> DAY, months -> MONTH, etc.
18404    fn normalize_date_part(&self, part: &str) -> String {
18405        let mut buf = [0u8; 64];
18406        let lower: &str = if part.len() <= 64 {
18407            for (i, b) in part.bytes().enumerate() {
18408                buf[i] = b.to_ascii_lowercase();
18409            }
18410            std::str::from_utf8(&buf[..part.len()]).unwrap_or(part)
18411        } else {
18412            return part.to_ascii_uppercase();
18413        };
18414        match lower {
18415            "day" | "days" | "d" => "DAY".to_string(),
18416            "month" | "months" | "mon" | "mm" => "MONTH".to_string(),
18417            "year" | "years" | "y" | "yy" | "yyyy" => "YEAR".to_string(),
18418            "week" | "weeks" | "w" | "wk" => "WEEK".to_string(),
18419            "hour" | "hours" | "h" | "hh" => "HOUR".to_string(),
18420            "minute" | "minutes" | "m" | "mi" | "n" => "MINUTE".to_string(),
18421            "second" | "seconds" | "s" | "ss" => "SECOND".to_string(),
18422            "millisecond" | "milliseconds" | "ms" => "MILLISECOND".to_string(),
18423            "microsecond" | "microseconds" | "us" => "MICROSECOND".to_string(),
18424            "quarter" | "quarters" | "q" | "qq" => "QUARTER".to_string(),
18425            _ => part.to_ascii_uppercase(),
18426        }
18427    }
18428
18429    fn write_datetime_field(&mut self, field: &DateTimeField) {
18430        match field {
18431            DateTimeField::Year => self.write_keyword("YEAR"),
18432            DateTimeField::Month => self.write_keyword("MONTH"),
18433            DateTimeField::Day => self.write_keyword("DAY"),
18434            DateTimeField::Hour => self.write_keyword("HOUR"),
18435            DateTimeField::Minute => self.write_keyword("MINUTE"),
18436            DateTimeField::Second => self.write_keyword("SECOND"),
18437            DateTimeField::Millisecond => self.write_keyword("MILLISECOND"),
18438            DateTimeField::Microsecond => self.write_keyword("MICROSECOND"),
18439            DateTimeField::DayOfWeek => {
18440                let name = match self.config.dialect {
18441                    Some(DialectType::DuckDB) | Some(DialectType::Snowflake) => "DAYOFWEEK",
18442                    Some(DialectType::TSQL) | Some(DialectType::Fabric) => "WEEKDAY",
18443                    _ => "DOW",
18444                };
18445                self.write_keyword(name);
18446            }
18447            DateTimeField::DayOfYear => {
18448                let name = match self.config.dialect {
18449                    Some(DialectType::DuckDB) | Some(DialectType::Snowflake) => "DAYOFYEAR",
18450                    _ => "DOY",
18451                };
18452                self.write_keyword(name);
18453            }
18454            DateTimeField::Week => self.write_keyword("WEEK"),
18455            DateTimeField::WeekWithModifier(modifier) => {
18456                self.write_keyword("WEEK");
18457                self.write("(");
18458                self.write(modifier);
18459                self.write(")");
18460            }
18461            DateTimeField::Quarter => self.write_keyword("QUARTER"),
18462            DateTimeField::Epoch => self.write_keyword("EPOCH"),
18463            DateTimeField::Timezone => self.write_keyword("TIMEZONE"),
18464            DateTimeField::TimezoneHour => self.write_keyword("TIMEZONE_HOUR"),
18465            DateTimeField::TimezoneMinute => self.write_keyword("TIMEZONE_MINUTE"),
18466            DateTimeField::Date => self.write_keyword("DATE"),
18467            DateTimeField::Time => self.write_keyword("TIME"),
18468            DateTimeField::Custom(name) => self.write(name),
18469        }
18470    }
18471
18472    /// Write datetime field in lowercase (for Spark/Hive/Databricks)
18473    fn write_datetime_field_lower(&mut self, field: &DateTimeField) {
18474        match field {
18475            DateTimeField::Year => self.write("year"),
18476            DateTimeField::Month => self.write("month"),
18477            DateTimeField::Day => self.write("day"),
18478            DateTimeField::Hour => self.write("hour"),
18479            DateTimeField::Minute => self.write("minute"),
18480            DateTimeField::Second => self.write("second"),
18481            DateTimeField::Millisecond => self.write("millisecond"),
18482            DateTimeField::Microsecond => self.write("microsecond"),
18483            DateTimeField::DayOfWeek => self.write("dow"),
18484            DateTimeField::DayOfYear => self.write("doy"),
18485            DateTimeField::Week => self.write("week"),
18486            DateTimeField::WeekWithModifier(modifier) => {
18487                self.write("week(");
18488                self.write(modifier);
18489                self.write(")");
18490            }
18491            DateTimeField::Quarter => self.write("quarter"),
18492            DateTimeField::Epoch => self.write("epoch"),
18493            DateTimeField::Timezone => self.write("timezone"),
18494            DateTimeField::TimezoneHour => self.write("timezone_hour"),
18495            DateTimeField::TimezoneMinute => self.write("timezone_minute"),
18496            DateTimeField::Date => self.write("date"),
18497            DateTimeField::Time => self.write("time"),
18498            DateTimeField::Custom(name) => self.write(name),
18499        }
18500    }
18501
18502    // Helper function generators
18503
18504    fn generate_simple_func(&mut self, name: &str, arg: &Expression) -> Result<()> {
18505        self.write_keyword(name);
18506        self.write("(");
18507        self.generate_expression(arg)?;
18508        self.write(")");
18509        Ok(())
18510    }
18511
18512    /// Generate a unary function, using the original name if available for round-trip preservation
18513    fn generate_unary_func(
18514        &mut self,
18515        default_name: &str,
18516        f: &crate::expressions::UnaryFunc,
18517    ) -> Result<()> {
18518        let name = f.original_name.as_deref().unwrap_or(default_name);
18519        self.write_keyword(name);
18520        self.write("(");
18521        self.generate_expression(&f.this)?;
18522        self.write(")");
18523        Ok(())
18524    }
18525
18526    /// Generate SQRT/CBRT - always use function form (matches Python SQLGlot normalization)
18527    fn generate_sqrt_cbrt(
18528        &mut self,
18529        f: &crate::expressions::UnaryFunc,
18530        func_name: &str,
18531        _op: &str,
18532    ) -> Result<()> {
18533        // Python SQLGlot normalizes |/ and ||/ to SQRT() and CBRT()
18534        // Always use function syntax for consistency
18535        self.write_keyword(func_name);
18536        self.write("(");
18537        self.generate_expression(&f.this)?;
18538        self.write(")");
18539        Ok(())
18540    }
18541
18542    fn generate_binary_func(
18543        &mut self,
18544        name: &str,
18545        arg1: &Expression,
18546        arg2: &Expression,
18547    ) -> Result<()> {
18548        self.write_keyword(name);
18549        self.write("(");
18550        self.generate_expression(arg1)?;
18551        self.write(", ");
18552        self.generate_expression(arg2)?;
18553        self.write(")");
18554        Ok(())
18555    }
18556
18557    /// Generate CHAR/CHR function with optional USING charset
18558    /// e.g., CHAR(77, 77.3, '77.3' USING utf8mb4)
18559    /// e.g., CHR(187 USING NCHAR_CS) -- Oracle
18560    fn generate_char_func(&mut self, f: &crate::expressions::CharFunc) -> Result<()> {
18561        // Use stored name if available, otherwise default to CHAR
18562        let func_name = f.name.as_deref().unwrap_or("CHAR");
18563        self.write_keyword(func_name);
18564        self.write("(");
18565        for (i, arg) in f.args.iter().enumerate() {
18566            if i > 0 {
18567                self.write(", ");
18568            }
18569            self.generate_expression(arg)?;
18570        }
18571        if let Some(ref charset) = f.charset {
18572            self.write(" ");
18573            self.write_keyword("USING");
18574            self.write(" ");
18575            self.write(charset);
18576        }
18577        self.write(")");
18578        Ok(())
18579    }
18580
18581    fn generate_power(&mut self, f: &BinaryFunc) -> Result<()> {
18582        use crate::dialects::DialectType;
18583
18584        match self.config.dialect {
18585            Some(DialectType::Teradata) => {
18586                // Teradata uses ** operator for exponentiation
18587                self.generate_expression(&f.this)?;
18588                self.write(" ** ");
18589                self.generate_expression(&f.expression)?;
18590                Ok(())
18591            }
18592            _ => {
18593                // Other dialects use POWER function
18594                self.generate_binary_func("POWER", &f.this, &f.expression)
18595            }
18596        }
18597    }
18598
18599    fn generate_vararg_func(&mut self, name: &str, args: &[Expression]) -> Result<()> {
18600        self.write_func_name(name);
18601        self.write("(");
18602        for (i, arg) in args.iter().enumerate() {
18603            if i > 0 {
18604                self.write(", ");
18605            }
18606            self.generate_expression(arg)?;
18607        }
18608        self.write(")");
18609        Ok(())
18610    }
18611
18612    // String function generators
18613
18614    fn generate_concat_ws(&mut self, f: &ConcatWs) -> Result<()> {
18615        self.write_keyword("CONCAT_WS");
18616        self.write("(");
18617        self.generate_expression(&f.separator)?;
18618        for expr in &f.expressions {
18619            self.write(", ");
18620            self.generate_expression(expr)?;
18621        }
18622        self.write(")");
18623        Ok(())
18624    }
18625
18626    fn collect_concat_operands<'a>(expr: &'a Expression, out: &mut Vec<&'a Expression>) {
18627        if let Expression::Concat(op) = expr {
18628            Self::collect_concat_operands(&op.left, out);
18629            Self::collect_concat_operands(&op.right, out);
18630        } else {
18631            out.push(expr);
18632        }
18633    }
18634
18635    fn generate_mysql_concat_from_concat(&mut self, op: &BinaryOp) -> Result<()> {
18636        let mut operands = Vec::new();
18637        Self::collect_concat_operands(&op.left, &mut operands);
18638        Self::collect_concat_operands(&op.right, &mut operands);
18639
18640        self.write_keyword("CONCAT");
18641        self.write("(");
18642        for (i, operand) in operands.iter().enumerate() {
18643            if i > 0 {
18644                self.write(", ");
18645            }
18646            self.generate_expression(operand)?;
18647        }
18648        self.write(")");
18649        Ok(())
18650    }
18651
18652    fn collect_dpipe_operands<'a>(expr: &'a Expression, out: &mut Vec<&'a Expression>) {
18653        if let Expression::DPipe(dpipe) = expr {
18654            Self::collect_dpipe_operands(&dpipe.this, out);
18655            Self::collect_dpipe_operands(&dpipe.expression, out);
18656        } else {
18657            out.push(expr);
18658        }
18659    }
18660
18661    fn generate_mysql_concat_from_dpipe(&mut self, e: &DPipe) -> Result<()> {
18662        let mut operands = Vec::new();
18663        Self::collect_dpipe_operands(&e.this, &mut operands);
18664        Self::collect_dpipe_operands(&e.expression, &mut operands);
18665
18666        self.write_keyword("CONCAT");
18667        self.write("(");
18668        for (i, operand) in operands.iter().enumerate() {
18669            if i > 0 {
18670                self.write(", ");
18671            }
18672            self.generate_expression(operand)?;
18673        }
18674        self.write(")");
18675        Ok(())
18676    }
18677
18678    fn generate_substring(&mut self, f: &SubstringFunc) -> Result<()> {
18679        // Oracle and Presto-family dialects use SUBSTR; most others use SUBSTRING
18680        let use_substr = matches!(
18681            self.config.dialect,
18682            Some(
18683                DialectType::Oracle
18684                    | DialectType::Presto
18685                    | DialectType::Trino
18686                    | DialectType::Athena
18687            )
18688        );
18689        if use_substr {
18690            self.write_keyword("SUBSTR");
18691        } else {
18692            self.write_keyword("SUBSTRING");
18693        }
18694        self.write("(");
18695        self.generate_expression(&f.this)?;
18696        // PostgreSQL always uses FROM/FOR syntax
18697        let force_from_for = matches!(self.config.dialect, Some(DialectType::PostgreSQL));
18698        // Spark/Hive/TSQL/Fabric use comma syntax, not FROM/FOR syntax
18699        let use_comma_syntax = matches!(
18700            self.config.dialect,
18701            Some(DialectType::Spark)
18702                | Some(DialectType::Hive)
18703                | Some(DialectType::Databricks)
18704                | Some(DialectType::TSQL)
18705                | Some(DialectType::Fabric)
18706        );
18707        if (f.from_for_syntax || force_from_for) && !use_comma_syntax {
18708            // SQL standard syntax: SUBSTRING(str FROM pos FOR len)
18709            self.write_space();
18710            self.write_keyword("FROM");
18711            self.write_space();
18712            self.generate_expression(&f.start)?;
18713            if let Some(length) = &f.length {
18714                self.write_space();
18715                self.write_keyword("FOR");
18716                self.write_space();
18717                self.generate_expression(length)?;
18718            }
18719        } else {
18720            // Comma-separated syntax: SUBSTRING(str, pos, len) or SUBSTR(str, pos, len)
18721            self.write(", ");
18722            self.generate_expression(&f.start)?;
18723            if let Some(length) = &f.length {
18724                self.write(", ");
18725                self.generate_expression(length)?;
18726            }
18727        }
18728        self.write(")");
18729        Ok(())
18730    }
18731
18732    fn generate_overlay(&mut self, f: &OverlayFunc) -> Result<()> {
18733        self.write_keyword("OVERLAY");
18734        self.write("(");
18735        self.generate_expression(&f.this)?;
18736        self.write_space();
18737        self.write_keyword("PLACING");
18738        self.write_space();
18739        self.generate_expression(&f.replacement)?;
18740        self.write_space();
18741        self.write_keyword("FROM");
18742        self.write_space();
18743        self.generate_expression(&f.from)?;
18744        if let Some(length) = &f.length {
18745            self.write_space();
18746            self.write_keyword("FOR");
18747            self.write_space();
18748            self.generate_expression(length)?;
18749        }
18750        self.write(")");
18751        Ok(())
18752    }
18753
18754    fn generate_trim(&mut self, f: &TrimFunc) -> Result<()> {
18755        // Special case: TRIM(LEADING str) -> LTRIM(str), TRIM(TRAILING str) -> RTRIM(str)
18756        // when no characters are specified (PostgreSQL style)
18757        if f.position_explicit && f.characters.is_none() {
18758            match f.position {
18759                TrimPosition::Leading => {
18760                    self.write_keyword("LTRIM");
18761                    self.write("(");
18762                    self.generate_expression(&f.this)?;
18763                    self.write(")");
18764                    return Ok(());
18765                }
18766                TrimPosition::Trailing => {
18767                    self.write_keyword("RTRIM");
18768                    self.write("(");
18769                    self.generate_expression(&f.this)?;
18770                    self.write(")");
18771                    return Ok(());
18772                }
18773                TrimPosition::Both => {
18774                    // TRIM(BOTH str) -> BTRIM(str) in PostgreSQL, but TRIM(str) is more standard
18775                    // Fall through to standard TRIM handling
18776                }
18777            }
18778        }
18779
18780        self.write_keyword("TRIM");
18781        self.write("(");
18782        // When BOTH is specified without trim characters, simplify to just TRIM(str)
18783        // Force standard syntax for dialects that require it (Hive, Spark, Databricks, ClickHouse)
18784        let force_standard = f.characters.is_some()
18785            && !f.sql_standard_syntax
18786            && matches!(
18787                self.config.dialect,
18788                Some(DialectType::Hive)
18789                    | Some(DialectType::Spark)
18790                    | Some(DialectType::Databricks)
18791                    | Some(DialectType::ClickHouse)
18792            );
18793        let use_standard = (f.sql_standard_syntax || force_standard)
18794            && !(f.position_explicit
18795                && f.characters.is_none()
18796                && matches!(f.position, TrimPosition::Both));
18797        if use_standard {
18798            // SQL standard syntax: TRIM(BOTH chars FROM str)
18799            // Only output position if it was explicitly specified
18800            if f.position_explicit {
18801                match f.position {
18802                    TrimPosition::Both => self.write_keyword("BOTH"),
18803                    TrimPosition::Leading => self.write_keyword("LEADING"),
18804                    TrimPosition::Trailing => self.write_keyword("TRAILING"),
18805                }
18806                self.write_space();
18807            }
18808            if let Some(chars) = &f.characters {
18809                self.generate_expression(chars)?;
18810                self.write_space();
18811            }
18812            self.write_keyword("FROM");
18813            self.write_space();
18814            self.generate_expression(&f.this)?;
18815        } else {
18816            // Simple function syntax: TRIM(str) or TRIM(str, chars)
18817            self.generate_expression(&f.this)?;
18818            if let Some(chars) = &f.characters {
18819                self.write(", ");
18820                self.generate_expression(chars)?;
18821            }
18822        }
18823        self.write(")");
18824        Ok(())
18825    }
18826
18827    fn generate_replace(&mut self, f: &ReplaceFunc) -> Result<()> {
18828        self.write_keyword("REPLACE");
18829        self.write("(");
18830        self.generate_expression(&f.this)?;
18831        self.write(", ");
18832        self.generate_expression(&f.old)?;
18833        self.write(", ");
18834        self.generate_expression(&f.new)?;
18835        self.write(")");
18836        Ok(())
18837    }
18838
18839    fn generate_left_right(&mut self, name: &str, f: &LeftRightFunc) -> Result<()> {
18840        self.write_keyword(name);
18841        self.write("(");
18842        self.generate_expression(&f.this)?;
18843        self.write(", ");
18844        self.generate_expression(&f.length)?;
18845        self.write(")");
18846        Ok(())
18847    }
18848
18849    fn generate_repeat(&mut self, f: &RepeatFunc) -> Result<()> {
18850        self.write_keyword("REPEAT");
18851        self.write("(");
18852        self.generate_expression(&f.this)?;
18853        self.write(", ");
18854        self.generate_expression(&f.times)?;
18855        self.write(")");
18856        Ok(())
18857    }
18858
18859    fn generate_pad(&mut self, name: &str, f: &PadFunc) -> Result<()> {
18860        self.write_keyword(name);
18861        self.write("(");
18862        self.generate_expression(&f.this)?;
18863        self.write(", ");
18864        self.generate_expression(&f.length)?;
18865        if let Some(fill) = &f.fill {
18866            self.write(", ");
18867            self.generate_expression(fill)?;
18868        }
18869        self.write(")");
18870        Ok(())
18871    }
18872
18873    fn generate_split(&mut self, f: &SplitFunc) -> Result<()> {
18874        self.write_keyword("SPLIT");
18875        self.write("(");
18876        self.generate_expression(&f.this)?;
18877        self.write(", ");
18878        self.generate_expression(&f.delimiter)?;
18879        self.write(")");
18880        Ok(())
18881    }
18882
18883    fn generate_regexp_like(&mut self, f: &RegexpFunc) -> Result<()> {
18884        use crate::dialects::DialectType;
18885        // PostgreSQL uses ~ operator for regex matching
18886        if matches!(self.config.dialect, Some(DialectType::PostgreSQL)) && f.flags.is_none() {
18887            self.generate_expression(&f.this)?;
18888            self.write(" ~ ");
18889            self.generate_expression(&f.pattern)?;
18890        } else if matches!(self.config.dialect, Some(DialectType::Exasol)) && f.flags.is_none() {
18891            // Exasol uses REGEXP_LIKE as infix binary operator
18892            self.generate_expression(&f.this)?;
18893            self.write_keyword(" REGEXP_LIKE ");
18894            self.generate_expression(&f.pattern)?;
18895        } else if matches!(
18896            self.config.dialect,
18897            Some(DialectType::SingleStore)
18898                | Some(DialectType::Spark)
18899                | Some(DialectType::Hive)
18900                | Some(DialectType::Databricks)
18901        ) && f.flags.is_none()
18902        {
18903            // SingleStore/Spark/Hive/Databricks use RLIKE infix operator
18904            self.generate_expression(&f.this)?;
18905            self.write_keyword(" RLIKE ");
18906            self.generate_expression(&f.pattern)?;
18907        } else if matches!(self.config.dialect, Some(DialectType::StarRocks)) {
18908            // StarRocks uses REGEXP function syntax
18909            self.write_keyword("REGEXP");
18910            self.write("(");
18911            self.generate_expression(&f.this)?;
18912            self.write(", ");
18913            self.generate_expression(&f.pattern)?;
18914            if let Some(flags) = &f.flags {
18915                self.write(", ");
18916                self.generate_expression(flags)?;
18917            }
18918            self.write(")");
18919        } else {
18920            self.write_keyword("REGEXP_LIKE");
18921            self.write("(");
18922            self.generate_expression(&f.this)?;
18923            self.write(", ");
18924            self.generate_expression(&f.pattern)?;
18925            if let Some(flags) = &f.flags {
18926                self.write(", ");
18927                self.generate_expression(flags)?;
18928            }
18929            self.write(")");
18930        }
18931        Ok(())
18932    }
18933
18934    fn generate_regexp_replace(&mut self, f: &RegexpReplaceFunc) -> Result<()> {
18935        self.write_keyword("REGEXP_REPLACE");
18936        self.write("(");
18937        self.generate_expression(&f.this)?;
18938        self.write(", ");
18939        self.generate_expression(&f.pattern)?;
18940        self.write(", ");
18941        self.generate_expression(&f.replacement)?;
18942        if let Some(flags) = &f.flags {
18943            self.write(", ");
18944            self.generate_expression(flags)?;
18945        }
18946        self.write(")");
18947        Ok(())
18948    }
18949
18950    fn generate_regexp_extract(&mut self, f: &RegexpExtractFunc) -> Result<()> {
18951        self.write_keyword("REGEXP_EXTRACT");
18952        self.write("(");
18953        self.generate_expression(&f.this)?;
18954        self.write(", ");
18955        self.generate_expression(&f.pattern)?;
18956        if let Some(group) = &f.group {
18957            self.write(", ");
18958            self.generate_expression(group)?;
18959        }
18960        self.write(")");
18961        Ok(())
18962    }
18963
18964    // Math function generators
18965
18966    fn generate_round(&mut self, f: &RoundFunc) -> Result<()> {
18967        self.write_keyword("ROUND");
18968        self.write("(");
18969        self.generate_expression(&f.this)?;
18970        if let Some(decimals) = &f.decimals {
18971            self.write(", ");
18972            self.generate_expression(decimals)?;
18973        }
18974        self.write(")");
18975        Ok(())
18976    }
18977
18978    fn generate_floor(&mut self, f: &FloorFunc) -> Result<()> {
18979        self.write_keyword("FLOOR");
18980        self.write("(");
18981        self.generate_expression(&f.this)?;
18982        // Handle Druid-style FLOOR(time TO unit) syntax
18983        if let Some(to) = &f.to {
18984            self.write(" ");
18985            self.write_keyword("TO");
18986            self.write(" ");
18987            self.generate_expression(to)?;
18988        } else if let Some(scale) = &f.scale {
18989            self.write(", ");
18990            self.generate_expression(scale)?;
18991        }
18992        self.write(")");
18993        Ok(())
18994    }
18995
18996    fn generate_ceil(&mut self, f: &CeilFunc) -> Result<()> {
18997        self.write_keyword("CEIL");
18998        self.write("(");
18999        self.generate_expression(&f.this)?;
19000        // Handle Druid-style CEIL(time TO unit) syntax
19001        if let Some(to) = &f.to {
19002            self.write(" ");
19003            self.write_keyword("TO");
19004            self.write(" ");
19005            self.generate_expression(to)?;
19006        } else if let Some(decimals) = &f.decimals {
19007            self.write(", ");
19008            self.generate_expression(decimals)?;
19009        }
19010        self.write(")");
19011        Ok(())
19012    }
19013
19014    fn generate_log(&mut self, f: &LogFunc) -> Result<()> {
19015        use crate::expressions::Literal;
19016
19017        if let Some(base) = &f.base {
19018            // Check for LOG_BASE_FIRST = None dialects (Presto, Trino, ClickHouse, Athena)
19019            // These dialects use LOG2()/LOG10() instead of LOG(base, value)
19020            if self.is_log_base_none() {
19021                if matches!(base, Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(s) if s == "2"))
19022                {
19023                    self.write_func_name("LOG2");
19024                    self.write("(");
19025                    self.generate_expression(&f.this)?;
19026                    self.write(")");
19027                    return Ok(());
19028                } else if matches!(base, Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(s) if s == "10"))
19029                {
19030                    self.write_func_name("LOG10");
19031                    self.write("(");
19032                    self.generate_expression(&f.this)?;
19033                    self.write(")");
19034                    return Ok(());
19035                }
19036                // Other bases: fall through to LOG(base, value) — best effort
19037            }
19038
19039            self.write_func_name("LOG");
19040            self.write("(");
19041            if self.is_log_value_first() {
19042                // BigQuery, TSQL, Tableau, Fabric: LOG(value, base)
19043                self.generate_expression(&f.this)?;
19044                self.write(", ");
19045                self.generate_expression(base)?;
19046            } else {
19047                // Default (PostgreSQL, etc.): LOG(base, value)
19048                self.generate_expression(base)?;
19049                self.write(", ");
19050                self.generate_expression(&f.this)?;
19051            }
19052            self.write(")");
19053        } else {
19054            // Single arg: LOG(x) — unspecified base (log base 10 in default dialect)
19055            self.write_func_name("LOG");
19056            self.write("(");
19057            self.generate_expression(&f.this)?;
19058            self.write(")");
19059        }
19060        Ok(())
19061    }
19062
19063    /// Whether the target dialect uses LOG(value, base) order (value first).
19064    /// BigQuery, TSQL, Tableau, Fabric use LOG(value, base).
19065    fn is_log_value_first(&self) -> bool {
19066        use crate::dialects::DialectType;
19067        matches!(
19068            self.config.dialect,
19069            Some(DialectType::BigQuery)
19070                | Some(DialectType::TSQL)
19071                | Some(DialectType::Tableau)
19072                | Some(DialectType::Fabric)
19073        )
19074    }
19075
19076    /// Whether the target dialect has LOG_BASE_FIRST = None (uses LOG2/LOG10 instead).
19077    /// Presto, Trino, ClickHouse, Athena.
19078    fn is_log_base_none(&self) -> bool {
19079        use crate::dialects::DialectType;
19080        matches!(
19081            self.config.dialect,
19082            Some(DialectType::Presto)
19083                | Some(DialectType::Trino)
19084                | Some(DialectType::ClickHouse)
19085                | Some(DialectType::Athena)
19086        )
19087    }
19088
19089    // Date/time function generators
19090
19091    fn generate_current_time(&mut self, f: &CurrentTime) -> Result<()> {
19092        self.write_keyword("CURRENT_TIME");
19093        if let Some(precision) = f.precision {
19094            self.write(&format!("({})", precision));
19095        } else if matches!(
19096            self.config.dialect,
19097            Some(crate::dialects::DialectType::MySQL)
19098                | Some(crate::dialects::DialectType::SingleStore)
19099                | Some(crate::dialects::DialectType::TiDB)
19100        ) {
19101            self.write("()");
19102        }
19103        Ok(())
19104    }
19105
19106    fn generate_current_timestamp(&mut self, f: &CurrentTimestamp) -> Result<()> {
19107        use crate::dialects::DialectType;
19108
19109        // Oracle/Redshift SYSDATE handling
19110        if f.sysdate {
19111            match self.config.dialect {
19112                Some(DialectType::Oracle) | Some(DialectType::Redshift) => {
19113                    self.write_keyword("SYSDATE");
19114                    return Ok(());
19115                }
19116                Some(DialectType::Snowflake) => {
19117                    // Snowflake uses SYSDATE() function
19118                    self.write_keyword("SYSDATE");
19119                    self.write("()");
19120                    return Ok(());
19121                }
19122                _ => {
19123                    // Other dialects use CURRENT_TIMESTAMP for SYSDATE
19124                }
19125            }
19126        }
19127
19128        self.write_keyword("CURRENT_TIMESTAMP");
19129        // MySQL, Spark, Hive always use CURRENT_TIMESTAMP() with parentheses
19130        if let Some(precision) = f.precision {
19131            self.write(&format!("({})", precision));
19132        } else if matches!(
19133            self.config.dialect,
19134            Some(crate::dialects::DialectType::MySQL)
19135                | Some(crate::dialects::DialectType::SingleStore)
19136                | Some(crate::dialects::DialectType::TiDB)
19137                | Some(crate::dialects::DialectType::Spark)
19138                | Some(crate::dialects::DialectType::Hive)
19139                | Some(crate::dialects::DialectType::Databricks)
19140                | Some(crate::dialects::DialectType::ClickHouse)
19141                | Some(crate::dialects::DialectType::BigQuery)
19142                | Some(crate::dialects::DialectType::Snowflake)
19143                | Some(crate::dialects::DialectType::Exasol)
19144        ) {
19145            self.write("()");
19146        }
19147        Ok(())
19148    }
19149
19150    fn generate_at_time_zone(&mut self, f: &AtTimeZone) -> Result<()> {
19151        // Exasol uses CONVERT_TZ(timestamp, 'UTC', zone) instead of AT TIME ZONE
19152        if self.config.dialect == Some(DialectType::Exasol) {
19153            self.write_keyword("CONVERT_TZ");
19154            self.write("(");
19155            self.generate_expression(&f.this)?;
19156            self.write(", 'UTC', ");
19157            self.generate_expression(&f.zone)?;
19158            self.write(")");
19159            return Ok(());
19160        }
19161
19162        self.generate_expression(&f.this)?;
19163        self.write_space();
19164        self.write_keyword("AT TIME ZONE");
19165        self.write_space();
19166        self.generate_expression(&f.zone)?;
19167        Ok(())
19168    }
19169
19170    fn generate_date_add(&mut self, f: &DateAddFunc, name: &str) -> Result<()> {
19171        use crate::dialects::DialectType;
19172
19173        // Presto/Trino use DATE_ADD('unit', interval, date) format
19174        // with the interval cast to BIGINT when needed
19175        let is_presto_like = matches!(
19176            self.config.dialect,
19177            Some(DialectType::Presto) | Some(DialectType::Trino)
19178        );
19179
19180        if is_presto_like {
19181            self.write_keyword(name);
19182            self.write("(");
19183            // Unit as string literal
19184            self.write("'");
19185            self.write_simple_interval_unit(&f.unit, false);
19186            self.write("'");
19187            self.write(", ");
19188            // Interval - wrap in CAST(...AS BIGINT) if it doesn't return integer type
19189            let needs_cast = !self.returns_integer_type(&f.interval);
19190            if needs_cast {
19191                self.write_keyword("CAST");
19192                self.write("(");
19193            }
19194            self.generate_expression(&f.interval)?;
19195            if needs_cast {
19196                self.write_space();
19197                self.write_keyword("AS");
19198                self.write_space();
19199                self.write_keyword("BIGINT");
19200                self.write(")");
19201            }
19202            self.write(", ");
19203            self.generate_expression(&f.this)?;
19204            self.write(")");
19205        } else if matches!(self.config.dialect, Some(DialectType::PostgreSQL)) {
19206            self.generate_expression(&f.this)?;
19207            self.write_space();
19208            if name.eq_ignore_ascii_case("DATE_SUB") {
19209                self.write("-");
19210            } else {
19211                self.write("+");
19212            }
19213            self.write_space();
19214            self.write_keyword("INTERVAL");
19215            self.write_space();
19216            self.write("'");
19217            let mut interval_gen = Generator::with_arc_config(self.config.clone());
19218            let interval_sql = interval_gen.generate(&f.interval)?;
19219            self.write(&interval_sql);
19220            self.write(" ");
19221            self.write_simple_interval_unit(&f.unit, false);
19222            self.write("'");
19223        } else {
19224            self.write_keyword(name);
19225            self.write("(");
19226            self.generate_expression(&f.this)?;
19227            self.write(", ");
19228            self.write_keyword("INTERVAL");
19229            self.write_space();
19230            self.generate_expression(&f.interval)?;
19231            self.write_space();
19232            self.write_simple_interval_unit(&f.unit, false); // Use singular form for DATEADD
19233            self.write(")");
19234        }
19235        Ok(())
19236    }
19237
19238    /// Check if an expression returns an integer type (doesn't need cast to BIGINT in Presto DATE_ADD)
19239    /// This is a heuristic to avoid full type inference
19240    fn returns_integer_type(&self, expr: &Expression) -> bool {
19241        use crate::expressions::{DataType, Literal};
19242        match expr {
19243            // Integer literals (no decimal point)
19244            Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(_)) => {
19245                let Literal::Number(n) = lit.as_ref() else {
19246                    unreachable!()
19247                };
19248                !n.contains('.')
19249            }
19250
19251            // FLOOR(x) returns integer if x is integer
19252            Expression::Floor(f) => self.returns_integer_type(&f.this),
19253
19254            // ROUND(x) returns integer if x is integer
19255            Expression::Round(f) => {
19256                // Only if no decimals arg or it's returning an integer
19257                f.decimals.is_none() && self.returns_integer_type(&f.this)
19258            }
19259
19260            // SIGN returns integer if input is integer
19261            Expression::Sign(f) => self.returns_integer_type(&f.this),
19262
19263            // ABS returns the same type as input
19264            Expression::Abs(f) => self.returns_integer_type(&f.this),
19265
19266            // Arithmetic operations on integers return integers
19267            Expression::Mul(op) => {
19268                self.returns_integer_type(&op.left) && self.returns_integer_type(&op.right)
19269            }
19270            Expression::Add(op) => {
19271                self.returns_integer_type(&op.left) && self.returns_integer_type(&op.right)
19272            }
19273            Expression::Sub(op) => {
19274                self.returns_integer_type(&op.left) && self.returns_integer_type(&op.right)
19275            }
19276            Expression::Mod(op) => self.returns_integer_type(&op.left),
19277
19278            // CAST(x AS BIGINT/INT/INTEGER/SMALLINT/TINYINT) returns integer
19279            Expression::Cast(c) => matches!(
19280                &c.to,
19281                DataType::BigInt { .. }
19282                    | DataType::Int { .. }
19283                    | DataType::SmallInt { .. }
19284                    | DataType::TinyInt { .. }
19285            ),
19286
19287            // Negation: -x returns integer if x is integer
19288            Expression::Neg(op) => self.returns_integer_type(&op.this),
19289
19290            // Parenthesized expression
19291            Expression::Paren(p) => self.returns_integer_type(&p.this),
19292
19293            // Column references and most expressions are assumed to need casting
19294            // since we don't have full type information
19295            _ => false,
19296        }
19297    }
19298
19299    fn generate_datediff(&mut self, f: &DateDiffFunc) -> Result<()> {
19300        self.write_keyword("DATEDIFF");
19301        self.write("(");
19302        if let Some(unit) = &f.unit {
19303            self.write_simple_interval_unit(unit, false); // Use singular form for DATEDIFF
19304            self.write(", ");
19305        }
19306        self.generate_expression(&f.this)?;
19307        self.write(", ");
19308        self.generate_expression(&f.expression)?;
19309        self.write(")");
19310        Ok(())
19311    }
19312
19313    fn generate_date_trunc(&mut self, f: &DateTruncFunc) -> Result<()> {
19314        if self.config.dialect == Some(DialectType::ClickHouse) {
19315            self.write("dateTrunc");
19316        } else {
19317            self.write_keyword("DATE_TRUNC");
19318        }
19319        self.write("('");
19320        self.write_datetime_field(&f.unit);
19321        self.write("', ");
19322        self.generate_expression(&f.this)?;
19323        self.write(")");
19324        Ok(())
19325    }
19326
19327    fn generate_last_day(&mut self, f: &LastDayFunc) -> Result<()> {
19328        use crate::dialects::DialectType;
19329        use crate::expressions::DateTimeField;
19330
19331        self.write_keyword("LAST_DAY");
19332        self.write("(");
19333        self.generate_expression(&f.this)?;
19334        if let Some(unit) = &f.unit {
19335            self.write(", ");
19336            // BigQuery: strip week-start modifier from WEEK(SUNDAY), WEEK(MONDAY), etc.
19337            // WEEK(SUNDAY) -> WEEK
19338            if matches!(self.config.dialect, Some(DialectType::BigQuery)) {
19339                if let DateTimeField::WeekWithModifier(_) = unit {
19340                    self.write_keyword("WEEK");
19341                } else {
19342                    self.write_datetime_field(unit);
19343                }
19344            } else {
19345                self.write_datetime_field(unit);
19346            }
19347        }
19348        self.write(")");
19349        Ok(())
19350    }
19351
19352    fn generate_extract(&mut self, f: &ExtractFunc) -> Result<()> {
19353        // TSQL/Fabric use DATEPART(part, expr) instead of EXTRACT(part FROM expr)
19354        if matches!(
19355            self.config.dialect,
19356            Some(DialectType::TSQL) | Some(DialectType::Fabric)
19357        ) {
19358            self.write_keyword("DATEPART");
19359            self.write("(");
19360            self.write_datetime_field(&f.field);
19361            self.write(", ");
19362            self.generate_expression(&f.this)?;
19363            self.write(")");
19364            return Ok(());
19365        }
19366        self.write_keyword("EXTRACT");
19367        self.write("(");
19368        // Hive/Spark use lowercase datetime fields in EXTRACT
19369        if matches!(
19370            self.config.dialect,
19371            Some(DialectType::Hive) | Some(DialectType::Spark) | Some(DialectType::Databricks)
19372        ) {
19373            self.write_datetime_field_lower(&f.field);
19374        } else {
19375            self.write_datetime_field(&f.field);
19376        }
19377        self.write_space();
19378        self.write_keyword("FROM");
19379        self.write_space();
19380        self.generate_expression(&f.this)?;
19381        self.write(")");
19382        Ok(())
19383    }
19384
19385    fn generate_to_date(&mut self, f: &ToDateFunc) -> Result<()> {
19386        self.write_keyword("TO_DATE");
19387        self.write("(");
19388        self.generate_expression(&f.this)?;
19389        if let Some(format) = &f.format {
19390            self.write(", ");
19391            self.generate_expression(format)?;
19392        }
19393        self.write(")");
19394        Ok(())
19395    }
19396
19397    fn generate_to_timestamp(&mut self, f: &ToTimestampFunc) -> Result<()> {
19398        self.write_keyword("TO_TIMESTAMP");
19399        self.write("(");
19400        self.generate_expression(&f.this)?;
19401        if let Some(format) = &f.format {
19402            self.write(", ");
19403            self.generate_expression(format)?;
19404        }
19405        self.write(")");
19406        Ok(())
19407    }
19408
19409    // Control flow function generators
19410
19411    fn generate_if_func(&mut self, f: &IfFunc) -> Result<()> {
19412        use crate::dialects::DialectType;
19413
19414        // Generic mode: normalize IF to CASE WHEN
19415        if self.config.dialect.is_none() || self.config.dialect == Some(DialectType::Generic) {
19416            self.write_keyword("CASE WHEN");
19417            self.write_space();
19418            self.generate_expression(&f.condition)?;
19419            self.write_space();
19420            self.write_keyword("THEN");
19421            self.write_space();
19422            self.generate_expression(&f.true_value)?;
19423            if let Some(false_val) = &f.false_value {
19424                self.write_space();
19425                self.write_keyword("ELSE");
19426                self.write_space();
19427                self.generate_expression(false_val)?;
19428            }
19429            self.write_space();
19430            self.write_keyword("END");
19431            return Ok(());
19432        }
19433
19434        // Exasol uses IF condition THEN true_value ELSE false_value ENDIF syntax
19435        if self.config.dialect == Some(DialectType::Exasol) {
19436            self.write_keyword("IF");
19437            self.write_space();
19438            self.generate_expression(&f.condition)?;
19439            self.write_space();
19440            self.write_keyword("THEN");
19441            self.write_space();
19442            self.generate_expression(&f.true_value)?;
19443            if let Some(false_val) = &f.false_value {
19444                self.write_space();
19445                self.write_keyword("ELSE");
19446                self.write_space();
19447                self.generate_expression(false_val)?;
19448            }
19449            self.write_space();
19450            self.write_keyword("ENDIF");
19451            return Ok(());
19452        }
19453
19454        // Choose function name based on target dialect
19455        let func_name = match self.config.dialect {
19456            Some(DialectType::Snowflake) => "IFF",
19457            Some(DialectType::SQLite) | Some(DialectType::TSQL) => "IIF",
19458            Some(DialectType::Drill) => "`IF`",
19459            _ => "IF",
19460        };
19461        self.write(func_name);
19462        self.write("(");
19463        self.generate_expression(&f.condition)?;
19464        self.write(", ");
19465        self.generate_expression(&f.true_value)?;
19466        if let Some(false_val) = &f.false_value {
19467            self.write(", ");
19468            self.generate_expression(false_val)?;
19469        }
19470        self.write(")");
19471        Ok(())
19472    }
19473
19474    fn generate_nvl2(&mut self, f: &Nvl2Func) -> Result<()> {
19475        self.write_keyword("NVL2");
19476        self.write("(");
19477        self.generate_expression(&f.this)?;
19478        self.write(", ");
19479        self.generate_expression(&f.true_value)?;
19480        self.write(", ");
19481        self.generate_expression(&f.false_value)?;
19482        self.write(")");
19483        Ok(())
19484    }
19485
19486    // Typed aggregate function generators
19487
19488    fn generate_count(&mut self, f: &CountFunc) -> Result<()> {
19489        // Use normalize_functions for COUNT to respect ClickHouse case preservation
19490        let count_name = match self.config.normalize_functions {
19491            NormalizeFunctions::Upper => "COUNT".to_string(),
19492            NormalizeFunctions::Lower => "count".to_string(),
19493            NormalizeFunctions::None => f
19494                .original_name
19495                .clone()
19496                .unwrap_or_else(|| "COUNT".to_string()),
19497        };
19498        self.write(&count_name);
19499        self.write("(");
19500        if f.distinct {
19501            self.write_keyword("DISTINCT");
19502            self.write_space();
19503        }
19504        if f.star {
19505            self.write("*");
19506        } else if let Some(ref expr) = f.this {
19507            // For COUNT(DISTINCT a, b), unwrap the Tuple to avoid extra parentheses
19508            if let Expression::Tuple(tuple) = expr {
19509                // Check if we need to transform multi-arg COUNT DISTINCT
19510                // When dialect doesn't support multi_arg_distinct, transform:
19511                // COUNT(DISTINCT a, b) -> COUNT(DISTINCT CASE WHEN a IS NULL THEN NULL WHEN b IS NULL THEN NULL ELSE (a, b) END)
19512                let needs_transform =
19513                    f.distinct && tuple.expressions.len() > 1 && !self.config.multi_arg_distinct;
19514
19515                if needs_transform {
19516                    // Generate: CASE WHEN a IS NULL THEN NULL WHEN b IS NULL THEN NULL ELSE (a, b) END
19517                    self.write_keyword("CASE");
19518                    for e in &tuple.expressions {
19519                        self.write_space();
19520                        self.write_keyword("WHEN");
19521                        self.write_space();
19522                        self.generate_expression(e)?;
19523                        self.write_space();
19524                        self.write_keyword("IS NULL THEN NULL");
19525                    }
19526                    self.write_space();
19527                    self.write_keyword("ELSE");
19528                    self.write(" (");
19529                    for (i, e) in tuple.expressions.iter().enumerate() {
19530                        if i > 0 {
19531                            self.write(", ");
19532                        }
19533                        self.generate_expression(e)?;
19534                    }
19535                    self.write(")");
19536                    self.write_space();
19537                    self.write_keyword("END");
19538                } else {
19539                    for (i, e) in tuple.expressions.iter().enumerate() {
19540                        if i > 0 {
19541                            self.write(", ");
19542                        }
19543                        self.generate_expression(e)?;
19544                    }
19545                }
19546            } else {
19547                self.generate_expression(expr)?;
19548            }
19549        }
19550        // RESPECT NULLS / IGNORE NULLS
19551        if let Some(ignore) = f.ignore_nulls {
19552            self.write_space();
19553            if ignore {
19554                self.write_keyword("IGNORE NULLS");
19555            } else {
19556                self.write_keyword("RESPECT NULLS");
19557            }
19558        }
19559        self.write(")");
19560        if let Some(ref filter) = f.filter {
19561            self.write_space();
19562            self.write_keyword("FILTER");
19563            self.write("(");
19564            self.write_keyword("WHERE");
19565            self.write_space();
19566            self.generate_expression(filter)?;
19567            self.write(")");
19568        }
19569        Ok(())
19570    }
19571
19572    fn generate_agg_func(&mut self, name: &str, f: &AggFunc) -> Result<()> {
19573        // Apply function name normalization based on config
19574        let func_name: Cow<'_, str> = match self.config.normalize_functions {
19575            NormalizeFunctions::Upper => Cow::Owned(name.to_ascii_uppercase()),
19576            NormalizeFunctions::Lower => Cow::Owned(name.to_ascii_lowercase()),
19577            NormalizeFunctions::None => {
19578                // Use the original function name from parsing if available,
19579                // otherwise fall back to lowercase of the hardcoded constant
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        // Skip generating the expression if it's a NULL placeholder for zero-arg aggregates like MODE()
19594        if !matches!(f.this, Expression::Null(_)) {
19595            self.generate_expression(&f.this)?;
19596        }
19597        // Generate IGNORE NULLS / RESPECT NULLS inside parens if config says so (BigQuery style)
19598        // DuckDB doesn't support IGNORE NULLS / RESPECT NULLS in aggregate functions - skip it
19599        if self.config.ignore_nulls_in_func
19600            && !matches!(self.config.dialect, Some(DialectType::DuckDB))
19601        {
19602            match f.ignore_nulls {
19603                Some(true) => {
19604                    self.write_space();
19605                    self.write_keyword("IGNORE NULLS");
19606                }
19607                Some(false) => {
19608                    self.write_space();
19609                    self.write_keyword("RESPECT NULLS");
19610                }
19611                None => {}
19612            }
19613        }
19614        // Generate HAVING MAX/MIN if present (BigQuery syntax)
19615        // e.g., ANY_VALUE(fruit HAVING MAX sold)
19616        if let Some((ref expr, is_max)) = f.having_max {
19617            self.write_space();
19618            self.write_keyword("HAVING");
19619            self.write_space();
19620            if is_max {
19621                self.write_keyword("MAX");
19622            } else {
19623                self.write_keyword("MIN");
19624            }
19625            self.write_space();
19626            self.generate_expression(expr)?;
19627        }
19628        // Generate ORDER BY if present (for aggregates like ARRAY_AGG(x ORDER BY y))
19629        if !f.order_by.is_empty() {
19630            self.write_space();
19631            self.write_keyword("ORDER BY");
19632            self.write_space();
19633            for (i, ord) in f.order_by.iter().enumerate() {
19634                if i > 0 {
19635                    self.write(", ");
19636                }
19637                self.generate_ordered(ord)?;
19638            }
19639        }
19640        // Generate LIMIT if present (for aggregates like ARRAY_AGG(x ORDER BY y LIMIT 2))
19641        if let Some(ref limit) = f.limit {
19642            self.write_space();
19643            self.write_keyword("LIMIT");
19644            self.write_space();
19645            // Check if this is a Tuple representing LIMIT offset, count
19646            if let Expression::Tuple(t) = limit.as_ref() {
19647                if t.expressions.len() == 2 {
19648                    self.generate_expression(&t.expressions[0])?;
19649                    self.write(", ");
19650                    self.generate_expression(&t.expressions[1])?;
19651                } else {
19652                    self.generate_expression(limit)?;
19653                }
19654            } else {
19655                self.generate_expression(limit)?;
19656            }
19657        }
19658        self.write(")");
19659        // Generate IGNORE NULLS / RESPECT NULLS outside parens if config says so (standard style)
19660        // DuckDB doesn't support IGNORE NULLS / RESPECT NULLS in aggregate functions - skip it
19661        if !self.config.ignore_nulls_in_func
19662            && !matches!(self.config.dialect, Some(DialectType::DuckDB))
19663        {
19664            match f.ignore_nulls {
19665                Some(true) => {
19666                    self.write_space();
19667                    self.write_keyword("IGNORE NULLS");
19668                }
19669                Some(false) => {
19670                    self.write_space();
19671                    self.write_keyword("RESPECT NULLS");
19672                }
19673                None => {}
19674            }
19675        }
19676        if let Some(ref filter) = f.filter {
19677            self.write_space();
19678            self.write_keyword("FILTER");
19679            self.write("(");
19680            self.write_keyword("WHERE");
19681            self.write_space();
19682            self.generate_expression(filter)?;
19683            self.write(")");
19684        }
19685        Ok(())
19686    }
19687
19688    /// Generate FIRST/LAST aggregate functions with Hive/Spark2-style boolean argument
19689    /// for IGNORE NULLS. In Hive/Spark2, `FIRST(col) IGNORE NULLS` is written as `FIRST(col, TRUE)`.
19690    fn generate_agg_func_with_ignore_nulls_bool(&mut self, name: &str, f: &AggFunc) -> Result<()> {
19691        // For Hive/Spark2 dialects, convert IGNORE NULLS to boolean TRUE argument
19692        if matches!(self.config.dialect, Some(DialectType::Hive)) && f.ignore_nulls == Some(true) {
19693            // Create a modified copy without ignore_nulls, add TRUE as part of the output
19694            let func_name: Cow<'_, str> = match self.config.normalize_functions {
19695                NormalizeFunctions::Upper => Cow::Owned(name.to_ascii_uppercase()),
19696                NormalizeFunctions::Lower => Cow::Owned(name.to_ascii_lowercase()),
19697                NormalizeFunctions::None => {
19698                    if let Some(ref original) = f.name {
19699                        Cow::Owned(original.clone())
19700                    } else {
19701                        Cow::Owned(name.to_ascii_lowercase())
19702                    }
19703                }
19704            };
19705            self.write(func_name.as_ref());
19706            self.write("(");
19707            if f.distinct {
19708                self.write_keyword("DISTINCT");
19709                self.write_space();
19710            }
19711            if !matches!(f.this, Expression::Null(_)) {
19712                self.generate_expression(&f.this)?;
19713            }
19714            self.write(", ");
19715            self.write_keyword("TRUE");
19716            self.write(")");
19717            return Ok(());
19718        }
19719        self.generate_agg_func(name, f)
19720    }
19721
19722    fn generate_group_concat(&mut self, f: &GroupConcatFunc) -> Result<()> {
19723        self.write_keyword("GROUP_CONCAT");
19724        self.write("(");
19725        if f.distinct {
19726            self.write_keyword("DISTINCT");
19727            self.write_space();
19728        }
19729        self.generate_expression(&f.this)?;
19730        if let Some(ref order_by) = f.order_by {
19731            self.write_space();
19732            self.write_keyword("ORDER BY");
19733            self.write_space();
19734            for (i, ord) in order_by.iter().enumerate() {
19735                if i > 0 {
19736                    self.write(", ");
19737                }
19738                self.generate_ordered(ord)?;
19739            }
19740        }
19741        if let Some(ref sep) = f.separator {
19742            // SQLite uses GROUP_CONCAT(x, sep) syntax (comma-separated)
19743            // MySQL and others use GROUP_CONCAT(x SEPARATOR sep) syntax
19744            if matches!(
19745                self.config.dialect,
19746                Some(crate::dialects::DialectType::SQLite)
19747            ) {
19748                self.write(", ");
19749                self.generate_expression(sep)?;
19750            } else {
19751                self.write_space();
19752                self.write_keyword("SEPARATOR");
19753                self.write_space();
19754                self.generate_expression(sep)?;
19755            }
19756        }
19757        if let Some(ref limit) = f.limit {
19758            self.write_space();
19759            self.write_keyword("LIMIT");
19760            self.write_space();
19761            self.generate_expression(limit)?;
19762        }
19763        self.write(")");
19764        if let Some(ref filter) = f.filter {
19765            self.write_space();
19766            self.write_keyword("FILTER");
19767            self.write("(");
19768            self.write_keyword("WHERE");
19769            self.write_space();
19770            self.generate_expression(filter)?;
19771            self.write(")");
19772        }
19773        Ok(())
19774    }
19775
19776    fn generate_string_agg(&mut self, f: &StringAggFunc) -> Result<()> {
19777        let is_tsql = matches!(
19778            self.config.dialect,
19779            Some(crate::dialects::DialectType::TSQL)
19780        );
19781        self.write_keyword("STRING_AGG");
19782        self.write("(");
19783        if f.distinct {
19784            self.write_keyword("DISTINCT");
19785            self.write_space();
19786        }
19787        self.generate_expression(&f.this)?;
19788        if let Some(ref separator) = f.separator {
19789            self.write(", ");
19790            self.generate_expression(separator)?;
19791        }
19792        // For TSQL, ORDER BY goes in WITHIN GROUP clause after the closing paren
19793        if !is_tsql {
19794            if let Some(ref order_by) = f.order_by {
19795                self.write_space();
19796                self.write_keyword("ORDER BY");
19797                self.write_space();
19798                for (i, ord) in order_by.iter().enumerate() {
19799                    if i > 0 {
19800                        self.write(", ");
19801                    }
19802                    self.generate_ordered(ord)?;
19803                }
19804            }
19805        }
19806        if let Some(ref limit) = f.limit {
19807            self.write_space();
19808            self.write_keyword("LIMIT");
19809            self.write_space();
19810            self.generate_expression(limit)?;
19811        }
19812        self.write(")");
19813        // TSQL uses WITHIN GROUP (ORDER BY ...) after the function call
19814        if is_tsql {
19815            if let Some(ref order_by) = f.order_by {
19816                self.write_space();
19817                self.write_keyword("WITHIN GROUP");
19818                self.write(" (");
19819                self.write_keyword("ORDER BY");
19820                self.write_space();
19821                for (i, ord) in order_by.iter().enumerate() {
19822                    if i > 0 {
19823                        self.write(", ");
19824                    }
19825                    self.generate_ordered(ord)?;
19826                }
19827                self.write(")");
19828            }
19829        }
19830        if let Some(ref filter) = f.filter {
19831            self.write_space();
19832            self.write_keyword("FILTER");
19833            self.write("(");
19834            self.write_keyword("WHERE");
19835            self.write_space();
19836            self.generate_expression(filter)?;
19837            self.write(")");
19838        }
19839        Ok(())
19840    }
19841
19842    fn generate_listagg(&mut self, f: &ListAggFunc) -> Result<()> {
19843        use crate::dialects::DialectType;
19844        self.write_keyword("LISTAGG");
19845        self.write("(");
19846        if f.distinct {
19847            self.write_keyword("DISTINCT");
19848            self.write_space();
19849        }
19850        self.generate_expression(&f.this)?;
19851        if let Some(ref sep) = f.separator {
19852            self.write(", ");
19853            self.generate_expression(sep)?;
19854        } else if matches!(
19855            self.config.dialect,
19856            Some(DialectType::Trino) | Some(DialectType::Presto)
19857        ) {
19858            // Trino/Presto require explicit separator; default to ','
19859            self.write(", ','");
19860        }
19861        if let Some(ref overflow) = f.on_overflow {
19862            self.write_space();
19863            self.write_keyword("ON OVERFLOW");
19864            self.write_space();
19865            match overflow {
19866                ListAggOverflow::Error => self.write_keyword("ERROR"),
19867                ListAggOverflow::Truncate { filler, with_count } => {
19868                    self.write_keyword("TRUNCATE");
19869                    if let Some(ref fill) = filler {
19870                        self.write_space();
19871                        self.generate_expression(fill)?;
19872                    }
19873                    if *with_count {
19874                        self.write_space();
19875                        self.write_keyword("WITH COUNT");
19876                    } else {
19877                        self.write_space();
19878                        self.write_keyword("WITHOUT COUNT");
19879                    }
19880                }
19881            }
19882        }
19883        self.write(")");
19884        if let Some(ref order_by) = f.order_by {
19885            self.write_space();
19886            self.write_keyword("WITHIN GROUP");
19887            self.write(" (");
19888            self.write_keyword("ORDER BY");
19889            self.write_space();
19890            for (i, ord) in order_by.iter().enumerate() {
19891                if i > 0 {
19892                    self.write(", ");
19893                }
19894                self.generate_ordered(ord)?;
19895            }
19896            self.write(")");
19897        }
19898        if let Some(ref filter) = f.filter {
19899            self.write_space();
19900            self.write_keyword("FILTER");
19901            self.write("(");
19902            self.write_keyword("WHERE");
19903            self.write_space();
19904            self.generate_expression(filter)?;
19905            self.write(")");
19906        }
19907        Ok(())
19908    }
19909
19910    fn generate_sum_if(&mut self, f: &SumIfFunc) -> Result<()> {
19911        self.write_keyword("SUM_IF");
19912        self.write("(");
19913        self.generate_expression(&f.this)?;
19914        self.write(", ");
19915        self.generate_expression(&f.condition)?;
19916        self.write(")");
19917        if let Some(ref filter) = f.filter {
19918            self.write_space();
19919            self.write_keyword("FILTER");
19920            self.write("(");
19921            self.write_keyword("WHERE");
19922            self.write_space();
19923            self.generate_expression(filter)?;
19924            self.write(")");
19925        }
19926        Ok(())
19927    }
19928
19929    fn generate_approx_percentile(&mut self, f: &ApproxPercentileFunc) -> Result<()> {
19930        self.write_keyword("APPROX_PERCENTILE");
19931        self.write("(");
19932        self.generate_expression(&f.this)?;
19933        self.write(", ");
19934        self.generate_expression(&f.percentile)?;
19935        if let Some(ref acc) = f.accuracy {
19936            self.write(", ");
19937            self.generate_expression(acc)?;
19938        }
19939        self.write(")");
19940        if let Some(ref filter) = f.filter {
19941            self.write_space();
19942            self.write_keyword("FILTER");
19943            self.write("(");
19944            self.write_keyword("WHERE");
19945            self.write_space();
19946            self.generate_expression(filter)?;
19947            self.write(")");
19948        }
19949        Ok(())
19950    }
19951
19952    fn generate_percentile(&mut self, name: &str, f: &PercentileFunc) -> Result<()> {
19953        self.write_keyword(name);
19954        self.write("(");
19955        self.generate_expression(&f.percentile)?;
19956        self.write(")");
19957        if let Some(ref order_by) = f.order_by {
19958            self.write_space();
19959            self.write_keyword("WITHIN GROUP");
19960            self.write(" (");
19961            self.write_keyword("ORDER BY");
19962            self.write_space();
19963            self.generate_expression(&f.this)?;
19964            for ord in order_by.iter() {
19965                if ord.desc {
19966                    self.write_space();
19967                    self.write_keyword("DESC");
19968                }
19969            }
19970            self.write(")");
19971        }
19972        if let Some(ref filter) = f.filter {
19973            self.write_space();
19974            self.write_keyword("FILTER");
19975            self.write("(");
19976            self.write_keyword("WHERE");
19977            self.write_space();
19978            self.generate_expression(filter)?;
19979            self.write(")");
19980        }
19981        Ok(())
19982    }
19983
19984    // Window function generators
19985
19986    fn generate_ntile(&mut self, f: &NTileFunc) -> Result<()> {
19987        self.write_keyword("NTILE");
19988        self.write("(");
19989        if let Some(num_buckets) = &f.num_buckets {
19990            self.generate_expression(num_buckets)?;
19991        }
19992        if let Some(order_by) = &f.order_by {
19993            self.write_keyword(" ORDER BY ");
19994            for (i, ob) in order_by.iter().enumerate() {
19995                if i > 0 {
19996                    self.write(", ");
19997                }
19998                self.generate_ordered(ob)?;
19999            }
20000        }
20001        self.write(")");
20002        Ok(())
20003    }
20004
20005    fn generate_lead_lag(&mut self, name: &str, f: &LeadLagFunc) -> Result<()> {
20006        self.write_keyword(name);
20007        self.write("(");
20008        self.generate_expression(&f.this)?;
20009        if let Some(ref offset) = f.offset {
20010            self.write(", ");
20011            self.generate_expression(offset)?;
20012            if let Some(ref default) = f.default {
20013                self.write(", ");
20014                self.generate_expression(default)?;
20015            }
20016        }
20017        // IGNORE NULLS / RESPECT NULLS inside parens for dialects like BigQuery
20018        if self.config.ignore_nulls_in_func {
20019            match f.ignore_nulls {
20020                Some(true) => {
20021                    self.write_space();
20022                    self.write_keyword("IGNORE NULLS");
20023                }
20024                Some(false) => {
20025                    self.write_space();
20026                    self.write_keyword("RESPECT NULLS");
20027                }
20028                None => {}
20029            }
20030        }
20031        self.write(")");
20032        // IGNORE NULLS / RESPECT NULLS outside parens for other dialects
20033        if !self.config.ignore_nulls_in_func {
20034            match f.ignore_nulls {
20035                Some(true) => {
20036                    self.write_space();
20037                    self.write_keyword("IGNORE NULLS");
20038                }
20039                Some(false) => {
20040                    self.write_space();
20041                    self.write_keyword("RESPECT NULLS");
20042                }
20043                None => {}
20044            }
20045        }
20046        Ok(())
20047    }
20048
20049    fn generate_value_func(&mut self, name: &str, f: &ValueFunc) -> Result<()> {
20050        self.write_keyword(name);
20051        self.write("(");
20052        self.generate_expression(&f.this)?;
20053        // ORDER BY inside parens (e.g., DuckDB: LAST_VALUE(x ORDER BY x))
20054        if !f.order_by.is_empty() {
20055            self.write_space();
20056            self.write_keyword("ORDER BY");
20057            self.write_space();
20058            for (i, ordered) in f.order_by.iter().enumerate() {
20059                if i > 0 {
20060                    self.write(", ");
20061                }
20062                self.generate_ordered(ordered)?;
20063            }
20064        }
20065        // IGNORE NULLS / RESPECT NULLS inside parens for dialects like BigQuery, DuckDB
20066        if self.config.ignore_nulls_in_func {
20067            match f.ignore_nulls {
20068                Some(true) => {
20069                    self.write_space();
20070                    self.write_keyword("IGNORE NULLS");
20071                }
20072                Some(false) => {
20073                    self.write_space();
20074                    self.write_keyword("RESPECT NULLS");
20075                }
20076                None => {}
20077            }
20078        }
20079        self.write(")");
20080        // IGNORE NULLS / RESPECT NULLS outside parens for other dialects
20081        if !self.config.ignore_nulls_in_func {
20082            match f.ignore_nulls {
20083                Some(true) => {
20084                    self.write_space();
20085                    self.write_keyword("IGNORE NULLS");
20086                }
20087                Some(false) => {
20088                    self.write_space();
20089                    self.write_keyword("RESPECT NULLS");
20090                }
20091                None => {}
20092            }
20093        }
20094        Ok(())
20095    }
20096
20097    /// Generate FIRST_VALUE/LAST_VALUE with Hive/Spark2-style boolean argument for IGNORE NULLS.
20098    /// In Hive/Spark2, `FIRST_VALUE(col) IGNORE NULLS` is written as `FIRST_VALUE(col, TRUE)`.
20099    fn generate_value_func_with_ignore_nulls_bool(
20100        &mut self,
20101        name: &str,
20102        f: &ValueFunc,
20103    ) -> Result<()> {
20104        if matches!(self.config.dialect, Some(DialectType::Hive)) && f.ignore_nulls == Some(true) {
20105            self.write_keyword(name);
20106            self.write("(");
20107            self.generate_expression(&f.this)?;
20108            self.write(", ");
20109            self.write_keyword("TRUE");
20110            self.write(")");
20111            return Ok(());
20112        }
20113        self.generate_value_func(name, f)
20114    }
20115
20116    fn generate_nth_value(&mut self, f: &NthValueFunc) -> Result<()> {
20117        self.write_keyword("NTH_VALUE");
20118        self.write("(");
20119        self.generate_expression(&f.this)?;
20120        self.write(", ");
20121        self.generate_expression(&f.offset)?;
20122        // IGNORE NULLS / RESPECT NULLS inside parens for dialects like BigQuery, DuckDB
20123        if self.config.ignore_nulls_in_func {
20124            match f.ignore_nulls {
20125                Some(true) => {
20126                    self.write_space();
20127                    self.write_keyword("IGNORE NULLS");
20128                }
20129                Some(false) => {
20130                    self.write_space();
20131                    self.write_keyword("RESPECT NULLS");
20132                }
20133                None => {}
20134            }
20135        }
20136        self.write(")");
20137        // FROM FIRST / FROM LAST (Snowflake-specific, before IGNORE/RESPECT NULLS)
20138        if matches!(
20139            self.config.dialect,
20140            Some(crate::dialects::DialectType::Snowflake)
20141        ) {
20142            match f.from_first {
20143                Some(true) => {
20144                    self.write_space();
20145                    self.write_keyword("FROM FIRST");
20146                }
20147                Some(false) => {
20148                    self.write_space();
20149                    self.write_keyword("FROM LAST");
20150                }
20151                None => {}
20152            }
20153        }
20154        // IGNORE NULLS / RESPECT NULLS outside parens for other dialects
20155        if !self.config.ignore_nulls_in_func {
20156            match f.ignore_nulls {
20157                Some(true) => {
20158                    self.write_space();
20159                    self.write_keyword("IGNORE NULLS");
20160                }
20161                Some(false) => {
20162                    self.write_space();
20163                    self.write_keyword("RESPECT NULLS");
20164                }
20165                None => {}
20166            }
20167        }
20168        Ok(())
20169    }
20170
20171    // Additional string function generators
20172
20173    fn generate_position(&mut self, f: &PositionFunc) -> Result<()> {
20174        // Standard syntax: POSITION(substr IN str)
20175        // ClickHouse prefers comma syntax with reversed arg order: POSITION(str, substr[, start])
20176        if matches!(
20177            self.config.dialect,
20178            Some(crate::dialects::DialectType::ClickHouse)
20179        ) {
20180            self.write_keyword("POSITION");
20181            self.write("(");
20182            self.generate_expression(&f.string)?;
20183            self.write(", ");
20184            self.generate_expression(&f.substring)?;
20185            if let Some(ref start) = f.start {
20186                self.write(", ");
20187                self.generate_expression(start)?;
20188            }
20189            self.write(")");
20190            return Ok(());
20191        }
20192
20193        self.write_keyword("POSITION");
20194        self.write("(");
20195        self.generate_expression(&f.substring)?;
20196        self.write_space();
20197        self.write_keyword("IN");
20198        self.write_space();
20199        self.generate_expression(&f.string)?;
20200        if let Some(ref start) = f.start {
20201            self.write(", ");
20202            self.generate_expression(start)?;
20203        }
20204        self.write(")");
20205        Ok(())
20206    }
20207
20208    // Additional math function generators
20209
20210    fn generate_rand(&mut self, f: &Rand) -> Result<()> {
20211        // Teradata RANDOM(lower, upper)
20212        if f.lower.is_some() || f.upper.is_some() {
20213            self.write_keyword("RANDOM");
20214            self.write("(");
20215            if let Some(ref lower) = f.lower {
20216                self.generate_expression(lower)?;
20217            }
20218            if let Some(ref upper) = f.upper {
20219                self.write(", ");
20220                self.generate_expression(upper)?;
20221            }
20222            self.write(")");
20223            return Ok(());
20224        }
20225        // Snowflake uses RANDOM instead of RAND, DuckDB uses RANDOM without seed
20226        let func_name = match self.config.dialect {
20227            Some(crate::dialects::DialectType::Snowflake)
20228            | Some(crate::dialects::DialectType::DuckDB) => "RANDOM",
20229            _ => "RAND",
20230        };
20231        self.write_keyword(func_name);
20232        self.write("(");
20233        // DuckDB doesn't support seeded RANDOM, so skip the seed
20234        if !matches!(
20235            self.config.dialect,
20236            Some(crate::dialects::DialectType::DuckDB)
20237        ) {
20238            if let Some(ref seed) = f.seed {
20239                self.generate_expression(seed)?;
20240            }
20241        }
20242        self.write(")");
20243        Ok(())
20244    }
20245
20246    fn generate_truncate_func(&mut self, f: &TruncateFunc) -> Result<()> {
20247        self.write_keyword("TRUNCATE");
20248        self.write("(");
20249        self.generate_expression(&f.this)?;
20250        if let Some(ref decimals) = f.decimals {
20251            self.write(", ");
20252            self.generate_expression(decimals)?;
20253        }
20254        self.write(")");
20255        Ok(())
20256    }
20257
20258    // Control flow generators
20259
20260    fn generate_decode(&mut self, f: &DecodeFunc) -> Result<()> {
20261        self.write_keyword("DECODE");
20262        self.write("(");
20263        self.generate_expression(&f.this)?;
20264        for (search, result) in &f.search_results {
20265            self.write(", ");
20266            self.generate_expression(search)?;
20267            self.write(", ");
20268            self.generate_expression(result)?;
20269        }
20270        if let Some(ref default) = f.default {
20271            self.write(", ");
20272            self.generate_expression(default)?;
20273        }
20274        self.write(")");
20275        Ok(())
20276    }
20277
20278    // Date/time function generators
20279
20280    fn generate_date_format(&mut self, name: &str, f: &DateFormatFunc) -> Result<()> {
20281        self.write_keyword(name);
20282        self.write("(");
20283        self.generate_expression(&f.this)?;
20284        self.write(", ");
20285        self.generate_expression(&f.format)?;
20286        self.write(")");
20287        Ok(())
20288    }
20289
20290    fn generate_from_unixtime(&mut self, f: &FromUnixtimeFunc) -> Result<()> {
20291        self.write_keyword("FROM_UNIXTIME");
20292        self.write("(");
20293        self.generate_expression(&f.this)?;
20294        if let Some(ref format) = f.format {
20295            self.write(", ");
20296            self.generate_expression(format)?;
20297        }
20298        self.write(")");
20299        Ok(())
20300    }
20301
20302    fn generate_unix_timestamp(&mut self, f: &UnixTimestampFunc) -> Result<()> {
20303        self.write_keyword("UNIX_TIMESTAMP");
20304        self.write("(");
20305        if let Some(ref expr) = f.this {
20306            self.generate_expression(expr)?;
20307            if let Some(ref format) = f.format {
20308                self.write(", ");
20309                self.generate_expression(format)?;
20310            }
20311        } else if matches!(
20312            self.config.dialect,
20313            Some(DialectType::Spark) | Some(DialectType::Hive) | Some(DialectType::Databricks)
20314        ) {
20315            // Spark/Hive: UNIX_TIMESTAMP() -> UNIX_TIMESTAMP(CURRENT_TIMESTAMP())
20316            self.write_keyword("CURRENT_TIMESTAMP");
20317            self.write("()");
20318        }
20319        self.write(")");
20320        Ok(())
20321    }
20322
20323    fn generate_make_date(&mut self, f: &MakeDateFunc) -> Result<()> {
20324        self.write_keyword("MAKE_DATE");
20325        self.write("(");
20326        self.generate_expression(&f.year)?;
20327        self.write(", ");
20328        self.generate_expression(&f.month)?;
20329        self.write(", ");
20330        self.generate_expression(&f.day)?;
20331        self.write(")");
20332        Ok(())
20333    }
20334
20335    fn generate_make_timestamp(&mut self, f: &MakeTimestampFunc) -> Result<()> {
20336        self.write_keyword("MAKE_TIMESTAMP");
20337        self.write("(");
20338        self.generate_expression(&f.year)?;
20339        self.write(", ");
20340        self.generate_expression(&f.month)?;
20341        self.write(", ");
20342        self.generate_expression(&f.day)?;
20343        self.write(", ");
20344        self.generate_expression(&f.hour)?;
20345        self.write(", ");
20346        self.generate_expression(&f.minute)?;
20347        self.write(", ");
20348        self.generate_expression(&f.second)?;
20349        if let Some(ref tz) = f.timezone {
20350            self.write(", ");
20351            self.generate_expression(tz)?;
20352        }
20353        self.write(")");
20354        Ok(())
20355    }
20356
20357    /// Extract field names from a struct expression (either Struct or Function named STRUCT with Alias args)
20358    fn extract_struct_field_names(expr: &Expression) -> Option<Vec<String>> {
20359        match expr {
20360            Expression::Struct(s) => {
20361                if s.fields.iter().all(|(name, _)| name.is_some()) {
20362                    Some(
20363                        s.fields
20364                            .iter()
20365                            .map(|(name, _)| name.as_deref().unwrap_or("").to_string())
20366                            .collect(),
20367                    )
20368                } else {
20369                    None
20370                }
20371            }
20372            Expression::Function(f) if f.name.eq_ignore_ascii_case("STRUCT") => {
20373                // Check if all args are Alias (named fields)
20374                if f.args.iter().all(|a| matches!(a, Expression::Alias(_))) {
20375                    Some(
20376                        f.args
20377                            .iter()
20378                            .filter_map(|a| {
20379                                if let Expression::Alias(alias) = a {
20380                                    Some(alias.alias.name.clone())
20381                                } else {
20382                                    None
20383                                }
20384                            })
20385                            .collect(),
20386                    )
20387                } else {
20388                    None
20389                }
20390            }
20391            _ => None,
20392        }
20393    }
20394
20395    /// Check if a struct expression has any unnamed fields
20396    fn struct_has_unnamed_fields(expr: &Expression) -> bool {
20397        match expr {
20398            Expression::Struct(s) => s.fields.iter().any(|(name, _)| name.is_none()),
20399            Expression::Function(f) if f.name.eq_ignore_ascii_case("STRUCT") => {
20400                f.args.iter().any(|a| !matches!(a, Expression::Alias(_)))
20401            }
20402            _ => false,
20403        }
20404    }
20405
20406    /// Get the field count of a struct expression
20407    fn struct_field_count(expr: &Expression) -> usize {
20408        match expr {
20409            Expression::Struct(s) => s.fields.len(),
20410            Expression::Function(f) if f.name.eq_ignore_ascii_case("STRUCT") => f.args.len(),
20411            _ => 0,
20412        }
20413    }
20414
20415    /// Apply field names to an unnamed struct expression, producing a new expression with names
20416    fn apply_struct_field_names(expr: &Expression, field_names: &[String]) -> Expression {
20417        match expr {
20418            Expression::Struct(s) => {
20419                let mut new_fields = Vec::with_capacity(s.fields.len());
20420                for (i, (name, value)) in s.fields.iter().enumerate() {
20421                    if name.is_none() && i < field_names.len() {
20422                        new_fields.push((Some(field_names[i].clone()), value.clone()));
20423                    } else {
20424                        new_fields.push((name.clone(), value.clone()));
20425                    }
20426                }
20427                Expression::Struct(Box::new(crate::expressions::Struct { fields: new_fields }))
20428            }
20429            Expression::Function(f) if f.name.eq_ignore_ascii_case("STRUCT") => {
20430                let mut new_args = Vec::with_capacity(f.args.len());
20431                for (i, arg) in f.args.iter().enumerate() {
20432                    if !matches!(arg, Expression::Alias(_)) && i < field_names.len() {
20433                        // Wrap the value in an Alias with the inherited name
20434                        new_args.push(Expression::Alias(Box::new(crate::expressions::Alias {
20435                            this: arg.clone(),
20436                            alias: crate::expressions::Identifier::new(field_names[i].clone()),
20437                            column_aliases: Vec::new(),
20438                            pre_alias_comments: Vec::new(),
20439                            trailing_comments: Vec::new(),
20440                            inferred_type: None,
20441                        })));
20442                    } else {
20443                        new_args.push(arg.clone());
20444                    }
20445                }
20446                Expression::Function(Box::new(crate::expressions::Function {
20447                    name: f.name.clone(),
20448                    args: new_args,
20449                    distinct: f.distinct,
20450                    trailing_comments: f.trailing_comments.clone(),
20451                    use_bracket_syntax: f.use_bracket_syntax,
20452                    no_parens: f.no_parens,
20453                    quoted: f.quoted,
20454                    span: None,
20455                    inferred_type: None,
20456                }))
20457            }
20458            _ => expr.clone(),
20459        }
20460    }
20461
20462    /// Propagate struct field names from the first struct in an array to subsequent unnamed structs.
20463    /// This implements BigQuery's implicit field name inheritance for struct arrays.
20464    /// Handles both Expression::Struct and Expression::Function named "STRUCT".
20465    fn inherit_struct_field_names(expressions: &[Expression]) -> Vec<Expression> {
20466        let first = match expressions.first() {
20467            Some(e) => e,
20468            None => return expressions.to_vec(),
20469        };
20470
20471        let field_names = match Self::extract_struct_field_names(first) {
20472            Some(names) if !names.is_empty() => names,
20473            _ => return expressions.to_vec(),
20474        };
20475
20476        let mut result = Vec::with_capacity(expressions.len());
20477        for (idx, expr) in expressions.iter().enumerate() {
20478            if idx == 0 {
20479                result.push(expr.clone());
20480                continue;
20481            }
20482            // Check if this is a struct with unnamed fields that needs name propagation
20483            if Self::struct_field_count(expr) == field_names.len()
20484                && Self::struct_has_unnamed_fields(expr)
20485            {
20486                result.push(Self::apply_struct_field_names(expr, &field_names));
20487            } else {
20488                result.push(expr.clone());
20489            }
20490        }
20491        result
20492    }
20493
20494    // Array function generators
20495
20496    fn generate_array_constructor(&mut self, f: &ArrayConstructor) -> Result<()> {
20497        // Apply struct name inheritance for target dialects that need it
20498        // (DuckDB, Spark, Databricks, Hive, Snowflake, Presto, Trino)
20499        let needs_inheritance = matches!(
20500            self.config.dialect,
20501            Some(DialectType::DuckDB)
20502                | Some(DialectType::Spark)
20503                | Some(DialectType::Databricks)
20504                | Some(DialectType::Hive)
20505                | Some(DialectType::Snowflake)
20506                | Some(DialectType::Presto)
20507                | Some(DialectType::Trino)
20508        );
20509        let propagated: Vec<Expression>;
20510        let expressions = if needs_inheritance && f.expressions.len() > 1 {
20511            propagated = Self::inherit_struct_field_names(&f.expressions);
20512            &propagated
20513        } else {
20514            &f.expressions
20515        };
20516
20517        // Check if elements should be split onto multiple lines (pretty + too wide)
20518        let should_split = if self.config.pretty && !expressions.is_empty() {
20519            let mut expr_strings: Vec<String> = Vec::with_capacity(expressions.len());
20520            for expr in expressions {
20521                let mut temp_gen = Generator::with_arc_config(self.config.clone());
20522                Arc::make_mut(&mut temp_gen.config).pretty = false;
20523                temp_gen.generate_expression(expr)?;
20524                expr_strings.push(temp_gen.output);
20525            }
20526            self.too_wide(&expr_strings)
20527        } else {
20528            false
20529        };
20530
20531        if f.bracket_notation {
20532            // For Spark/Databricks, use ARRAY(...) with parens
20533            // For Presto/Trino/PostgreSQL, use ARRAY[...] with keyword prefix
20534            // For others (DuckDB, Snowflake), use bare [...]
20535            let (open, close) = match self.config.dialect {
20536                None
20537                | Some(DialectType::Generic)
20538                | Some(DialectType::Spark)
20539                | Some(DialectType::Databricks)
20540                | Some(DialectType::Hive) => {
20541                    self.write_keyword("ARRAY");
20542                    ("(", ")")
20543                }
20544                Some(DialectType::Presto)
20545                | Some(DialectType::Trino)
20546                | Some(DialectType::PostgreSQL)
20547                | Some(DialectType::Redshift)
20548                | Some(DialectType::Materialize)
20549                | Some(DialectType::RisingWave)
20550                | Some(DialectType::CockroachDB) => {
20551                    self.write_keyword("ARRAY");
20552                    ("[", "]")
20553                }
20554                _ => ("[", "]"),
20555            };
20556            self.write(open);
20557            if should_split {
20558                self.write_newline();
20559                self.indent_level += 1;
20560                for (i, expr) in expressions.iter().enumerate() {
20561                    self.write_indent();
20562                    self.generate_expression(expr)?;
20563                    if i + 1 < expressions.len() {
20564                        self.write(",");
20565                    }
20566                    self.write_newline();
20567                }
20568                self.indent_level -= 1;
20569                self.write_indent();
20570            } else {
20571                for (i, expr) in expressions.iter().enumerate() {
20572                    if i > 0 {
20573                        self.write(", ");
20574                    }
20575                    self.generate_expression(expr)?;
20576                }
20577            }
20578            self.write(close);
20579        } else {
20580            // Use LIST keyword if that was the original syntax (DuckDB)
20581            if f.use_list_keyword {
20582                self.write_keyword("LIST");
20583            } else {
20584                self.write_keyword("ARRAY");
20585            }
20586            // For Spark/Hive, always use ARRAY(...) with parens
20587            // Also use parens for BigQuery when the array contains a subquery (ARRAY(SELECT ...))
20588            let has_subquery = expressions
20589                .iter()
20590                .any(|e| matches!(e, Expression::Select(_)));
20591            let (open, close) = if matches!(
20592                self.config.dialect,
20593                Some(DialectType::Spark) | Some(DialectType::Databricks) | Some(DialectType::Hive)
20594            ) || (matches!(self.config.dialect, Some(DialectType::BigQuery))
20595                && has_subquery)
20596            {
20597                ("(", ")")
20598            } else {
20599                ("[", "]")
20600            };
20601            self.write(open);
20602            if should_split {
20603                self.write_newline();
20604                self.indent_level += 1;
20605                for (i, expr) in expressions.iter().enumerate() {
20606                    self.write_indent();
20607                    self.generate_expression(expr)?;
20608                    if i + 1 < expressions.len() {
20609                        self.write(",");
20610                    }
20611                    self.write_newline();
20612                }
20613                self.indent_level -= 1;
20614                self.write_indent();
20615            } else {
20616                for (i, expr) in expressions.iter().enumerate() {
20617                    if i > 0 {
20618                        self.write(", ");
20619                    }
20620                    self.generate_expression(expr)?;
20621                }
20622            }
20623            self.write(close);
20624        }
20625        Ok(())
20626    }
20627
20628    fn generate_array_sort(&mut self, f: &ArraySortFunc) -> Result<()> {
20629        self.write_keyword("ARRAY_SORT");
20630        self.write("(");
20631        self.generate_expression(&f.this)?;
20632        if let Some(ref comp) = f.comparator {
20633            self.write(", ");
20634            self.generate_expression(comp)?;
20635        }
20636        self.write(")");
20637        Ok(())
20638    }
20639
20640    fn generate_array_join(&mut self, name: &str, f: &ArrayJoinFunc) -> Result<()> {
20641        self.write_keyword(name);
20642        self.write("(");
20643        self.generate_expression(&f.this)?;
20644        self.write(", ");
20645        self.generate_expression(&f.separator)?;
20646        if let Some(ref null_rep) = f.null_replacement {
20647            self.write(", ");
20648            self.generate_expression(null_rep)?;
20649        }
20650        self.write(")");
20651        Ok(())
20652    }
20653
20654    fn generate_unnest(&mut self, f: &UnnestFunc) -> Result<()> {
20655        self.write_keyword("UNNEST");
20656        self.write("(");
20657        self.generate_expression(&f.this)?;
20658        for extra in &f.expressions {
20659            self.write(", ");
20660            self.generate_expression(extra)?;
20661        }
20662        self.write(")");
20663        if f.with_ordinality {
20664            self.write_space();
20665            if self.config.unnest_with_ordinality {
20666                // Presto/Trino: UNNEST(arr) WITH ORDINALITY [AS alias]
20667                self.write_keyword("WITH ORDINALITY");
20668            } else if f.offset_alias.is_some() {
20669                // BigQuery: UNNEST(arr) [AS col] WITH OFFSET AS pos
20670                // Alias (if any) comes BEFORE WITH OFFSET
20671                if let Some(ref alias) = f.alias {
20672                    self.write_keyword("AS");
20673                    self.write_space();
20674                    self.generate_identifier(alias)?;
20675                    self.write_space();
20676                }
20677                self.write_keyword("WITH OFFSET");
20678                if let Some(ref offset_alias) = f.offset_alias {
20679                    self.write_space();
20680                    self.write_keyword("AS");
20681                    self.write_space();
20682                    self.generate_identifier(offset_alias)?;
20683                }
20684            } else {
20685                // WITH OFFSET (BigQuery identity) - add default "AS offset" if no explicit alias
20686                self.write_keyword("WITH OFFSET");
20687                if f.alias.is_none() {
20688                    self.write(" AS offset");
20689                }
20690            }
20691        }
20692        if let Some(ref alias) = f.alias {
20693            // Add alias for: non-WITH-OFFSET cases, Presto/Trino WITH ORDINALITY, or BigQuery WITH OFFSET + alias (no offset_alias)
20694            let should_add_alias = if !f.with_ordinality {
20695                true
20696            } else if self.config.unnest_with_ordinality {
20697                // Presto/Trino: alias comes after WITH ORDINALITY
20698                true
20699            } else if f.offset_alias.is_some() {
20700                // BigQuery expansion: alias already handled above
20701                false
20702            } else {
20703                // BigQuery WITH OFFSET + alias but no offset_alias: alias comes after
20704                true
20705            };
20706            if should_add_alias {
20707                self.write_space();
20708                self.write_keyword("AS");
20709                self.write_space();
20710                self.generate_identifier(alias)?;
20711            }
20712        }
20713        Ok(())
20714    }
20715
20716    fn generate_array_filter(&mut self, f: &ArrayFilterFunc) -> Result<()> {
20717        self.write_keyword("FILTER");
20718        self.write("(");
20719        self.generate_expression(&f.this)?;
20720        self.write(", ");
20721        self.generate_expression(&f.filter)?;
20722        self.write(")");
20723        Ok(())
20724    }
20725
20726    fn generate_array_transform(&mut self, f: &ArrayTransformFunc) -> Result<()> {
20727        self.write_keyword("TRANSFORM");
20728        self.write("(");
20729        self.generate_expression(&f.this)?;
20730        self.write(", ");
20731        self.generate_expression(&f.transform)?;
20732        self.write(")");
20733        Ok(())
20734    }
20735
20736    fn generate_sequence(&mut self, name: &str, f: &SequenceFunc) -> Result<()> {
20737        self.write_keyword(name);
20738        self.write("(");
20739        self.generate_expression(&f.start)?;
20740        self.write(", ");
20741        self.generate_expression(&f.stop)?;
20742        if let Some(ref step) = f.step {
20743            self.write(", ");
20744            self.generate_expression(step)?;
20745        }
20746        self.write(")");
20747        Ok(())
20748    }
20749
20750    // Struct function generators
20751
20752    fn generate_struct_constructor(&mut self, f: &StructConstructor) -> Result<()> {
20753        self.write_keyword("STRUCT");
20754        self.write("(");
20755        for (i, (name, expr)) in f.fields.iter().enumerate() {
20756            if i > 0 {
20757                self.write(", ");
20758            }
20759            if let Some(ref id) = name {
20760                self.generate_identifier(id)?;
20761                self.write(" ");
20762                self.write_keyword("AS");
20763                self.write(" ");
20764            }
20765            self.generate_expression(expr)?;
20766        }
20767        self.write(")");
20768        Ok(())
20769    }
20770
20771    /// Convert BigQuery STRUCT function (parsed as Function with Alias args) to target dialect
20772    fn generate_struct_function_cross_dialect(&mut self, func: &Function) -> Result<()> {
20773        // Extract named/unnamed fields from function args
20774        // Args are either Alias(this=value, alias=name) for named or plain expressions for unnamed
20775        let mut names: Vec<Option<String>> = Vec::new();
20776        let mut values: Vec<&Expression> = Vec::new();
20777        let mut all_named = true;
20778
20779        for arg in &func.args {
20780            match arg {
20781                Expression::Alias(a) => {
20782                    names.push(Some(a.alias.name.clone()));
20783                    values.push(&a.this);
20784                }
20785                _ => {
20786                    names.push(None);
20787                    values.push(arg);
20788                    all_named = false;
20789                }
20790            }
20791        }
20792
20793        if matches!(self.config.dialect, Some(DialectType::DuckDB)) {
20794            // DuckDB: {'name': value, ...} for named, {'_0': value, ...} for unnamed
20795            self.write("{");
20796            for (i, (name, value)) in names.iter().zip(values.iter()).enumerate() {
20797                if i > 0 {
20798                    self.write(", ");
20799                }
20800                if let Some(n) = name {
20801                    self.write("'");
20802                    self.write(n);
20803                    self.write("'");
20804                } else {
20805                    self.write("'_");
20806                    self.write(&i.to_string());
20807                    self.write("'");
20808                }
20809                self.write(": ");
20810                self.generate_expression(value)?;
20811            }
20812            self.write("}");
20813            return Ok(());
20814        }
20815
20816        if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
20817            // Snowflake: OBJECT_CONSTRUCT('name', value, ...)
20818            self.write_keyword("OBJECT_CONSTRUCT");
20819            self.write("(");
20820            for (i, (name, value)) in names.iter().zip(values.iter()).enumerate() {
20821                if i > 0 {
20822                    self.write(", ");
20823                }
20824                if let Some(n) = name {
20825                    self.write("'");
20826                    self.write(n);
20827                    self.write("'");
20828                } else {
20829                    self.write("'_");
20830                    self.write(&i.to_string());
20831                    self.write("'");
20832                }
20833                self.write(", ");
20834                self.generate_expression(value)?;
20835            }
20836            self.write(")");
20837            return Ok(());
20838        }
20839
20840        if matches!(
20841            self.config.dialect,
20842            Some(DialectType::Presto) | Some(DialectType::Trino)
20843        ) {
20844            if all_named && !names.is_empty() {
20845                // Presto/Trino: CAST(ROW(values...) AS ROW(name TYPE, ...))
20846                // Need to infer types from values
20847                self.write_keyword("CAST");
20848                self.write("(");
20849                self.write_keyword("ROW");
20850                self.write("(");
20851                for (i, value) in values.iter().enumerate() {
20852                    if i > 0 {
20853                        self.write(", ");
20854                    }
20855                    self.generate_expression(value)?;
20856                }
20857                self.write(")");
20858                self.write(" ");
20859                self.write_keyword("AS");
20860                self.write(" ");
20861                self.write_keyword("ROW");
20862                self.write("(");
20863                for (i, (name, value)) in names.iter().zip(values.iter()).enumerate() {
20864                    if i > 0 {
20865                        self.write(", ");
20866                    }
20867                    if let Some(n) = name {
20868                        self.write(n);
20869                    }
20870                    self.write(" ");
20871                    let type_str = Self::infer_sql_type_for_presto(value);
20872                    self.write_keyword(&type_str);
20873                }
20874                self.write(")");
20875                self.write(")");
20876            } else {
20877                // Unnamed: ROW(values...)
20878                self.write_keyword("ROW");
20879                self.write("(");
20880                for (i, value) in values.iter().enumerate() {
20881                    if i > 0 {
20882                        self.write(", ");
20883                    }
20884                    self.generate_expression(value)?;
20885                }
20886                self.write(")");
20887            }
20888            return Ok(());
20889        }
20890
20891        // Default: ROW(values...) for other dialects
20892        self.write_keyword("ROW");
20893        self.write("(");
20894        for (i, value) in values.iter().enumerate() {
20895            if i > 0 {
20896                self.write(", ");
20897            }
20898            self.generate_expression(value)?;
20899        }
20900        self.write(")");
20901        Ok(())
20902    }
20903
20904    /// Infer SQL type name for a Presto/Trino ROW CAST from a literal expression
20905    fn infer_sql_type_for_presto(expr: &Expression) -> String {
20906        match expr {
20907            Expression::Literal(lit)
20908                if matches!(lit.as_ref(), crate::expressions::Literal::String(_)) =>
20909            {
20910                "VARCHAR".to_string()
20911            }
20912            Expression::Literal(lit)
20913                if matches!(lit.as_ref(), crate::expressions::Literal::Number(_)) =>
20914            {
20915                let crate::expressions::Literal::Number(n) = lit.as_ref() else {
20916                    unreachable!()
20917                };
20918                if n.contains('.') {
20919                    "DOUBLE".to_string()
20920                } else {
20921                    "INTEGER".to_string()
20922                }
20923            }
20924            Expression::Boolean(_) => "BOOLEAN".to_string(),
20925            Expression::Literal(lit)
20926                if matches!(lit.as_ref(), crate::expressions::Literal::Date(_)) =>
20927            {
20928                "DATE".to_string()
20929            }
20930            Expression::Literal(lit)
20931                if matches!(lit.as_ref(), crate::expressions::Literal::Timestamp(_)) =>
20932            {
20933                "TIMESTAMP".to_string()
20934            }
20935            Expression::Literal(lit)
20936                if matches!(lit.as_ref(), crate::expressions::Literal::Datetime(_)) =>
20937            {
20938                "TIMESTAMP".to_string()
20939            }
20940            Expression::Array(_) | Expression::ArrayFunc(_) => {
20941                // Try to infer element type from first element
20942                "ARRAY(VARCHAR)".to_string()
20943            }
20944            // For nested structs - generate a nested ROW type by inspecting fields
20945            Expression::Struct(_) | Expression::StructFunc(_) => "ROW".to_string(),
20946            Expression::Function(f) => {
20947                if f.name.eq_ignore_ascii_case("STRUCT") {
20948                    "ROW".to_string()
20949                } else if f.name.eq_ignore_ascii_case("CURRENT_DATE") {
20950                    "DATE".to_string()
20951                } else if f.name.eq_ignore_ascii_case("CURRENT_TIMESTAMP")
20952                    || f.name.eq_ignore_ascii_case("NOW")
20953                {
20954                    "TIMESTAMP".to_string()
20955                } else {
20956                    "VARCHAR".to_string()
20957                }
20958            }
20959            _ => "VARCHAR".to_string(),
20960        }
20961    }
20962
20963    fn generate_struct_extract(&mut self, f: &StructExtractFunc) -> Result<()> {
20964        // DuckDB uses STRUCT_EXTRACT function syntax
20965        if matches!(self.config.dialect, Some(DialectType::DuckDB)) {
20966            self.write_keyword("STRUCT_EXTRACT");
20967            self.write("(");
20968            self.generate_expression(&f.this)?;
20969            self.write(", ");
20970            // Output field name as string literal
20971            self.write("'");
20972            self.write(&f.field.name);
20973            self.write("'");
20974            self.write(")");
20975            return Ok(());
20976        }
20977        self.generate_expression(&f.this)?;
20978        self.write(".");
20979        self.generate_identifier(&f.field)
20980    }
20981
20982    fn generate_named_struct(&mut self, f: &NamedStructFunc) -> Result<()> {
20983        if matches!(
20984            self.config.dialect,
20985            Some(DialectType::Spark | DialectType::Databricks)
20986        ) {
20987            self.write_keyword("STRUCT");
20988            self.write("(");
20989            for (i, (name, value)) in f.pairs.iter().enumerate() {
20990                if i > 0 {
20991                    self.write(", ");
20992                }
20993                self.generate_expression(value)?;
20994                self.write(" ");
20995                self.write_keyword("AS");
20996                self.write(" ");
20997                if let Expression::Literal(lit) = name {
20998                    if let Literal::String(field_name) = lit.as_ref() {
20999                        self.generate_identifier(&Identifier::new(field_name))?;
21000                    } else {
21001                        self.generate_expression(name)?;
21002                    }
21003                } else {
21004                    self.generate_expression(name)?;
21005                }
21006            }
21007            self.write(")");
21008            return Ok(());
21009        }
21010
21011        self.write_keyword("NAMED_STRUCT");
21012        self.write("(");
21013        for (i, (name, value)) in f.pairs.iter().enumerate() {
21014            if i > 0 {
21015                self.write(", ");
21016            }
21017            self.generate_expression(name)?;
21018            self.write(", ");
21019            self.generate_expression(value)?;
21020        }
21021        self.write(")");
21022        Ok(())
21023    }
21024
21025    // Map function generators
21026
21027    fn generate_map_constructor(&mut self, f: &MapConstructor) -> Result<()> {
21028        if f.curly_brace_syntax {
21029            // Curly brace syntax: MAP {'a': 1, 'b': 2} or just {'a': 1, 'b': 2}
21030            if f.with_map_keyword {
21031                self.write_keyword("MAP");
21032                self.write(" ");
21033            }
21034            self.write("{");
21035            for (i, (key, val)) in f.keys.iter().zip(f.values.iter()).enumerate() {
21036                if i > 0 {
21037                    self.write(", ");
21038                }
21039                self.generate_expression(key)?;
21040                self.write(": ");
21041                self.generate_expression(val)?;
21042            }
21043            self.write("}");
21044        } else {
21045            // MAP function syntax: MAP(ARRAY[keys], ARRAY[values])
21046            self.write_keyword("MAP");
21047            self.write("(");
21048            self.write_keyword("ARRAY");
21049            self.write("[");
21050            for (i, key) in f.keys.iter().enumerate() {
21051                if i > 0 {
21052                    self.write(", ");
21053                }
21054                self.generate_expression(key)?;
21055            }
21056            self.write("], ");
21057            self.write_keyword("ARRAY");
21058            self.write("[");
21059            for (i, val) in f.values.iter().enumerate() {
21060                if i > 0 {
21061                    self.write(", ");
21062                }
21063                self.generate_expression(val)?;
21064            }
21065            self.write("])");
21066        }
21067        Ok(())
21068    }
21069
21070    fn generate_transform_func(&mut self, name: &str, f: &TransformFunc) -> Result<()> {
21071        self.write_keyword(name);
21072        self.write("(");
21073        self.generate_expression(&f.this)?;
21074        self.write(", ");
21075        self.generate_expression(&f.transform)?;
21076        self.write(")");
21077        Ok(())
21078    }
21079
21080    // JSON function generators
21081
21082    fn generate_json_extract(&mut self, name: &str, f: &JsonExtractFunc) -> Result<()> {
21083        use crate::dialects::DialectType;
21084
21085        // Check if we should use arrow syntax (-> or ->>)
21086        let use_arrow = f.arrow_syntax && self.dialect_supports_json_arrow();
21087
21088        if use_arrow {
21089            // Output arrow syntax: expr -> path or expr ->> path
21090            self.generate_expression(&f.this)?;
21091            if name == "JSON_EXTRACT_SCALAR" || name == "JSON_EXTRACT_PATH_TEXT" {
21092                self.write(" ->> ");
21093            } else {
21094                self.write(" -> ");
21095            }
21096            self.generate_expression(&f.path)?;
21097            return Ok(());
21098        }
21099
21100        // PostgreSQL uses #>> operator for JSONB path text extraction (only when hash_arrow_syntax is true)
21101        if f.hash_arrow_syntax
21102            && matches!(
21103                self.config.dialect,
21104                Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
21105            )
21106        {
21107            self.generate_expression(&f.this)?;
21108            self.write(" #>> ");
21109            self.generate_expression(&f.path)?;
21110            return Ok(());
21111        }
21112
21113        // For PostgreSQL/Redshift, use JSON_EXTRACT_PATH / JSON_EXTRACT_PATH_TEXT for extraction without arrow syntax
21114        // Redshift maps everything to JSON_EXTRACT_PATH_TEXT since it doesn't have JSON_EXTRACT_PATH
21115        let func_name = if matches!(self.config.dialect, Some(DialectType::Redshift)) {
21116            match name {
21117                "JSON_EXTRACT_SCALAR"
21118                | "JSON_EXTRACT_PATH_TEXT"
21119                | "JSON_EXTRACT"
21120                | "JSON_EXTRACT_PATH" => "JSON_EXTRACT_PATH_TEXT",
21121                _ => name,
21122            }
21123        } else if matches!(self.config.dialect, Some(DialectType::PostgreSQL)) {
21124            match name {
21125                "JSON_EXTRACT_SCALAR" | "JSON_EXTRACT_PATH_TEXT" => "JSON_EXTRACT_PATH_TEXT",
21126                "JSON_EXTRACT" | "JSON_EXTRACT_PATH" => "JSON_EXTRACT_PATH",
21127                _ => name,
21128            }
21129        } else {
21130            name
21131        };
21132
21133        self.write_keyword(func_name);
21134        self.write("(");
21135        // For Redshift, strip CAST(... AS JSON) wrapper from the expression
21136        if matches!(self.config.dialect, Some(DialectType::Redshift)) {
21137            if let Expression::Cast(ref cast) = f.this {
21138                if matches!(cast.to, crate::expressions::DataType::Json) {
21139                    self.generate_expression(&cast.this)?;
21140                } else {
21141                    self.generate_expression(&f.this)?;
21142                }
21143            } else {
21144                self.generate_expression(&f.this)?;
21145            }
21146        } else {
21147            self.generate_expression(&f.this)?;
21148        }
21149        // For PostgreSQL/Redshift JSON_EXTRACT_PATH/JSON_EXTRACT_PATH_TEXT,
21150        // decompose JSON path into separate string arguments
21151        if matches!(
21152            self.config.dialect,
21153            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
21154        ) && (func_name == "JSON_EXTRACT_PATH" || func_name == "JSON_EXTRACT_PATH_TEXT")
21155        {
21156            if let Expression::Literal(ref lit) = f.path {
21157                if let Literal::String(ref s) = lit.as_ref() {
21158                    let parts = Self::decompose_json_path(s);
21159                    for part in &parts {
21160                        self.write(", '");
21161                        self.write(part);
21162                        self.write("'");
21163                    }
21164                }
21165            } else {
21166                self.write(", ");
21167                self.generate_expression(&f.path)?;
21168            }
21169        } else {
21170            self.write(", ");
21171            self.generate_expression(&f.path)?;
21172        }
21173
21174        // Output JSON_QUERY/JSON_VALUE options (Trino/Presto style)
21175        // These go BEFORE the closing parenthesis
21176        if let Some(ref wrapper) = f.wrapper_option {
21177            self.write_space();
21178            self.write_keyword(wrapper);
21179        }
21180        if let Some(ref quotes) = f.quotes_option {
21181            self.write_space();
21182            self.write_keyword(quotes);
21183            if f.on_scalar_string {
21184                self.write_space();
21185                self.write_keyword("ON SCALAR STRING");
21186            }
21187        }
21188        if let Some(ref on_err) = f.on_error {
21189            self.write_space();
21190            self.write_keyword(on_err);
21191        }
21192        if let Some(ref ret_type) = f.returning {
21193            self.write_space();
21194            self.write_keyword("RETURNING");
21195            self.write_space();
21196            self.generate_data_type(ret_type)?;
21197        }
21198
21199        self.write(")");
21200        Ok(())
21201    }
21202
21203    /// Check if the current dialect supports JSON arrow operators (-> and ->>)
21204    fn dialect_supports_json_arrow(&self) -> bool {
21205        use crate::dialects::DialectType;
21206        match self.config.dialect {
21207            // PostgreSQL, MySQL, DuckDB support -> and ->> operators
21208            Some(DialectType::PostgreSQL) => true,
21209            Some(DialectType::MySQL) => true,
21210            Some(DialectType::DuckDB) => true,
21211            Some(DialectType::CockroachDB) => true,
21212            Some(DialectType::StarRocks) => true,
21213            Some(DialectType::SQLite) => true,
21214            // Other dialects use function syntax
21215            _ => false,
21216        }
21217    }
21218
21219    fn generate_json_path(&mut self, name: &str, f: &JsonPathFunc) -> Result<()> {
21220        use crate::dialects::DialectType;
21221
21222        // PostgreSQL uses #> operator for JSONB path extraction
21223        if matches!(
21224            self.config.dialect,
21225            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
21226        ) && name == "JSON_EXTRACT_PATH"
21227        {
21228            self.generate_expression(&f.this)?;
21229            self.write(" #> ");
21230            if f.paths.len() == 1 {
21231                self.generate_expression(&f.paths[0])?;
21232            } else {
21233                // Multiple paths: ARRAY[path1, path2, ...]
21234                self.write_keyword("ARRAY");
21235                self.write("[");
21236                for (i, path) in f.paths.iter().enumerate() {
21237                    if i > 0 {
21238                        self.write(", ");
21239                    }
21240                    self.generate_expression(path)?;
21241                }
21242                self.write("]");
21243            }
21244            return Ok(());
21245        }
21246
21247        self.write_keyword(name);
21248        self.write("(");
21249        self.generate_expression(&f.this)?;
21250        for path in &f.paths {
21251            self.write(", ");
21252            self.generate_expression(path)?;
21253        }
21254        self.write(")");
21255        Ok(())
21256    }
21257
21258    fn generate_json_object(&mut self, f: &JsonObjectFunc) -> Result<()> {
21259        use crate::dialects::DialectType;
21260
21261        self.write_keyword("JSON_OBJECT");
21262        self.write("(");
21263        if f.star {
21264            self.write("*");
21265        } else {
21266            // BigQuery, MySQL, and SQLite use comma syntax: JSON_OBJECT('key', value)
21267            // Standard SQL uses colon syntax: JSON_OBJECT('key': value)
21268            // Also respect the json_key_value_pair_sep config
21269            let use_comma_syntax = self.config.json_key_value_pair_sep == ","
21270                || matches!(
21271                    self.config.dialect,
21272                    Some(DialectType::BigQuery)
21273                        | Some(DialectType::MySQL)
21274                        | Some(DialectType::SQLite)
21275                );
21276
21277            for (i, (key, value)) in f.pairs.iter().enumerate() {
21278                if i > 0 {
21279                    self.write(", ");
21280                }
21281                self.generate_expression(key)?;
21282                if use_comma_syntax {
21283                    self.write(", ");
21284                } else {
21285                    self.write(": ");
21286                }
21287                self.generate_expression(value)?;
21288            }
21289        }
21290        if let Some(null_handling) = f.null_handling {
21291            self.write_space();
21292            match null_handling {
21293                JsonNullHandling::NullOnNull => self.write_keyword("NULL ON NULL"),
21294                JsonNullHandling::AbsentOnNull => self.write_keyword("ABSENT ON NULL"),
21295            }
21296        }
21297        if f.with_unique_keys {
21298            self.write_space();
21299            self.write_keyword("WITH UNIQUE KEYS");
21300        }
21301        if let Some(ref ret_type) = f.returning_type {
21302            self.write_space();
21303            self.write_keyword("RETURNING");
21304            self.write_space();
21305            self.generate_data_type(ret_type)?;
21306            if f.format_json {
21307                self.write_space();
21308                self.write_keyword("FORMAT JSON");
21309            }
21310            if let Some(ref enc) = f.encoding {
21311                self.write_space();
21312                self.write_keyword("ENCODING");
21313                self.write_space();
21314                self.write(enc);
21315            }
21316        }
21317        self.write(")");
21318        Ok(())
21319    }
21320
21321    fn generate_json_modify(&mut self, name: &str, f: &JsonModifyFunc) -> Result<()> {
21322        self.write_keyword(name);
21323        self.write("(");
21324        self.generate_expression(&f.this)?;
21325        for (path, value) in &f.path_values {
21326            self.write(", ");
21327            self.generate_expression(path)?;
21328            self.write(", ");
21329            self.generate_expression(value)?;
21330        }
21331        self.write(")");
21332        Ok(())
21333    }
21334
21335    fn generate_json_array_agg(&mut self, f: &JsonArrayAggFunc) -> Result<()> {
21336        self.write_keyword("JSON_ARRAYAGG");
21337        self.write("(");
21338        self.generate_expression(&f.this)?;
21339        if let Some(ref order_by) = f.order_by {
21340            self.write_space();
21341            self.write_keyword("ORDER BY");
21342            self.write_space();
21343            for (i, ord) in order_by.iter().enumerate() {
21344                if i > 0 {
21345                    self.write(", ");
21346                }
21347                self.generate_ordered(ord)?;
21348            }
21349        }
21350        if let Some(null_handling) = f.null_handling {
21351            self.write_space();
21352            match null_handling {
21353                JsonNullHandling::NullOnNull => self.write_keyword("NULL ON NULL"),
21354                JsonNullHandling::AbsentOnNull => self.write_keyword("ABSENT ON NULL"),
21355            }
21356        }
21357        self.write(")");
21358        if let Some(ref filter) = f.filter {
21359            self.write_space();
21360            self.write_keyword("FILTER");
21361            self.write("(");
21362            self.write_keyword("WHERE");
21363            self.write_space();
21364            self.generate_expression(filter)?;
21365            self.write(")");
21366        }
21367        Ok(())
21368    }
21369
21370    fn generate_json_object_agg(&mut self, f: &JsonObjectAggFunc) -> Result<()> {
21371        self.write_keyword("JSON_OBJECTAGG");
21372        self.write("(");
21373        self.generate_expression(&f.key)?;
21374        self.write(": ");
21375        self.generate_expression(&f.value)?;
21376        if let Some(null_handling) = f.null_handling {
21377            self.write_space();
21378            match null_handling {
21379                JsonNullHandling::NullOnNull => self.write_keyword("NULL ON NULL"),
21380                JsonNullHandling::AbsentOnNull => self.write_keyword("ABSENT ON NULL"),
21381            }
21382        }
21383        self.write(")");
21384        if let Some(ref filter) = f.filter {
21385            self.write_space();
21386            self.write_keyword("FILTER");
21387            self.write("(");
21388            self.write_keyword("WHERE");
21389            self.write_space();
21390            self.generate_expression(filter)?;
21391            self.write(")");
21392        }
21393        Ok(())
21394    }
21395
21396    // Type casting/conversion generators
21397
21398    fn generate_convert(&mut self, f: &ConvertFunc) -> Result<()> {
21399        use crate::dialects::DialectType;
21400
21401        // Redshift: CONVERT(type, expr) -> CAST(expr AS type)
21402        if self.config.dialect == Some(DialectType::Redshift) {
21403            self.write_keyword("CAST");
21404            self.write("(");
21405            self.generate_expression(&f.this)?;
21406            self.write_space();
21407            self.write_keyword("AS");
21408            self.write_space();
21409            self.generate_data_type(&f.to)?;
21410            self.write(")");
21411            return Ok(());
21412        }
21413
21414        self.write_keyword("CONVERT");
21415        self.write("(");
21416        self.generate_data_type(&f.to)?;
21417        self.write(", ");
21418        self.generate_expression(&f.this)?;
21419        if let Some(ref style) = f.style {
21420            self.write(", ");
21421            self.generate_expression(style)?;
21422        }
21423        self.write(")");
21424        Ok(())
21425    }
21426
21427    // Additional expression generators
21428
21429    fn generate_lambda(&mut self, f: &LambdaExpr) -> Result<()> {
21430        if f.colon {
21431            // DuckDB syntax: LAMBDA x : expr
21432            self.write_keyword("LAMBDA");
21433            self.write_space();
21434            for (i, param) in f.parameters.iter().enumerate() {
21435                if i > 0 {
21436                    self.write(", ");
21437                }
21438                self.generate_identifier(param)?;
21439            }
21440            self.write(" : ");
21441        } else {
21442            // Standard syntax: x -> expr or (x, y) -> expr
21443            if f.parameters.len() == 1 {
21444                self.generate_identifier(&f.parameters[0])?;
21445            } else {
21446                self.write("(");
21447                for (i, param) in f.parameters.iter().enumerate() {
21448                    if i > 0 {
21449                        self.write(", ");
21450                    }
21451                    self.generate_identifier(param)?;
21452                }
21453                self.write(")");
21454            }
21455            self.write(" -> ");
21456        }
21457        self.generate_expression(&f.body)
21458    }
21459
21460    fn generate_named_argument(&mut self, f: &NamedArgument) -> Result<()> {
21461        self.generate_identifier(&f.name)?;
21462        match f.separator {
21463            NamedArgSeparator::DArrow => self.write(" => "),
21464            NamedArgSeparator::ColonEq => self.write(" := "),
21465            NamedArgSeparator::Eq => self.write(" = "),
21466        }
21467        self.generate_expression(&f.value)
21468    }
21469
21470    fn generate_table_argument(&mut self, f: &TableArgument) -> Result<()> {
21471        self.write_keyword(&f.prefix);
21472        self.write(" ");
21473        self.generate_expression(&f.this)
21474    }
21475
21476    fn generate_parameter(&mut self, f: &Parameter) -> Result<()> {
21477        match f.style {
21478            ParameterStyle::Question => self.write("?"),
21479            ParameterStyle::Dollar => {
21480                self.write("$");
21481                if let Some(idx) = f.index {
21482                    self.write(&idx.to_string());
21483                } else if let Some(ref name) = f.name {
21484                    // Session variable like $x or $query_id
21485                    self.write(name);
21486                }
21487            }
21488            ParameterStyle::DollarBrace => {
21489                // Template variable like ${x} or ${hiveconf:name} (Databricks, Hive)
21490                self.write("${");
21491                if let Some(ref name) = f.name {
21492                    self.write(name);
21493                }
21494                if let Some(ref expr) = f.expression {
21495                    self.write(":");
21496                    self.write(expr);
21497                }
21498                self.write("}");
21499            }
21500            ParameterStyle::Colon => {
21501                self.write(":");
21502                if let Some(idx) = f.index {
21503                    self.write(&idx.to_string());
21504                } else if let Some(ref name) = f.name {
21505                    self.write(name);
21506                }
21507            }
21508            ParameterStyle::At => {
21509                self.write("@");
21510                if let Some(ref name) = f.name {
21511                    if f.string_quoted {
21512                        self.write("'");
21513                        self.write(name);
21514                        self.write("'");
21515                    } else if f.quoted {
21516                        self.write("\"");
21517                        self.write(name);
21518                        self.write("\"");
21519                    } else {
21520                        self.write(name);
21521                    }
21522                }
21523            }
21524            ParameterStyle::DoubleAt => {
21525                self.write("@@");
21526                if let Some(ref name) = f.name {
21527                    self.write(name);
21528                }
21529            }
21530            ParameterStyle::DoubleDollar => {
21531                self.write("$$");
21532                if let Some(ref name) = f.name {
21533                    self.write(name);
21534                }
21535            }
21536            ParameterStyle::Percent => {
21537                if let Some(ref name) = f.name {
21538                    // %(name)s format
21539                    self.write("%(");
21540                    self.write(name);
21541                    self.write(")s");
21542                } else {
21543                    // %s format
21544                    self.write("%s");
21545                }
21546            }
21547            ParameterStyle::Brace => {
21548                // Spark/Databricks widget template variable: {name}
21549                // ClickHouse query parameter may include kind: {name: Type}
21550                self.write("{");
21551                if let Some(ref name) = f.name {
21552                    self.write(name);
21553                }
21554                if let Some(ref expr) = f.expression {
21555                    self.write(": ");
21556                    self.write(expr);
21557                }
21558                self.write("}");
21559            }
21560        }
21561        Ok(())
21562    }
21563
21564    fn generate_placeholder(&mut self, f: &Placeholder) -> Result<()> {
21565        self.write("?");
21566        if let Some(idx) = f.index {
21567            self.write(&idx.to_string());
21568        }
21569        Ok(())
21570    }
21571
21572    fn generate_sql_comment(&mut self, f: &SqlComment) -> Result<()> {
21573        if f.is_block {
21574            self.write("/*");
21575            self.write(&f.text);
21576            self.write("*/");
21577        } else {
21578            self.write("--");
21579            self.write(&f.text);
21580        }
21581        Ok(())
21582    }
21583
21584    // Additional predicate generators
21585
21586    fn generate_similar_to(&mut self, f: &SimilarToExpr) -> Result<()> {
21587        self.generate_expression(&f.this)?;
21588        if f.not {
21589            self.write_space();
21590            self.write_keyword("NOT");
21591        }
21592        self.write_space();
21593        self.write_keyword("SIMILAR TO");
21594        self.write_space();
21595        self.generate_expression(&f.pattern)?;
21596        if let Some(ref escape) = f.escape {
21597            self.write_space();
21598            self.write_keyword("ESCAPE");
21599            self.write_space();
21600            self.generate_expression(escape)?;
21601        }
21602        Ok(())
21603    }
21604
21605    fn generate_quantified(&mut self, name: &str, f: &QuantifiedExpr) -> Result<()> {
21606        self.generate_expression(&f.this)?;
21607        self.write_space();
21608        // Output comparison operator if present
21609        if let Some(op) = &f.op {
21610            match op {
21611                QuantifiedOp::Eq => self.write("="),
21612                QuantifiedOp::Neq => self.write("<>"),
21613                QuantifiedOp::Lt => self.write("<"),
21614                QuantifiedOp::Lte => self.write("<="),
21615                QuantifiedOp::Gt => self.write(">"),
21616                QuantifiedOp::Gte => self.write(">="),
21617            }
21618            self.write_space();
21619        }
21620        self.write_keyword(name);
21621
21622        // If the child is a Subquery, it provides its own parens — output with space
21623        if matches!(&f.subquery, Expression::Subquery(_)) {
21624            self.write_space();
21625            self.generate_expression(&f.subquery)?;
21626        } else {
21627            self.write("(");
21628
21629            let is_statement = matches!(
21630                &f.subquery,
21631                Expression::Select(_)
21632                    | Expression::Union(_)
21633                    | Expression::Intersect(_)
21634                    | Expression::Except(_)
21635            );
21636
21637            if self.config.pretty && is_statement {
21638                self.write_newline();
21639                self.indent_level += 1;
21640                self.write_indent();
21641            }
21642            self.generate_expression(&f.subquery)?;
21643            if self.config.pretty && is_statement {
21644                self.write_newline();
21645                self.indent_level -= 1;
21646                self.write_indent();
21647            }
21648            self.write(")");
21649        }
21650        Ok(())
21651    }
21652
21653    fn generate_overlaps(&mut self, f: &OverlapsExpr) -> Result<()> {
21654        // Check if this is a simple binary form (this OVERLAPS expression)
21655        if let (Some(this), Some(expr)) = (&f.this, &f.expression) {
21656            self.generate_expression(this)?;
21657            self.write_space();
21658            self.write_keyword("OVERLAPS");
21659            self.write_space();
21660            self.generate_expression(expr)?;
21661        } else if let (Some(ls), Some(le), Some(rs), Some(re)) =
21662            (&f.left_start, &f.left_end, &f.right_start, &f.right_end)
21663        {
21664            // Full ANSI form: (a, b) OVERLAPS (c, d)
21665            self.write("(");
21666            self.generate_expression(ls)?;
21667            self.write(", ");
21668            self.generate_expression(le)?;
21669            self.write(")");
21670            self.write_space();
21671            self.write_keyword("OVERLAPS");
21672            self.write_space();
21673            self.write("(");
21674            self.generate_expression(rs)?;
21675            self.write(", ");
21676            self.generate_expression(re)?;
21677            self.write(")");
21678        }
21679        Ok(())
21680    }
21681
21682    // Type conversion generators
21683
21684    fn generate_try_cast(&mut self, cast: &Cast) -> Result<()> {
21685        use crate::dialects::DialectType;
21686
21687        // SingleStore uses !:> syntax for try cast
21688        if matches!(self.config.dialect, Some(DialectType::SingleStore)) {
21689            self.generate_expression(&cast.this)?;
21690            self.write(" !:> ");
21691            self.generate_data_type(&cast.to)?;
21692            return Ok(());
21693        }
21694
21695        // Teradata uses TRYCAST (no underscore)
21696        if matches!(self.config.dialect, Some(DialectType::Teradata)) {
21697            self.write_keyword("TRYCAST");
21698            self.write("(");
21699            self.generate_expression(&cast.this)?;
21700            self.write_space();
21701            self.write_keyword("AS");
21702            self.write_space();
21703            self.generate_data_type(&cast.to)?;
21704            self.write(")");
21705            return Ok(());
21706        }
21707
21708        // Dialects without TRY_CAST: generate as regular CAST
21709        let keyword = if matches!(
21710            self.config.dialect,
21711            Some(DialectType::Hive)
21712                | Some(DialectType::MySQL)
21713                | Some(DialectType::SQLite)
21714                | Some(DialectType::Oracle)
21715                | Some(DialectType::ClickHouse)
21716                | Some(DialectType::Redshift)
21717                | Some(DialectType::PostgreSQL)
21718                | Some(DialectType::StarRocks)
21719                | Some(DialectType::Doris)
21720        ) {
21721            "CAST"
21722        } else {
21723            "TRY_CAST"
21724        };
21725
21726        self.write_keyword(keyword);
21727        self.write("(");
21728        self.generate_expression(&cast.this)?;
21729        self.write_space();
21730        self.write_keyword("AS");
21731        self.write_space();
21732        self.generate_data_type(&cast.to)?;
21733
21734        // Output FORMAT clause if present
21735        if let Some(format) = &cast.format {
21736            self.write_space();
21737            self.write_keyword("FORMAT");
21738            self.write_space();
21739            self.generate_expression(format)?;
21740        }
21741
21742        self.write(")");
21743        Ok(())
21744    }
21745
21746    fn generate_safe_cast(&mut self, cast: &Cast) -> Result<()> {
21747        self.write_keyword("SAFE_CAST");
21748        self.write("(");
21749        self.generate_expression(&cast.this)?;
21750        self.write_space();
21751        self.write_keyword("AS");
21752        self.write_space();
21753        self.generate_data_type(&cast.to)?;
21754
21755        // Output FORMAT clause if present
21756        if let Some(format) = &cast.format {
21757            self.write_space();
21758            self.write_keyword("FORMAT");
21759            self.write_space();
21760            self.generate_expression(format)?;
21761        }
21762
21763        self.write(")");
21764        Ok(())
21765    }
21766
21767    // Array/struct/map access generators
21768
21769    fn generate_subscript(&mut self, s: &Subscript) -> Result<()> {
21770        // Wrap the base expression in parentheses when it uses arrow syntax (->)
21771        // which has lower precedence than bracket subscript ([]).
21772        // E.g., (t.v -> '$.a')[s.x] instead of t.v -> '$.a'[s.x]
21773        let needs_parens = matches!(&s.this, Expression::JsonExtract(ref f) if f.arrow_syntax);
21774        if needs_parens {
21775            self.write("(");
21776        }
21777        self.generate_expression(&s.this)?;
21778        if needs_parens {
21779            self.write(")");
21780        }
21781        self.write("[");
21782        self.generate_expression(&s.index)?;
21783        self.write("]");
21784        Ok(())
21785    }
21786
21787    fn generate_dot_access(&mut self, d: &DotAccess) -> Result<()> {
21788        self.generate_expression(&d.this)?;
21789        // Snowflake uses : (colon) for first-level struct/object field access on CAST/column expressions
21790        // e.g., CAST(col AS OBJECT(fld1 OBJECT(fld2 INT))):fld1.fld2
21791        let use_colon = matches!(self.config.dialect, Some(DialectType::Snowflake))
21792            && matches!(
21793                &d.this,
21794                Expression::Cast(_) | Expression::SafeCast(_) | Expression::TryCast(_)
21795            );
21796        if use_colon {
21797            self.write(":");
21798        } else {
21799            self.write(".");
21800        }
21801        self.generate_identifier(&d.field)
21802    }
21803
21804    fn generate_method_call(&mut self, m: &MethodCall) -> Result<()> {
21805        self.generate_expression(&m.this)?;
21806        self.write(".");
21807        // Method names after a dot should not be quoted based on reserved keywords
21808        // Only quote if explicitly marked as quoted in the AST
21809        if m.method.quoted {
21810            let q = self.config.identifier_quote;
21811            self.write(&format!("{}{}{}", q, m.method.name, q));
21812        } else {
21813            self.write(&m.method.name);
21814        }
21815        self.write("(");
21816        for (i, arg) in m.args.iter().enumerate() {
21817            if i > 0 {
21818                self.write(", ");
21819            }
21820            self.generate_expression(arg)?;
21821        }
21822        self.write(")");
21823        Ok(())
21824    }
21825
21826    fn generate_array_slice(&mut self, s: &ArraySlice) -> Result<()> {
21827        // Check if we need to wrap the inner expression in parentheses
21828        // JSON arrow expressions have lower precedence than array subscript
21829        let needs_parens = matches!(
21830            &s.this,
21831            Expression::JsonExtract(f) if f.arrow_syntax
21832        ) || matches!(
21833            &s.this,
21834            Expression::JsonExtractScalar(f) if f.arrow_syntax
21835        );
21836
21837        if needs_parens {
21838            self.write("(");
21839        }
21840        self.generate_expression(&s.this)?;
21841        if needs_parens {
21842            self.write(")");
21843        }
21844        self.write("[");
21845        if let Some(start) = &s.start {
21846            self.generate_expression(start)?;
21847        }
21848        self.write(":");
21849        if let Some(end) = &s.end {
21850            self.generate_expression(end)?;
21851        }
21852        self.write("]");
21853        Ok(())
21854    }
21855
21856    fn generate_binary_op(&mut self, op: &BinaryOp, operator: &str) -> Result<()> {
21857        // Generate left expression, but skip trailing comments if they're already in left_comments
21858        // to avoid duplication (comments are captured as both expr.trailing_comments
21859        // and BinaryOp.left_comments during parsing)
21860        match &op.left {
21861            Expression::Column(col) => {
21862                // Generate column with trailing comments but skip them if they're
21863                // already captured in BinaryOp.left_comments to avoid duplication
21864                if let Some(table) = &col.table {
21865                    self.generate_identifier(table)?;
21866                    self.write(".");
21867                }
21868                self.generate_identifier(&col.name)?;
21869                // Oracle-style join marker (+)
21870                if col.join_mark && self.config.supports_column_join_marks {
21871                    self.write(" (+)");
21872                }
21873                // Output column trailing comments if they're not already in left_comments
21874                if op.left_comments.is_empty() {
21875                    for comment in &col.trailing_comments {
21876                        self.write_space();
21877                        self.write_formatted_comment(comment);
21878                    }
21879                }
21880            }
21881            Expression::Add(inner_op)
21882            | Expression::Sub(inner_op)
21883            | Expression::Mul(inner_op)
21884            | Expression::Div(inner_op)
21885            | Expression::Concat(inner_op) => {
21886                // Generate binary op without its trailing comments
21887                self.generate_binary_op_no_trailing(inner_op, match &op.left {
21888                    Expression::Add(_) => "+",
21889                    Expression::Sub(_) => "-",
21890                    Expression::Mul(_) => "*",
21891                    Expression::Div(_) => "/",
21892                    Expression::Concat(_) => "||",
21893                    _ => unreachable!("op.left variant already matched by outer arm as Add/Sub/Mul/Div/Concat"),
21894                })?;
21895            }
21896            _ => {
21897                self.generate_expression(&op.left)?;
21898            }
21899        }
21900        // Output comments after left operand
21901        for comment in &op.left_comments {
21902            self.write_space();
21903            self.write_formatted_comment(comment);
21904        }
21905        if self.config.pretty
21906            && matches!(self.config.dialect, Some(DialectType::Snowflake))
21907            && (operator == "AND" || operator == "OR")
21908        {
21909            self.write_newline();
21910            self.write_indent();
21911            self.write_keyword(operator);
21912        } else {
21913            self.write_space();
21914            if operator.chars().all(|c| c.is_alphabetic()) {
21915                self.write_keyword(operator);
21916            } else {
21917                self.write(operator);
21918            }
21919        }
21920        // Output comments after operator (before right operand)
21921        for comment in &op.operator_comments {
21922            self.write_space();
21923            self.write_formatted_comment(comment);
21924        }
21925        self.write_space();
21926        self.generate_expression(&op.right)?;
21927        // Output trailing comments after right operand
21928        for comment in &op.trailing_comments {
21929            self.write_space();
21930            self.write_formatted_comment(comment);
21931        }
21932        Ok(())
21933    }
21934
21935    fn generate_connector_op(&mut self, op: &BinaryOp, connector: ConnectorOperator) -> Result<()> {
21936        let keyword = connector.keyword();
21937        let Some(terms) = self.flatten_connector_terms(op, connector) else {
21938            return self.generate_binary_op(op, keyword);
21939        };
21940
21941        self.generate_expression(terms[0])?;
21942        for term in terms.iter().skip(1) {
21943            if self.config.pretty && matches!(self.config.dialect, Some(DialectType::Snowflake)) {
21944                self.write_newline();
21945                self.write_indent();
21946                self.write_keyword(keyword);
21947            } else {
21948                self.write_space();
21949                self.write_keyword(keyword);
21950            }
21951            self.write_space();
21952            self.generate_expression(term)?;
21953        }
21954
21955        Ok(())
21956    }
21957
21958    fn flatten_connector_terms<'a>(
21959        &self,
21960        root: &'a BinaryOp,
21961        connector: ConnectorOperator,
21962    ) -> Option<Vec<&'a Expression>> {
21963        if !root.left_comments.is_empty()
21964            || !root.operator_comments.is_empty()
21965            || !root.trailing_comments.is_empty()
21966        {
21967            return None;
21968        }
21969
21970        let mut terms = Vec::new();
21971        let mut stack: Vec<&Expression> = vec![&root.right, &root.left];
21972
21973        while let Some(expr) = stack.pop() {
21974            match (connector, expr) {
21975                (ConnectorOperator::And, Expression::And(inner))
21976                    if inner.left_comments.is_empty()
21977                        && inner.operator_comments.is_empty()
21978                        && inner.trailing_comments.is_empty() =>
21979                {
21980                    stack.push(&inner.right);
21981                    stack.push(&inner.left);
21982                }
21983                (ConnectorOperator::Or, Expression::Or(inner))
21984                    if inner.left_comments.is_empty()
21985                        && inner.operator_comments.is_empty()
21986                        && inner.trailing_comments.is_empty() =>
21987                {
21988                    stack.push(&inner.right);
21989                    stack.push(&inner.left);
21990                }
21991                _ => terms.push(expr),
21992            }
21993        }
21994
21995        if terms.len() > 1 {
21996            Some(terms)
21997        } else {
21998            None
21999        }
22000    }
22001
22002    /// Generate LIKE/ILIKE operation with optional ESCAPE clause
22003    fn generate_like_op(&mut self, op: &LikeOp, operator: &str) -> Result<()> {
22004        self.generate_expression(&op.left)?;
22005        self.write_space();
22006        // Drill backtick-quotes ILIKE
22007        if operator == "ILIKE" && matches!(self.config.dialect, Some(DialectType::Drill)) {
22008            self.write("`ILIKE`");
22009        } else {
22010            self.write_keyword(operator);
22011        }
22012        if let Some(quantifier) = &op.quantifier {
22013            self.write_space();
22014            self.write_keyword(quantifier);
22015            // Match Python sqlglot behavior:
22016            // ANY + Paren (single value): no space → ILIKE ANY('%a%')
22017            // ANY + Tuple (multiple values): space → LIKE ANY ('a', 'b')
22018            // ALL + anything: always space → LIKE ALL ('%a%'), LIKE ALL ('a', 'b')
22019            let is_any =
22020                quantifier.eq_ignore_ascii_case("ANY") || quantifier.eq_ignore_ascii_case("SOME");
22021            if !(is_any && matches!(&op.right, Expression::Paren(_))) {
22022                self.write_space();
22023            }
22024        } else {
22025            self.write_space();
22026        }
22027        self.generate_expression(&op.right)?;
22028        if let Some(escape) = &op.escape {
22029            self.write_space();
22030            self.write_keyword("ESCAPE");
22031            self.write_space();
22032            self.generate_expression(escape)?;
22033        }
22034        Ok(())
22035    }
22036
22037    /// Generate null-safe equality
22038    /// MySQL uses <=>, other dialects use IS NOT DISTINCT FROM
22039    fn generate_null_safe_eq(&mut self, op: &BinaryOp) -> Result<()> {
22040        use crate::dialects::DialectType;
22041        self.generate_expression(&op.left)?;
22042        self.write_space();
22043        if matches!(self.config.dialect, Some(DialectType::MySQL)) {
22044            self.write("<=>");
22045        } else {
22046            self.write_keyword("IS NOT DISTINCT FROM");
22047        }
22048        self.write_space();
22049        self.generate_expression(&op.right)?;
22050        Ok(())
22051    }
22052
22053    /// Generate IS DISTINCT FROM (null-safe inequality)
22054    fn generate_null_safe_neq(&mut self, op: &BinaryOp) -> Result<()> {
22055        self.generate_expression(&op.left)?;
22056        self.write_space();
22057        self.write_keyword("IS DISTINCT FROM");
22058        self.write_space();
22059        self.generate_expression(&op.right)?;
22060        Ok(())
22061    }
22062
22063    /// Generate binary op without trailing comments (used when nested inside another binary op)
22064    fn generate_binary_op_no_trailing(&mut self, op: &BinaryOp, operator: &str) -> Result<()> {
22065        // Generate left expression, but skip trailing comments
22066        match &op.left {
22067            Expression::Column(col) => {
22068                if let Some(table) = &col.table {
22069                    self.generate_identifier(table)?;
22070                    self.write(".");
22071                }
22072                self.generate_identifier(&col.name)?;
22073                // Oracle-style join marker (+)
22074                if col.join_mark && self.config.supports_column_join_marks {
22075                    self.write(" (+)");
22076                }
22077            }
22078            Expression::Add(inner_op)
22079            | Expression::Sub(inner_op)
22080            | Expression::Mul(inner_op)
22081            | Expression::Div(inner_op)
22082            | Expression::Concat(inner_op) => {
22083                self.generate_binary_op_no_trailing(inner_op, match &op.left {
22084                    Expression::Add(_) => "+",
22085                    Expression::Sub(_) => "-",
22086                    Expression::Mul(_) => "*",
22087                    Expression::Div(_) => "/",
22088                    Expression::Concat(_) => "||",
22089                    _ => unreachable!("op.left variant already matched by outer arm as Add/Sub/Mul/Div/Concat"),
22090                })?;
22091            }
22092            _ => {
22093                self.generate_expression(&op.left)?;
22094            }
22095        }
22096        // Output left_comments
22097        for comment in &op.left_comments {
22098            self.write_space();
22099            self.write_formatted_comment(comment);
22100        }
22101        self.write_space();
22102        if operator.chars().all(|c| c.is_alphabetic()) {
22103            self.write_keyword(operator);
22104        } else {
22105            self.write(operator);
22106        }
22107        // Output operator_comments
22108        for comment in &op.operator_comments {
22109            self.write_space();
22110            self.write_formatted_comment(comment);
22111        }
22112        self.write_space();
22113        // Generate right expression, but skip trailing comments if it's a Column
22114        // (the parent's left_comments will output them)
22115        match &op.right {
22116            Expression::Column(col) => {
22117                if let Some(table) = &col.table {
22118                    self.generate_identifier(table)?;
22119                    self.write(".");
22120                }
22121                self.generate_identifier(&col.name)?;
22122                // Oracle-style join marker (+)
22123                if col.join_mark && self.config.supports_column_join_marks {
22124                    self.write(" (+)");
22125                }
22126            }
22127            _ => {
22128                self.generate_expression(&op.right)?;
22129            }
22130        }
22131        // Skip trailing_comments - parent will handle them via its left_comments
22132        Ok(())
22133    }
22134
22135    fn generate_unary_op(&mut self, op: &UnaryOp, operator: &str) -> Result<()> {
22136        if operator.chars().all(|c| c.is_alphabetic()) {
22137            self.write_keyword(operator);
22138            self.write_space();
22139        } else {
22140            self.write(operator);
22141            // Add space between consecutive unary operators (e.g., "- -5" not "--5")
22142            if matches!(&op.this, Expression::Neg(_) | Expression::BitwiseNot(_)) {
22143                self.write_space();
22144            }
22145        }
22146        self.generate_expression(&op.this)
22147    }
22148
22149    fn generate_in(&mut self, in_expr: &In) -> Result<()> {
22150        // Generic mode supports two styles for negated IN:
22151        // - Prefix: NOT a IN (...)
22152        // - Infix:  a NOT IN (...)
22153        let is_generic =
22154            self.config.dialect.is_none() || self.config.dialect == Some(DialectType::Generic);
22155        let use_prefix_not =
22156            in_expr.not && is_generic && self.config.not_in_style == NotInStyle::Prefix;
22157        if use_prefix_not {
22158            self.write_keyword("NOT");
22159            self.write_space();
22160        }
22161        self.generate_expression(&in_expr.this)?;
22162        if in_expr.global {
22163            self.write_space();
22164            self.write_keyword("GLOBAL");
22165        }
22166        if in_expr.not && !use_prefix_not {
22167            self.write_space();
22168            self.write_keyword("NOT");
22169        }
22170        self.write_space();
22171        self.write_keyword("IN");
22172
22173        // BigQuery: IN UNNEST(expr)
22174        if let Some(unnest_expr) = &in_expr.unnest {
22175            self.write_space();
22176            self.write_keyword("UNNEST");
22177            self.write("(");
22178            self.generate_expression(unnest_expr)?;
22179            self.write(")");
22180            return Ok(());
22181        }
22182
22183        if let Some(query) = &in_expr.query {
22184            // Check if this is a bare identifier (PIVOT FOR foo IN y_enum)
22185            // vs a subquery (col IN (SELECT ...))
22186            let is_bare = in_expr.expressions.is_empty()
22187                && !matches!(
22188                    query,
22189                    Expression::Select(_)
22190                        | Expression::Union(_)
22191                        | Expression::Intersect(_)
22192                        | Expression::Except(_)
22193                        | Expression::Subquery(_)
22194                );
22195            if is_bare {
22196                // Bare identifier: no parentheses
22197                self.write_space();
22198                self.generate_expression(query)?;
22199            } else {
22200                // Subquery: with parentheses
22201                self.write(" (");
22202                let is_statement = matches!(
22203                    query,
22204                    Expression::Select(_)
22205                        | Expression::Union(_)
22206                        | Expression::Intersect(_)
22207                        | Expression::Except(_)
22208                        | Expression::Subquery(_)
22209                );
22210                if self.config.pretty && is_statement {
22211                    self.write_newline();
22212                    self.indent_level += 1;
22213                    self.write_indent();
22214                }
22215                self.generate_expression(query)?;
22216                if self.config.pretty && is_statement {
22217                    self.write_newline();
22218                    self.indent_level -= 1;
22219                    self.write_indent();
22220                }
22221                self.write(")");
22222            }
22223        } else {
22224            // DuckDB: IN without parentheses for single expression that is NOT a literal
22225            // (array/list membership like 'red' IN tbl.flags)
22226            // ClickHouse: IN without parentheses for single non-array expressions
22227            let is_duckdb = matches!(
22228                self.config.dialect,
22229                Some(crate::dialects::DialectType::DuckDB)
22230            );
22231            let is_clickhouse = matches!(
22232                self.config.dialect,
22233                Some(crate::dialects::DialectType::ClickHouse)
22234            );
22235            let single_expr = in_expr.expressions.len() == 1;
22236            if is_clickhouse && single_expr {
22237                if let Expression::Array(arr) = &in_expr.expressions[0] {
22238                    // ClickHouse: x IN [1, 2] -> x IN (1, 2)
22239                    self.write(" (");
22240                    for (i, expr) in arr.expressions.iter().enumerate() {
22241                        if i > 0 {
22242                            self.write(", ");
22243                        }
22244                        self.generate_expression(expr)?;
22245                    }
22246                    self.write(")");
22247                } else {
22248                    self.write_space();
22249                    self.generate_expression(&in_expr.expressions[0])?;
22250                }
22251            } else {
22252                let is_bare_ref = single_expr
22253                    && matches!(
22254                        &in_expr.expressions[0],
22255                        Expression::Column(_) | Expression::Identifier(_) | Expression::Dot(_)
22256                    );
22257                if (is_duckdb && is_bare_ref) || (in_expr.is_field && single_expr) {
22258                    // Bare field reference (no parens in source): IN identifier
22259                    // Also DuckDB: IN without parentheses for array/list membership
22260                    self.write_space();
22261                    self.generate_expression(&in_expr.expressions[0])?;
22262                } else {
22263                    // Standard IN (list)
22264                    self.write(" (");
22265                    for (i, expr) in in_expr.expressions.iter().enumerate() {
22266                        if i > 0 {
22267                            self.write(", ");
22268                        }
22269                        self.generate_expression(expr)?;
22270                    }
22271                    self.write(")");
22272                }
22273            }
22274        }
22275
22276        Ok(())
22277    }
22278
22279    fn generate_between(&mut self, between: &Between) -> Result<()> {
22280        // Generic mode: normalize NOT BETWEEN to prefix form: NOT a BETWEEN b AND c
22281        let use_prefix_not = between.not
22282            && (self.config.dialect.is_none() || self.config.dialect == Some(DialectType::Generic));
22283        if use_prefix_not {
22284            self.write_keyword("NOT");
22285            self.write_space();
22286        }
22287        self.generate_expression(&between.this)?;
22288        if between.not && !use_prefix_not {
22289            self.write_space();
22290            self.write_keyword("NOT");
22291        }
22292        self.write_space();
22293        self.write_keyword("BETWEEN");
22294        // Emit SYMMETRIC/ASYMMETRIC if present
22295        if let Some(sym) = between.symmetric {
22296            if sym {
22297                self.write(" SYMMETRIC");
22298            } else {
22299                self.write(" ASYMMETRIC");
22300            }
22301        }
22302        self.write_space();
22303        self.generate_expression(&between.low)?;
22304        self.write_space();
22305        self.write_keyword("AND");
22306        self.write_space();
22307        self.generate_expression(&between.high)
22308    }
22309
22310    fn generate_is_null(&mut self, is_null: &IsNull) -> Result<()> {
22311        // Generic mode: normalize IS NOT NULL to prefix form: NOT x IS NULL
22312        let use_prefix_not = is_null.not
22313            && (self.config.dialect.is_none()
22314                || self.config.dialect == Some(DialectType::Generic)
22315                || is_null.postfix_form);
22316        if use_prefix_not {
22317            // NOT x IS NULL (generic normalization and NOTNULL postfix form)
22318            self.write_keyword("NOT");
22319            self.write_space();
22320            self.generate_expression(&is_null.this)?;
22321            self.write_space();
22322            self.write_keyword("IS");
22323            self.write_space();
22324            self.write_keyword("NULL");
22325        } else {
22326            self.generate_expression(&is_null.this)?;
22327            self.write_space();
22328            self.write_keyword("IS");
22329            if is_null.not {
22330                self.write_space();
22331                self.write_keyword("NOT");
22332            }
22333            self.write_space();
22334            self.write_keyword("NULL");
22335        }
22336        Ok(())
22337    }
22338
22339    fn generate_is_true(&mut self, is_true: &IsTrueFalse) -> Result<()> {
22340        self.generate_expression(&is_true.this)?;
22341        self.write_space();
22342        self.write_keyword("IS");
22343        if is_true.not {
22344            self.write_space();
22345            self.write_keyword("NOT");
22346        }
22347        self.write_space();
22348        self.write_keyword("TRUE");
22349        Ok(())
22350    }
22351
22352    fn generate_is_false(&mut self, is_false: &IsTrueFalse) -> Result<()> {
22353        self.generate_expression(&is_false.this)?;
22354        self.write_space();
22355        self.write_keyword("IS");
22356        if is_false.not {
22357            self.write_space();
22358            self.write_keyword("NOT");
22359        }
22360        self.write_space();
22361        self.write_keyword("FALSE");
22362        Ok(())
22363    }
22364
22365    fn generate_is_json(&mut self, is_json: &IsJson) -> Result<()> {
22366        self.generate_expression(&is_json.this)?;
22367        self.write_space();
22368        self.write_keyword("IS");
22369        if is_json.negated {
22370            self.write_space();
22371            self.write_keyword("NOT");
22372        }
22373        self.write_space();
22374        self.write_keyword("JSON");
22375
22376        // Output JSON type if specified (VALUE, SCALAR, OBJECT, ARRAY)
22377        if let Some(ref json_type) = is_json.json_type {
22378            self.write_space();
22379            self.write_keyword(json_type);
22380        }
22381
22382        // Output key uniqueness constraint if specified
22383        match &is_json.unique_keys {
22384            Some(JsonUniqueKeys::With) => {
22385                self.write_space();
22386                self.write_keyword("WITH UNIQUE KEYS");
22387            }
22388            Some(JsonUniqueKeys::Without) => {
22389                self.write_space();
22390                self.write_keyword("WITHOUT UNIQUE KEYS");
22391            }
22392            Some(JsonUniqueKeys::Shorthand) => {
22393                self.write_space();
22394                self.write_keyword("UNIQUE KEYS");
22395            }
22396            None => {}
22397        }
22398
22399        Ok(())
22400    }
22401
22402    fn generate_is(&mut self, is_expr: &BinaryOp) -> Result<()> {
22403        self.generate_expression(&is_expr.left)?;
22404        self.write_space();
22405        self.write_keyword("IS");
22406        self.write_space();
22407        self.generate_expression(&is_expr.right)
22408    }
22409
22410    fn generate_exists(&mut self, exists: &Exists) -> Result<()> {
22411        if exists.not {
22412            self.write_keyword("NOT");
22413            self.write_space();
22414        }
22415        self.write_keyword("EXISTS");
22416        self.write("(");
22417        let is_statement = matches!(
22418            &exists.this,
22419            Expression::Select(_)
22420                | Expression::Union(_)
22421                | Expression::Intersect(_)
22422                | Expression::Except(_)
22423        );
22424        if self.config.pretty && is_statement {
22425            self.write_newline();
22426            self.indent_level += 1;
22427            self.write_indent();
22428            self.generate_expression(&exists.this)?;
22429            self.write_newline();
22430            self.indent_level -= 1;
22431            self.write_indent();
22432            self.write(")");
22433        } else {
22434            self.generate_expression(&exists.this)?;
22435            self.write(")");
22436        }
22437        Ok(())
22438    }
22439
22440    fn generate_member_of(&mut self, op: &BinaryOp) -> Result<()> {
22441        self.generate_expression(&op.left)?;
22442        self.write_space();
22443        self.write_keyword("MEMBER OF");
22444        self.write("(");
22445        self.generate_expression(&op.right)?;
22446        self.write(")");
22447        Ok(())
22448    }
22449
22450    fn generate_subquery(&mut self, subquery: &Subquery) -> Result<()> {
22451        if subquery.lateral {
22452            self.write_keyword("LATERAL");
22453            self.write_space();
22454        }
22455
22456        // If the inner expression is a Paren wrapping a statement, don't add extra parentheses
22457        // This handles cases like ((SELECT 1)) LIMIT 1 where we wrap Paren in Subquery
22458        // to carry the LIMIT modifier without adding more parens
22459        let skip_outer_parens = if let Expression::Paren(ref p) = &subquery.this {
22460            matches!(
22461                &p.this,
22462                Expression::Select(_)
22463                    | Expression::Union(_)
22464                    | Expression::Intersect(_)
22465                    | Expression::Except(_)
22466                    | Expression::Subquery(_)
22467            )
22468        } else {
22469            false
22470        };
22471
22472        // Check if inner expression is a statement for pretty formatting
22473        let is_statement = matches!(
22474            &subquery.this,
22475            Expression::Select(_)
22476                | Expression::Union(_)
22477                | Expression::Intersect(_)
22478                | Expression::Except(_)
22479                | Expression::Merge(_)
22480        );
22481
22482        if !skip_outer_parens {
22483            self.write("(");
22484            if self.config.pretty && is_statement {
22485                self.write_newline();
22486                self.indent_level += 1;
22487                self.write_indent();
22488            }
22489        }
22490        self.generate_expression(&subquery.this)?;
22491
22492        // Generate ORDER BY, LIMIT, OFFSET based on modifiers_inside flag
22493        if subquery.modifiers_inside {
22494            // Generate modifiers INSIDE the parentheses: (SELECT ... LIMIT 1)
22495            if let Some(order_by) = &subquery.order_by {
22496                self.write_space();
22497                self.write_keyword("ORDER BY");
22498                self.write_space();
22499                for (i, ord) in order_by.expressions.iter().enumerate() {
22500                    if i > 0 {
22501                        self.write(", ");
22502                    }
22503                    self.generate_ordered(ord)?;
22504                }
22505            }
22506
22507            if let Some(limit) = &subquery.limit {
22508                self.write_space();
22509                self.write_keyword("LIMIT");
22510                self.write_space();
22511                self.generate_expression(&limit.this)?;
22512                if limit.percent {
22513                    self.write_space();
22514                    self.write_keyword("PERCENT");
22515                }
22516            }
22517
22518            if let Some(offset) = &subquery.offset {
22519                self.write_space();
22520                self.write_keyword("OFFSET");
22521                self.write_space();
22522                self.generate_expression(&offset.this)?;
22523            }
22524        }
22525
22526        if !skip_outer_parens {
22527            if self.config.pretty && is_statement {
22528                self.write_newline();
22529                self.indent_level -= 1;
22530                self.write_indent();
22531            }
22532            self.write(")");
22533        }
22534
22535        // Generate modifiers OUTSIDE the parentheses: (SELECT ...) LIMIT 1
22536        if !subquery.modifiers_inside {
22537            if let Some(order_by) = &subquery.order_by {
22538                self.write_space();
22539                self.write_keyword("ORDER BY");
22540                self.write_space();
22541                for (i, ord) in order_by.expressions.iter().enumerate() {
22542                    if i > 0 {
22543                        self.write(", ");
22544                    }
22545                    self.generate_ordered(ord)?;
22546                }
22547            }
22548
22549            if let Some(limit) = &subquery.limit {
22550                self.write_space();
22551                self.write_keyword("LIMIT");
22552                self.write_space();
22553                self.generate_expression(&limit.this)?;
22554                if limit.percent {
22555                    self.write_space();
22556                    self.write_keyword("PERCENT");
22557                }
22558            }
22559
22560            if let Some(offset) = &subquery.offset {
22561                self.write_space();
22562                self.write_keyword("OFFSET");
22563                self.write_space();
22564                self.generate_expression(&offset.this)?;
22565            }
22566
22567            // Generate DISTRIBUTE BY (Hive/Spark)
22568            if let Some(distribute_by) = &subquery.distribute_by {
22569                self.write_space();
22570                self.write_keyword("DISTRIBUTE BY");
22571                self.write_space();
22572                for (i, expr) in distribute_by.expressions.iter().enumerate() {
22573                    if i > 0 {
22574                        self.write(", ");
22575                    }
22576                    self.generate_expression(expr)?;
22577                }
22578            }
22579
22580            // Generate SORT BY (Hive/Spark)
22581            if let Some(sort_by) = &subquery.sort_by {
22582                self.write_space();
22583                self.write_keyword("SORT BY");
22584                self.write_space();
22585                for (i, ord) in sort_by.expressions.iter().enumerate() {
22586                    if i > 0 {
22587                        self.write(", ");
22588                    }
22589                    self.generate_ordered(ord)?;
22590                }
22591            }
22592
22593            // Generate CLUSTER BY (Hive/Spark)
22594            if let Some(cluster_by) = &subquery.cluster_by {
22595                self.write_space();
22596                self.write_keyword("CLUSTER BY");
22597                self.write_space();
22598                for (i, ord) in cluster_by.expressions.iter().enumerate() {
22599                    if i > 0 {
22600                        self.write(", ");
22601                    }
22602                    self.generate_ordered(ord)?;
22603                }
22604            }
22605        }
22606
22607        if let Some(alias) = &subquery.alias {
22608            self.write_space();
22609            // Oracle doesn't use AS for subquery aliases
22610            let skip_as = matches!(
22611                self.config.dialect,
22612                Some(crate::dialects::DialectType::Oracle)
22613            );
22614            if !skip_as {
22615                self.write_keyword("AS");
22616                self.write_space();
22617            }
22618            self.generate_identifier(alias)?;
22619            if !subquery.column_aliases.is_empty() {
22620                self.write("(");
22621                for (i, col) in subquery.column_aliases.iter().enumerate() {
22622                    if i > 0 {
22623                        self.write(", ");
22624                    }
22625                    self.generate_identifier(col)?;
22626                }
22627                self.write(")");
22628            }
22629        }
22630        // Output trailing comments
22631        for comment in &subquery.trailing_comments {
22632            self.write(" ");
22633            self.write_formatted_comment(comment);
22634        }
22635        Ok(())
22636    }
22637
22638    fn generate_pivot(&mut self, pivot: &Pivot) -> Result<()> {
22639        // Generate WITH clause if present
22640        if let Some(ref with) = pivot.with {
22641            self.generate_with(with)?;
22642            self.write_space();
22643        }
22644
22645        let direction = if pivot.unpivot { "UNPIVOT" } else { "PIVOT" };
22646
22647        // Check for Redshift UNPIVOT in FROM clause:
22648        // UNPIVOT expr [AS val AT attr]
22649        // This is when unpivot=true, expressions is empty, fields is empty, and this is not Null
22650        let is_redshift_unpivot = pivot.unpivot
22651            && pivot.expressions.is_empty()
22652            && pivot.fields.is_empty()
22653            && pivot.using.is_empty()
22654            && pivot.into.is_none()
22655            && !matches!(&pivot.this, Expression::Null(_));
22656
22657        if is_redshift_unpivot {
22658            // Redshift UNPIVOT: UNPIVOT expr [AS alias]
22659            self.write_keyword("UNPIVOT");
22660            self.write_space();
22661            self.generate_expression(&pivot.this)?;
22662            // Alias - for Redshift it can be "val AT attr" format
22663            if let Some(alias) = &pivot.alias {
22664                self.write_space();
22665                self.write_keyword("AS");
22666                self.write_space();
22667                // The alias might contain " AT " for the attr part
22668                self.write(&alias.name);
22669            }
22670            return Ok(());
22671        }
22672
22673        // Check if this is a DuckDB simplified pivot (has `using` or `into`, or no `fields`)
22674        let is_simplified = !pivot.using.is_empty()
22675            || pivot.into.is_some()
22676            || (pivot.fields.is_empty()
22677                && !pivot.expressions.is_empty()
22678                && !matches!(&pivot.this, Expression::Null(_)));
22679
22680        if is_simplified {
22681            // DuckDB simplified syntax:
22682            //   PIVOT table ON cols [IN (...)] USING agg [AS alias], ... [GROUP BY ...]
22683            //   UNPIVOT table ON cols INTO NAME col VALUE col
22684            self.write_keyword(direction);
22685            self.write_space();
22686            self.generate_expression(&pivot.this)?;
22687
22688            if !pivot.expressions.is_empty() {
22689                self.write_space();
22690                self.write_keyword("ON");
22691                self.write_space();
22692                for (i, expr) in pivot.expressions.iter().enumerate() {
22693                    if i > 0 {
22694                        self.write(", ");
22695                    }
22696                    self.generate_expression(expr)?;
22697                }
22698            }
22699
22700            // INTO (for UNPIVOT)
22701            if let Some(into) = &pivot.into {
22702                self.write_space();
22703                self.write_keyword("INTO");
22704                self.write_space();
22705                self.generate_expression(into)?;
22706            }
22707
22708            // USING (for PIVOT)
22709            if !pivot.using.is_empty() {
22710                self.write_space();
22711                self.write_keyword("USING");
22712                self.write_space();
22713                for (i, expr) in pivot.using.iter().enumerate() {
22714                    if i > 0 {
22715                        self.write(", ");
22716                    }
22717                    self.generate_expression(expr)?;
22718                }
22719            }
22720
22721            // GROUP BY
22722            if let Some(group) = &pivot.group {
22723                self.write_space();
22724                self.generate_expression(group)?;
22725            }
22726        } else {
22727            // Standard syntax:
22728            //   table PIVOT(agg [AS alias], ... FOR col IN (val [AS alias], ...) [GROUP BY ...])
22729            //   table UNPIVOT(value_col FOR name_col IN (col1, col2, ...))
22730            // Only output the table expression if it's not a Null (null is used when PIVOT comes after JOIN ON)
22731            if !matches!(&pivot.this, Expression::Null(_)) {
22732                self.generate_expression(&pivot.this)?;
22733                self.write_space();
22734            }
22735            self.write_keyword(direction);
22736            self.write("(");
22737
22738            // Aggregation expressions
22739            for (i, expr) in pivot.expressions.iter().enumerate() {
22740                if i > 0 {
22741                    self.write(", ");
22742                }
22743                self.generate_expression(expr)?;
22744            }
22745
22746            // FOR...IN fields
22747            if !pivot.fields.is_empty() {
22748                if !pivot.expressions.is_empty() {
22749                    self.write_space();
22750                }
22751                self.write_keyword("FOR");
22752                self.write_space();
22753                for (i, field) in pivot.fields.iter().enumerate() {
22754                    if i > 0 {
22755                        self.write_space();
22756                    }
22757                    // field is an In expression: column IN (values)
22758                    self.generate_expression(field)?;
22759                }
22760            }
22761
22762            // DEFAULT ON NULL
22763            if let Some(default_val) = &pivot.default_on_null {
22764                self.write_space();
22765                self.write_keyword("DEFAULT ON NULL");
22766                self.write(" (");
22767                self.generate_expression(default_val)?;
22768                self.write(")");
22769            }
22770
22771            // GROUP BY inside PIVOT parens
22772            if let Some(group) = &pivot.group {
22773                self.write_space();
22774                self.generate_expression(group)?;
22775            }
22776
22777            self.write(")");
22778        }
22779
22780        // Alias
22781        if let Some(alias) = &pivot.alias {
22782            self.write_space();
22783            self.write_keyword("AS");
22784            self.write_space();
22785            self.generate_identifier(alias)?;
22786        }
22787
22788        Ok(())
22789    }
22790
22791    fn generate_unpivot(&mut self, unpivot: &Unpivot) -> Result<()> {
22792        self.generate_expression(&unpivot.this)?;
22793        self.write_space();
22794        self.write_keyword("UNPIVOT");
22795        // Output INCLUDE NULLS or EXCLUDE NULLS if specified
22796        if let Some(include) = unpivot.include_nulls {
22797            self.write_space();
22798            if include {
22799                self.write_keyword("INCLUDE NULLS");
22800            } else {
22801                self.write_keyword("EXCLUDE NULLS");
22802            }
22803            self.write_space();
22804        }
22805        self.write("(");
22806        if unpivot.value_column_parenthesized {
22807            self.write("(");
22808        }
22809        self.generate_identifier(&unpivot.value_column)?;
22810        // Output additional value columns if present
22811        for extra_col in &unpivot.extra_value_columns {
22812            self.write(", ");
22813            self.generate_identifier(extra_col)?;
22814        }
22815        if unpivot.value_column_parenthesized {
22816            self.write(")");
22817        }
22818        self.write_space();
22819        self.write_keyword("FOR");
22820        self.write_space();
22821        self.generate_identifier(&unpivot.name_column)?;
22822        self.write_space();
22823        self.write_keyword("IN");
22824        self.write(" (");
22825        for (i, col) in unpivot.columns.iter().enumerate() {
22826            if i > 0 {
22827                self.write(", ");
22828            }
22829            self.generate_expression(col)?;
22830        }
22831        self.write("))");
22832        if let Some(alias) = &unpivot.alias {
22833            self.write_space();
22834            self.write_keyword("AS");
22835            self.write_space();
22836            self.generate_identifier(alias)?;
22837        }
22838        Ok(())
22839    }
22840
22841    fn generate_values(&mut self, values: &Values) -> Result<()> {
22842        self.write_keyword("VALUES");
22843        for (i, row) in values.expressions.iter().enumerate() {
22844            if i > 0 {
22845                self.write(",");
22846            }
22847            self.write(" (");
22848            for (j, expr) in row.expressions.iter().enumerate() {
22849                if j > 0 {
22850                    self.write(", ");
22851                }
22852                self.generate_expression(expr)?;
22853            }
22854            self.write(")");
22855        }
22856        if let Some(alias) = &values.alias {
22857            self.write_space();
22858            self.write_keyword("AS");
22859            self.write_space();
22860            self.generate_identifier(alias)?;
22861            if !values.column_aliases.is_empty() {
22862                self.write("(");
22863                for (i, col) in values.column_aliases.iter().enumerate() {
22864                    if i > 0 {
22865                        self.write(", ");
22866                    }
22867                    self.generate_identifier(col)?;
22868                }
22869                self.write(")");
22870            }
22871        }
22872        Ok(())
22873    }
22874
22875    fn generate_array(&mut self, arr: &Array) -> Result<()> {
22876        // Apply struct name inheritance for target dialects that need it
22877        let needs_inheritance = matches!(
22878            self.config.dialect,
22879            Some(DialectType::DuckDB)
22880                | Some(DialectType::Spark)
22881                | Some(DialectType::Databricks)
22882                | Some(DialectType::Hive)
22883                | Some(DialectType::Snowflake)
22884                | Some(DialectType::Presto)
22885                | Some(DialectType::Trino)
22886        );
22887        let propagated: Vec<Expression>;
22888        let expressions = if needs_inheritance && arr.expressions.len() > 1 {
22889            propagated = Self::inherit_struct_field_names(&arr.expressions);
22890            &propagated
22891        } else {
22892            &arr.expressions
22893        };
22894
22895        // Generic mode: ARRAY(1, 2, 3) with parentheses
22896        // Dialect mode: ARRAY[1, 2, 3] with brackets (or just [1, 2, 3] if array_bracket_only)
22897        let use_parens =
22898            self.config.dialect.is_none() || self.config.dialect == Some(DialectType::Generic);
22899        if !self.config.array_bracket_only {
22900            self.write_keyword("ARRAY");
22901        }
22902        if use_parens {
22903            self.write("(");
22904        } else {
22905            self.write("[");
22906        }
22907        for (i, expr) in expressions.iter().enumerate() {
22908            if i > 0 {
22909                self.write(", ");
22910            }
22911            self.generate_expression(expr)?;
22912        }
22913        if use_parens {
22914            self.write(")");
22915        } else {
22916            self.write("]");
22917        }
22918        Ok(())
22919    }
22920
22921    fn generate_tuple(&mut self, tuple: &Tuple) -> Result<()> {
22922        // Special case: Tuple(function/expr, TableAlias) pattern for table functions with typed aliases
22923        // Used for PostgreSQL functions like JSON_TO_RECORDSET: FUNC(args) AS alias(col1 type1, col2 type2)
22924        if tuple.expressions.len() == 2 {
22925            if let Expression::TableAlias(_) = &tuple.expressions[1] {
22926                // First element is the function/expression, second is the TableAlias
22927                self.generate_expression(&tuple.expressions[0])?;
22928                self.write_space();
22929                self.write_keyword("AS");
22930                self.write_space();
22931                self.generate_expression(&tuple.expressions[1])?;
22932                return Ok(());
22933            }
22934        }
22935
22936        // In pretty mode, format long tuples with each element on a new line
22937        // Only expand if total width exceeds threshold
22938        let expand_tuple = if self.config.pretty && tuple.expressions.len() > 1 {
22939            let mut expr_strings: Vec<String> = Vec::with_capacity(tuple.expressions.len());
22940            for expr in &tuple.expressions {
22941                expr_strings.push(self.generate_to_string(expr)?);
22942            }
22943            self.too_wide(&expr_strings)
22944        } else {
22945            false
22946        };
22947
22948        if expand_tuple {
22949            self.write("(");
22950            self.write_newline();
22951            self.indent_level += 1;
22952            for (i, expr) in tuple.expressions.iter().enumerate() {
22953                if i > 0 {
22954                    self.write(",");
22955                    self.write_newline();
22956                }
22957                self.write_indent();
22958                self.generate_expression(expr)?;
22959            }
22960            self.indent_level -= 1;
22961            self.write_newline();
22962            self.write_indent();
22963            self.write(")");
22964        } else {
22965            self.write("(");
22966            for (i, expr) in tuple.expressions.iter().enumerate() {
22967                if i > 0 {
22968                    self.write(", ");
22969                }
22970                self.generate_expression(expr)?;
22971            }
22972            self.write(")");
22973        }
22974        Ok(())
22975    }
22976
22977    fn generate_pipe_operator(&mut self, pipe: &PipeOperator) -> Result<()> {
22978        self.generate_expression(&pipe.this)?;
22979        self.write(" |> ");
22980        self.generate_expression(&pipe.expression)?;
22981        Ok(())
22982    }
22983
22984    fn generate_ordered(&mut self, ordered: &Ordered) -> Result<()> {
22985        self.generate_expression(&ordered.this)?;
22986        if ordered.desc {
22987            self.write_space();
22988            self.write_keyword("DESC");
22989        } else if ordered.explicit_asc {
22990            self.write_space();
22991            self.write_keyword("ASC");
22992        }
22993        if let Some(nulls_first) = ordered.nulls_first {
22994            if self.config.null_ordering_supported
22995                || !matches!(self.config.dialect, Some(DialectType::Fabric))
22996            {
22997                // Determine if we should skip outputting NULLS FIRST/LAST when it's the default
22998                // for the dialect. Different dialects have different NULL ordering defaults:
22999                //
23000                // nulls_are_large (Oracle, Postgres, Snowflake, etc.):
23001                //   - ASC: NULLS LAST is default (omit NULLS LAST for ASC)
23002                //   - DESC: NULLS FIRST is default (omit NULLS FIRST for DESC)
23003                //
23004                // nulls_are_small (Spark, Hive, BigQuery, most others):
23005                //   - ASC: NULLS FIRST is default
23006                //   - DESC: NULLS LAST is default
23007                //
23008                // nulls_are_last (DuckDB, Presto, Trino, Dremio, etc.):
23009                //   - NULLS LAST is always the default regardless of sort direction
23010                let is_asc = !ordered.desc;
23011                let is_nulls_are_large = matches!(
23012                    self.config.dialect,
23013                    Some(DialectType::Oracle)
23014                        | Some(DialectType::PostgreSQL)
23015                        | Some(DialectType::Redshift)
23016                        | Some(DialectType::Snowflake)
23017                );
23018                let is_nulls_are_last = matches!(
23019                    self.config.dialect,
23020                    Some(DialectType::Dremio)
23021                        | Some(DialectType::DuckDB)
23022                        | Some(DialectType::Presto)
23023                        | Some(DialectType::Trino)
23024                        | Some(DialectType::Athena)
23025                        | Some(DialectType::ClickHouse)
23026                        | Some(DialectType::Drill)
23027                        | Some(DialectType::Exasol)
23028                );
23029
23030                // Check if the NULLS ordering matches the default for this dialect
23031                let is_default_nulls = if is_nulls_are_large {
23032                    // For nulls_are_large: ASC + NULLS LAST or DESC + NULLS FIRST is default
23033                    (is_asc && !nulls_first) || (!is_asc && nulls_first)
23034                } else if is_nulls_are_last {
23035                    // For nulls_are_last: NULLS LAST is always default
23036                    !nulls_first
23037                } else {
23038                    false
23039                };
23040
23041                if !is_default_nulls {
23042                    self.write_space();
23043                    self.write_keyword("NULLS");
23044                    self.write_space();
23045                    self.write_keyword(if nulls_first { "FIRST" } else { "LAST" });
23046                }
23047            }
23048        }
23049        // WITH FILL clause (ClickHouse)
23050        if let Some(ref with_fill) = ordered.with_fill {
23051            self.write_space();
23052            self.generate_with_fill(with_fill)?;
23053        }
23054        Ok(())
23055    }
23056
23057    /// Write a ClickHouse type string, wrapping in Nullable unless in map key context.
23058    fn write_clickhouse_type(&mut self, type_str: &str) {
23059        if self.clickhouse_nullable_depth < 0 {
23060            // Map key context: don't wrap in Nullable
23061            self.write(type_str);
23062        } else {
23063            self.write(&format!("Nullable({})", type_str));
23064        }
23065    }
23066
23067    fn generate_data_type(&mut self, dt: &DataType) -> Result<()> {
23068        use crate::dialects::DialectType;
23069
23070        match dt {
23071            DataType::Boolean => {
23072                // Dialect-specific boolean type mappings
23073                match self.config.dialect {
23074                    Some(DialectType::TSQL) => self.write_keyword("BIT"),
23075                    Some(DialectType::MySQL) => self.write_keyword("BOOLEAN"), // alias for TINYINT(1)
23076                    Some(DialectType::Oracle) => {
23077                        // Oracle 23c+ supports BOOLEAN, older versions use NUMBER(1)
23078                        self.write_keyword("NUMBER(1)")
23079                    }
23080                    Some(DialectType::ClickHouse) => self.write("Bool"), // ClickHouse uses Bool (case-sensitive)
23081                    _ => self.write_keyword("BOOLEAN"),
23082                }
23083            }
23084            DataType::TinyInt { length } => {
23085                // PostgreSQL, Oracle, and Exasol don't have TINYINT, use SMALLINT
23086                // Dremio maps TINYINT to INT
23087                // ClickHouse maps TINYINT to Int8
23088                match self.config.dialect {
23089                    Some(DialectType::PostgreSQL)
23090                    | Some(DialectType::Redshift)
23091                    | Some(DialectType::Oracle)
23092                    | Some(DialectType::Exasol) => {
23093                        self.write_keyword("SMALLINT");
23094                    }
23095                    Some(DialectType::Teradata) => {
23096                        // Teradata uses BYTEINT for smallest integer
23097                        self.write_keyword("BYTEINT");
23098                    }
23099                    Some(DialectType::Dremio) => {
23100                        // Dremio maps TINYINT to INT
23101                        self.write_keyword("INT");
23102                    }
23103                    Some(DialectType::ClickHouse) => {
23104                        self.write_clickhouse_type("Int8");
23105                    }
23106                    _ => {
23107                        self.write_keyword("TINYINT");
23108                    }
23109                }
23110                if let Some(n) = length {
23111                    if !matches!(
23112                        self.config.dialect,
23113                        Some(DialectType::Dremio) | Some(DialectType::ClickHouse)
23114                    ) {
23115                        self.write(&format!("({})", n));
23116                    }
23117                }
23118            }
23119            DataType::SmallInt { length } => {
23120                // Dremio maps SMALLINT to INT, SQLite/Drill maps SMALLINT to INTEGER
23121                match self.config.dialect {
23122                    Some(DialectType::Dremio) => {
23123                        self.write_keyword("INT");
23124                    }
23125                    Some(DialectType::SQLite) | Some(DialectType::Drill) => {
23126                        self.write_keyword("INTEGER");
23127                    }
23128                    Some(DialectType::BigQuery) => {
23129                        self.write_keyword("INT64");
23130                    }
23131                    Some(DialectType::ClickHouse) => {
23132                        self.write_clickhouse_type("Int16");
23133                    }
23134                    _ => {
23135                        self.write_keyword("SMALLINT");
23136                        if let Some(n) = length {
23137                            self.write(&format!("({})", n));
23138                        }
23139                    }
23140                }
23141            }
23142            DataType::Int {
23143                length,
23144                integer_spelling: _,
23145            } => {
23146                // BigQuery uses INT64 for INT
23147                if matches!(self.config.dialect, Some(DialectType::BigQuery)) {
23148                    self.write_keyword("INT64");
23149                } else if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
23150                    self.write_clickhouse_type("Int32");
23151                } else {
23152                    // TSQL, Presto, Trino, SQLite, Redshift use INTEGER as the canonical form
23153                    let use_integer = match self.config.dialect {
23154                        Some(DialectType::TSQL)
23155                        | Some(DialectType::Fabric)
23156                        | Some(DialectType::Presto)
23157                        | Some(DialectType::Trino)
23158                        | Some(DialectType::SQLite)
23159                        | Some(DialectType::Redshift) => true,
23160                        _ => false,
23161                    };
23162                    if use_integer {
23163                        self.write_keyword("INTEGER");
23164                    } else {
23165                        self.write_keyword("INT");
23166                    }
23167                    if let Some(n) = length {
23168                        self.write(&format!("({})", n));
23169                    }
23170                }
23171            }
23172            DataType::BigInt { length } => {
23173                // Dialect-specific bigint type mappings
23174                match self.config.dialect {
23175                    Some(DialectType::Oracle) => {
23176                        // Oracle doesn't have BIGINT, uses INT
23177                        self.write_keyword("INT");
23178                    }
23179                    Some(DialectType::ClickHouse) => {
23180                        self.write_clickhouse_type("Int64");
23181                    }
23182                    _ => {
23183                        self.write_keyword("BIGINT");
23184                        if let Some(n) = length {
23185                            self.write(&format!("({})", n));
23186                        }
23187                    }
23188                }
23189            }
23190            DataType::Float {
23191                precision,
23192                scale,
23193                real_spelling,
23194            } => {
23195                // Dialect-specific float type mappings
23196                // If real_spelling is true, preserve REAL; otherwise use dialect default
23197                // Spark/Hive don't support REAL, always use FLOAT
23198                if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
23199                    self.write_clickhouse_type("Float32");
23200                } else if *real_spelling
23201                    && !matches!(
23202                        self.config.dialect,
23203                        Some(DialectType::Spark)
23204                            | Some(DialectType::Databricks)
23205                            | Some(DialectType::Hive)
23206                            | Some(DialectType::Snowflake)
23207                            | Some(DialectType::MySQL)
23208                            | Some(DialectType::BigQuery)
23209                    )
23210                {
23211                    self.write_keyword("REAL")
23212                } else {
23213                    match self.config.dialect {
23214                        Some(DialectType::PostgreSQL) => self.write_keyword("REAL"),
23215                        Some(DialectType::BigQuery) => self.write_keyword("FLOAT64"),
23216                        _ => self.write_keyword("FLOAT"),
23217                    }
23218                }
23219                // MySQL supports FLOAT(precision) or FLOAT(precision, scale)
23220                // Spark/Hive don't support FLOAT(precision)
23221                if !matches!(
23222                    self.config.dialect,
23223                    Some(DialectType::Spark)
23224                        | Some(DialectType::Databricks)
23225                        | Some(DialectType::Hive)
23226                        | Some(DialectType::Presto)
23227                        | Some(DialectType::Trino)
23228                ) {
23229                    if let Some(p) = precision {
23230                        self.write(&format!("({}", p));
23231                        if let Some(s) = scale {
23232                            self.write(&format!(", {})", s));
23233                        } else {
23234                            self.write(")");
23235                        }
23236                    }
23237                }
23238            }
23239            DataType::Double { precision, scale } => {
23240                // Dialect-specific double type mappings
23241                match self.config.dialect {
23242                    Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
23243                        self.write_keyword("FLOAT")
23244                    } // SQL Server/Fabric FLOAT is double
23245                    Some(DialectType::Oracle) => self.write_keyword("DOUBLE PRECISION"),
23246                    Some(DialectType::ClickHouse) => self.write_clickhouse_type("Float64"),
23247                    Some(DialectType::BigQuery) => self.write_keyword("FLOAT64"),
23248                    Some(DialectType::SQLite) => self.write_keyword("REAL"),
23249                    Some(DialectType::PostgreSQL)
23250                    | Some(DialectType::Redshift)
23251                    | Some(DialectType::Teradata)
23252                    | Some(DialectType::Materialize) => self.write_keyword("DOUBLE PRECISION"),
23253                    _ => self.write_keyword("DOUBLE"),
23254                }
23255                // MySQL supports DOUBLE(precision, scale)
23256                if let Some(p) = precision {
23257                    self.write(&format!("({}", p));
23258                    if let Some(s) = scale {
23259                        self.write(&format!(", {})", s));
23260                    } else {
23261                        self.write(")");
23262                    }
23263                }
23264            }
23265            DataType::Decimal { precision, scale } => {
23266                // Dialect-specific decimal type mappings
23267                match self.config.dialect {
23268                    Some(DialectType::ClickHouse) => {
23269                        self.write("Decimal");
23270                        if let Some(p) = precision {
23271                            self.write(&format!("({}", p));
23272                            if let Some(s) = scale {
23273                                self.write(&format!(", {}", s));
23274                            }
23275                            self.write(")");
23276                        }
23277                    }
23278                    Some(DialectType::Oracle) => {
23279                        // Oracle uses NUMBER instead of DECIMAL
23280                        self.write_keyword("NUMBER");
23281                        if let Some(p) = precision {
23282                            self.write(&format!("({}", p));
23283                            if let Some(s) = scale {
23284                                self.write(&format!(", {}", s));
23285                            }
23286                            self.write(")");
23287                        }
23288                    }
23289                    Some(DialectType::BigQuery) => {
23290                        // BigQuery uses NUMERIC instead of DECIMAL
23291                        self.write_keyword("NUMERIC");
23292                        if let Some(p) = precision {
23293                            self.write(&format!("({}", p));
23294                            if let Some(s) = scale {
23295                                self.write(&format!(", {}", s));
23296                            }
23297                            self.write(")");
23298                        }
23299                    }
23300                    _ => {
23301                        self.write_keyword("DECIMAL");
23302                        if let Some(p) = precision {
23303                            self.write(&format!("({}", p));
23304                            if let Some(s) = scale {
23305                                self.write(&format!(", {}", s));
23306                            }
23307                            self.write(")");
23308                        }
23309                    }
23310                }
23311            }
23312            DataType::Char { length } => {
23313                // Dialect-specific char type mappings
23314                match self.config.dialect {
23315                    Some(DialectType::DuckDB) | Some(DialectType::SQLite) => {
23316                        // DuckDB/SQLite maps CHAR to TEXT
23317                        self.write_keyword("TEXT");
23318                    }
23319                    Some(DialectType::Hive)
23320                    | Some(DialectType::Spark)
23321                    | Some(DialectType::Databricks) => {
23322                        // Hive/Spark/Databricks maps CHAR to STRING (when no length)
23323                        // CHAR(n) with explicit length is kept as CHAR(n) for Spark/Databricks
23324                        if length.is_some()
23325                            && !matches!(self.config.dialect, Some(DialectType::Hive))
23326                        {
23327                            self.write_keyword("CHAR");
23328                            if let Some(n) = length {
23329                                self.write(&format!("({})", n));
23330                            }
23331                        } else {
23332                            self.write_keyword("STRING");
23333                        }
23334                    }
23335                    Some(DialectType::Dremio) => {
23336                        // Dremio maps CHAR to VARCHAR
23337                        self.write_keyword("VARCHAR");
23338                        if let Some(n) = length {
23339                            self.write(&format!("({})", n));
23340                        }
23341                    }
23342                    _ => {
23343                        self.write_keyword("CHAR");
23344                        if let Some(n) = length {
23345                            self.write(&format!("({})", n));
23346                        }
23347                    }
23348                }
23349            }
23350            DataType::VarChar {
23351                length,
23352                parenthesized_length,
23353            } => {
23354                // Dialect-specific varchar type mappings
23355                match self.config.dialect {
23356                    Some(DialectType::Oracle) => {
23357                        self.write_keyword("VARCHAR2");
23358                        if let Some(n) = length {
23359                            self.write(&format!("({})", n));
23360                        }
23361                    }
23362                    Some(DialectType::DuckDB) => {
23363                        // DuckDB maps VARCHAR to TEXT, preserving length
23364                        self.write_keyword("TEXT");
23365                        if let Some(n) = length {
23366                            self.write(&format!("({})", n));
23367                        }
23368                    }
23369                    Some(DialectType::SQLite) => {
23370                        // SQLite maps VARCHAR to TEXT, preserving length
23371                        self.write_keyword("TEXT");
23372                        if let Some(n) = length {
23373                            self.write(&format!("({})", n));
23374                        }
23375                    }
23376                    Some(DialectType::MySQL) if length.is_none() => {
23377                        // MySQL requires VARCHAR to have a size - if it doesn't, use TEXT
23378                        self.write_keyword("TEXT");
23379                    }
23380                    Some(DialectType::Hive)
23381                    | Some(DialectType::Spark)
23382                    | Some(DialectType::Databricks)
23383                        if length.is_none() =>
23384                    {
23385                        // Hive/Spark/Databricks: VARCHAR without length → STRING
23386                        self.write_keyword("STRING");
23387                    }
23388                    _ => {
23389                        self.write_keyword("VARCHAR");
23390                        if let Some(n) = length {
23391                            // Hive uses VARCHAR((n)) with extra parentheses in STRUCT definitions
23392                            if *parenthesized_length {
23393                                self.write(&format!("(({}))", n));
23394                            } else {
23395                                self.write(&format!("({})", n));
23396                            }
23397                        }
23398                    }
23399                }
23400            }
23401            DataType::Text => {
23402                // Dialect-specific text type mappings
23403                match self.config.dialect {
23404                    Some(DialectType::Oracle) => self.write_keyword("CLOB"),
23405                    Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
23406                        self.write_keyword("VARCHAR(MAX)")
23407                    }
23408                    Some(DialectType::BigQuery) => self.write_keyword("STRING"),
23409                    Some(DialectType::Snowflake)
23410                    | Some(DialectType::Dremio)
23411                    | Some(DialectType::Drill) => self.write_keyword("VARCHAR"),
23412                    Some(DialectType::Exasol) => self.write_keyword("LONG VARCHAR"),
23413                    Some(DialectType::Presto)
23414                    | Some(DialectType::Trino)
23415                    | Some(DialectType::Athena) => self.write_keyword("VARCHAR"),
23416                    Some(DialectType::Spark)
23417                    | Some(DialectType::Databricks)
23418                    | Some(DialectType::Hive) => self.write_keyword("STRING"),
23419                    Some(DialectType::Redshift) => self.write_keyword("VARCHAR(MAX)"),
23420                    Some(DialectType::StarRocks) | Some(DialectType::Doris) => {
23421                        self.write_keyword("STRING")
23422                    }
23423                    Some(DialectType::ClickHouse) => self.write_clickhouse_type("String"),
23424                    _ => self.write_keyword("TEXT"),
23425                }
23426            }
23427            DataType::TextWithLength { length } => {
23428                // TEXT(n) - dialect-specific type with length
23429                match self.config.dialect {
23430                    Some(DialectType::Oracle) => self.write(&format!("CLOB({})", length)),
23431                    Some(DialectType::Hive)
23432                    | Some(DialectType::Spark)
23433                    | Some(DialectType::Databricks) => {
23434                        self.write(&format!("VARCHAR({})", length));
23435                    }
23436                    Some(DialectType::Redshift) => self.write(&format!("VARCHAR({})", length)),
23437                    Some(DialectType::BigQuery) => self.write(&format!("STRING({})", length)),
23438                    Some(DialectType::Snowflake)
23439                    | Some(DialectType::Presto)
23440                    | Some(DialectType::Trino)
23441                    | Some(DialectType::Athena)
23442                    | Some(DialectType::Drill)
23443                    | Some(DialectType::Dremio) => {
23444                        self.write(&format!("VARCHAR({})", length));
23445                    }
23446                    Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
23447                        self.write(&format!("VARCHAR({})", length))
23448                    }
23449                    Some(DialectType::StarRocks) | Some(DialectType::Doris) => {
23450                        self.write(&format!("STRING({})", length))
23451                    }
23452                    Some(DialectType::ClickHouse) => self.write_clickhouse_type("String"),
23453                    _ => self.write(&format!("TEXT({})", length)),
23454                }
23455            }
23456            DataType::String { length } => {
23457                // STRING type with optional length (BigQuery STRING(n))
23458                match self.config.dialect {
23459                    Some(DialectType::ClickHouse) => {
23460                        // ClickHouse uses String with specific casing
23461                        self.write("String");
23462                        if let Some(n) = length {
23463                            self.write(&format!("({})", n));
23464                        }
23465                    }
23466                    Some(DialectType::BigQuery)
23467                    | Some(DialectType::Hive)
23468                    | Some(DialectType::Spark)
23469                    | Some(DialectType::Databricks)
23470                    | Some(DialectType::StarRocks)
23471                    | Some(DialectType::Doris) => {
23472                        self.write_keyword("STRING");
23473                        if let Some(n) = length {
23474                            self.write(&format!("({})", n));
23475                        }
23476                    }
23477                    Some(DialectType::PostgreSQL) => {
23478                        // PostgreSQL doesn't have STRING - use VARCHAR or TEXT
23479                        if let Some(n) = length {
23480                            self.write_keyword("VARCHAR");
23481                            self.write(&format!("({})", n));
23482                        } else {
23483                            self.write_keyword("TEXT");
23484                        }
23485                    }
23486                    Some(DialectType::Redshift) => {
23487                        // Redshift: STRING -> VARCHAR(MAX)
23488                        if let Some(n) = length {
23489                            self.write_keyword("VARCHAR");
23490                            self.write(&format!("({})", n));
23491                        } else {
23492                            self.write_keyword("VARCHAR(MAX)");
23493                        }
23494                    }
23495                    Some(DialectType::MySQL) => {
23496                        // MySQL doesn't have STRING - use VARCHAR or TEXT
23497                        if let Some(n) = length {
23498                            self.write_keyword("VARCHAR");
23499                            self.write(&format!("({})", n));
23500                        } else {
23501                            self.write_keyword("TEXT");
23502                        }
23503                    }
23504                    Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
23505                        // TSQL: STRING -> VARCHAR(MAX)
23506                        if let Some(n) = length {
23507                            self.write_keyword("VARCHAR");
23508                            self.write(&format!("({})", n));
23509                        } else {
23510                            self.write_keyword("VARCHAR(MAX)");
23511                        }
23512                    }
23513                    Some(DialectType::Oracle) => {
23514                        // Oracle: STRING -> CLOB
23515                        self.write_keyword("CLOB");
23516                    }
23517                    Some(DialectType::DuckDB) | Some(DialectType::Materialize) => {
23518                        // DuckDB/Materialize uses TEXT for string types
23519                        self.write_keyword("TEXT");
23520                        if let Some(n) = length {
23521                            self.write(&format!("({})", n));
23522                        }
23523                    }
23524                    Some(DialectType::Presto)
23525                    | Some(DialectType::Trino)
23526                    | Some(DialectType::Drill)
23527                    | Some(DialectType::Dremio) => {
23528                        // Presto/Trino/Drill use VARCHAR for string types
23529                        self.write_keyword("VARCHAR");
23530                        if let Some(n) = length {
23531                            self.write(&format!("({})", n));
23532                        }
23533                    }
23534                    Some(DialectType::Snowflake) => {
23535                        // Snowflake: STRING stays as STRING (identity/DDL)
23536                        // CAST context STRING -> VARCHAR is handled in generate_cast
23537                        self.write_keyword("STRING");
23538                        if let Some(n) = length {
23539                            self.write(&format!("({})", n));
23540                        }
23541                    }
23542                    _ => {
23543                        // Default: output STRING with optional length
23544                        self.write_keyword("STRING");
23545                        if let Some(n) = length {
23546                            self.write(&format!("({})", n));
23547                        }
23548                    }
23549                }
23550            }
23551            DataType::Binary { length } => {
23552                // Dialect-specific binary type mappings
23553                match self.config.dialect {
23554                    Some(DialectType::PostgreSQL) | Some(DialectType::Materialize) => {
23555                        self.write_keyword("BYTEA");
23556                        if let Some(n) = length {
23557                            self.write(&format!("({})", n));
23558                        }
23559                    }
23560                    Some(DialectType::Redshift) => {
23561                        self.write_keyword("VARBYTE");
23562                        if let Some(n) = length {
23563                            self.write(&format!("({})", n));
23564                        }
23565                    }
23566                    Some(DialectType::DuckDB)
23567                    | Some(DialectType::SQLite)
23568                    | Some(DialectType::Oracle) => {
23569                        // DuckDB/SQLite/Oracle maps BINARY to BLOB
23570                        self.write_keyword("BLOB");
23571                        if let Some(n) = length {
23572                            self.write(&format!("({})", n));
23573                        }
23574                    }
23575                    Some(DialectType::Presto)
23576                    | Some(DialectType::Trino)
23577                    | Some(DialectType::Athena)
23578                    | Some(DialectType::Drill)
23579                    | Some(DialectType::Dremio) => {
23580                        // These dialects map BINARY to VARBINARY
23581                        self.write_keyword("VARBINARY");
23582                        if let Some(n) = length {
23583                            self.write(&format!("({})", n));
23584                        }
23585                    }
23586                    Some(DialectType::ClickHouse) => {
23587                        // ClickHouse: wrap BINARY in Nullable (unless map key context)
23588                        if self.clickhouse_nullable_depth < 0 {
23589                            self.write("BINARY");
23590                        } else {
23591                            self.write("Nullable(BINARY");
23592                        }
23593                        if let Some(n) = length {
23594                            self.write(&format!("({})", n));
23595                        }
23596                        if self.clickhouse_nullable_depth >= 0 {
23597                            self.write(")");
23598                        }
23599                    }
23600                    _ => {
23601                        self.write_keyword("BINARY");
23602                        if let Some(n) = length {
23603                            self.write(&format!("({})", n));
23604                        }
23605                    }
23606                }
23607            }
23608            DataType::VarBinary { length } => {
23609                // Dialect-specific varbinary type mappings
23610                match self.config.dialect {
23611                    Some(DialectType::PostgreSQL) | Some(DialectType::Materialize) => {
23612                        self.write_keyword("BYTEA");
23613                        if let Some(n) = length {
23614                            self.write(&format!("({})", n));
23615                        }
23616                    }
23617                    Some(DialectType::Redshift) => {
23618                        self.write_keyword("VARBYTE");
23619                        if let Some(n) = length {
23620                            self.write(&format!("({})", n));
23621                        }
23622                    }
23623                    Some(DialectType::DuckDB)
23624                    | Some(DialectType::SQLite)
23625                    | Some(DialectType::Oracle) => {
23626                        // DuckDB/SQLite/Oracle maps VARBINARY to BLOB
23627                        self.write_keyword("BLOB");
23628                        if let Some(n) = length {
23629                            self.write(&format!("({})", n));
23630                        }
23631                    }
23632                    Some(DialectType::Exasol) => {
23633                        // Exasol maps VARBINARY to VARCHAR
23634                        self.write_keyword("VARCHAR");
23635                    }
23636                    Some(DialectType::Spark)
23637                    | Some(DialectType::Hive)
23638                    | Some(DialectType::Databricks) => {
23639                        // Spark/Hive use BINARY instead of VARBINARY
23640                        self.write_keyword("BINARY");
23641                        if let Some(n) = length {
23642                            self.write(&format!("({})", n));
23643                        }
23644                    }
23645                    Some(DialectType::ClickHouse) => {
23646                        // ClickHouse maps VARBINARY to String (wrapped in Nullable unless map key)
23647                        self.write_clickhouse_type("String");
23648                    }
23649                    _ => {
23650                        self.write_keyword("VARBINARY");
23651                        if let Some(n) = length {
23652                            self.write(&format!("({})", n));
23653                        }
23654                    }
23655                }
23656            }
23657            DataType::Blob => {
23658                // Dialect-specific blob type mappings
23659                match self.config.dialect {
23660                    Some(DialectType::PostgreSQL) => self.write_keyword("BYTEA"),
23661                    Some(DialectType::Redshift) => self.write_keyword("VARBYTE"),
23662                    Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
23663                        self.write_keyword("VARBINARY")
23664                    }
23665                    Some(DialectType::BigQuery) => self.write_keyword("BYTES"),
23666                    Some(DialectType::Exasol) => self.write_keyword("VARCHAR"),
23667                    Some(DialectType::Presto)
23668                    | Some(DialectType::Trino)
23669                    | Some(DialectType::Athena) => self.write_keyword("VARBINARY"),
23670                    Some(DialectType::DuckDB) => {
23671                        // Python sqlglot: BLOB -> VARBINARY for DuckDB (base TYPE_MAPPING)
23672                        // DuckDB identity works via: BLOB -> transform VarBinary -> generator BLOB
23673                        self.write_keyword("VARBINARY");
23674                    }
23675                    Some(DialectType::Spark)
23676                    | Some(DialectType::Databricks)
23677                    | Some(DialectType::Hive) => self.write_keyword("BINARY"),
23678                    Some(DialectType::ClickHouse) => {
23679                        // BLOB maps to Nullable(String) in ClickHouse, even in column defs
23680                        // where we normally suppress Nullable wrapping (clickhouse_nullable_depth = -1).
23681                        // This matches Python sqlglot behavior.
23682                        self.write("Nullable(String)");
23683                    }
23684                    _ => self.write_keyword("BLOB"),
23685                }
23686            }
23687            DataType::Bit { length } => {
23688                // Dialect-specific bit type mappings
23689                match self.config.dialect {
23690                    Some(DialectType::Dremio)
23691                    | Some(DialectType::Spark)
23692                    | Some(DialectType::Databricks)
23693                    | Some(DialectType::Hive)
23694                    | Some(DialectType::Snowflake)
23695                    | Some(DialectType::BigQuery)
23696                    | Some(DialectType::Presto)
23697                    | Some(DialectType::Trino)
23698                    | Some(DialectType::ClickHouse)
23699                    | Some(DialectType::Redshift) => {
23700                        // These dialects don't support BIT type, use BOOLEAN
23701                        self.write_keyword("BOOLEAN");
23702                    }
23703                    _ => {
23704                        self.write_keyword("BIT");
23705                        if let Some(n) = length {
23706                            self.write(&format!("({})", n));
23707                        }
23708                    }
23709                }
23710            }
23711            DataType::VarBit { length } => {
23712                self.write_keyword("VARBIT");
23713                if let Some(n) = length {
23714                    self.write(&format!("({})", n));
23715                }
23716            }
23717            DataType::Date => self.write_keyword("DATE"),
23718            DataType::Time {
23719                precision,
23720                timezone,
23721            } => {
23722                if *timezone {
23723                    // Dialect-specific TIME WITH TIME ZONE output
23724                    match self.config.dialect {
23725                        Some(DialectType::DuckDB) => {
23726                            // DuckDB: TIMETZ (drops precision)
23727                            self.write_keyword("TIMETZ");
23728                        }
23729                        Some(DialectType::PostgreSQL) => {
23730                            // PostgreSQL: TIMETZ or TIMETZ(p)
23731                            self.write_keyword("TIMETZ");
23732                            if let Some(p) = precision {
23733                                self.write(&format!("({})", p));
23734                            }
23735                        }
23736                        _ => {
23737                            // Presto/Trino/Redshift/others: TIME(p) WITH TIME ZONE
23738                            self.write_keyword("TIME");
23739                            if let Some(p) = precision {
23740                                self.write(&format!("({})", p));
23741                            }
23742                            self.write_keyword(" WITH TIME ZONE");
23743                        }
23744                    }
23745                } else {
23746                    // Spark/Hive/Databricks: TIME -> TIMESTAMP (TIME not supported)
23747                    if matches!(
23748                        self.config.dialect,
23749                        Some(DialectType::Spark)
23750                            | Some(DialectType::Databricks)
23751                            | Some(DialectType::Hive)
23752                    ) {
23753                        self.write_keyword("TIMESTAMP");
23754                    } else {
23755                        self.write_keyword("TIME");
23756                        if let Some(p) = precision {
23757                            self.write(&format!("({})", p));
23758                        }
23759                    }
23760                }
23761            }
23762            DataType::Timestamp {
23763                precision,
23764                timezone,
23765            } => {
23766                // Dialect-specific timestamp type mappings
23767                match self.config.dialect {
23768                    Some(DialectType::ClickHouse) => {
23769                        self.write("DateTime");
23770                        if let Some(p) = precision {
23771                            self.write(&format!("({})", p));
23772                        }
23773                    }
23774                    Some(DialectType::TSQL) => {
23775                        if *timezone {
23776                            self.write_keyword("DATETIMEOFFSET");
23777                        } else {
23778                            self.write_keyword("DATETIME2");
23779                        }
23780                        if let Some(p) = precision {
23781                            self.write(&format!("({})", p));
23782                        }
23783                    }
23784                    Some(DialectType::MySQL) => {
23785                        // MySQL: TIMESTAMP stays as TIMESTAMP in DDL; CAST mapping handled separately
23786                        self.write_keyword("TIMESTAMP");
23787                        if let Some(p) = precision {
23788                            self.write(&format!("({})", p));
23789                        }
23790                    }
23791                    Some(DialectType::Doris) | Some(DialectType::StarRocks) => {
23792                        // Doris/StarRocks: TIMESTAMP -> DATETIME
23793                        self.write_keyword("DATETIME");
23794                        if let Some(p) = precision {
23795                            self.write(&format!("({})", p));
23796                        }
23797                    }
23798                    Some(DialectType::BigQuery) => {
23799                        // BigQuery: TIMESTAMP is always UTC, DATETIME is timezone-naive
23800                        if *timezone {
23801                            self.write_keyword("TIMESTAMP");
23802                        } else {
23803                            self.write_keyword("DATETIME");
23804                        }
23805                    }
23806                    Some(DialectType::DuckDB) => {
23807                        // DuckDB: TIMESTAMPTZ shorthand
23808                        if *timezone {
23809                            self.write_keyword("TIMESTAMPTZ");
23810                        } else {
23811                            self.write_keyword("TIMESTAMP");
23812                            if let Some(p) = precision {
23813                                self.write(&format!("({})", p));
23814                            }
23815                        }
23816                    }
23817                    _ => {
23818                        if *timezone && !self.config.tz_to_with_time_zone {
23819                            // Use TIMESTAMPTZ shorthand when dialect doesn't prefer WITH TIME ZONE
23820                            self.write_keyword("TIMESTAMPTZ");
23821                            if let Some(p) = precision {
23822                                self.write(&format!("({})", p));
23823                            }
23824                        } else {
23825                            self.write_keyword("TIMESTAMP");
23826                            if let Some(p) = precision {
23827                                self.write(&format!("({})", p));
23828                            }
23829                            if *timezone {
23830                                self.write_space();
23831                                self.write_keyword("WITH TIME ZONE");
23832                            }
23833                        }
23834                    }
23835                }
23836            }
23837            DataType::Interval { unit, to } => {
23838                self.write_keyword("INTERVAL");
23839                if let Some(u) = unit {
23840                    self.write_space();
23841                    self.write_keyword(u);
23842                }
23843                // Handle range intervals like DAY TO HOUR
23844                if let Some(t) = to {
23845                    self.write_space();
23846                    self.write_keyword("TO");
23847                    self.write_space();
23848                    self.write_keyword(t);
23849                }
23850            }
23851            DataType::Json => {
23852                // Dialect-specific JSON type mappings
23853                match self.config.dialect {
23854                    Some(DialectType::Oracle) => self.write_keyword("JSON"), // Oracle 21c+
23855                    Some(DialectType::TSQL) => self.write_keyword("NVARCHAR(MAX)"), // No native JSON type
23856                    Some(DialectType::MySQL) => self.write_keyword("JSON"),
23857                    Some(DialectType::Snowflake) => self.write_keyword("VARIANT"),
23858                    _ => self.write_keyword("JSON"),
23859                }
23860            }
23861            DataType::JsonB => {
23862                // JSONB is PostgreSQL specific, but Doris also supports it
23863                match self.config.dialect {
23864                    Some(DialectType::PostgreSQL) => self.write_keyword("JSONB"),
23865                    Some(DialectType::Doris) => self.write_keyword("JSONB"),
23866                    Some(DialectType::Snowflake) => self.write_keyword("VARIANT"),
23867                    Some(DialectType::TSQL) => self.write_keyword("NVARCHAR(MAX)"),
23868                    Some(DialectType::DuckDB) => self.write_keyword("JSON"), // DuckDB maps JSONB to JSON
23869                    _ => self.write_keyword("JSON"), // Fall back to JSON for other dialects
23870                }
23871            }
23872            DataType::Uuid => {
23873                // Dialect-specific UUID type mappings
23874                match self.config.dialect {
23875                    Some(DialectType::TSQL) => self.write_keyword("UNIQUEIDENTIFIER"),
23876                    Some(DialectType::MySQL) => self.write_keyword("CHAR(36)"),
23877                    Some(DialectType::Oracle) => self.write_keyword("RAW(16)"),
23878                    Some(DialectType::BigQuery)
23879                    | Some(DialectType::Spark)
23880                    | Some(DialectType::Databricks) => self.write_keyword("STRING"),
23881                    _ => self.write_keyword("UUID"),
23882                }
23883            }
23884            DataType::Array {
23885                element_type,
23886                dimension,
23887            } => {
23888                // Dialect-specific array syntax
23889                match self.config.dialect {
23890                    Some(DialectType::PostgreSQL)
23891                    | Some(DialectType::Redshift)
23892                    | Some(DialectType::DuckDB) => {
23893                        // PostgreSQL uses TYPE[] or TYPE[N] syntax
23894                        self.generate_data_type(element_type)?;
23895                        if let Some(dim) = dimension {
23896                            self.write(&format!("[{}]", dim));
23897                        } else {
23898                            self.write("[]");
23899                        }
23900                    }
23901                    Some(DialectType::BigQuery) => {
23902                        self.write_keyword("ARRAY<");
23903                        self.generate_data_type(element_type)?;
23904                        self.write(">");
23905                    }
23906                    Some(DialectType::Snowflake)
23907                    | Some(DialectType::Presto)
23908                    | Some(DialectType::Trino)
23909                    | Some(DialectType::ClickHouse) => {
23910                        // These dialects use Array(TYPE) parentheses syntax
23911                        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
23912                            self.write("Array(");
23913                        } else {
23914                            self.write_keyword("ARRAY(");
23915                        }
23916                        self.generate_data_type(element_type)?;
23917                        self.write(")");
23918                    }
23919                    Some(DialectType::TSQL)
23920                    | Some(DialectType::MySQL)
23921                    | Some(DialectType::Oracle) => {
23922                        // These dialects don't have native array types
23923                        // Fall back to JSON or use native workarounds
23924                        match self.config.dialect {
23925                            Some(DialectType::MySQL) => self.write_keyword("JSON"),
23926                            Some(DialectType::TSQL) => self.write_keyword("NVARCHAR(MAX)"),
23927                            _ => self.write_keyword("JSON"),
23928                        }
23929                    }
23930                    _ => {
23931                        // Default: use angle bracket syntax (ARRAY<T>)
23932                        self.write_keyword("ARRAY<");
23933                        self.generate_data_type(element_type)?;
23934                        self.write(">");
23935                    }
23936                }
23937            }
23938            DataType::List { element_type } => {
23939                // Materialize: element_type LIST (postfix syntax)
23940                self.generate_data_type(element_type)?;
23941                self.write_keyword(" LIST");
23942            }
23943            DataType::Map {
23944                key_type,
23945                value_type,
23946            } => {
23947                // Use parentheses for Snowflake and RisingWave, bracket syntax for Materialize, angle brackets for others
23948                match self.config.dialect {
23949                    Some(DialectType::Materialize) => {
23950                        // Materialize: MAP[key_type => value_type]
23951                        self.write_keyword("MAP[");
23952                        self.generate_data_type(key_type)?;
23953                        self.write(" => ");
23954                        self.generate_data_type(value_type)?;
23955                        self.write("]");
23956                    }
23957                    Some(DialectType::Snowflake)
23958                    | Some(DialectType::RisingWave)
23959                    | Some(DialectType::DuckDB)
23960                    | Some(DialectType::Presto)
23961                    | Some(DialectType::Trino)
23962                    | Some(DialectType::Athena) => {
23963                        self.write_keyword("MAP(");
23964                        self.generate_data_type(key_type)?;
23965                        self.write(", ");
23966                        self.generate_data_type(value_type)?;
23967                        self.write(")");
23968                    }
23969                    Some(DialectType::ClickHouse) => {
23970                        // ClickHouse: Map(key_type, value_type) with parenthesized syntax
23971                        // Key types must NOT be wrapped in Nullable
23972                        self.write("Map(");
23973                        self.clickhouse_nullable_depth = -1; // suppress Nullable for key
23974                        self.generate_data_type(key_type)?;
23975                        self.clickhouse_nullable_depth = 0;
23976                        self.write(", ");
23977                        self.generate_data_type(value_type)?;
23978                        self.write(")");
23979                    }
23980                    _ => {
23981                        self.write_keyword("MAP<");
23982                        self.generate_data_type(key_type)?;
23983                        self.write(", ");
23984                        self.generate_data_type(value_type)?;
23985                        self.write(">");
23986                    }
23987                }
23988            }
23989            DataType::Vector {
23990                element_type,
23991                dimension,
23992            } => {
23993                if matches!(self.config.dialect, Some(DialectType::SingleStore)) {
23994                    // SingleStore format: VECTOR(dimension, type_alias)
23995                    self.write_keyword("VECTOR(");
23996                    if let Some(dim) = dimension {
23997                        self.write(&dim.to_string());
23998                    }
23999                    // Map type back to SingleStore alias
24000                    let type_alias = element_type.as_ref().and_then(|et| match et.as_ref() {
24001                        DataType::TinyInt { .. } => Some("I8"),
24002                        DataType::SmallInt { .. } => Some("I16"),
24003                        DataType::Int { .. } => Some("I32"),
24004                        DataType::BigInt { .. } => Some("I64"),
24005                        DataType::Float { .. } => Some("F32"),
24006                        DataType::Double { .. } => Some("F64"),
24007                        _ => None,
24008                    });
24009                    if let Some(alias) = type_alias {
24010                        if dimension.is_some() {
24011                            self.write(", ");
24012                        }
24013                        self.write(alias);
24014                    }
24015                    self.write(")");
24016                } else {
24017                    // Snowflake format: VECTOR(type, dimension)
24018                    self.write_keyword("VECTOR(");
24019                    if let Some(ref et) = element_type {
24020                        self.generate_data_type(et)?;
24021                        if dimension.is_some() {
24022                            self.write(", ");
24023                        }
24024                    }
24025                    if let Some(dim) = dimension {
24026                        self.write(&dim.to_string());
24027                    }
24028                    self.write(")");
24029                }
24030            }
24031            DataType::Object { fields, modifier } => {
24032                self.write_keyword("OBJECT(");
24033                for (i, (name, dt, not_null)) in fields.iter().enumerate() {
24034                    if i > 0 {
24035                        self.write(", ");
24036                    }
24037                    self.write(name);
24038                    self.write(" ");
24039                    self.generate_data_type(dt)?;
24040                    if *not_null {
24041                        self.write_keyword(" NOT NULL");
24042                    }
24043                }
24044                self.write(")");
24045                if let Some(mod_str) = modifier {
24046                    self.write(" ");
24047                    self.write_keyword(mod_str);
24048                }
24049            }
24050            DataType::Struct { fields, nested } => {
24051                // Dialect-specific struct type mappings
24052                match self.config.dialect {
24053                    Some(DialectType::Snowflake) => {
24054                        // Snowflake maps STRUCT to OBJECT
24055                        self.write_keyword("OBJECT(");
24056                        for (i, field) in fields.iter().enumerate() {
24057                            if i > 0 {
24058                                self.write(", ");
24059                            }
24060                            if !field.name.is_empty() {
24061                                self.write(&field.name);
24062                                self.write(" ");
24063                            }
24064                            self.generate_data_type(&field.data_type)?;
24065                        }
24066                        self.write(")");
24067                    }
24068                    Some(DialectType::Presto) | Some(DialectType::Trino) => {
24069                        // Presto/Trino use ROW(name TYPE, ...) syntax
24070                        self.write_keyword("ROW(");
24071                        for (i, field) in fields.iter().enumerate() {
24072                            if i > 0 {
24073                                self.write(", ");
24074                            }
24075                            if !field.name.is_empty() {
24076                                self.write(&field.name);
24077                                self.write(" ");
24078                            }
24079                            self.generate_data_type(&field.data_type)?;
24080                        }
24081                        self.write(")");
24082                    }
24083                    Some(DialectType::DuckDB) => {
24084                        // DuckDB uses parenthesized syntax: STRUCT(name TYPE, ...)
24085                        self.write_keyword("STRUCT(");
24086                        for (i, field) in fields.iter().enumerate() {
24087                            if i > 0 {
24088                                self.write(", ");
24089                            }
24090                            if !field.name.is_empty() {
24091                                self.write(&field.name);
24092                                self.write(" ");
24093                            }
24094                            self.generate_data_type(&field.data_type)?;
24095                        }
24096                        self.write(")");
24097                    }
24098                    Some(DialectType::ClickHouse) => {
24099                        // ClickHouse uses Tuple(name TYPE, ...) for struct types
24100                        self.write("Tuple(");
24101                        for (i, field) in fields.iter().enumerate() {
24102                            if i > 0 {
24103                                self.write(", ");
24104                            }
24105                            if !field.name.is_empty() {
24106                                self.write(&field.name);
24107                                self.write(" ");
24108                            }
24109                            self.generate_data_type(&field.data_type)?;
24110                        }
24111                        self.write(")");
24112                    }
24113                    Some(DialectType::SingleStore) => {
24114                        // SingleStore uses RECORD(name TYPE, ...) for struct types
24115                        self.write_keyword("RECORD(");
24116                        for (i, field) in fields.iter().enumerate() {
24117                            if i > 0 {
24118                                self.write(", ");
24119                            }
24120                            if !field.name.is_empty() {
24121                                self.write(&field.name);
24122                                self.write(" ");
24123                            }
24124                            self.generate_data_type(&field.data_type)?;
24125                        }
24126                        self.write(")");
24127                    }
24128                    _ => {
24129                        // Hive/Spark always use angle bracket syntax: STRUCT<name: TYPE>
24130                        let force_angle_brackets = matches!(
24131                            self.config.dialect,
24132                            Some(DialectType::Hive)
24133                                | Some(DialectType::Spark)
24134                                | Some(DialectType::Databricks)
24135                        );
24136                        if *nested && !force_angle_brackets {
24137                            self.write_keyword("STRUCT(");
24138                            for (i, field) in fields.iter().enumerate() {
24139                                if i > 0 {
24140                                    self.write(", ");
24141                                }
24142                                if !field.name.is_empty() {
24143                                    self.write(&field.name);
24144                                    self.write(" ");
24145                                }
24146                                self.generate_data_type(&field.data_type)?;
24147                            }
24148                            self.write(")");
24149                        } else {
24150                            self.write_keyword("STRUCT<");
24151                            for (i, field) in fields.iter().enumerate() {
24152                                if i > 0 {
24153                                    self.write(", ");
24154                                }
24155                                if !field.name.is_empty() {
24156                                    // Named field: name TYPE (with configurable separator for Hive)
24157                                    self.write(&field.name);
24158                                    self.write(self.config.struct_field_sep);
24159                                }
24160                                // For anonymous fields, just output the type
24161                                self.generate_data_type(&field.data_type)?;
24162                                // Spark/Databricks: Output COMMENT clause if present
24163                                if let Some(comment) = &field.comment {
24164                                    self.write(" COMMENT '");
24165                                    self.write(comment);
24166                                    self.write("'");
24167                                }
24168                                // BigQuery: Output OPTIONS clause if present
24169                                if !field.options.is_empty() {
24170                                    self.write(" ");
24171                                    self.generate_options_clause(&field.options)?;
24172                                }
24173                            }
24174                            self.write(">");
24175                        }
24176                    }
24177                }
24178            }
24179            DataType::Enum {
24180                values,
24181                assignments,
24182            } => {
24183                // DuckDB ENUM type: ENUM('RED', 'GREEN', 'BLUE')
24184                // ClickHouse: Enum('hello' = 1, 'world' = 2)
24185                if self.config.dialect == Some(DialectType::ClickHouse) {
24186                    self.write("Enum(");
24187                } else {
24188                    self.write_keyword("ENUM(");
24189                }
24190                for (i, val) in values.iter().enumerate() {
24191                    if i > 0 {
24192                        self.write(", ");
24193                    }
24194                    self.write("'");
24195                    self.write(val);
24196                    self.write("'");
24197                    if let Some(Some(assignment)) = assignments.get(i) {
24198                        self.write(" = ");
24199                        self.write(assignment);
24200                    }
24201                }
24202                self.write(")");
24203            }
24204            DataType::Set { values } => {
24205                // MySQL SET type: SET('a', 'b', 'c')
24206                self.write_keyword("SET(");
24207                for (i, val) in values.iter().enumerate() {
24208                    if i > 0 {
24209                        self.write(", ");
24210                    }
24211                    self.write("'");
24212                    self.write(val);
24213                    self.write("'");
24214                }
24215                self.write(")");
24216            }
24217            DataType::Union { fields } => {
24218                // DuckDB UNION type: UNION(num INT, str TEXT)
24219                self.write_keyword("UNION(");
24220                for (i, (name, dt)) in fields.iter().enumerate() {
24221                    if i > 0 {
24222                        self.write(", ");
24223                    }
24224                    if !name.is_empty() {
24225                        self.write(name);
24226                        self.write(" ");
24227                    }
24228                    self.generate_data_type(dt)?;
24229                }
24230                self.write(")");
24231            }
24232            DataType::Nullable { inner } => {
24233                // ClickHouse: Nullable(T), other dialects: just the inner type
24234                if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
24235                    self.write("Nullable(");
24236                    // Suppress inner Nullable wrapping to prevent Nullable(Nullable(...))
24237                    let saved_depth = self.clickhouse_nullable_depth;
24238                    self.clickhouse_nullable_depth = -1;
24239                    self.generate_data_type(inner)?;
24240                    self.clickhouse_nullable_depth = saved_depth;
24241                    self.write(")");
24242                } else {
24243                    // Map ClickHouse-specific custom type names to standard types
24244                    match inner.as_ref() {
24245                        DataType::Custom { name } if name.eq_ignore_ascii_case("DATETIME") => {
24246                            self.generate_data_type(&DataType::Timestamp {
24247                                precision: None,
24248                                timezone: false,
24249                            })?;
24250                        }
24251                        _ => {
24252                            self.generate_data_type(inner)?;
24253                        }
24254                    }
24255                }
24256            }
24257            DataType::Custom { name } => {
24258                // Handle dialect-specific type transformations
24259                let name_upper = name.to_ascii_uppercase();
24260                match self.config.dialect {
24261                    Some(DialectType::ClickHouse) => {
24262                        let (base_upper, suffix) = if let Some(idx) = name.find('(') {
24263                            (name_upper[..idx].to_string(), &name[idx..])
24264                        } else {
24265                            (name_upper.clone(), "")
24266                        };
24267                        let mapped = match base_upper.as_str() {
24268                            "DATETIME" | "TIMESTAMPTZ" | "TIMESTAMP" | "TIMESTAMPNTZ"
24269                            | "SMALLDATETIME" | "DATETIME2" => "DateTime",
24270                            "DATETIME64" => "DateTime64",
24271                            "DATE32" => "Date32",
24272                            "INT" => "Int32",
24273                            "MEDIUMINT" => "Int32",
24274                            "INT8" => "Int8",
24275                            "INT16" => "Int16",
24276                            "INT32" => "Int32",
24277                            "INT64" => "Int64",
24278                            "INT128" => "Int128",
24279                            "INT256" => "Int256",
24280                            "UINT8" => "UInt8",
24281                            "UINT16" => "UInt16",
24282                            "UINT32" => "UInt32",
24283                            "UINT64" => "UInt64",
24284                            "UINT128" => "UInt128",
24285                            "UINT256" => "UInt256",
24286                            "FLOAT32" => "Float32",
24287                            "FLOAT64" => "Float64",
24288                            "DECIMAL32" => "Decimal32",
24289                            "DECIMAL64" => "Decimal64",
24290                            "DECIMAL128" => "Decimal128",
24291                            "DECIMAL256" => "Decimal256",
24292                            "ENUM" => "Enum",
24293                            "ENUM8" => "Enum8",
24294                            "ENUM16" => "Enum16",
24295                            "FIXEDSTRING" => "FixedString",
24296                            "NESTED" => "Nested",
24297                            "LOWCARDINALITY" => "LowCardinality",
24298                            "NULLABLE" => "Nullable",
24299                            "IPV4" => "IPv4",
24300                            "IPV6" => "IPv6",
24301                            "POINT" => "Point",
24302                            "RING" => "Ring",
24303                            "LINESTRING" => "LineString",
24304                            "MULTILINESTRING" => "MultiLineString",
24305                            "POLYGON" => "Polygon",
24306                            "MULTIPOLYGON" => "MultiPolygon",
24307                            "AGGREGATEFUNCTION" => "AggregateFunction",
24308                            "SIMPLEAGGREGATEFUNCTION" => "SimpleAggregateFunction",
24309                            "DYNAMIC" => "Dynamic",
24310                            _ => "",
24311                        };
24312                        if mapped.is_empty() {
24313                            self.write(name);
24314                        } else {
24315                            self.write(mapped);
24316                            self.write(suffix);
24317                        }
24318                    }
24319                    Some(DialectType::MySQL)
24320                        if name_upper == "TIMESTAMPTZ" || name_upper == "TIMESTAMPLTZ" =>
24321                    {
24322                        // MySQL doesn't support TIMESTAMPTZ/TIMESTAMPLTZ, use TIMESTAMP
24323                        self.write_keyword("TIMESTAMP");
24324                    }
24325                    Some(DialectType::TSQL) if name_upper == "VARIANT" => {
24326                        self.write_keyword("SQL_VARIANT");
24327                    }
24328                    Some(DialectType::DuckDB) if name_upper == "DECFLOAT" => {
24329                        self.write_keyword("DECIMAL(38, 5)");
24330                    }
24331                    Some(DialectType::Exasol) => {
24332                        // Exasol type mappings for custom types
24333                        match name_upper.as_str() {
24334                            // Binary types → VARCHAR
24335                            "LONGBLOB" | "MEDIUMBLOB" | "TINYBLOB" => self.write_keyword("VARCHAR"),
24336                            // Text types → VARCHAR (TEXT → LONG VARCHAR is handled by DataType::Text)
24337                            "LONGTEXT" | "MEDIUMTEXT" | "TINYTEXT" => self.write_keyword("VARCHAR"),
24338                            // Integer types
24339                            "MEDIUMINT" => self.write_keyword("INT"),
24340                            // Decimal types → DECIMAL
24341                            "DECIMAL32" | "DECIMAL64" | "DECIMAL128" | "DECIMAL256" => {
24342                                self.write_keyword("DECIMAL")
24343                            }
24344                            // Timestamp types
24345                            "DATETIME" => self.write_keyword("TIMESTAMP"),
24346                            "TIMESTAMPLTZ" => self.write_keyword("TIMESTAMP WITH LOCAL TIME ZONE"),
24347                            _ => self.write(name),
24348                        }
24349                    }
24350                    Some(DialectType::Dremio) => {
24351                        // Dremio type mappings for custom types
24352                        match name_upper.as_str() {
24353                            "TIMESTAMPNTZ" | "DATETIME" => self.write_keyword("TIMESTAMP"),
24354                            "ARRAY" => self.write_keyword("LIST"),
24355                            "NCHAR" => self.write_keyword("VARCHAR"),
24356                            _ => self.write(name),
24357                        }
24358                    }
24359                    // Map dialect-specific custom types to standard SQL types for other dialects
24360                    _ => {
24361                        // Extract base name and args for types with parenthesized args (e.g., DATETIME2(3))
24362                        let (base_upper, _args_str) = if let Some(idx) = name_upper.find('(') {
24363                            (name_upper[..idx].to_string(), Some(&name[idx..]))
24364                        } else {
24365                            (name_upper.clone(), None)
24366                        };
24367
24368                        match base_upper.as_str() {
24369                            "INT64"
24370                                if !matches!(self.config.dialect, Some(DialectType::BigQuery)) =>
24371                            {
24372                                self.write_keyword("BIGINT");
24373                            }
24374                            "FLOAT64"
24375                                if !matches!(self.config.dialect, Some(DialectType::BigQuery)) =>
24376                            {
24377                                self.write_keyword("DOUBLE");
24378                            }
24379                            "BOOL"
24380                                if !matches!(self.config.dialect, Some(DialectType::BigQuery)) =>
24381                            {
24382                                self.write_keyword("BOOLEAN");
24383                            }
24384                            "BYTES"
24385                                if matches!(
24386                                    self.config.dialect,
24387                                    Some(DialectType::Spark)
24388                                        | Some(DialectType::Hive)
24389                                        | Some(DialectType::Databricks)
24390                                ) =>
24391                            {
24392                                self.write_keyword("BINARY");
24393                            }
24394                            "BYTES"
24395                                if !matches!(self.config.dialect, Some(DialectType::BigQuery)) =>
24396                            {
24397                                self.write_keyword("VARBINARY");
24398                            }
24399                            // TSQL DATETIME2/SMALLDATETIME -> TIMESTAMP
24400                            "DATETIME2" | "SMALLDATETIME"
24401                                if !matches!(
24402                                    self.config.dialect,
24403                                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
24404                                ) =>
24405                            {
24406                                // PostgreSQL preserves precision, others don't
24407                                if matches!(
24408                                    self.config.dialect,
24409                                    Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
24410                                ) {
24411                                    self.write_keyword("TIMESTAMP");
24412                                    if let Some(args) = _args_str {
24413                                        self.write(args);
24414                                    }
24415                                } else {
24416                                    self.write_keyword("TIMESTAMP");
24417                                }
24418                            }
24419                            // TSQL DATETIMEOFFSET -> TIMESTAMPTZ
24420                            "DATETIMEOFFSET"
24421                                if !matches!(
24422                                    self.config.dialect,
24423                                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
24424                                ) =>
24425                            {
24426                                if matches!(
24427                                    self.config.dialect,
24428                                    Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
24429                                ) {
24430                                    self.write_keyword("TIMESTAMPTZ");
24431                                    if let Some(args) = _args_str {
24432                                        self.write(args);
24433                                    }
24434                                } else {
24435                                    self.write_keyword("TIMESTAMPTZ");
24436                                }
24437                            }
24438                            // TSQL UNIQUEIDENTIFIER -> UUID or STRING
24439                            "UNIQUEIDENTIFIER"
24440                                if !matches!(
24441                                    self.config.dialect,
24442                                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
24443                                ) =>
24444                            {
24445                                match self.config.dialect {
24446                                    Some(DialectType::Spark)
24447                                    | Some(DialectType::Databricks)
24448                                    | Some(DialectType::Hive) => self.write_keyword("STRING"),
24449                                    _ => self.write_keyword("UUID"),
24450                                }
24451                            }
24452                            // TSQL BIT -> BOOLEAN for most dialects
24453                            "BIT"
24454                                if !matches!(
24455                                    self.config.dialect,
24456                                    Some(DialectType::TSQL)
24457                                        | Some(DialectType::Fabric)
24458                                        | Some(DialectType::PostgreSQL)
24459                                        | Some(DialectType::MySQL)
24460                                        | Some(DialectType::DuckDB)
24461                                ) =>
24462                            {
24463                                self.write_keyword("BOOLEAN");
24464                            }
24465                            // TSQL NVARCHAR -> VARCHAR (with default size 30 for some dialects)
24466                            "NVARCHAR"
24467                                if !matches!(
24468                                    self.config.dialect,
24469                                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
24470                                ) =>
24471                            {
24472                                match self.config.dialect {
24473                                    Some(DialectType::Oracle) => {
24474                                        // Oracle: NVARCHAR -> NVARCHAR2
24475                                        self.write_keyword("NVARCHAR2");
24476                                        if let Some(args) = _args_str {
24477                                            self.write(args);
24478                                        }
24479                                    }
24480                                    Some(DialectType::BigQuery) => {
24481                                        // BigQuery: NVARCHAR -> STRING
24482                                        self.write_keyword("STRING");
24483                                    }
24484                                    Some(DialectType::SQLite) | Some(DialectType::DuckDB) => {
24485                                        self.write_keyword("TEXT");
24486                                        if let Some(args) = _args_str {
24487                                            self.write(args);
24488                                        }
24489                                    }
24490                                    Some(DialectType::Hive) => {
24491                                        // Hive: NVARCHAR -> STRING
24492                                        self.write_keyword("STRING");
24493                                    }
24494                                    Some(DialectType::Spark) | Some(DialectType::Databricks) => {
24495                                        if _args_str.is_some() {
24496                                            self.write_keyword("VARCHAR");
24497                                            self.write(_args_str.unwrap());
24498                                        } else {
24499                                            self.write_keyword("STRING");
24500                                        }
24501                                    }
24502                                    _ => {
24503                                        self.write_keyword("VARCHAR");
24504                                        if let Some(args) = _args_str {
24505                                            self.write(args);
24506                                        }
24507                                    }
24508                                }
24509                            }
24510                            // NCHAR -> CHAR (NCHAR for Oracle/TSQL, STRING for BigQuery/Hive)
24511                            "NCHAR"
24512                                if !matches!(
24513                                    self.config.dialect,
24514                                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
24515                                ) =>
24516                            {
24517                                match self.config.dialect {
24518                                    Some(DialectType::Oracle) => {
24519                                        // Oracle natively supports NCHAR
24520                                        self.write_keyword("NCHAR");
24521                                        if let Some(args) = _args_str {
24522                                            self.write(args);
24523                                        }
24524                                    }
24525                                    Some(DialectType::BigQuery) => {
24526                                        // BigQuery: NCHAR -> STRING
24527                                        self.write_keyword("STRING");
24528                                    }
24529                                    Some(DialectType::Hive) => {
24530                                        // Hive: NCHAR -> STRING
24531                                        self.write_keyword("STRING");
24532                                    }
24533                                    Some(DialectType::SQLite) | Some(DialectType::DuckDB) => {
24534                                        self.write_keyword("TEXT");
24535                                        if let Some(args) = _args_str {
24536                                            self.write(args);
24537                                        }
24538                                    }
24539                                    Some(DialectType::Spark) | Some(DialectType::Databricks) => {
24540                                        if _args_str.is_some() {
24541                                            self.write_keyword("CHAR");
24542                                            self.write(_args_str.unwrap());
24543                                        } else {
24544                                            self.write_keyword("STRING");
24545                                        }
24546                                    }
24547                                    _ => {
24548                                        self.write_keyword("CHAR");
24549                                        if let Some(args) = _args_str {
24550                                            self.write(args);
24551                                        }
24552                                    }
24553                                }
24554                            }
24555                            // MySQL text variant types -> map to appropriate target type
24556                            // For MySQL/SingleStore: keep original name (column definitions), CAST handling is in generate_cast
24557                            "LONGTEXT" | "MEDIUMTEXT" | "TINYTEXT" => match self.config.dialect {
24558                                Some(DialectType::MySQL)
24559                                | Some(DialectType::SingleStore)
24560                                | Some(DialectType::TiDB) => self.write_keyword(&base_upper),
24561                                Some(DialectType::Spark)
24562                                | Some(DialectType::Databricks)
24563                                | Some(DialectType::Hive) => self.write_keyword("TEXT"),
24564                                Some(DialectType::BigQuery) => self.write_keyword("STRING"),
24565                                Some(DialectType::Presto)
24566                                | Some(DialectType::Trino)
24567                                | Some(DialectType::Athena) => self.write_keyword("VARCHAR"),
24568                                Some(DialectType::Snowflake)
24569                                | Some(DialectType::Redshift)
24570                                | Some(DialectType::Dremio) => self.write_keyword("VARCHAR"),
24571                                _ => self.write_keyword("TEXT"),
24572                            },
24573                            // MySQL blob variant types -> map to appropriate target type
24574                            // For MySQL/SingleStore: keep original name (column definitions), CAST handling is in generate_cast
24575                            "LONGBLOB" | "MEDIUMBLOB" | "TINYBLOB" => match self.config.dialect {
24576                                Some(DialectType::MySQL)
24577                                | Some(DialectType::SingleStore)
24578                                | Some(DialectType::TiDB) => self.write_keyword(&base_upper),
24579                                Some(DialectType::Spark)
24580                                | Some(DialectType::Databricks)
24581                                | Some(DialectType::Hive) => self.write_keyword("BLOB"),
24582                                Some(DialectType::DuckDB) => self.write_keyword("VARBINARY"),
24583                                Some(DialectType::BigQuery) => self.write_keyword("BYTES"),
24584                                Some(DialectType::Presto)
24585                                | Some(DialectType::Trino)
24586                                | Some(DialectType::Athena) => self.write_keyword("VARBINARY"),
24587                                Some(DialectType::Snowflake)
24588                                | Some(DialectType::Redshift)
24589                                | Some(DialectType::Dremio) => self.write_keyword("VARBINARY"),
24590                                _ => self.write_keyword("BLOB"),
24591                            },
24592                            // LONGVARCHAR -> TEXT for SQLite, VARCHAR for others
24593                            "LONGVARCHAR" => match self.config.dialect {
24594                                Some(DialectType::SQLite) => self.write_keyword("TEXT"),
24595                                _ => self.write_keyword("VARCHAR"),
24596                            },
24597                            // DATETIME -> TIMESTAMP for most, DATETIME for MySQL/Doris/StarRocks/Snowflake
24598                            "DATETIME" => {
24599                                match self.config.dialect {
24600                                    Some(DialectType::MySQL)
24601                                    | Some(DialectType::Doris)
24602                                    | Some(DialectType::StarRocks)
24603                                    | Some(DialectType::TSQL)
24604                                    | Some(DialectType::Fabric)
24605                                    | Some(DialectType::BigQuery)
24606                                    | Some(DialectType::SQLite)
24607                                    | Some(DialectType::Snowflake) => {
24608                                        self.write_keyword("DATETIME");
24609                                        if let Some(args) = _args_str {
24610                                            self.write(args);
24611                                        }
24612                                    }
24613                                    Some(_) => {
24614                                        // Only map to TIMESTAMP when we have a specific target dialect
24615                                        self.write_keyword("TIMESTAMP");
24616                                        if let Some(args) = _args_str {
24617                                            self.write(args);
24618                                        }
24619                                    }
24620                                    None => {
24621                                        // No dialect - preserve original
24622                                        self.write(name);
24623                                    }
24624                                }
24625                            }
24626                            // VARCHAR2/NVARCHAR2 (Oracle) -> VARCHAR for non-Oracle targets
24627                            "VARCHAR2"
24628                                if !matches!(self.config.dialect, Some(DialectType::Oracle)) =>
24629                            {
24630                                match self.config.dialect {
24631                                    Some(DialectType::DuckDB) | Some(DialectType::SQLite) => {
24632                                        self.write_keyword("TEXT");
24633                                    }
24634                                    Some(DialectType::Hive)
24635                                    | Some(DialectType::Spark)
24636                                    | Some(DialectType::Databricks)
24637                                    | Some(DialectType::BigQuery)
24638                                    | Some(DialectType::ClickHouse)
24639                                    | Some(DialectType::StarRocks)
24640                                    | Some(DialectType::Doris) => {
24641                                        self.write_keyword("STRING");
24642                                    }
24643                                    _ => {
24644                                        self.write_keyword("VARCHAR");
24645                                        if let Some(args) = _args_str {
24646                                            self.write(args);
24647                                        }
24648                                    }
24649                                }
24650                            }
24651                            "NVARCHAR2"
24652                                if !matches!(self.config.dialect, Some(DialectType::Oracle)) =>
24653                            {
24654                                match self.config.dialect {
24655                                    Some(DialectType::DuckDB) | Some(DialectType::SQLite) => {
24656                                        self.write_keyword("TEXT");
24657                                    }
24658                                    Some(DialectType::Hive)
24659                                    | Some(DialectType::Spark)
24660                                    | Some(DialectType::Databricks)
24661                                    | Some(DialectType::BigQuery)
24662                                    | Some(DialectType::ClickHouse)
24663                                    | Some(DialectType::StarRocks)
24664                                    | Some(DialectType::Doris) => {
24665                                        self.write_keyword("STRING");
24666                                    }
24667                                    _ => {
24668                                        self.write_keyword("VARCHAR");
24669                                        if let Some(args) = _args_str {
24670                                            self.write(args);
24671                                        }
24672                                    }
24673                                }
24674                            }
24675                            _ => self.write(name),
24676                        }
24677                    }
24678                }
24679            }
24680            DataType::Geometry { subtype, srid } => {
24681                // Dialect-specific geometry type mappings
24682                match self.config.dialect {
24683                    Some(DialectType::MySQL) => {
24684                        // MySQL uses POINT SRID 4326 syntax for specific types
24685                        if let Some(sub) = subtype {
24686                            self.write_keyword(sub);
24687                            if let Some(s) = srid {
24688                                self.write(" SRID ");
24689                                self.write(&s.to_string());
24690                            }
24691                        } else {
24692                            self.write_keyword("GEOMETRY");
24693                        }
24694                    }
24695                    Some(DialectType::BigQuery) => {
24696                        // BigQuery only supports GEOGRAPHY, not GEOMETRY
24697                        self.write_keyword("GEOGRAPHY");
24698                    }
24699                    Some(DialectType::Teradata) => {
24700                        // Teradata uses ST_GEOMETRY
24701                        self.write_keyword("ST_GEOMETRY");
24702                        if subtype.is_some() || srid.is_some() {
24703                            self.write("(");
24704                            if let Some(sub) = subtype {
24705                                self.write_keyword(sub);
24706                            }
24707                            if let Some(s) = srid {
24708                                if subtype.is_some() {
24709                                    self.write(", ");
24710                                }
24711                                self.write(&s.to_string());
24712                            }
24713                            self.write(")");
24714                        }
24715                    }
24716                    _ => {
24717                        // PostgreSQL, Snowflake, DuckDB use GEOMETRY(subtype, srid) syntax
24718                        self.write_keyword("GEOMETRY");
24719                        if subtype.is_some() || srid.is_some() {
24720                            self.write("(");
24721                            if let Some(sub) = subtype {
24722                                self.write_keyword(sub);
24723                            }
24724                            if let Some(s) = srid {
24725                                if subtype.is_some() {
24726                                    self.write(", ");
24727                                }
24728                                self.write(&s.to_string());
24729                            }
24730                            self.write(")");
24731                        }
24732                    }
24733                }
24734            }
24735            DataType::Geography { subtype, srid } => {
24736                // Dialect-specific geography type mappings
24737                match self.config.dialect {
24738                    Some(DialectType::MySQL) => {
24739                        // MySQL doesn't have native GEOGRAPHY, use GEOMETRY with SRID 4326
24740                        if let Some(sub) = subtype {
24741                            self.write_keyword(sub);
24742                        } else {
24743                            self.write_keyword("GEOMETRY");
24744                        }
24745                        // Geography implies SRID 4326 (WGS84)
24746                        let effective_srid = srid.unwrap_or(4326);
24747                        self.write(" SRID ");
24748                        self.write(&effective_srid.to_string());
24749                    }
24750                    Some(DialectType::BigQuery) => {
24751                        // BigQuery uses simple GEOGRAPHY without parameters
24752                        self.write_keyword("GEOGRAPHY");
24753                    }
24754                    Some(DialectType::Snowflake) => {
24755                        // Snowflake uses GEOGRAPHY without parameters
24756                        self.write_keyword("GEOGRAPHY");
24757                    }
24758                    _ => {
24759                        // PostgreSQL uses GEOGRAPHY(subtype, srid) syntax
24760                        self.write_keyword("GEOGRAPHY");
24761                        if subtype.is_some() || srid.is_some() {
24762                            self.write("(");
24763                            if let Some(sub) = subtype {
24764                                self.write_keyword(sub);
24765                            }
24766                            if let Some(s) = srid {
24767                                if subtype.is_some() {
24768                                    self.write(", ");
24769                                }
24770                                self.write(&s.to_string());
24771                            }
24772                            self.write(")");
24773                        }
24774                    }
24775                }
24776            }
24777            DataType::CharacterSet { name } => {
24778                // For MySQL CONVERT USING - output as CHAR CHARACTER SET name
24779                self.write_keyword("CHAR CHARACTER SET ");
24780                self.write(name);
24781            }
24782            _ => self.write("UNKNOWN"),
24783        }
24784        Ok(())
24785    }
24786
24787    // === Helper methods ===
24788
24789    #[inline]
24790    fn write(&mut self, s: &str) {
24791        self.output.push_str(s);
24792    }
24793
24794    #[inline]
24795    fn write_space(&mut self) {
24796        self.output.push(' ');
24797    }
24798
24799    #[inline]
24800    fn write_keyword(&mut self, keyword: &str) {
24801        if self.config.uppercase_keywords {
24802            self.output.push_str(keyword);
24803        } else {
24804            for b in keyword.bytes() {
24805                self.output.push(b.to_ascii_lowercase() as char);
24806            }
24807        }
24808    }
24809
24810    /// Write a function name respecting the normalize_functions config setting
24811    fn write_func_name(&mut self, name: &str) {
24812        let normalized = self.normalize_func_name(name);
24813        self.output.push_str(normalized.as_ref());
24814    }
24815
24816    /// Convert strptime format string to Exasol format string
24817    /// Exasol TIME_MAPPING (reverse of Python sqlglot):
24818    /// %Y -> YYYY, %y -> YY, %m -> MM, %d -> DD, %H -> HH, %M -> MI, %S -> SS, %a -> DY
24819    fn convert_strptime_to_exasol_format(format: &str) -> String {
24820        let mut result = String::new();
24821        let chars: Vec<char> = format.chars().collect();
24822        let mut i = 0;
24823        while i < chars.len() {
24824            if chars[i] == '%' && i + 1 < chars.len() {
24825                let spec = chars[i + 1];
24826                let exasol_spec = match spec {
24827                    'Y' => "YYYY",
24828                    'y' => "YY",
24829                    'm' => "MM",
24830                    'd' => "DD",
24831                    'H' => "HH",
24832                    'M' => "MI",
24833                    'S' => "SS",
24834                    'a' => "DY",    // abbreviated weekday name
24835                    'A' => "DAY",   // full weekday name
24836                    'b' => "MON",   // abbreviated month name
24837                    'B' => "MONTH", // full month name
24838                    'I' => "H12",   // 12-hour format
24839                    'u' => "ID",    // ISO weekday (1-7)
24840                    'V' => "IW",    // ISO week number
24841                    'G' => "IYYY",  // ISO year
24842                    'W' => "UW",    // Week number (Monday as first day)
24843                    'U' => "UW",    // Week number (Sunday as first day)
24844                    'z' => "Z",     // timezone offset
24845                    _ => {
24846                        // Unknown specifier, keep as-is
24847                        result.push('%');
24848                        result.push(spec);
24849                        i += 2;
24850                        continue;
24851                    }
24852                };
24853                result.push_str(exasol_spec);
24854                i += 2;
24855            } else {
24856                result.push(chars[i]);
24857                i += 1;
24858            }
24859        }
24860        result
24861    }
24862
24863    /// Convert strptime format string to PostgreSQL/Redshift format string
24864    /// PostgreSQL INVERSE_TIME_MAPPING from Python sqlglot:
24865    /// %Y -> YYYY, %y -> YY, %m -> MM, %d -> DD, %H -> HH24, %M -> MI, %S -> SS, %f -> US, etc.
24866    fn convert_strptime_to_postgres_format(format: &str) -> String {
24867        let mut result = String::new();
24868        let chars: Vec<char> = format.chars().collect();
24869        let mut i = 0;
24870        while i < chars.len() {
24871            if chars[i] == '%' && i + 1 < chars.len() {
24872                // Check for %-d, %-m, etc. (non-padded, 3-char sequence)
24873                if chars[i + 1] == '-' && i + 2 < chars.len() {
24874                    let spec = chars[i + 2];
24875                    let pg_spec = match spec {
24876                        'd' => "FMDD",
24877                        'm' => "FMMM",
24878                        'H' => "FMHH24",
24879                        'M' => "FMMI",
24880                        'S' => "FMSS",
24881                        _ => {
24882                            result.push('%');
24883                            result.push('-');
24884                            result.push(spec);
24885                            i += 3;
24886                            continue;
24887                        }
24888                    };
24889                    result.push_str(pg_spec);
24890                    i += 3;
24891                    continue;
24892                }
24893                let spec = chars[i + 1];
24894                let pg_spec = match spec {
24895                    'Y' => "YYYY",
24896                    'y' => "YY",
24897                    'm' => "MM",
24898                    'd' => "DD",
24899                    'H' => "HH24",
24900                    'I' => "HH12",
24901                    'M' => "MI",
24902                    'S' => "SS",
24903                    'f' => "US",      // microseconds
24904                    'u' => "D",       // day of week (1=Monday)
24905                    'j' => "DDD",     // day of year
24906                    'z' => "OF",      // UTC offset
24907                    'Z' => "TZ",      // timezone name
24908                    'A' => "TMDay",   // full weekday name
24909                    'a' => "TMDy",    // abbreviated weekday name
24910                    'b' => "TMMon",   // abbreviated month name
24911                    'B' => "TMMonth", // full month name
24912                    'U' => "WW",      // week number
24913                    _ => {
24914                        // Unknown specifier, keep as-is
24915                        result.push('%');
24916                        result.push(spec);
24917                        i += 2;
24918                        continue;
24919                    }
24920                };
24921                result.push_str(pg_spec);
24922                i += 2;
24923            } else {
24924                result.push(chars[i]);
24925                i += 1;
24926            }
24927        }
24928        result
24929    }
24930
24931    /// Write a LIMIT expression value, evaluating constant expressions if limit_only_literals is set
24932    fn write_limit_expr(&mut self, expr: &Expression) -> Result<()> {
24933        if self.config.limit_only_literals {
24934            if let Some(value) = Self::try_evaluate_constant(expr) {
24935                self.write(&value.to_string());
24936                return Ok(());
24937            }
24938        }
24939        self.generate_expression(expr)
24940    }
24941
24942    /// Format a comment with proper spacing.
24943    /// Converts `/*text*/` to `/* text */` (adding internal spaces if not present).
24944    /// Python SQLGlot normalizes comment format to have spaces inside block comments.
24945    fn write_formatted_comment(&mut self, comment: &str) {
24946        // Normalize all comments to block comment format /* ... */
24947        // This matches Python sqlglot behavior which always outputs block comments
24948        let content = if comment.starts_with("/*") && comment.ends_with("*/") {
24949            // Already block comment - extract inner content
24950            // Preserve internal whitespace, but ensure at least one space padding
24951            &comment[2..comment.len() - 2]
24952        } else if comment.starts_with("--") {
24953            // Line comment - extract content after --
24954            // Preserve internal whitespace (e.g., "--       x" -> "/*       x */")
24955            &comment[2..]
24956        } else {
24957            // Raw content (no delimiters)
24958            comment
24959        };
24960        // Skip empty comments (e.g., bare "--" with no content)
24961        if content.trim().is_empty() {
24962            return;
24963        }
24964        // Escape nested block comment markers to prevent premature closure or unintended nesting.
24965        // This matches Python sqlglot's sanitize_comment behavior.
24966        let sanitized = content.replace("*/", "* /").replace("/*", "/ *");
24967        let content = &sanitized;
24968        // Ensure at least one space after /* and before */
24969        self.output.push_str("/*");
24970        if !content.starts_with(' ') {
24971            self.output.push(' ');
24972        }
24973        self.output.push_str(content);
24974        if !content.ends_with(' ') {
24975            self.output.push(' ');
24976        }
24977        self.output.push_str("*/");
24978    }
24979
24980    /// Escape a raw block content (from dollar-quoted string) for single-quoted output.
24981    /// Escapes single quotes with backslash, and for Snowflake also escapes backslashes.
24982    fn escape_block_for_single_quote(&self, block: &str) -> String {
24983        let escape_backslash = matches!(
24984            self.config.dialect,
24985            Some(crate::dialects::DialectType::Snowflake)
24986        );
24987        let mut escaped = String::with_capacity(block.len() + 4);
24988        for ch in block.chars() {
24989            if ch == '\'' {
24990                escaped.push('\\');
24991                escaped.push('\'');
24992            } else if escape_backslash && ch == '\\' {
24993                escaped.push('\\');
24994                escaped.push('\\');
24995            } else {
24996                escaped.push(ch);
24997            }
24998        }
24999        escaped
25000    }
25001
25002    fn write_newline(&mut self) {
25003        self.output.push('\n');
25004    }
25005
25006    fn write_indent(&mut self) {
25007        for _ in 0..self.indent_level {
25008            self.output.push_str(self.config.indent);
25009        }
25010    }
25011
25012    // === SQLGlot-style pretty printing helpers ===
25013
25014    /// Returns the separator string for pretty printing.
25015    /// Check if the total length of arguments exceeds max_text_width.
25016    /// Used for dynamic line breaking in expressions() formatting.
25017    fn too_wide(&self, args: &[String]) -> bool {
25018        args.iter().map(|s| s.len()).sum::<usize>() > self.config.max_text_width
25019    }
25020
25021    /// Generate an expression to a string using a temporary non-pretty generator.
25022    /// Useful for width calculations before deciding on formatting.
25023    fn generate_to_string(&self, expr: &Expression) -> Result<String> {
25024        let config = GeneratorConfig {
25025            pretty: false,
25026            dialect: self.config.dialect,
25027            ..Default::default()
25028        };
25029        let mut gen = Generator::with_config(config);
25030        gen.generate_expression(expr)?;
25031        Ok(gen.output)
25032    }
25033
25034    /// Writes a clause with a single condition (WHERE, HAVING, QUALIFY).
25035    /// In pretty mode: newline + indented keyword + newline + indented condition
25036    fn write_clause_condition(&mut self, keyword: &str, condition: &Expression) -> Result<()> {
25037        if self.config.pretty {
25038            self.write_newline();
25039            self.write_indent();
25040            self.write_keyword(keyword);
25041            self.write_newline();
25042            self.indent_level += 1;
25043            self.write_indent();
25044            self.generate_expression(condition)?;
25045            self.indent_level -= 1;
25046        } else {
25047            self.write_space();
25048            self.write_keyword(keyword);
25049            self.write_space();
25050            self.generate_expression(condition)?;
25051        }
25052        Ok(())
25053    }
25054
25055    /// Writes a clause with a list of expressions (GROUP BY, DISTRIBUTE BY, CLUSTER BY).
25056    /// In pretty mode: each expression on new line with indentation
25057    fn write_clause_expressions(&mut self, keyword: &str, exprs: &[Expression]) -> Result<()> {
25058        if exprs.is_empty() {
25059            return Ok(());
25060        }
25061
25062        if self.config.pretty {
25063            self.write_newline();
25064            self.write_indent();
25065            self.write_keyword(keyword);
25066            self.write_newline();
25067            self.indent_level += 1;
25068            for (i, expr) in exprs.iter().enumerate() {
25069                if i > 0 {
25070                    self.write(",");
25071                    self.write_newline();
25072                }
25073                self.write_indent();
25074                self.generate_expression(expr)?;
25075            }
25076            self.indent_level -= 1;
25077        } else {
25078            self.write_space();
25079            self.write_keyword(keyword);
25080            self.write_space();
25081            for (i, expr) in exprs.iter().enumerate() {
25082                if i > 0 {
25083                    self.write(", ");
25084                }
25085                self.generate_expression(expr)?;
25086            }
25087        }
25088        Ok(())
25089    }
25090
25091    /// Writes ORDER BY / SORT BY clause with Ordered expressions
25092    fn write_order_clause(&mut self, keyword: &str, orderings: &[Ordered]) -> Result<()> {
25093        if orderings.is_empty() {
25094            return Ok(());
25095        }
25096
25097        if self.config.pretty {
25098            self.write_newline();
25099            self.write_indent();
25100            self.write_keyword(keyword);
25101            self.write_newline();
25102            self.indent_level += 1;
25103            for (i, ordered) in orderings.iter().enumerate() {
25104                if i > 0 {
25105                    self.write(",");
25106                    self.write_newline();
25107                }
25108                self.write_indent();
25109                self.generate_ordered(ordered)?;
25110            }
25111            self.indent_level -= 1;
25112        } else {
25113            self.write_space();
25114            self.write_keyword(keyword);
25115            self.write_space();
25116            for (i, ordered) in orderings.iter().enumerate() {
25117                if i > 0 {
25118                    self.write(", ");
25119                }
25120                self.generate_ordered(ordered)?;
25121            }
25122        }
25123        Ok(())
25124    }
25125
25126    /// Writes WINDOW clause with named window definitions
25127    fn write_window_clause(&mut self, windows: &[NamedWindow]) -> Result<()> {
25128        if windows.is_empty() {
25129            return Ok(());
25130        }
25131
25132        if self.config.pretty {
25133            self.write_newline();
25134            self.write_indent();
25135            self.write_keyword("WINDOW");
25136            self.write_newline();
25137            self.indent_level += 1;
25138            for (i, named_window) in windows.iter().enumerate() {
25139                if i > 0 {
25140                    self.write(",");
25141                    self.write_newline();
25142                }
25143                self.write_indent();
25144                self.generate_identifier(&named_window.name)?;
25145                self.write_space();
25146                self.write_keyword("AS");
25147                self.write(" (");
25148                self.generate_over(&named_window.spec)?;
25149                self.write(")");
25150            }
25151            self.indent_level -= 1;
25152        } else {
25153            self.write_space();
25154            self.write_keyword("WINDOW");
25155            self.write_space();
25156            for (i, named_window) in windows.iter().enumerate() {
25157                if i > 0 {
25158                    self.write(", ");
25159                }
25160                self.generate_identifier(&named_window.name)?;
25161                self.write_space();
25162                self.write_keyword("AS");
25163                self.write(" (");
25164                self.generate_over(&named_window.spec)?;
25165                self.write(")");
25166            }
25167        }
25168        Ok(())
25169    }
25170
25171    // === BATCH-GENERATED STUB METHODS (481 variants) ===
25172    fn generate_ai_agg(&mut self, e: &AIAgg) -> Result<()> {
25173        // AI_AGG(this, expression)
25174        self.write_keyword("AI_AGG");
25175        self.write("(");
25176        self.generate_expression(&e.this)?;
25177        self.write(", ");
25178        self.generate_expression(&e.expression)?;
25179        self.write(")");
25180        Ok(())
25181    }
25182
25183    fn generate_ai_classify(&mut self, e: &AIClassify) -> Result<()> {
25184        // AI_CLASSIFY(input, [categories], [config])
25185        self.write_keyword("AI_CLASSIFY");
25186        self.write("(");
25187        self.generate_expression(&e.this)?;
25188        if let Some(categories) = &e.categories {
25189            self.write(", ");
25190            self.generate_expression(categories)?;
25191        }
25192        if let Some(config) = &e.config {
25193            self.write(", ");
25194            self.generate_expression(config)?;
25195        }
25196        self.write(")");
25197        Ok(())
25198    }
25199
25200    fn generate_add_partition(&mut self, e: &AddPartition) -> Result<()> {
25201        // Python: return f"ADD {exists}{self.sql(expression.this)}{location}"
25202        self.write_keyword("ADD");
25203        self.write_space();
25204        if e.exists {
25205            self.write_keyword("IF NOT EXISTS");
25206            self.write_space();
25207        }
25208        self.generate_expression(&e.this)?;
25209        if let Some(location) = &e.location {
25210            self.write_space();
25211            self.generate_expression(location)?;
25212        }
25213        Ok(())
25214    }
25215
25216    fn generate_algorithm_property(&mut self, e: &AlgorithmProperty) -> Result<()> {
25217        // Python: return f"ALGORITHM={self.sql(expression, 'this')}"
25218        self.write_keyword("ALGORITHM");
25219        self.write("=");
25220        self.generate_expression(&e.this)?;
25221        Ok(())
25222    }
25223
25224    fn generate_aliases(&mut self, e: &Aliases) -> Result<()> {
25225        // Python: return f"{self.sql(expression, 'this')} AS ({self.expressions(expression, flat=True)})"
25226        self.generate_expression(&e.this)?;
25227        self.write_space();
25228        self.write_keyword("AS");
25229        self.write(" (");
25230        for (i, expr) in e.expressions.iter().enumerate() {
25231            if i > 0 {
25232                self.write(", ");
25233            }
25234            self.generate_expression(expr)?;
25235        }
25236        self.write(")");
25237        Ok(())
25238    }
25239
25240    fn generate_allowed_values_property(&mut self, e: &AllowedValuesProperty) -> Result<()> {
25241        // Python: return f"ALLOWED_VALUES {self.expressions(e, flat=True)}"
25242        self.write_keyword("ALLOWED_VALUES");
25243        self.write_space();
25244        for (i, expr) in e.expressions.iter().enumerate() {
25245            if i > 0 {
25246                self.write(", ");
25247            }
25248            self.generate_expression(expr)?;
25249        }
25250        Ok(())
25251    }
25252
25253    fn generate_alter_column(&mut self, e: &AlterColumn) -> Result<()> {
25254        // Python: complex logic based on dtype, default, comment, visible, etc.
25255        self.write_keyword("ALTER COLUMN");
25256        self.write_space();
25257        self.generate_expression(&e.this)?;
25258
25259        if let Some(dtype) = &e.dtype {
25260            self.write_space();
25261            self.write_keyword("SET DATA TYPE");
25262            self.write_space();
25263            self.generate_expression(dtype)?;
25264            if let Some(collate) = &e.collate {
25265                self.write_space();
25266                self.write_keyword("COLLATE");
25267                self.write_space();
25268                self.generate_expression(collate)?;
25269            }
25270            if let Some(using) = &e.using {
25271                self.write_space();
25272                self.write_keyword("USING");
25273                self.write_space();
25274                self.generate_expression(using)?;
25275            }
25276        } else if let Some(default) = &e.default {
25277            self.write_space();
25278            self.write_keyword("SET DEFAULT");
25279            self.write_space();
25280            self.generate_expression(default)?;
25281        } else if let Some(comment) = &e.comment {
25282            self.write_space();
25283            self.write_keyword("COMMENT");
25284            self.write_space();
25285            self.generate_expression(comment)?;
25286        } else if let Some(drop) = &e.drop {
25287            self.write_space();
25288            self.write_keyword("DROP");
25289            self.write_space();
25290            self.generate_expression(drop)?;
25291        } else if let Some(visible) = &e.visible {
25292            self.write_space();
25293            self.generate_expression(visible)?;
25294        } else if let Some(rename_to) = &e.rename_to {
25295            self.write_space();
25296            self.write_keyword("RENAME TO");
25297            self.write_space();
25298            self.generate_expression(rename_to)?;
25299        } else if let Some(allow_null) = &e.allow_null {
25300            self.write_space();
25301            self.generate_expression(allow_null)?;
25302        }
25303        Ok(())
25304    }
25305
25306    fn generate_alter_session(&mut self, e: &AlterSession) -> Result<()> {
25307        // Python: keyword = "UNSET" if expression.args.get("unset") else "SET"; return f"{keyword} {items_sql}"
25308        self.write_keyword("ALTER SESSION");
25309        self.write_space();
25310        if e.unset.is_some() {
25311            self.write_keyword("UNSET");
25312        } else {
25313            self.write_keyword("SET");
25314        }
25315        self.write_space();
25316        for (i, expr) in e.expressions.iter().enumerate() {
25317            if i > 0 {
25318                self.write(", ");
25319            }
25320            self.generate_expression(expr)?;
25321        }
25322        Ok(())
25323    }
25324
25325    fn generate_alter_set(&mut self, e: &AlterSet) -> Result<()> {
25326        // Python (Snowflake): return f"SET{exprs}{file_format}{copy_options}{tag}"
25327        self.write_keyword("SET");
25328
25329        // Generate option (e.g., AUTHORIZATION, LOGGED, UNLOGGED, etc.)
25330        if let Some(opt) = &e.option {
25331            self.write_space();
25332            self.generate_expression(opt)?;
25333        }
25334
25335        // Generate PROPERTIES (for Trino SET PROPERTIES x = y, ...)
25336        // Check if expressions look like property assignments
25337        if !e.expressions.is_empty() {
25338            // Check if this looks like property assignments (for SET PROPERTIES)
25339            let is_properties = e
25340                .expressions
25341                .iter()
25342                .any(|expr| matches!(expr, Expression::Eq(_)));
25343            if is_properties && e.option.is_none() {
25344                self.write_space();
25345                self.write_keyword("PROPERTIES");
25346            }
25347            self.write_space();
25348            for (i, expr) in e.expressions.iter().enumerate() {
25349                if i > 0 {
25350                    self.write(", ");
25351                }
25352                self.generate_expression(expr)?;
25353            }
25354        }
25355
25356        // Generate STAGE_FILE_FORMAT = (...) with space-separated properties
25357        if let Some(file_format) = &e.file_format {
25358            self.write(" ");
25359            self.write_keyword("STAGE_FILE_FORMAT");
25360            self.write(" = (");
25361            self.generate_space_separated_properties(file_format)?;
25362            self.write(")");
25363        }
25364
25365        // Generate STAGE_COPY_OPTIONS = (...) with space-separated properties
25366        if let Some(copy_options) = &e.copy_options {
25367            self.write(" ");
25368            self.write_keyword("STAGE_COPY_OPTIONS");
25369            self.write(" = (");
25370            self.generate_space_separated_properties(copy_options)?;
25371            self.write(")");
25372        }
25373
25374        // Generate TAG ...
25375        if let Some(tag) = &e.tag {
25376            self.write(" ");
25377            self.write_keyword("TAG");
25378            self.write(" ");
25379            self.generate_expression(tag)?;
25380        }
25381
25382        Ok(())
25383    }
25384
25385    /// Generate space-separated properties (for Snowflake STAGE_FILE_FORMAT, etc.)
25386    fn generate_space_separated_properties(&mut self, expr: &Expression) -> Result<()> {
25387        match expr {
25388            Expression::Tuple(t) => {
25389                for (i, prop) in t.expressions.iter().enumerate() {
25390                    if i > 0 {
25391                        self.write(" ");
25392                    }
25393                    self.generate_expression(prop)?;
25394                }
25395            }
25396            _ => {
25397                self.generate_expression(expr)?;
25398            }
25399        }
25400        Ok(())
25401    }
25402
25403    fn generate_alter_sort_key(&mut self, e: &AlterSortKey) -> Result<()> {
25404        // Python: return f"ALTER{compound} SORTKEY {this or expressions}"
25405        self.write_keyword("ALTER");
25406        if e.compound.is_some() {
25407            self.write_space();
25408            self.write_keyword("COMPOUND");
25409        }
25410        self.write_space();
25411        self.write_keyword("SORTKEY");
25412        self.write_space();
25413        if let Some(this) = &e.this {
25414            self.generate_expression(this)?;
25415        } else if !e.expressions.is_empty() {
25416            self.write("(");
25417            for (i, expr) in e.expressions.iter().enumerate() {
25418                if i > 0 {
25419                    self.write(", ");
25420                }
25421                self.generate_expression(expr)?;
25422            }
25423            self.write(")");
25424        }
25425        Ok(())
25426    }
25427
25428    fn generate_analyze(&mut self, e: &Analyze) -> Result<()> {
25429        // Python: return f"ANALYZE{options}{kind}{this}{partition}{mode}{inner_expression}{properties}"
25430        self.write_keyword("ANALYZE");
25431        if !e.options.is_empty() {
25432            self.write_space();
25433            for (i, opt) in e.options.iter().enumerate() {
25434                if i > 0 {
25435                    self.write_space();
25436                }
25437                // Write options as keywords (not identifiers) to avoid quoting reserved words like FULL
25438                if let Expression::Identifier(id) = opt {
25439                    self.write_keyword(&id.name);
25440                } else {
25441                    self.generate_expression(opt)?;
25442                }
25443            }
25444        }
25445        if let Some(kind) = &e.kind {
25446            self.write_space();
25447            self.write_keyword(kind);
25448        }
25449        if let Some(this) = &e.this {
25450            self.write_space();
25451            self.generate_expression(this)?;
25452        }
25453        // Column list: ANALYZE tbl(col1, col2) (PostgreSQL)
25454        if !e.columns.is_empty() {
25455            self.write("(");
25456            for (i, col) in e.columns.iter().enumerate() {
25457                if i > 0 {
25458                    self.write(", ");
25459                }
25460                self.write(col);
25461            }
25462            self.write(")");
25463        }
25464        if let Some(partition) = &e.partition {
25465            self.write_space();
25466            self.generate_expression(partition)?;
25467        }
25468        if let Some(mode) = &e.mode {
25469            self.write_space();
25470            self.generate_expression(mode)?;
25471        }
25472        if let Some(expression) = &e.expression {
25473            self.write_space();
25474            self.generate_expression(expression)?;
25475        }
25476        if !e.properties.is_empty() {
25477            self.write_space();
25478            self.write_keyword(self.config.with_properties_prefix);
25479            self.write(" (");
25480            for (i, prop) in e.properties.iter().enumerate() {
25481                if i > 0 {
25482                    self.write(", ");
25483                }
25484                self.generate_expression(prop)?;
25485            }
25486            self.write(")");
25487        }
25488        Ok(())
25489    }
25490
25491    fn generate_analyze_delete(&mut self, e: &AnalyzeDelete) -> Result<()> {
25492        // Python: return f"DELETE{kind} STATISTICS"
25493        self.write_keyword("DELETE");
25494        if let Some(kind) = &e.kind {
25495            self.write_space();
25496            self.write_keyword(kind);
25497        }
25498        self.write_space();
25499        self.write_keyword("STATISTICS");
25500        Ok(())
25501    }
25502
25503    fn generate_analyze_histogram(&mut self, e: &AnalyzeHistogram) -> Result<()> {
25504        // Python: return f"{this} HISTOGRAM ON {columns}{inner_expression}{update_options}"
25505        // Write `this` (UPDATE or DROP) as keyword to avoid quoting reserved words
25506        if let Expression::Identifier(id) = e.this.as_ref() {
25507            self.write_keyword(&id.name);
25508        } else {
25509            self.generate_expression(&e.this)?;
25510        }
25511        self.write_space();
25512        self.write_keyword("HISTOGRAM ON");
25513        self.write_space();
25514        for (i, expr) in e.expressions.iter().enumerate() {
25515            if i > 0 {
25516                self.write(", ");
25517            }
25518            self.generate_expression(expr)?;
25519        }
25520        if let Some(expression) = &e.expression {
25521            self.write_space();
25522            self.generate_expression(expression)?;
25523        }
25524        if let Some(update_options) = &e.update_options {
25525            self.write_space();
25526            self.generate_expression(update_options)?;
25527            self.write_space();
25528            self.write_keyword("UPDATE");
25529        }
25530        Ok(())
25531    }
25532
25533    fn generate_analyze_list_chained_rows(&mut self, e: &AnalyzeListChainedRows) -> Result<()> {
25534        // Python: return f"LIST CHAINED ROWS{inner_expression}"
25535        self.write_keyword("LIST CHAINED ROWS");
25536        if let Some(expression) = &e.expression {
25537            self.write_space();
25538            self.write_keyword("INTO");
25539            self.write_space();
25540            self.generate_expression(expression)?;
25541        }
25542        Ok(())
25543    }
25544
25545    fn generate_analyze_sample(&mut self, e: &AnalyzeSample) -> Result<()> {
25546        // Python: return f"SAMPLE {sample} {kind}"
25547        self.write_keyword("SAMPLE");
25548        self.write_space();
25549        if let Some(sample) = &e.sample {
25550            self.generate_expression(sample)?;
25551            self.write_space();
25552        }
25553        self.write_keyword(&e.kind);
25554        Ok(())
25555    }
25556
25557    fn generate_analyze_statistics(&mut self, e: &AnalyzeStatistics) -> Result<()> {
25558        // Python: return f"{kind}{option} STATISTICS{this}{columns}"
25559        self.write_keyword(&e.kind);
25560        if let Some(option) = &e.option {
25561            self.write_space();
25562            self.generate_expression(option)?;
25563        }
25564        self.write_space();
25565        self.write_keyword("STATISTICS");
25566        if let Some(this) = &e.this {
25567            self.write_space();
25568            self.generate_expression(this)?;
25569        }
25570        if !e.expressions.is_empty() {
25571            self.write_space();
25572            for (i, expr) in e.expressions.iter().enumerate() {
25573                if i > 0 {
25574                    self.write(", ");
25575                }
25576                self.generate_expression(expr)?;
25577            }
25578        }
25579        Ok(())
25580    }
25581
25582    fn generate_analyze_validate(&mut self, e: &AnalyzeValidate) -> Result<()> {
25583        // Python: return f"VALIDATE {kind}{this}{inner_expression}"
25584        self.write_keyword("VALIDATE");
25585        self.write_space();
25586        self.write_keyword(&e.kind);
25587        if let Some(this) = &e.this {
25588            self.write_space();
25589            // this is a keyword string like "UPDATE", "CASCADE FAST", etc. - write as keywords
25590            if let Expression::Identifier(id) = this.as_ref() {
25591                self.write_keyword(&id.name);
25592            } else {
25593                self.generate_expression(this)?;
25594            }
25595        }
25596        if let Some(expression) = &e.expression {
25597            self.write_space();
25598            self.write_keyword("INTO");
25599            self.write_space();
25600            self.generate_expression(expression)?;
25601        }
25602        Ok(())
25603    }
25604
25605    fn generate_analyze_with(&mut self, e: &AnalyzeWith) -> Result<()> {
25606        // Python: return f"WITH {expressions}"
25607        self.write_keyword("WITH");
25608        self.write_space();
25609        for (i, expr) in e.expressions.iter().enumerate() {
25610            if i > 0 {
25611                self.write(", ");
25612            }
25613            self.generate_expression(expr)?;
25614        }
25615        Ok(())
25616    }
25617
25618    fn generate_anonymous(&mut self, e: &Anonymous) -> Result<()> {
25619        // Anonymous represents a generic function call: FUNC_NAME(args...)
25620        // Python: return self.func(self.sql(expression, "this"), *expression.expressions)
25621        self.generate_expression(&e.this)?;
25622        self.write("(");
25623        for (i, arg) in e.expressions.iter().enumerate() {
25624            if i > 0 {
25625                self.write(", ");
25626            }
25627            self.generate_expression(arg)?;
25628        }
25629        self.write(")");
25630        Ok(())
25631    }
25632
25633    fn generate_anonymous_agg_func(&mut self, e: &AnonymousAggFunc) -> Result<()> {
25634        // Same as Anonymous but for aggregate functions
25635        self.generate_expression(&e.this)?;
25636        self.write("(");
25637        for (i, arg) in e.expressions.iter().enumerate() {
25638            if i > 0 {
25639                self.write(", ");
25640            }
25641            self.generate_expression(arg)?;
25642        }
25643        self.write(")");
25644        Ok(())
25645    }
25646
25647    fn generate_apply(&mut self, e: &Apply) -> Result<()> {
25648        // Python: return f"{this} APPLY({expr})"
25649        self.generate_expression(&e.this)?;
25650        self.write_space();
25651        self.write_keyword("APPLY");
25652        self.write("(");
25653        self.generate_expression(&e.expression)?;
25654        self.write(")");
25655        Ok(())
25656    }
25657
25658    fn generate_approx_percentile_estimate(&mut self, e: &ApproxPercentileEstimate) -> Result<()> {
25659        // APPROX_PERCENTILE_ESTIMATE(this, percentile)
25660        self.write_keyword("APPROX_PERCENTILE_ESTIMATE");
25661        self.write("(");
25662        self.generate_expression(&e.this)?;
25663        if let Some(percentile) = &e.percentile {
25664            self.write(", ");
25665            self.generate_expression(percentile)?;
25666        }
25667        self.write(")");
25668        Ok(())
25669    }
25670
25671    fn generate_approx_quantile(&mut self, e: &ApproxQuantile) -> Result<()> {
25672        // APPROX_QUANTILE(this, quantile[, accuracy][, weight])
25673        self.write_keyword("APPROX_QUANTILE");
25674        self.write("(");
25675        self.generate_expression(&e.this)?;
25676        if let Some(quantile) = &e.quantile {
25677            self.write(", ");
25678            self.generate_expression(quantile)?;
25679        }
25680        if let Some(accuracy) = &e.accuracy {
25681            self.write(", ");
25682            self.generate_expression(accuracy)?;
25683        }
25684        if let Some(weight) = &e.weight {
25685            self.write(", ");
25686            self.generate_expression(weight)?;
25687        }
25688        self.write(")");
25689        Ok(())
25690    }
25691
25692    fn generate_approx_quantiles(&mut self, e: &ApproxQuantiles) -> Result<()> {
25693        // APPROX_QUANTILES(this, expression)
25694        self.write_keyword("APPROX_QUANTILES");
25695        self.write("(");
25696        self.generate_expression(&e.this)?;
25697        if let Some(expression) = &e.expression {
25698            self.write(", ");
25699            self.generate_expression(expression)?;
25700        }
25701        self.write(")");
25702        Ok(())
25703    }
25704
25705    fn generate_approx_top_k(&mut self, e: &ApproxTopK) -> Result<()> {
25706        // APPROX_TOP_K(this[, expression][, counters])
25707        self.write_keyword("APPROX_TOP_K");
25708        self.write("(");
25709        self.generate_expression(&e.this)?;
25710        if let Some(expression) = &e.expression {
25711            self.write(", ");
25712            self.generate_expression(expression)?;
25713        }
25714        if let Some(counters) = &e.counters {
25715            self.write(", ");
25716            self.generate_expression(counters)?;
25717        }
25718        self.write(")");
25719        Ok(())
25720    }
25721
25722    fn generate_approx_top_k_accumulate(&mut self, e: &ApproxTopKAccumulate) -> Result<()> {
25723        // APPROX_TOP_K_ACCUMULATE(this[, expression])
25724        self.write_keyword("APPROX_TOP_K_ACCUMULATE");
25725        self.write("(");
25726        self.generate_expression(&e.this)?;
25727        if let Some(expression) = &e.expression {
25728            self.write(", ");
25729            self.generate_expression(expression)?;
25730        }
25731        self.write(")");
25732        Ok(())
25733    }
25734
25735    fn generate_approx_top_k_combine(&mut self, e: &ApproxTopKCombine) -> Result<()> {
25736        // APPROX_TOP_K_COMBINE(this[, expression])
25737        self.write_keyword("APPROX_TOP_K_COMBINE");
25738        self.write("(");
25739        self.generate_expression(&e.this)?;
25740        if let Some(expression) = &e.expression {
25741            self.write(", ");
25742            self.generate_expression(expression)?;
25743        }
25744        self.write(")");
25745        Ok(())
25746    }
25747
25748    fn generate_approx_top_k_estimate(&mut self, e: &ApproxTopKEstimate) -> Result<()> {
25749        // APPROX_TOP_K_ESTIMATE(this[, expression])
25750        self.write_keyword("APPROX_TOP_K_ESTIMATE");
25751        self.write("(");
25752        self.generate_expression(&e.this)?;
25753        if let Some(expression) = &e.expression {
25754            self.write(", ");
25755            self.generate_expression(expression)?;
25756        }
25757        self.write(")");
25758        Ok(())
25759    }
25760
25761    fn generate_approx_top_sum(&mut self, e: &ApproxTopSum) -> Result<()> {
25762        // APPROX_TOP_SUM(this, expression[, count])
25763        self.write_keyword("APPROX_TOP_SUM");
25764        self.write("(");
25765        self.generate_expression(&e.this)?;
25766        self.write(", ");
25767        self.generate_expression(&e.expression)?;
25768        if let Some(count) = &e.count {
25769            self.write(", ");
25770            self.generate_expression(count)?;
25771        }
25772        self.write(")");
25773        Ok(())
25774    }
25775
25776    fn generate_arg_max(&mut self, e: &ArgMax) -> Result<()> {
25777        // ARG_MAX(this, expression[, count])
25778        self.write_keyword("ARG_MAX");
25779        self.write("(");
25780        self.generate_expression(&e.this)?;
25781        self.write(", ");
25782        self.generate_expression(&e.expression)?;
25783        if let Some(count) = &e.count {
25784            self.write(", ");
25785            self.generate_expression(count)?;
25786        }
25787        self.write(")");
25788        Ok(())
25789    }
25790
25791    fn generate_arg_min(&mut self, e: &ArgMin) -> Result<()> {
25792        // ARG_MIN(this, expression[, count])
25793        self.write_keyword("ARG_MIN");
25794        self.write("(");
25795        self.generate_expression(&e.this)?;
25796        self.write(", ");
25797        self.generate_expression(&e.expression)?;
25798        if let Some(count) = &e.count {
25799            self.write(", ");
25800            self.generate_expression(count)?;
25801        }
25802        self.write(")");
25803        Ok(())
25804    }
25805
25806    fn generate_array_all(&mut self, e: &ArrayAll) -> Result<()> {
25807        // ARRAY_ALL(this, expression)
25808        self.write_keyword("ARRAY_ALL");
25809        self.write("(");
25810        self.generate_expression(&e.this)?;
25811        self.write(", ");
25812        self.generate_expression(&e.expression)?;
25813        self.write(")");
25814        Ok(())
25815    }
25816
25817    fn generate_array_any(&mut self, e: &ArrayAny) -> Result<()> {
25818        // ARRAY_ANY(this, expression) - fallback implementation
25819        self.write_keyword("ARRAY_ANY");
25820        self.write("(");
25821        self.generate_expression(&e.this)?;
25822        self.write(", ");
25823        self.generate_expression(&e.expression)?;
25824        self.write(")");
25825        Ok(())
25826    }
25827
25828    fn generate_array_construct_compact(&mut self, e: &ArrayConstructCompact) -> Result<()> {
25829        // ARRAY_CONSTRUCT_COMPACT(expressions...)
25830        self.write_keyword("ARRAY_CONSTRUCT_COMPACT");
25831        self.write("(");
25832        for (i, expr) in e.expressions.iter().enumerate() {
25833            if i > 0 {
25834                self.write(", ");
25835            }
25836            self.generate_expression(expr)?;
25837        }
25838        self.write(")");
25839        Ok(())
25840    }
25841
25842    fn generate_array_sum(&mut self, e: &ArraySum) -> Result<()> {
25843        // ARRAY_SUM(this[, expression])
25844        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
25845            self.write("arraySum");
25846        } else {
25847            self.write_keyword("ARRAY_SUM");
25848        }
25849        self.write("(");
25850        self.generate_expression(&e.this)?;
25851        if let Some(expression) = &e.expression {
25852            self.write(", ");
25853            self.generate_expression(expression)?;
25854        }
25855        self.write(")");
25856        Ok(())
25857    }
25858
25859    fn generate_at_index(&mut self, e: &AtIndex) -> Result<()> {
25860        // Python: return f"{this} AT {index}"
25861        self.generate_expression(&e.this)?;
25862        self.write_space();
25863        self.write_keyword("AT");
25864        self.write_space();
25865        self.generate_expression(&e.expression)?;
25866        Ok(())
25867    }
25868
25869    fn generate_attach(&mut self, e: &Attach) -> Result<()> {
25870        // Python: return f"ATTACH{exists_sql} {this}{expressions}"
25871        self.write_keyword("ATTACH");
25872        if e.exists {
25873            self.write_space();
25874            self.write_keyword("IF NOT EXISTS");
25875        }
25876        self.write_space();
25877        self.generate_expression(&e.this)?;
25878        if !e.expressions.is_empty() {
25879            self.write(" (");
25880            for (i, expr) in e.expressions.iter().enumerate() {
25881                if i > 0 {
25882                    self.write(", ");
25883                }
25884                self.generate_expression(expr)?;
25885            }
25886            self.write(")");
25887        }
25888        Ok(())
25889    }
25890
25891    fn generate_attach_option(&mut self, e: &AttachOption) -> Result<()> {
25892        // AttachOption: this [expression]
25893        // Python sqlglot: no equals sign, just space-separated
25894        self.generate_expression(&e.this)?;
25895        if let Some(expression) = &e.expression {
25896            self.write_space();
25897            self.generate_expression(expression)?;
25898        }
25899        Ok(())
25900    }
25901
25902    /// Generate the auto_increment keyword and options for a column definition.
25903    /// Different dialects use different syntax: IDENTITY, AUTOINCREMENT, AUTO_INCREMENT,
25904    /// GENERATED AS IDENTITY, etc.
25905    fn generate_auto_increment_keyword(
25906        &mut self,
25907        col: &crate::expressions::ColumnDef,
25908    ) -> Result<()> {
25909        use crate::dialects::DialectType;
25910        if matches!(self.config.dialect, Some(DialectType::Redshift)) {
25911            self.write_keyword("IDENTITY");
25912            if col.auto_increment_start.is_some() || col.auto_increment_increment.is_some() {
25913                self.write("(");
25914                if let Some(ref start) = col.auto_increment_start {
25915                    self.generate_expression(start)?;
25916                } else {
25917                    self.write("0");
25918                }
25919                self.write(", ");
25920                if let Some(ref inc) = col.auto_increment_increment {
25921                    self.generate_expression(inc)?;
25922                } else {
25923                    self.write("1");
25924                }
25925                self.write(")");
25926            }
25927        } else if matches!(
25928            self.config.dialect,
25929            Some(DialectType::Snowflake) | Some(DialectType::SQLite)
25930        ) {
25931            self.write_keyword("AUTOINCREMENT");
25932            if let Some(ref start) = col.auto_increment_start {
25933                self.write_space();
25934                self.write_keyword("START");
25935                self.write_space();
25936                self.generate_expression(start)?;
25937            }
25938            if let Some(ref inc) = col.auto_increment_increment {
25939                self.write_space();
25940                self.write_keyword("INCREMENT");
25941                self.write_space();
25942                self.generate_expression(inc)?;
25943            }
25944            if let Some(order) = col.auto_increment_order {
25945                self.write_space();
25946                if order {
25947                    self.write_keyword("ORDER");
25948                } else {
25949                    self.write_keyword("NOORDER");
25950                }
25951            }
25952        } else if matches!(self.config.dialect, Some(DialectType::PostgreSQL)) {
25953            self.write_keyword("GENERATED BY DEFAULT AS IDENTITY");
25954            if col.auto_increment_start.is_some() || col.auto_increment_increment.is_some() {
25955                self.write(" (");
25956                let mut first = true;
25957                if let Some(ref start) = col.auto_increment_start {
25958                    self.write_keyword("START WITH");
25959                    self.write_space();
25960                    self.generate_expression(start)?;
25961                    first = false;
25962                }
25963                if let Some(ref inc) = col.auto_increment_increment {
25964                    if !first {
25965                        self.write_space();
25966                    }
25967                    self.write_keyword("INCREMENT BY");
25968                    self.write_space();
25969                    self.generate_expression(inc)?;
25970                }
25971                self.write(")");
25972            }
25973        } else if matches!(self.config.dialect, Some(DialectType::Databricks)) {
25974            // IDENTITY(start, increment) -> GENERATED BY DEFAULT AS IDENTITY
25975            // Plain IDENTITY/AUTO_INCREMENT -> GENERATED ALWAYS AS IDENTITY
25976            if col.auto_increment_start.is_some() || col.auto_increment_increment.is_some() {
25977                self.write_keyword("GENERATED BY DEFAULT AS IDENTITY");
25978            } else {
25979                self.write_keyword("GENERATED ALWAYS AS IDENTITY");
25980            }
25981            if col.auto_increment_start.is_some() || col.auto_increment_increment.is_some() {
25982                self.write(" (");
25983                let mut first = true;
25984                if let Some(ref start) = col.auto_increment_start {
25985                    self.write_keyword("START WITH");
25986                    self.write_space();
25987                    self.generate_expression(start)?;
25988                    first = false;
25989                }
25990                if let Some(ref inc) = col.auto_increment_increment {
25991                    if !first {
25992                        self.write_space();
25993                    }
25994                    self.write_keyword("INCREMENT BY");
25995                    self.write_space();
25996                    self.generate_expression(inc)?;
25997                }
25998                self.write(")");
25999            }
26000        } else if matches!(
26001            self.config.dialect,
26002            Some(DialectType::TSQL) | Some(DialectType::Fabric)
26003        ) {
26004            self.write_keyword("IDENTITY");
26005            if col.auto_increment_start.is_some() || col.auto_increment_increment.is_some() {
26006                self.write("(");
26007                if let Some(ref start) = col.auto_increment_start {
26008                    self.generate_expression(start)?;
26009                } else {
26010                    self.write("0");
26011                }
26012                self.write(", ");
26013                if let Some(ref inc) = col.auto_increment_increment {
26014                    self.generate_expression(inc)?;
26015                } else {
26016                    self.write("1");
26017                }
26018                self.write(")");
26019            }
26020        } else {
26021            self.write_keyword("AUTO_INCREMENT");
26022            if let Some(ref start) = col.auto_increment_start {
26023                self.write_space();
26024                self.write_keyword("START");
26025                self.write_space();
26026                self.generate_expression(start)?;
26027            }
26028            if let Some(ref inc) = col.auto_increment_increment {
26029                self.write_space();
26030                self.write_keyword("INCREMENT");
26031                self.write_space();
26032                self.generate_expression(inc)?;
26033            }
26034            if let Some(order) = col.auto_increment_order {
26035                self.write_space();
26036                if order {
26037                    self.write_keyword("ORDER");
26038                } else {
26039                    self.write_keyword("NOORDER");
26040                }
26041            }
26042        }
26043        Ok(())
26044    }
26045
26046    fn generate_auto_increment_property(&mut self, e: &AutoIncrementProperty) -> Result<()> {
26047        // AUTO_INCREMENT=value
26048        self.write_keyword("AUTO_INCREMENT");
26049        self.write("=");
26050        self.generate_expression(&e.this)?;
26051        Ok(())
26052    }
26053
26054    fn generate_auto_refresh_property(&mut self, e: &AutoRefreshProperty) -> Result<()> {
26055        // AUTO_REFRESH=value
26056        self.write_keyword("AUTO_REFRESH");
26057        self.write("=");
26058        self.generate_expression(&e.this)?;
26059        Ok(())
26060    }
26061
26062    fn generate_backup_property(&mut self, e: &BackupProperty) -> Result<()> {
26063        // BACKUP YES|NO (Redshift syntax uses space, not equals)
26064        self.write_keyword("BACKUP");
26065        self.write_space();
26066        self.generate_expression(&e.this)?;
26067        Ok(())
26068    }
26069
26070    fn generate_base64_decode_binary(&mut self, e: &Base64DecodeBinary) -> Result<()> {
26071        // BASE64_DECODE_BINARY(this[, alphabet])
26072        self.write_keyword("BASE64_DECODE_BINARY");
26073        self.write("(");
26074        self.generate_expression(&e.this)?;
26075        if let Some(alphabet) = &e.alphabet {
26076            self.write(", ");
26077            self.generate_expression(alphabet)?;
26078        }
26079        self.write(")");
26080        Ok(())
26081    }
26082
26083    fn generate_base64_decode_string(&mut self, e: &Base64DecodeString) -> Result<()> {
26084        // BASE64_DECODE_STRING(this[, alphabet])
26085        self.write_keyword("BASE64_DECODE_STRING");
26086        self.write("(");
26087        self.generate_expression(&e.this)?;
26088        if let Some(alphabet) = &e.alphabet {
26089            self.write(", ");
26090            self.generate_expression(alphabet)?;
26091        }
26092        self.write(")");
26093        Ok(())
26094    }
26095
26096    fn generate_base64_encode(&mut self, e: &Base64Encode) -> Result<()> {
26097        // BASE64_ENCODE(this[, max_line_length][, alphabet])
26098        self.write_keyword("BASE64_ENCODE");
26099        self.write("(");
26100        self.generate_expression(&e.this)?;
26101        if let Some(max_line_length) = &e.max_line_length {
26102            self.write(", ");
26103            self.generate_expression(max_line_length)?;
26104        }
26105        if let Some(alphabet) = &e.alphabet {
26106            self.write(", ");
26107            self.generate_expression(alphabet)?;
26108        }
26109        self.write(")");
26110        Ok(())
26111    }
26112
26113    fn generate_block_compression_property(&mut self, e: &BlockCompressionProperty) -> Result<()> {
26114        // BLOCKCOMPRESSION=... (complex Teradata property)
26115        self.write_keyword("BLOCKCOMPRESSION");
26116        self.write("=");
26117        if let Some(autotemp) = &e.autotemp {
26118            self.write_keyword("AUTOTEMP");
26119            self.write("(");
26120            self.generate_expression(autotemp)?;
26121            self.write(")");
26122        }
26123        if let Some(always) = &e.always {
26124            self.generate_expression(always)?;
26125        }
26126        if let Some(default) = &e.default {
26127            self.generate_expression(default)?;
26128        }
26129        if let Some(manual) = &e.manual {
26130            self.generate_expression(manual)?;
26131        }
26132        if let Some(never) = &e.never {
26133            self.generate_expression(never)?;
26134        }
26135        Ok(())
26136    }
26137
26138    fn generate_booland(&mut self, e: &Booland) -> Result<()> {
26139        // Python: return f"(({self.sql(expression, 'this')}) AND ({self.sql(expression, 'expression')}))"
26140        self.write("((");
26141        self.generate_expression(&e.this)?;
26142        self.write(") ");
26143        self.write_keyword("AND");
26144        self.write(" (");
26145        self.generate_expression(&e.expression)?;
26146        self.write("))");
26147        Ok(())
26148    }
26149
26150    fn generate_boolor(&mut self, e: &Boolor) -> Result<()> {
26151        // Python: return f"(({self.sql(expression, 'this')}) OR ({self.sql(expression, 'expression')}))"
26152        self.write("((");
26153        self.generate_expression(&e.this)?;
26154        self.write(") ");
26155        self.write_keyword("OR");
26156        self.write(" (");
26157        self.generate_expression(&e.expression)?;
26158        self.write("))");
26159        Ok(())
26160    }
26161
26162    fn generate_build_property(&mut self, e: &BuildProperty) -> Result<()> {
26163        // BUILD value (e.g., BUILD IMMEDIATE, BUILD DEFERRED)
26164        self.write_keyword("BUILD");
26165        self.write_space();
26166        self.generate_expression(&e.this)?;
26167        Ok(())
26168    }
26169
26170    fn generate_byte_string(&mut self, e: &ByteString) -> Result<()> {
26171        // Byte string literal like B'...' or X'...'
26172        self.generate_expression(&e.this)?;
26173        Ok(())
26174    }
26175
26176    fn generate_case_specific_column_constraint(
26177        &mut self,
26178        e: &CaseSpecificColumnConstraint,
26179    ) -> Result<()> {
26180        // CASESPECIFIC or NOT CASESPECIFIC (Teradata)
26181        if e.not_.is_some() {
26182            self.write_keyword("NOT");
26183            self.write_space();
26184        }
26185        self.write_keyword("CASESPECIFIC");
26186        Ok(())
26187    }
26188
26189    fn generate_cast_to_str_type(&mut self, e: &CastToStrType) -> Result<()> {
26190        // Cast to string type (dialect-specific)
26191        self.write_keyword("CAST");
26192        self.write("(");
26193        self.generate_expression(&e.this)?;
26194        if self.config.dialect == Some(DialectType::ClickHouse) {
26195            // ClickHouse: CAST(expr, 'type_string')
26196            self.write(", ");
26197        } else {
26198            self.write_space();
26199            self.write_keyword("AS");
26200            self.write_space();
26201        }
26202        if let Some(to) = &e.to {
26203            self.generate_expression(to)?;
26204        }
26205        self.write(")");
26206        Ok(())
26207    }
26208
26209    fn generate_changes(&mut self, e: &Changes) -> Result<()> {
26210        // CHANGES (INFORMATION => value) AT|BEFORE (...) END (...)
26211        // Python: f"CHANGES ({information}){at_before}{end}"
26212        self.write_keyword("CHANGES");
26213        self.write(" (");
26214        if let Some(information) = &e.information {
26215            self.write_keyword("INFORMATION");
26216            self.write(" => ");
26217            self.generate_expression(information)?;
26218        }
26219        self.write(")");
26220        // at_before and end are HistoricalData expressions that generate their own keywords
26221        if let Some(at_before) = &e.at_before {
26222            self.write(" ");
26223            self.generate_expression(at_before)?;
26224        }
26225        if let Some(end) = &e.end {
26226            self.write(" ");
26227            self.generate_expression(end)?;
26228        }
26229        Ok(())
26230    }
26231
26232    fn generate_character_set_column_constraint(
26233        &mut self,
26234        e: &CharacterSetColumnConstraint,
26235    ) -> Result<()> {
26236        // CHARACTER SET charset_name
26237        self.write_keyword("CHARACTER SET");
26238        self.write_space();
26239        self.generate_expression(&e.this)?;
26240        Ok(())
26241    }
26242
26243    fn generate_character_set_property(&mut self, e: &CharacterSetProperty) -> Result<()> {
26244        // [DEFAULT] CHARACTER SET=value
26245        if e.default.is_some() {
26246            self.write_keyword("DEFAULT");
26247            self.write_space();
26248        }
26249        self.write_keyword("CHARACTER SET");
26250        self.write("=");
26251        self.generate_expression(&e.this)?;
26252        Ok(())
26253    }
26254
26255    fn generate_check_column_constraint(&mut self, e: &CheckColumnConstraint) -> Result<()> {
26256        // Python: return f"CHECK ({self.sql(expression, 'this')}){enforced}"
26257        self.write_keyword("CHECK");
26258        self.write(" (");
26259        self.generate_expression(&e.this)?;
26260        self.write(")");
26261        if e.enforced.is_some() {
26262            self.write_space();
26263            self.write_keyword("ENFORCED");
26264        }
26265        Ok(())
26266    }
26267
26268    fn generate_assume_column_constraint(&mut self, e: &AssumeColumnConstraint) -> Result<()> {
26269        // Python: return f"ASSUME ({self.sql(e, 'this')})"
26270        self.write_keyword("ASSUME");
26271        self.write(" (");
26272        self.generate_expression(&e.this)?;
26273        self.write(")");
26274        Ok(())
26275    }
26276
26277    fn generate_check_json(&mut self, e: &CheckJson) -> Result<()> {
26278        // CHECK_JSON(this)
26279        self.write_keyword("CHECK_JSON");
26280        self.write("(");
26281        self.generate_expression(&e.this)?;
26282        self.write(")");
26283        Ok(())
26284    }
26285
26286    fn generate_check_xml(&mut self, e: &CheckXml) -> Result<()> {
26287        // CHECK_XML(this)
26288        self.write_keyword("CHECK_XML");
26289        self.write("(");
26290        self.generate_expression(&e.this)?;
26291        self.write(")");
26292        Ok(())
26293    }
26294
26295    fn generate_checksum_property(&mut self, e: &ChecksumProperty) -> Result<()> {
26296        // CHECKSUM=[ON|OFF|DEFAULT]
26297        self.write_keyword("CHECKSUM");
26298        self.write("=");
26299        if e.on.is_some() {
26300            self.write_keyword("ON");
26301        } else if e.default.is_some() {
26302            self.write_keyword("DEFAULT");
26303        } else {
26304            self.write_keyword("OFF");
26305        }
26306        Ok(())
26307    }
26308
26309    fn generate_clone(&mut self, e: &Clone) -> Result<()> {
26310        // Python: return f"{shallow}{keyword} {this}"
26311        if e.shallow.is_some() {
26312            self.write_keyword("SHALLOW");
26313            self.write_space();
26314        }
26315        if e.copy.is_some() {
26316            self.write_keyword("COPY");
26317        } else {
26318            self.write_keyword("CLONE");
26319        }
26320        self.write_space();
26321        self.generate_expression(&e.this)?;
26322        Ok(())
26323    }
26324
26325    fn generate_cluster_by(&mut self, e: &ClusterBy) -> Result<()> {
26326        // CLUSTER BY (expressions)
26327        self.write_keyword("CLUSTER BY");
26328        self.write(" (");
26329        for (i, ord) in e.expressions.iter().enumerate() {
26330            if i > 0 {
26331                self.write(", ");
26332            }
26333            self.generate_ordered(ord)?;
26334        }
26335        self.write(")");
26336        Ok(())
26337    }
26338
26339    fn generate_cluster_by_columns_property(&mut self, e: &ClusterByColumnsProperty) -> Result<()> {
26340        // BigQuery table property: CLUSTER BY col1, col2
26341        self.write_keyword("CLUSTER BY");
26342        self.write_space();
26343        for (i, col) in e.columns.iter().enumerate() {
26344            if i > 0 {
26345                self.write(", ");
26346            }
26347            self.generate_identifier(col)?;
26348        }
26349        Ok(())
26350    }
26351
26352    fn generate_clustered_by_property(&mut self, e: &ClusteredByProperty) -> Result<()> {
26353        // Python: return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS"
26354        self.write_keyword("CLUSTERED BY");
26355        self.write(" (");
26356        for (i, expr) in e.expressions.iter().enumerate() {
26357            if i > 0 {
26358                self.write(", ");
26359            }
26360            self.generate_expression(expr)?;
26361        }
26362        self.write(")");
26363        if let Some(sorted_by) = &e.sorted_by {
26364            self.write_space();
26365            self.write_keyword("SORTED BY");
26366            self.write(" (");
26367            // Unwrap Tuple to avoid double parentheses
26368            if let Expression::Tuple(t) = sorted_by.as_ref() {
26369                for (i, expr) in t.expressions.iter().enumerate() {
26370                    if i > 0 {
26371                        self.write(", ");
26372                    }
26373                    self.generate_expression(expr)?;
26374                }
26375            } else {
26376                self.generate_expression(sorted_by)?;
26377            }
26378            self.write(")");
26379        }
26380        if let Some(buckets) = &e.buckets {
26381            self.write_space();
26382            self.write_keyword("INTO");
26383            self.write_space();
26384            self.generate_expression(buckets)?;
26385            self.write_space();
26386            self.write_keyword("BUCKETS");
26387        }
26388        Ok(())
26389    }
26390
26391    fn generate_collate_property(&mut self, e: &CollateProperty) -> Result<()> {
26392        // [DEFAULT] COLLATE [=] value
26393        // BigQuery uses space: DEFAULT COLLATE 'en'
26394        // Others use equals: COLLATE='en'
26395        if e.default.is_some() {
26396            self.write_keyword("DEFAULT");
26397            self.write_space();
26398        }
26399        self.write_keyword("COLLATE");
26400        // BigQuery uses space between COLLATE and value
26401        match self.config.dialect {
26402            Some(DialectType::BigQuery) => self.write_space(),
26403            _ => self.write("="),
26404        }
26405        self.generate_expression(&e.this)?;
26406        Ok(())
26407    }
26408
26409    fn generate_column_constraint(&mut self, e: &ColumnConstraint) -> Result<()> {
26410        // ColumnConstraint is an enum
26411        match e {
26412            ColumnConstraint::NotNull => {
26413                self.write_keyword("NOT NULL");
26414            }
26415            ColumnConstraint::Null => {
26416                self.write_keyword("NULL");
26417            }
26418            ColumnConstraint::Unique => {
26419                self.write_keyword("UNIQUE");
26420            }
26421            ColumnConstraint::PrimaryKey => {
26422                self.write_keyword("PRIMARY KEY");
26423            }
26424            ColumnConstraint::Default(expr) => {
26425                self.write_keyword("DEFAULT");
26426                self.write_space();
26427                self.generate_expression(expr)?;
26428            }
26429            ColumnConstraint::Check(expr) => {
26430                self.write_keyword("CHECK");
26431                self.write(" (");
26432                self.generate_expression(expr)?;
26433                self.write(")");
26434            }
26435            ColumnConstraint::References(fk_ref) => {
26436                if fk_ref.has_foreign_key_keywords {
26437                    self.write_keyword("FOREIGN KEY");
26438                    self.write_space();
26439                }
26440                self.write_keyword("REFERENCES");
26441                self.write_space();
26442                self.generate_table(&fk_ref.table)?;
26443                if !fk_ref.columns.is_empty() {
26444                    self.write(" (");
26445                    for (i, col) in fk_ref.columns.iter().enumerate() {
26446                        if i > 0 {
26447                            self.write(", ");
26448                        }
26449                        self.generate_identifier(col)?;
26450                    }
26451                    self.write(")");
26452                }
26453            }
26454            ColumnConstraint::GeneratedAsIdentity(gen) => {
26455                self.write_keyword("GENERATED");
26456                self.write_space();
26457                if gen.always {
26458                    self.write_keyword("ALWAYS");
26459                } else {
26460                    self.write_keyword("BY DEFAULT");
26461                    if gen.on_null {
26462                        self.write_space();
26463                        self.write_keyword("ON NULL");
26464                    }
26465                }
26466                self.write_space();
26467                self.write_keyword("AS IDENTITY");
26468            }
26469            ColumnConstraint::Collate(collation) => {
26470                self.write_keyword("COLLATE");
26471                self.write_space();
26472                self.generate_identifier(collation)?;
26473            }
26474            ColumnConstraint::Comment(comment) => {
26475                self.write_keyword("COMMENT");
26476                self.write(" '");
26477                self.write(comment);
26478                self.write("'");
26479            }
26480            ColumnConstraint::ComputedColumn(cc) => {
26481                self.generate_computed_column_inline(cc)?;
26482            }
26483            ColumnConstraint::GeneratedAsRow(gar) => {
26484                self.generate_generated_as_row_inline(gar)?;
26485            }
26486            ColumnConstraint::Tags(tags) => {
26487                self.write_keyword("TAG");
26488                self.write(" (");
26489                for (i, expr) in tags.expressions.iter().enumerate() {
26490                    if i > 0 {
26491                        self.write(", ");
26492                    }
26493                    self.generate_expression(expr)?;
26494                }
26495                self.write(")");
26496            }
26497            ColumnConstraint::Path(path_expr) => {
26498                self.write_keyword("PATH");
26499                self.write_space();
26500                self.generate_expression(path_expr)?;
26501            }
26502        }
26503        Ok(())
26504    }
26505
26506    fn generate_column_position(&mut self, e: &ColumnPosition) -> Result<()> {
26507        // ColumnPosition is an enum
26508        match e {
26509            ColumnPosition::First => {
26510                self.write_keyword("FIRST");
26511            }
26512            ColumnPosition::After(ident) => {
26513                self.write_keyword("AFTER");
26514                self.write_space();
26515                self.generate_identifier(ident)?;
26516            }
26517        }
26518        Ok(())
26519    }
26520
26521    fn generate_column_prefix(&mut self, e: &ColumnPrefix) -> Result<()> {
26522        // column(prefix)
26523        self.generate_expression(&e.this)?;
26524        self.write("(");
26525        self.generate_expression(&e.expression)?;
26526        self.write(")");
26527        Ok(())
26528    }
26529
26530    fn generate_columns(&mut self, e: &Columns) -> Result<()> {
26531        // If unpack is true, this came from * COLUMNS(pattern)
26532        // DuckDB syntax: * COLUMNS(c ILIKE '%suffix') or COLUMNS(pattern)
26533        if let Some(ref unpack) = e.unpack {
26534            if let Expression::Boolean(b) = unpack.as_ref() {
26535                if b.value {
26536                    self.write("*");
26537                }
26538            }
26539        }
26540        self.write_keyword("COLUMNS");
26541        self.write("(");
26542        self.generate_expression(&e.this)?;
26543        self.write(")");
26544        Ok(())
26545    }
26546
26547    fn generate_combined_agg_func(&mut self, e: &CombinedAggFunc) -> Result<()> {
26548        // Combined aggregate: FUNC(args) combined
26549        self.generate_expression(&e.this)?;
26550        self.write("(");
26551        for (i, expr) in e.expressions.iter().enumerate() {
26552            if i > 0 {
26553                self.write(", ");
26554            }
26555            self.generate_expression(expr)?;
26556        }
26557        self.write(")");
26558        Ok(())
26559    }
26560
26561    fn generate_combined_parameterized_agg(&mut self, e: &CombinedParameterizedAgg) -> Result<()> {
26562        // Combined parameterized aggregate: FUNC(params)(expressions)
26563        self.generate_expression(&e.this)?;
26564        self.write("(");
26565        for (i, param) in e.params.iter().enumerate() {
26566            if i > 0 {
26567                self.write(", ");
26568            }
26569            self.generate_expression(param)?;
26570        }
26571        self.write(")(");
26572        for (i, expr) in e.expressions.iter().enumerate() {
26573            if i > 0 {
26574                self.write(", ");
26575            }
26576            self.generate_expression(expr)?;
26577        }
26578        self.write(")");
26579        Ok(())
26580    }
26581
26582    fn generate_commit(&mut self, e: &Commit) -> Result<()> {
26583        // COMMIT [TRANSACTION [transaction_name]] [WITH (DELAYED_DURABILITY = ON|OFF)] [AND [NO] CHAIN]
26584        self.write_keyword("COMMIT");
26585
26586        // TSQL always uses COMMIT TRANSACTION
26587        if e.this.is_none()
26588            && matches!(
26589                self.config.dialect,
26590                Some(DialectType::TSQL) | Some(DialectType::Fabric)
26591            )
26592        {
26593            self.write_space();
26594            self.write_keyword("TRANSACTION");
26595        }
26596
26597        // Check if this has TRANSACTION keyword or transaction name
26598        if let Some(this) = &e.this {
26599            // Check if it's just the "TRANSACTION" marker or an actual transaction name
26600            let is_transaction_marker = matches!(
26601                this.as_ref(),
26602                Expression::Identifier(id) if id.name == "TRANSACTION"
26603            );
26604
26605            self.write_space();
26606            self.write_keyword("TRANSACTION");
26607
26608            // If it's a real transaction name, output it
26609            if !is_transaction_marker {
26610                self.write_space();
26611                self.generate_expression(this)?;
26612            }
26613        }
26614
26615        // Output WITH (DELAYED_DURABILITY = ON|OFF) for TSQL
26616        if let Some(durability) = &e.durability {
26617            self.write_space();
26618            self.write_keyword("WITH");
26619            self.write(" (");
26620            self.write_keyword("DELAYED_DURABILITY");
26621            self.write(" = ");
26622            if let Expression::Boolean(BooleanLiteral { value: true }) = durability.as_ref() {
26623                self.write_keyword("ON");
26624            } else {
26625                self.write_keyword("OFF");
26626            }
26627            self.write(")");
26628        }
26629
26630        // Output AND [NO] CHAIN
26631        if let Some(chain) = &e.chain {
26632            self.write_space();
26633            if let Expression::Boolean(BooleanLiteral { value: false }) = chain.as_ref() {
26634                self.write_keyword("AND NO CHAIN");
26635            } else {
26636                self.write_keyword("AND CHAIN");
26637            }
26638        }
26639        Ok(())
26640    }
26641
26642    fn generate_comprehension(&mut self, e: &Comprehension) -> Result<()> {
26643        // Python-style comprehension: [expr FOR var[, pos] IN iterator IF condition]
26644        self.write("[");
26645        self.generate_expression(&e.this)?;
26646        self.write_space();
26647        self.write_keyword("FOR");
26648        self.write_space();
26649        self.generate_expression(&e.expression)?;
26650        // Handle optional position variable (for enumerate-like syntax)
26651        if let Some(pos) = &e.position {
26652            self.write(", ");
26653            self.generate_expression(pos)?;
26654        }
26655        if let Some(iterator) = &e.iterator {
26656            self.write_space();
26657            self.write_keyword("IN");
26658            self.write_space();
26659            self.generate_expression(iterator)?;
26660        }
26661        if let Some(condition) = &e.condition {
26662            self.write_space();
26663            self.write_keyword("IF");
26664            self.write_space();
26665            self.generate_expression(condition)?;
26666        }
26667        self.write("]");
26668        Ok(())
26669    }
26670
26671    fn generate_compress(&mut self, e: &Compress) -> Result<()> {
26672        // COMPRESS(this[, method])
26673        self.write_keyword("COMPRESS");
26674        self.write("(");
26675        self.generate_expression(&e.this)?;
26676        if let Some(method) = &e.method {
26677            self.write(", '");
26678            self.write(method);
26679            self.write("'");
26680        }
26681        self.write(")");
26682        Ok(())
26683    }
26684
26685    fn generate_compress_column_constraint(&mut self, e: &CompressColumnConstraint) -> Result<()> {
26686        // Python: return f"COMPRESS {this}"
26687        self.write_keyword("COMPRESS");
26688        if let Some(this) = &e.this {
26689            self.write_space();
26690            self.generate_expression(this)?;
26691        }
26692        Ok(())
26693    }
26694
26695    fn generate_computed_column_constraint(&mut self, e: &ComputedColumnConstraint) -> Result<()> {
26696        // Python: return f"AS {this}{persisted}"
26697        self.write_keyword("AS");
26698        self.write_space();
26699        self.generate_expression(&e.this)?;
26700        if e.not_null.is_some() {
26701            self.write_space();
26702            self.write_keyword("PERSISTED NOT NULL");
26703        } else if e.persisted.is_some() {
26704            self.write_space();
26705            self.write_keyword("PERSISTED");
26706        }
26707        Ok(())
26708    }
26709
26710    /// Generate a ComputedColumn constraint inline within a column definition.
26711    /// Handles MySQL/PostgreSQL: GENERATED ALWAYS AS (expr) STORED|VIRTUAL
26712    /// Handles TSQL: AS (expr) [PERSISTED] [NOT NULL]
26713    fn generate_computed_column_inline(&mut self, cc: &ComputedColumn) -> Result<()> {
26714        let computed_expr = if matches!(
26715            self.config.dialect,
26716            Some(DialectType::TSQL) | Some(DialectType::Fabric)
26717        ) {
26718            match &*cc.expression {
26719                Expression::Year(y) if !matches!(&y.this, Expression::Cast(c) if matches!(c.to, DataType::Date)) =>
26720                {
26721                    let wrapped = Expression::Cast(Box::new(Cast {
26722                        this: y.this.clone(),
26723                        to: DataType::Date,
26724                        trailing_comments: Vec::new(),
26725                        double_colon_syntax: false,
26726                        format: None,
26727                        default: None,
26728                        inferred_type: None,
26729                    }));
26730                    Expression::Year(Box::new(UnaryFunc::new(wrapped)))
26731                }
26732                Expression::Function(f)
26733                    if f.name.eq_ignore_ascii_case("YEAR")
26734                        && f.args.len() == 1
26735                        && !matches!(&f.args[0], Expression::Cast(c) if matches!(c.to, DataType::Date)) =>
26736                {
26737                    let wrapped = Expression::Cast(Box::new(Cast {
26738                        this: f.args[0].clone(),
26739                        to: DataType::Date,
26740                        trailing_comments: Vec::new(),
26741                        double_colon_syntax: false,
26742                        format: None,
26743                        default: None,
26744                        inferred_type: None,
26745                    }));
26746                    Expression::Function(Box::new(Function::new("YEAR".to_string(), vec![wrapped])))
26747                }
26748                _ => *cc.expression.clone(),
26749            }
26750        } else {
26751            *cc.expression.clone()
26752        };
26753
26754        match cc.persistence_kind.as_deref() {
26755            Some("STORED") | Some("VIRTUAL") => {
26756                // MySQL/PostgreSQL: GENERATED ALWAYS AS (expr) STORED|VIRTUAL
26757                self.write_keyword("GENERATED ALWAYS AS");
26758                self.write(" (");
26759                self.generate_expression(&computed_expr)?;
26760                self.write(")");
26761                self.write_space();
26762                if cc.persisted {
26763                    self.write_keyword("STORED");
26764                } else {
26765                    self.write_keyword("VIRTUAL");
26766                }
26767            }
26768            Some("PERSISTED") => {
26769                // TSQL/SingleStore: AS (expr) PERSISTED [TYPE] [NOT NULL]
26770                self.write_keyword("AS");
26771                self.write(" (");
26772                self.generate_expression(&computed_expr)?;
26773                self.write(")");
26774                self.write_space();
26775                self.write_keyword("PERSISTED");
26776                // Output data type if present (SingleStore: PERSISTED TYPE NOT NULL)
26777                if let Some(ref dt) = cc.data_type {
26778                    self.write_space();
26779                    self.generate_data_type(dt)?;
26780                }
26781                if cc.not_null {
26782                    self.write_space();
26783                    self.write_keyword("NOT NULL");
26784                }
26785            }
26786            _ => {
26787                // Spark/Databricks/Hive: GENERATED ALWAYS AS (expr)
26788                // TSQL computed column without PERSISTED: AS (expr)
26789                if matches!(
26790                    self.config.dialect,
26791                    Some(DialectType::Spark)
26792                        | Some(DialectType::Databricks)
26793                        | Some(DialectType::Hive)
26794                ) {
26795                    self.write_keyword("GENERATED ALWAYS AS");
26796                    self.write(" (");
26797                    self.generate_expression(&computed_expr)?;
26798                    self.write(")");
26799                } else if matches!(
26800                    self.config.dialect,
26801                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
26802                ) {
26803                    self.write_keyword("AS");
26804                    let omit_parens = matches!(computed_expr, Expression::Year(_))
26805                        || matches!(&computed_expr, Expression::Function(f) if f.name.eq_ignore_ascii_case("YEAR"));
26806                    if omit_parens {
26807                        self.write_space();
26808                        self.generate_expression(&computed_expr)?;
26809                    } else {
26810                        self.write(" (");
26811                        self.generate_expression(&computed_expr)?;
26812                        self.write(")");
26813                    }
26814                } else {
26815                    self.write_keyword("AS");
26816                    self.write(" (");
26817                    self.generate_expression(&computed_expr)?;
26818                    self.write(")");
26819                }
26820            }
26821        }
26822        Ok(())
26823    }
26824
26825    /// Generate a GeneratedAsRow constraint inline within a column definition.
26826    /// TSQL temporal: GENERATED ALWAYS AS ROW START|END [HIDDEN]
26827    fn generate_generated_as_row_inline(&mut self, gar: &GeneratedAsRow) -> Result<()> {
26828        self.write_keyword("GENERATED ALWAYS AS ROW ");
26829        if gar.start {
26830            self.write_keyword("START");
26831        } else {
26832            self.write_keyword("END");
26833        }
26834        if gar.hidden {
26835            self.write_space();
26836            self.write_keyword("HIDDEN");
26837        }
26838        Ok(())
26839    }
26840
26841    /// Generate just the SYSTEM_VERSIONING=ON(...) content without WITH() wrapper.
26842    fn generate_system_versioning_content(
26843        &mut self,
26844        e: &WithSystemVersioningProperty,
26845    ) -> Result<()> {
26846        let mut parts = Vec::new();
26847
26848        if let Some(this) = &e.this {
26849            let mut s = String::from("HISTORY_TABLE=");
26850            let mut gen = Generator::with_arc_config(self.config.clone());
26851            gen.generate_expression(this)?;
26852            s.push_str(&gen.output);
26853            parts.push(s);
26854        }
26855
26856        if let Some(data_consistency) = &e.data_consistency {
26857            let mut s = String::from("DATA_CONSISTENCY_CHECK=");
26858            let mut gen = Generator::with_arc_config(self.config.clone());
26859            gen.generate_expression(data_consistency)?;
26860            s.push_str(&gen.output);
26861            parts.push(s);
26862        }
26863
26864        if let Some(retention_period) = &e.retention_period {
26865            let mut s = String::from("HISTORY_RETENTION_PERIOD=");
26866            let mut gen = Generator::with_arc_config(self.config.clone());
26867            gen.generate_expression(retention_period)?;
26868            s.push_str(&gen.output);
26869            parts.push(s);
26870        }
26871
26872        self.write_keyword("SYSTEM_VERSIONING");
26873        self.write("=");
26874
26875        if !parts.is_empty() {
26876            self.write_keyword("ON");
26877            self.write("(");
26878            self.write(&parts.join(", "));
26879            self.write(")");
26880        } else if e.on.is_some() {
26881            self.write_keyword("ON");
26882        } else {
26883            self.write_keyword("OFF");
26884        }
26885
26886        Ok(())
26887    }
26888
26889    fn generate_conditional_insert(&mut self, e: &ConditionalInsert) -> Result<()> {
26890        // Conditional INSERT for multi-table inserts
26891        // Output: [WHEN cond THEN | ELSE] INTO table [(cols)] [VALUES (...)]
26892        if e.else_.is_some() {
26893            self.write_keyword("ELSE");
26894            self.write_space();
26895        } else if let Some(expression) = &e.expression {
26896            self.write_keyword("WHEN");
26897            self.write_space();
26898            self.generate_expression(expression)?;
26899            self.write_space();
26900            self.write_keyword("THEN");
26901            self.write_space();
26902        }
26903
26904        // Handle Insert expression specially - output "INTO table (cols) VALUES (...)"
26905        // without the "INSERT " prefix
26906        if let Expression::Insert(insert) = e.this.as_ref() {
26907            self.write_keyword("INTO");
26908            self.write_space();
26909            self.generate_table(&insert.table)?;
26910
26911            // Optional column list
26912            if !insert.columns.is_empty() {
26913                self.write(" (");
26914                for (i, col) in insert.columns.iter().enumerate() {
26915                    if i > 0 {
26916                        self.write(", ");
26917                    }
26918                    self.generate_identifier(col)?;
26919                }
26920                self.write(")");
26921            }
26922
26923            // Optional VALUES clause
26924            if !insert.values.is_empty() {
26925                self.write_space();
26926                self.write_keyword("VALUES");
26927                for (row_idx, row) in insert.values.iter().enumerate() {
26928                    if row_idx > 0 {
26929                        self.write(", ");
26930                    }
26931                    self.write(" (");
26932                    for (i, val) in row.iter().enumerate() {
26933                        if i > 0 {
26934                            self.write(", ");
26935                        }
26936                        self.generate_expression(val)?;
26937                    }
26938                    self.write(")");
26939                }
26940            }
26941        } else {
26942            // Fallback for non-Insert expressions
26943            self.generate_expression(&e.this)?;
26944        }
26945        Ok(())
26946    }
26947
26948    fn generate_constraint(&mut self, e: &Constraint) -> Result<()> {
26949        // Python: return f"CONSTRAINT {this} {expressions}"
26950        self.write_keyword("CONSTRAINT");
26951        self.write_space();
26952        self.generate_expression(&e.this)?;
26953        if !e.expressions.is_empty() {
26954            self.write_space();
26955            for (i, expr) in e.expressions.iter().enumerate() {
26956                if i > 0 {
26957                    self.write_space();
26958                }
26959                self.generate_expression(expr)?;
26960            }
26961        }
26962        Ok(())
26963    }
26964
26965    fn generate_convert_timezone(&mut self, e: &ConvertTimezone) -> Result<()> {
26966        // CONVERT_TIMEZONE([source_tz,] target_tz, timestamp)
26967        self.write_keyword("CONVERT_TIMEZONE");
26968        self.write("(");
26969        let mut first = true;
26970        if let Some(source_tz) = &e.source_tz {
26971            self.generate_expression(source_tz)?;
26972            first = false;
26973        }
26974        if let Some(target_tz) = &e.target_tz {
26975            if !first {
26976                self.write(", ");
26977            }
26978            self.generate_expression(target_tz)?;
26979            first = false;
26980        }
26981        if let Some(timestamp) = &e.timestamp {
26982            if !first {
26983                self.write(", ");
26984            }
26985            self.generate_expression(timestamp)?;
26986        }
26987        self.write(")");
26988        Ok(())
26989    }
26990
26991    fn generate_convert_to_charset(&mut self, e: &ConvertToCharset) -> Result<()> {
26992        // CONVERT(this USING dest)
26993        self.write_keyword("CONVERT");
26994        self.write("(");
26995        self.generate_expression(&e.this)?;
26996        if let Some(dest) = &e.dest {
26997            self.write_space();
26998            self.write_keyword("USING");
26999            self.write_space();
27000            self.generate_expression(dest)?;
27001        }
27002        self.write(")");
27003        Ok(())
27004    }
27005
27006    fn generate_copy(&mut self, e: &CopyStmt) -> Result<()> {
27007        self.write_keyword("COPY");
27008        if e.is_into {
27009            self.write_space();
27010            self.write_keyword("INTO");
27011        }
27012        self.write_space();
27013
27014        // Generate target table or query (or stage for COPY INTO @stage)
27015        if let Expression::Literal(lit) = &e.this {
27016            if let Literal::String(s) = lit.as_ref() {
27017                if s.starts_with('@') {
27018                    self.write(s);
27019                } else {
27020                    self.generate_expression(&e.this)?;
27021                }
27022            }
27023        } else {
27024            self.generate_expression(&e.this)?;
27025        }
27026
27027        // FROM or TO based on kind
27028        if e.kind {
27029            // kind=true means FROM (loading into table)
27030            if self.config.pretty {
27031                self.write_newline();
27032            } else {
27033                self.write_space();
27034            }
27035            self.write_keyword("FROM");
27036            self.write_space();
27037        } else if !e.files.is_empty() {
27038            // kind=false means TO (exporting)
27039            if self.config.pretty {
27040                self.write_newline();
27041            } else {
27042                self.write_space();
27043            }
27044            self.write_keyword("TO");
27045            self.write_space();
27046        }
27047
27048        // Generate source/destination files
27049        for (i, file) in e.files.iter().enumerate() {
27050            if i > 0 {
27051                self.write_space();
27052            }
27053            // For stage references (strings starting with @), output without quotes
27054            if let Expression::Literal(lit) = file {
27055                if let Literal::String(s) = lit.as_ref() {
27056                    if s.starts_with('@') {
27057                        self.write(s);
27058                    } else {
27059                        self.generate_expression(file)?;
27060                    }
27061                }
27062            } else if let Expression::Identifier(id) = file {
27063                // Backtick-quoted file path (Databricks style: `s3://link`)
27064                if id.quoted {
27065                    self.write("`");
27066                    self.write(&id.name);
27067                    self.write("`");
27068                } else {
27069                    self.generate_expression(file)?;
27070                }
27071            } else {
27072                self.generate_expression(file)?;
27073            }
27074        }
27075
27076        // Generate credentials if present (Snowflake style - not wrapped in WITH)
27077        if !e.with_wrapped {
27078            if let Some(ref creds) = e.credentials {
27079                if let Some(ref storage) = creds.storage {
27080                    if self.config.pretty {
27081                        self.write_newline();
27082                    } else {
27083                        self.write_space();
27084                    }
27085                    self.write_keyword("STORAGE_INTEGRATION");
27086                    self.write(" = ");
27087                    self.write(storage);
27088                }
27089                if creds.credentials.is_empty() {
27090                    // Empty credentials: CREDENTIALS = ()
27091                    if self.config.pretty {
27092                        self.write_newline();
27093                    } else {
27094                        self.write_space();
27095                    }
27096                    self.write_keyword("CREDENTIALS");
27097                    self.write(" = ()");
27098                } else {
27099                    if self.config.pretty {
27100                        self.write_newline();
27101                    } else {
27102                        self.write_space();
27103                    }
27104                    self.write_keyword("CREDENTIALS");
27105                    // Check if this is Redshift-style (single value with empty key)
27106                    // vs Snowflake-style (multiple key=value pairs)
27107                    if creds.credentials.len() == 1 && creds.credentials[0].0.is_empty() {
27108                        // Redshift style: CREDENTIALS 'value'
27109                        self.write(" '");
27110                        self.write(&creds.credentials[0].1);
27111                        self.write("'");
27112                    } else {
27113                        // Snowflake style: CREDENTIALS = (KEY='value' ...)
27114                        self.write(" = (");
27115                        for (i, (k, v)) in creds.credentials.iter().enumerate() {
27116                            if i > 0 {
27117                                self.write_space();
27118                            }
27119                            self.write(k);
27120                            self.write("='");
27121                            self.write(v);
27122                            self.write("'");
27123                        }
27124                        self.write(")");
27125                    }
27126                }
27127                if let Some(ref encryption) = creds.encryption {
27128                    self.write_space();
27129                    self.write_keyword("ENCRYPTION");
27130                    self.write(" = ");
27131                    self.write(encryption);
27132                }
27133            }
27134        }
27135
27136        // Generate parameters
27137        if !e.params.is_empty() {
27138            if e.with_wrapped {
27139                // DuckDB/PostgreSQL/TSQL WITH (...) format
27140                self.write_space();
27141                self.write_keyword("WITH");
27142                self.write(" (");
27143                for (i, param) in e.params.iter().enumerate() {
27144                    if i > 0 {
27145                        self.write(", ");
27146                    }
27147                    self.generate_copy_param_with_format(param)?;
27148                }
27149                self.write(")");
27150            } else {
27151                // Snowflake/Redshift format: KEY = VALUE or KEY VALUE (space separated, no WITH wrapper)
27152                // For Redshift: IAM_ROLE value, CREDENTIALS 'value', REGION 'value', FORMAT type
27153                // For Snowflake: KEY = VALUE
27154                for param in &e.params {
27155                    if self.config.pretty {
27156                        self.write_newline();
27157                    } else {
27158                        self.write_space();
27159                    }
27160                    // Preserve original case of parameter name (important for Redshift COPY options)
27161                    self.write(&param.name);
27162                    if let Some(ref value) = param.value {
27163                        // Use = only if it was present in the original (param.eq)
27164                        if param.eq {
27165                            self.write(" = ");
27166                        } else {
27167                            self.write(" ");
27168                        }
27169                        if !param.values.is_empty() {
27170                            self.write("(");
27171                            for (i, v) in param.values.iter().enumerate() {
27172                                if i > 0 {
27173                                    self.write_space();
27174                                }
27175                                self.generate_copy_nested_param(v)?;
27176                            }
27177                            self.write(")");
27178                        } else {
27179                            // For COPY parameter values, output identifiers without quoting
27180                            self.generate_copy_param_value(value)?;
27181                        }
27182                    } else if !param.values.is_empty() {
27183                        // For varlen options like FORMAT_OPTIONS, COPY_OPTIONS - no = before (
27184                        if param.eq {
27185                            self.write(" = (");
27186                        } else {
27187                            self.write(" (");
27188                        }
27189                        // Determine separator for values inside parentheses:
27190                        // - Snowflake FILE_FORMAT = (TYPE=CSV FIELD_DELIMITER='|') → space-separated (has = before parens)
27191                        // - Databricks FORMAT_OPTIONS ('opt1'='true', 'opt2'='test') → comma-separated (no = before parens)
27192                        // - Simple value lists like FILES = ('file1', 'file2') → comma-separated
27193                        let is_key_value_pairs = param
27194                            .values
27195                            .first()
27196                            .map_or(false, |v| matches!(v, Expression::Eq(_)));
27197                        let sep = if is_key_value_pairs && param.eq {
27198                            " "
27199                        } else {
27200                            ", "
27201                        };
27202                        for (i, v) in param.values.iter().enumerate() {
27203                            if i > 0 {
27204                                self.write(sep);
27205                            }
27206                            self.generate_copy_nested_param(v)?;
27207                        }
27208                        self.write(")");
27209                    }
27210                }
27211            }
27212        }
27213
27214        Ok(())
27215    }
27216
27217    /// Generate a COPY parameter in WITH (...) format
27218    /// Handles both KEY = VALUE (TSQL) and KEY VALUE (DuckDB/PostgreSQL) formats
27219    fn generate_copy_param_with_format(&mut self, param: &CopyParameter) -> Result<()> {
27220        self.write_keyword(&param.name);
27221        if !param.values.is_empty() {
27222            // Nested values: CREDENTIAL = (IDENTITY='...', SECRET='...')
27223            self.write(" = (");
27224            for (i, v) in param.values.iter().enumerate() {
27225                if i > 0 {
27226                    self.write(", ");
27227                }
27228                self.generate_copy_nested_param(v)?;
27229            }
27230            self.write(")");
27231        } else if let Some(ref value) = param.value {
27232            if param.eq {
27233                self.write(" = ");
27234            } else {
27235                self.write(" ");
27236            }
27237            self.generate_expression(value)?;
27238        }
27239        Ok(())
27240    }
27241
27242    /// Generate nested parameter for COPY statements (KEY=VALUE without spaces)
27243    fn generate_copy_nested_param(&mut self, expr: &Expression) -> Result<()> {
27244        match expr {
27245            Expression::Eq(eq) => {
27246                // Generate key
27247                match &eq.left {
27248                    Expression::Column(c) => self.write(&c.name.name),
27249                    _ => self.generate_expression(&eq.left)?,
27250                }
27251                self.write("=");
27252                // Generate value
27253                match &eq.right {
27254                    Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
27255                        let Literal::String(s) = lit.as_ref() else {
27256                            unreachable!()
27257                        };
27258                        self.write("'");
27259                        self.write(s);
27260                        self.write("'");
27261                    }
27262                    Expression::Tuple(t) => {
27263                        // For lists like NULL_IF=('', 'str1')
27264                        self.write("(");
27265                        if self.config.pretty {
27266                            self.write_newline();
27267                            self.indent_level += 1;
27268                            for (i, item) in t.expressions.iter().enumerate() {
27269                                if i > 0 {
27270                                    self.write(", ");
27271                                }
27272                                self.write_indent();
27273                                self.generate_expression(item)?;
27274                            }
27275                            self.write_newline();
27276                            self.indent_level -= 1;
27277                        } else {
27278                            for (i, item) in t.expressions.iter().enumerate() {
27279                                if i > 0 {
27280                                    self.write(", ");
27281                                }
27282                                self.generate_expression(item)?;
27283                            }
27284                        }
27285                        self.write(")");
27286                    }
27287                    _ => self.generate_expression(&eq.right)?,
27288                }
27289                Ok(())
27290            }
27291            Expression::Column(c) => {
27292                // Standalone keyword like COMPRESSION
27293                self.write(&c.name.name);
27294                Ok(())
27295            }
27296            _ => self.generate_expression(expr),
27297        }
27298    }
27299
27300    /// Generate a COPY parameter value, outputting identifiers/columns without quoting
27301    /// This is needed for Redshift-style COPY params like: IAM_ROLE default, FORMAT orc
27302    fn generate_copy_param_value(&mut self, expr: &Expression) -> Result<()> {
27303        match expr {
27304            Expression::Column(c) => {
27305                // Output identifier, preserving quotes if originally quoted
27306                if c.name.quoted {
27307                    self.write("\"");
27308                    self.write(&c.name.name);
27309                    self.write("\"");
27310                } else {
27311                    self.write(&c.name.name);
27312                }
27313                Ok(())
27314            }
27315            Expression::Identifier(id) => {
27316                // Output identifier, preserving quotes if originally quoted
27317                if id.quoted {
27318                    self.write("\"");
27319                    self.write(&id.name);
27320                    self.write("\"");
27321                } else {
27322                    self.write(&id.name);
27323                }
27324                Ok(())
27325            }
27326            Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
27327                let Literal::String(s) = lit.as_ref() else {
27328                    unreachable!()
27329                };
27330                // Output string with quotes
27331                self.write("'");
27332                self.write(s);
27333                self.write("'");
27334                Ok(())
27335            }
27336            _ => self.generate_expression(expr),
27337        }
27338    }
27339
27340    fn generate_copy_parameter(&mut self, e: &CopyParameter) -> Result<()> {
27341        self.write_keyword(&e.name);
27342        if let Some(ref value) = e.value {
27343            if e.eq {
27344                self.write(" = ");
27345            } else {
27346                self.write(" ");
27347            }
27348            self.generate_expression(value)?;
27349        }
27350        if !e.values.is_empty() {
27351            if e.eq {
27352                self.write(" = ");
27353            } else {
27354                self.write(" ");
27355            }
27356            self.write("(");
27357            for (i, v) in e.values.iter().enumerate() {
27358                if i > 0 {
27359                    self.write(", ");
27360                }
27361                self.generate_expression(v)?;
27362            }
27363            self.write(")");
27364        }
27365        Ok(())
27366    }
27367
27368    fn generate_corr(&mut self, e: &Corr) -> Result<()> {
27369        // CORR(this, expression)
27370        self.write_keyword("CORR");
27371        self.write("(");
27372        self.generate_expression(&e.this)?;
27373        self.write(", ");
27374        self.generate_expression(&e.expression)?;
27375        self.write(")");
27376        Ok(())
27377    }
27378
27379    fn generate_cosine_distance(&mut self, e: &CosineDistance) -> Result<()> {
27380        // COSINE_DISTANCE(this, expression)
27381        self.write_keyword("COSINE_DISTANCE");
27382        self.write("(");
27383        self.generate_expression(&e.this)?;
27384        self.write(", ");
27385        self.generate_expression(&e.expression)?;
27386        self.write(")");
27387        Ok(())
27388    }
27389
27390    fn generate_covar_pop(&mut self, e: &CovarPop) -> Result<()> {
27391        // COVAR_POP(this, expression)
27392        self.write_keyword("COVAR_POP");
27393        self.write("(");
27394        self.generate_expression(&e.this)?;
27395        self.write(", ");
27396        self.generate_expression(&e.expression)?;
27397        self.write(")");
27398        Ok(())
27399    }
27400
27401    fn generate_covar_samp(&mut self, e: &CovarSamp) -> Result<()> {
27402        // COVAR_SAMP(this, expression)
27403        self.write_keyword("COVAR_SAMP");
27404        self.write("(");
27405        self.generate_expression(&e.this)?;
27406        self.write(", ");
27407        self.generate_expression(&e.expression)?;
27408        self.write(")");
27409        Ok(())
27410    }
27411
27412    fn generate_credentials(&mut self, e: &Credentials) -> Result<()> {
27413        // CREDENTIALS (key1='value1', key2='value2')
27414        self.write_keyword("CREDENTIALS");
27415        self.write(" (");
27416        for (i, (key, value)) in e.credentials.iter().enumerate() {
27417            if i > 0 {
27418                self.write(", ");
27419            }
27420            self.write(key);
27421            self.write("='");
27422            self.write(value);
27423            self.write("'");
27424        }
27425        self.write(")");
27426        Ok(())
27427    }
27428
27429    fn generate_credentials_property(&mut self, e: &CredentialsProperty) -> Result<()> {
27430        // CREDENTIALS=(expressions)
27431        self.write_keyword("CREDENTIALS");
27432        self.write("=(");
27433        for (i, expr) in e.expressions.iter().enumerate() {
27434            if i > 0 {
27435                self.write(", ");
27436            }
27437            self.generate_expression(expr)?;
27438        }
27439        self.write(")");
27440        Ok(())
27441    }
27442
27443    fn generate_cte(&mut self, e: &Cte) -> Result<()> {
27444        use crate::dialects::DialectType;
27445
27446        // Python: return f"{alias_sql}{key_expressions} AS {materialized or ''}{self.wrap(expression)}"
27447        // Output: alias [(col1, col2, ...)] AS [MATERIALIZED|NOT MATERIALIZED] (subquery)
27448        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) && !e.alias_first {
27449            self.generate_expression(&e.this)?;
27450            self.write_space();
27451            self.write_keyword("AS");
27452            self.write_space();
27453            self.generate_identifier(&e.alias)?;
27454            return Ok(());
27455        }
27456        self.write(&e.alias.name);
27457
27458        // BigQuery doesn't support column aliases in CTE definitions
27459        let skip_cte_columns = matches!(self.config.dialect, Some(DialectType::BigQuery));
27460
27461        if !e.columns.is_empty() && !skip_cte_columns {
27462            self.write("(");
27463            for (i, col) in e.columns.iter().enumerate() {
27464                if i > 0 {
27465                    self.write(", ");
27466                }
27467                self.write(&col.name);
27468            }
27469            self.write(")");
27470        }
27471        // USING KEY (columns) for DuckDB recursive CTEs
27472        if !e.key_expressions.is_empty() {
27473            self.write_space();
27474            self.write_keyword("USING KEY");
27475            self.write(" (");
27476            for (i, key) in e.key_expressions.iter().enumerate() {
27477                if i > 0 {
27478                    self.write(", ");
27479                }
27480                self.write(&key.name);
27481            }
27482            self.write(")");
27483        }
27484        self.write_space();
27485        self.write_keyword("AS");
27486        self.write_space();
27487        if let Some(materialized) = e.materialized {
27488            if materialized {
27489                self.write_keyword("MATERIALIZED");
27490            } else {
27491                self.write_keyword("NOT MATERIALIZED");
27492            }
27493            self.write_space();
27494        }
27495        self.write("(");
27496        self.generate_expression(&e.this)?;
27497        self.write(")");
27498        Ok(())
27499    }
27500
27501    fn generate_cube(&mut self, e: &Cube) -> Result<()> {
27502        // Python: return f"CUBE {self.wrap(expressions)}" if expressions else "WITH CUBE"
27503        if e.expressions.is_empty() {
27504            self.write_keyword("WITH CUBE");
27505        } else {
27506            self.write_keyword("CUBE");
27507            self.write("(");
27508            for (i, expr) in e.expressions.iter().enumerate() {
27509                if i > 0 {
27510                    self.write(", ");
27511                }
27512                self.generate_expression(expr)?;
27513            }
27514            self.write(")");
27515        }
27516        Ok(())
27517    }
27518
27519    fn generate_current_datetime(&mut self, e: &CurrentDatetime) -> Result<()> {
27520        // CURRENT_DATETIME or CURRENT_DATETIME(timezone)
27521        self.write_keyword("CURRENT_DATETIME");
27522        if let Some(this) = &e.this {
27523            self.write("(");
27524            self.generate_expression(this)?;
27525            self.write(")");
27526        }
27527        Ok(())
27528    }
27529
27530    fn generate_current_schema(&mut self, _e: &CurrentSchema) -> Result<()> {
27531        // CURRENT_SCHEMA - no arguments
27532        self.write_keyword("CURRENT_SCHEMA");
27533        Ok(())
27534    }
27535
27536    fn generate_current_schemas(&mut self, e: &CurrentSchemas) -> Result<()> {
27537        // CURRENT_SCHEMAS(include_implicit)
27538        self.write_keyword("CURRENT_SCHEMAS");
27539        self.write("(");
27540        // Snowflake: drop the argument (CURRENT_SCHEMAS() takes no args)
27541        if !matches!(
27542            self.config.dialect,
27543            Some(crate::dialects::DialectType::Snowflake)
27544        ) {
27545            if let Some(this) = &e.this {
27546                self.generate_expression(this)?;
27547            }
27548        }
27549        self.write(")");
27550        Ok(())
27551    }
27552
27553    fn generate_current_user(&mut self, e: &CurrentUser) -> Result<()> {
27554        // CURRENT_USER or CURRENT_USER()
27555        self.write_keyword("CURRENT_USER");
27556        // Some dialects always need parens: Snowflake, Spark, Hive, DuckDB, BigQuery, MySQL, Databricks
27557        let needs_parens = e.this.is_some()
27558            || matches!(
27559                self.config.dialect,
27560                Some(DialectType::Snowflake)
27561                    | Some(DialectType::Spark)
27562                    | Some(DialectType::Hive)
27563                    | Some(DialectType::DuckDB)
27564                    | Some(DialectType::BigQuery)
27565                    | Some(DialectType::MySQL)
27566                    | Some(DialectType::Databricks)
27567            );
27568        if needs_parens {
27569            self.write("()");
27570        }
27571        Ok(())
27572    }
27573
27574    fn generate_d_pipe(&mut self, e: &DPipe) -> Result<()> {
27575        // In Solr, || is OR, not string concatenation (DPIPE_IS_STRING_CONCAT = False)
27576        if self.config.dialect == Some(DialectType::Solr) {
27577            self.generate_expression(&e.this)?;
27578            self.write(" ");
27579            self.write_keyword("OR");
27580            self.write(" ");
27581            self.generate_expression(&e.expression)?;
27582        } else if self.config.dialect == Some(DialectType::MySQL) {
27583            self.generate_mysql_concat_from_dpipe(e)?;
27584        } else {
27585            // String concatenation: this || expression
27586            self.generate_expression(&e.this)?;
27587            self.write(" || ");
27588            self.generate_expression(&e.expression)?;
27589        }
27590        Ok(())
27591    }
27592
27593    fn generate_data_blocksize_property(&mut self, e: &DataBlocksizeProperty) -> Result<()> {
27594        // DATABLOCKSIZE=... (Teradata)
27595        self.write_keyword("DATABLOCKSIZE");
27596        self.write("=");
27597        if let Some(size) = e.size {
27598            self.write(&size.to_string());
27599            if let Some(units) = &e.units {
27600                self.write_space();
27601                self.generate_expression(units)?;
27602            }
27603        } else if e.minimum.is_some() {
27604            self.write_keyword("MINIMUM");
27605        } else if e.maximum.is_some() {
27606            self.write_keyword("MAXIMUM");
27607        } else if e.default.is_some() {
27608            self.write_keyword("DEFAULT");
27609        }
27610        Ok(())
27611    }
27612
27613    fn generate_data_deletion_property(&mut self, e: &DataDeletionProperty) -> Result<()> {
27614        // DATA_DELETION=ON or DATA_DELETION=OFF or DATA_DELETION=ON(FILTER_COLUMN=col, RETENTION_PERIOD=...)
27615        self.write_keyword("DATA_DELETION");
27616        self.write("=");
27617
27618        let is_on = matches!(&*e.on, Expression::Boolean(BooleanLiteral { value: true }));
27619        let has_options = e.filter_column.is_some() || e.retention_period.is_some();
27620
27621        if is_on {
27622            self.write_keyword("ON");
27623            if has_options {
27624                self.write("(");
27625                let mut first = true;
27626                if let Some(filter_column) = &e.filter_column {
27627                    self.write_keyword("FILTER_COLUMN");
27628                    self.write("=");
27629                    self.generate_expression(filter_column)?;
27630                    first = false;
27631                }
27632                if let Some(retention_period) = &e.retention_period {
27633                    if !first {
27634                        self.write(", ");
27635                    }
27636                    self.write_keyword("RETENTION_PERIOD");
27637                    self.write("=");
27638                    self.generate_expression(retention_period)?;
27639                }
27640                self.write(")");
27641            }
27642        } else {
27643            self.write_keyword("OFF");
27644        }
27645        Ok(())
27646    }
27647
27648    /// Generate a Date function expression
27649    /// For Exasol: {d'value'} -> TO_DATE('value')
27650    /// For other dialects: DATE('value')
27651    fn generate_date_func(&mut self, e: &UnaryFunc) -> Result<()> {
27652        use crate::dialects::DialectType;
27653        use crate::expressions::Literal;
27654
27655        match self.config.dialect {
27656            // Exasol uses TO_DATE for Date expressions
27657            Some(DialectType::Exasol) => {
27658                self.write_keyword("TO_DATE");
27659                self.write("(");
27660                // Extract the string value from the expression if it's a string literal
27661                match &e.this {
27662                    Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
27663                        let Literal::String(s) = lit.as_ref() else {
27664                            unreachable!()
27665                        };
27666                        self.write("'");
27667                        self.write(s);
27668                        self.write("'");
27669                    }
27670                    _ => {
27671                        self.generate_expression(&e.this)?;
27672                    }
27673                }
27674                self.write(")");
27675            }
27676            // Standard: DATE(value)
27677            _ => {
27678                self.write_keyword("DATE");
27679                self.write("(");
27680                self.generate_expression(&e.this)?;
27681                self.write(")");
27682            }
27683        }
27684        Ok(())
27685    }
27686
27687    fn generate_date_bin(&mut self, e: &DateBin) -> Result<()> {
27688        // DATE_BIN(interval, timestamp[, origin])
27689        self.write_keyword("DATE_BIN");
27690        self.write("(");
27691        self.generate_expression(&e.this)?;
27692        self.write(", ");
27693        self.generate_expression(&e.expression)?;
27694        if let Some(origin) = &e.origin {
27695            self.write(", ");
27696            self.generate_expression(origin)?;
27697        }
27698        self.write(")");
27699        Ok(())
27700    }
27701
27702    fn generate_date_format_column_constraint(
27703        &mut self,
27704        e: &DateFormatColumnConstraint,
27705    ) -> Result<()> {
27706        // FORMAT 'format_string' (Teradata)
27707        self.write_keyword("FORMAT");
27708        self.write_space();
27709        self.generate_expression(&e.this)?;
27710        Ok(())
27711    }
27712
27713    fn generate_date_from_parts(&mut self, e: &DateFromParts) -> Result<()> {
27714        // DATE_FROM_PARTS(year, month, day) or DATEFROMPARTS(year, month, day)
27715        self.write_keyword("DATE_FROM_PARTS");
27716        self.write("(");
27717        let mut first = true;
27718        if let Some(year) = &e.year {
27719            self.generate_expression(year)?;
27720            first = false;
27721        }
27722        if let Some(month) = &e.month {
27723            if !first {
27724                self.write(", ");
27725            }
27726            self.generate_expression(month)?;
27727            first = false;
27728        }
27729        if let Some(day) = &e.day {
27730            if !first {
27731                self.write(", ");
27732            }
27733            self.generate_expression(day)?;
27734        }
27735        self.write(")");
27736        Ok(())
27737    }
27738
27739    fn generate_datetime(&mut self, e: &Datetime) -> Result<()> {
27740        // DATETIME(this) or DATETIME(this, expression)
27741        self.write_keyword("DATETIME");
27742        self.write("(");
27743        self.generate_expression(&e.this)?;
27744        if let Some(expr) = &e.expression {
27745            self.write(", ");
27746            self.generate_expression(expr)?;
27747        }
27748        self.write(")");
27749        Ok(())
27750    }
27751
27752    fn generate_datetime_add(&mut self, e: &DatetimeAdd) -> Result<()> {
27753        // DATETIME_ADD(this, expression, unit)
27754        self.write_keyword("DATETIME_ADD");
27755        self.write("(");
27756        self.generate_expression(&e.this)?;
27757        self.write(", ");
27758        self.generate_expression(&e.expression)?;
27759        if let Some(unit) = &e.unit {
27760            self.write(", ");
27761            self.write_keyword(unit);
27762        }
27763        self.write(")");
27764        Ok(())
27765    }
27766
27767    fn generate_datetime_diff(&mut self, e: &DatetimeDiff) -> Result<()> {
27768        // DATETIME_DIFF(this, expression, unit)
27769        self.write_keyword("DATETIME_DIFF");
27770        self.write("(");
27771        self.generate_expression(&e.this)?;
27772        self.write(", ");
27773        self.generate_expression(&e.expression)?;
27774        if let Some(unit) = &e.unit {
27775            self.write(", ");
27776            self.write_keyword(unit);
27777        }
27778        self.write(")");
27779        Ok(())
27780    }
27781
27782    fn generate_datetime_sub(&mut self, e: &DatetimeSub) -> Result<()> {
27783        // DATETIME_SUB(this, expression, unit)
27784        self.write_keyword("DATETIME_SUB");
27785        self.write("(");
27786        self.generate_expression(&e.this)?;
27787        self.write(", ");
27788        self.generate_expression(&e.expression)?;
27789        if let Some(unit) = &e.unit {
27790            self.write(", ");
27791            self.write_keyword(unit);
27792        }
27793        self.write(")");
27794        Ok(())
27795    }
27796
27797    fn generate_datetime_trunc(&mut self, e: &DatetimeTrunc) -> Result<()> {
27798        // DATETIME_TRUNC(this, unit, zone)
27799        self.write_keyword("DATETIME_TRUNC");
27800        self.write("(");
27801        self.generate_expression(&e.this)?;
27802        self.write(", ");
27803        self.write_keyword(&e.unit);
27804        if let Some(zone) = &e.zone {
27805            self.write(", ");
27806            self.generate_expression(zone)?;
27807        }
27808        self.write(")");
27809        Ok(())
27810    }
27811
27812    fn generate_dayname(&mut self, e: &Dayname) -> Result<()> {
27813        // DAYNAME(this)
27814        self.write_keyword("DAYNAME");
27815        self.write("(");
27816        self.generate_expression(&e.this)?;
27817        self.write(")");
27818        Ok(())
27819    }
27820
27821    fn generate_declare(&mut self, e: &Declare) -> Result<()> {
27822        // DECLARE [OR REPLACE] var1 AS type1, var2 AS type2, ...
27823        self.write_keyword("DECLARE");
27824        self.write_space();
27825        if e.replace {
27826            self.write_keyword("OR");
27827            self.write_space();
27828            self.write_keyword("REPLACE");
27829            self.write_space();
27830        }
27831        for (i, expr) in e.expressions.iter().enumerate() {
27832            if i > 0 {
27833                self.write(", ");
27834            }
27835            self.generate_expression(expr)?;
27836        }
27837        Ok(())
27838    }
27839
27840    fn generate_declare_item(&mut self, e: &DeclareItem) -> Result<()> {
27841        use crate::dialects::DialectType;
27842
27843        // variable TYPE [DEFAULT default]
27844        self.generate_expression(&e.this)?;
27845        // BigQuery multi-variable: DECLARE X, Y, Z INT64
27846        for name in &e.additional_names {
27847            self.write(", ");
27848            self.generate_expression(name)?;
27849        }
27850        if let Some(kind) = &e.kind {
27851            self.write_space();
27852            // BigQuery uses: DECLARE x INT64 DEFAULT value (no AS)
27853            // TSQL: Always includes AS (normalization)
27854            // Others: Include AS if present in original
27855            match self.config.dialect {
27856                Some(DialectType::BigQuery) => {
27857                    self.write(kind);
27858                }
27859                Some(DialectType::TSQL) => {
27860                    // TSQL DECLARE: no AS keyword (sqlglot convention)
27861                    // Normalize INT to INTEGER for simple declarations
27862                    // Complex TABLE declarations (with CLUSTERED/INDEX) are preserved as-is
27863                    let is_complex_table = kind.starts_with("TABLE")
27864                        && (kind.contains("CLUSTERED") || kind.contains("INDEX"));
27865                    if is_complex_table {
27866                        self.write(kind);
27867                    } else if kind == "INT" {
27868                        self.write("INTEGER");
27869                    } else if kind.starts_with("TABLE") {
27870                        // Normalize INT to INTEGER inside simple TABLE column definitions
27871                        let normalized = kind
27872                            .replace(" INT ", " INTEGER ")
27873                            .replace(" INT,", " INTEGER,")
27874                            .replace(" INT)", " INTEGER)")
27875                            .replace("(INT ", "(INTEGER ");
27876                        self.write(&normalized);
27877                    } else {
27878                        self.write(kind);
27879                    }
27880                }
27881                _ => {
27882                    if e.has_as {
27883                        self.write_keyword("AS");
27884                        self.write_space();
27885                    }
27886                    self.write(kind);
27887                }
27888            }
27889        }
27890        if let Some(default) = &e.default {
27891            // BigQuery uses DEFAULT, others use =
27892            match self.config.dialect {
27893                Some(DialectType::BigQuery) => {
27894                    self.write_space();
27895                    self.write_keyword("DEFAULT");
27896                    self.write_space();
27897                }
27898                _ => {
27899                    self.write(" = ");
27900                }
27901            }
27902            self.generate_expression(default)?;
27903        }
27904        Ok(())
27905    }
27906
27907    fn generate_decode_case(&mut self, e: &DecodeCase) -> Result<()> {
27908        // DECODE(expr, search1, result1, search2, result2, ..., default)
27909        self.write_keyword("DECODE");
27910        self.write("(");
27911        for (i, expr) in e.expressions.iter().enumerate() {
27912            if i > 0 {
27913                self.write(", ");
27914            }
27915            self.generate_expression(expr)?;
27916        }
27917        self.write(")");
27918        Ok(())
27919    }
27920
27921    fn generate_decompress_binary(&mut self, e: &DecompressBinary) -> Result<()> {
27922        // DECOMPRESS(expr, 'method')
27923        self.write_keyword("DECOMPRESS");
27924        self.write("(");
27925        self.generate_expression(&e.this)?;
27926        self.write(", '");
27927        self.write(&e.method);
27928        self.write("')");
27929        Ok(())
27930    }
27931
27932    fn generate_decompress_string(&mut self, e: &DecompressString) -> Result<()> {
27933        // DECOMPRESS(expr, 'method')
27934        self.write_keyword("DECOMPRESS");
27935        self.write("(");
27936        self.generate_expression(&e.this)?;
27937        self.write(", '");
27938        self.write(&e.method);
27939        self.write("')");
27940        Ok(())
27941    }
27942
27943    fn generate_decrypt(&mut self, e: &Decrypt) -> Result<()> {
27944        // DECRYPT(value, passphrase [, aad [, algorithm]])
27945        self.write_keyword("DECRYPT");
27946        self.write("(");
27947        self.generate_expression(&e.this)?;
27948        if let Some(passphrase) = &e.passphrase {
27949            self.write(", ");
27950            self.generate_expression(passphrase)?;
27951        }
27952        if let Some(aad) = &e.aad {
27953            self.write(", ");
27954            self.generate_expression(aad)?;
27955        }
27956        if let Some(method) = &e.encryption_method {
27957            self.write(", ");
27958            self.generate_expression(method)?;
27959        }
27960        self.write(")");
27961        Ok(())
27962    }
27963
27964    fn generate_decrypt_raw(&mut self, e: &DecryptRaw) -> Result<()> {
27965        // DECRYPT_RAW(value, key [, iv [, aad [, algorithm]]])
27966        self.write_keyword("DECRYPT_RAW");
27967        self.write("(");
27968        self.generate_expression(&e.this)?;
27969        if let Some(key) = &e.key {
27970            self.write(", ");
27971            self.generate_expression(key)?;
27972        }
27973        if let Some(iv) = &e.iv {
27974            self.write(", ");
27975            self.generate_expression(iv)?;
27976        }
27977        if let Some(aad) = &e.aad {
27978            self.write(", ");
27979            self.generate_expression(aad)?;
27980        }
27981        if let Some(method) = &e.encryption_method {
27982            self.write(", ");
27983            self.generate_expression(method)?;
27984        }
27985        self.write(")");
27986        Ok(())
27987    }
27988
27989    fn generate_definer_property(&mut self, e: &DefinerProperty) -> Result<()> {
27990        // DEFINER = user
27991        self.write_keyword("DEFINER");
27992        self.write(" = ");
27993        self.generate_expression(&e.this)?;
27994        Ok(())
27995    }
27996
27997    fn generate_detach(&mut self, e: &Detach) -> Result<()> {
27998        // Python: DETACH[DATABASE IF EXISTS] this
27999        self.write_keyword("DETACH");
28000        if e.exists {
28001            self.write_keyword(" DATABASE IF EXISTS");
28002        }
28003        self.write_space();
28004        self.generate_expression(&e.this)?;
28005        Ok(())
28006    }
28007
28008    fn generate_dict_property(&mut self, e: &DictProperty) -> Result<()> {
28009        let property_name = match e.this.as_ref() {
28010            Expression::Identifier(id) => id.name.as_str(),
28011            Expression::Var(v) => v.this.as_str(),
28012            _ => "DICTIONARY",
28013        };
28014        self.write_keyword(property_name);
28015        self.write("(");
28016        self.write(&e.kind);
28017        if let Some(settings) = &e.settings {
28018            self.write("(");
28019            if let Expression::Tuple(t) = settings.as_ref() {
28020                if self.config.pretty && !t.expressions.is_empty() {
28021                    self.write_newline();
28022                    self.indent_level += 1;
28023                    for (i, pair) in t.expressions.iter().enumerate() {
28024                        if i > 0 {
28025                            self.write(",");
28026                            self.write_newline();
28027                        }
28028                        self.write_indent();
28029                        if let Expression::Tuple(pair_tuple) = pair {
28030                            if let Some(k) = pair_tuple.expressions.first() {
28031                                self.generate_expression(k)?;
28032                            }
28033                            if let Some(v) = pair_tuple.expressions.get(1) {
28034                                self.write(" ");
28035                                self.generate_expression(v)?;
28036                            }
28037                        } else {
28038                            self.generate_expression(pair)?;
28039                        }
28040                    }
28041                    self.indent_level -= 1;
28042                    self.write_newline();
28043                    self.write_indent();
28044                } else {
28045                    for (i, pair) in t.expressions.iter().enumerate() {
28046                        if i > 0 {
28047                            // ClickHouse dict properties are space-separated, not comma-separated
28048                            self.write(" ");
28049                        }
28050                        if let Expression::Tuple(pair_tuple) = pair {
28051                            if let Some(k) = pair_tuple.expressions.first() {
28052                                self.generate_expression(k)?;
28053                            }
28054                            if let Some(v) = pair_tuple.expressions.get(1) {
28055                                self.write(" ");
28056                                self.generate_expression(v)?;
28057                            }
28058                        } else {
28059                            self.generate_expression(pair)?;
28060                        }
28061                    }
28062                }
28063            } else {
28064                self.generate_expression(settings)?;
28065            }
28066            self.write(")");
28067        } else {
28068            // No settings but kind had parens (e.g., SOURCE(NULL()), LAYOUT(FLAT()))
28069            self.write("()");
28070        }
28071        self.write(")");
28072        Ok(())
28073    }
28074
28075    fn generate_dict_range(&mut self, e: &DictRange) -> Result<()> {
28076        let property_name = match e.this.as_ref() {
28077            Expression::Identifier(id) => id.name.as_str(),
28078            Expression::Var(v) => v.this.as_str(),
28079            _ => "RANGE",
28080        };
28081        self.write_keyword(property_name);
28082        self.write("(");
28083        if let Some(min) = &e.min {
28084            self.write_keyword("MIN");
28085            self.write_space();
28086            self.generate_expression(min)?;
28087        }
28088        if let Some(max) = &e.max {
28089            self.write_space();
28090            self.write_keyword("MAX");
28091            self.write_space();
28092            self.generate_expression(max)?;
28093        }
28094        self.write(")");
28095        Ok(())
28096    }
28097
28098    fn generate_directory(&mut self, e: &Directory) -> Result<()> {
28099        // Python: {local}DIRECTORY {this}{row_format}
28100        if e.local.is_some() {
28101            self.write_keyword("LOCAL ");
28102        }
28103        self.write_keyword("DIRECTORY");
28104        self.write_space();
28105        self.generate_expression(&e.this)?;
28106        if let Some(row_format) = &e.row_format {
28107            self.write_space();
28108            self.generate_expression(row_format)?;
28109        }
28110        Ok(())
28111    }
28112
28113    fn generate_dist_key_property(&mut self, e: &DistKeyProperty) -> Result<()> {
28114        // Redshift: DISTKEY(column)
28115        self.write_keyword("DISTKEY");
28116        self.write("(");
28117        self.generate_expression(&e.this)?;
28118        self.write(")");
28119        Ok(())
28120    }
28121
28122    fn generate_dist_style_property(&mut self, e: &DistStyleProperty) -> Result<()> {
28123        // Redshift: DISTSTYLE KEY|ALL|EVEN|AUTO
28124        self.write_keyword("DISTSTYLE");
28125        self.write_space();
28126        self.generate_expression(&e.this)?;
28127        Ok(())
28128    }
28129
28130    fn generate_distribute_by(&mut self, e: &DistributeBy) -> Result<()> {
28131        // Python: "DISTRIBUTE BY" expressions
28132        self.write_keyword("DISTRIBUTE BY");
28133        self.write_space();
28134        for (i, expr) in e.expressions.iter().enumerate() {
28135            if i > 0 {
28136                self.write(", ");
28137            }
28138            self.generate_expression(expr)?;
28139        }
28140        Ok(())
28141    }
28142
28143    fn generate_distributed_by_property(&mut self, e: &DistributedByProperty) -> Result<()> {
28144        // Python: DISTRIBUTED BY kind (expressions) BUCKETS buckets order
28145        self.write_keyword("DISTRIBUTED BY");
28146        self.write_space();
28147        self.write(&e.kind);
28148        if !e.expressions.is_empty() {
28149            self.write(" (");
28150            for (i, expr) in e.expressions.iter().enumerate() {
28151                if i > 0 {
28152                    self.write(", ");
28153                }
28154                self.generate_expression(expr)?;
28155            }
28156            self.write(")");
28157        }
28158        if let Some(buckets) = &e.buckets {
28159            self.write_space();
28160            self.write_keyword("BUCKETS");
28161            self.write_space();
28162            self.generate_expression(buckets)?;
28163        }
28164        if let Some(order) = &e.order {
28165            self.write_space();
28166            self.generate_expression(order)?;
28167        }
28168        Ok(())
28169    }
28170
28171    fn generate_dot_product(&mut self, e: &DotProduct) -> Result<()> {
28172        // DOT_PRODUCT(vector1, vector2)
28173        self.write_keyword("DOT_PRODUCT");
28174        self.write("(");
28175        self.generate_expression(&e.this)?;
28176        self.write(", ");
28177        self.generate_expression(&e.expression)?;
28178        self.write(")");
28179        Ok(())
28180    }
28181
28182    fn generate_drop_partition(&mut self, e: &DropPartition) -> Result<()> {
28183        // Python: DROP{IF EXISTS }expressions
28184        self.write_keyword("DROP");
28185        if e.exists {
28186            self.write_keyword(" IF EXISTS ");
28187        } else {
28188            self.write_space();
28189        }
28190        for (i, expr) in e.expressions.iter().enumerate() {
28191            if i > 0 {
28192                self.write(", ");
28193            }
28194            self.generate_expression(expr)?;
28195        }
28196        Ok(())
28197    }
28198
28199    fn generate_duplicate_key_property(&mut self, e: &DuplicateKeyProperty) -> Result<()> {
28200        // Python: DUPLICATE KEY (expressions)
28201        self.write_keyword("DUPLICATE KEY");
28202        self.write(" (");
28203        for (i, expr) in e.expressions.iter().enumerate() {
28204            if i > 0 {
28205                self.write(", ");
28206            }
28207            self.generate_expression(expr)?;
28208        }
28209        self.write(")");
28210        Ok(())
28211    }
28212
28213    fn generate_elt(&mut self, e: &Elt) -> Result<()> {
28214        // ELT(index, str1, str2, ...)
28215        self.write_keyword("ELT");
28216        self.write("(");
28217        self.generate_expression(&e.this)?;
28218        for expr in &e.expressions {
28219            self.write(", ");
28220            self.generate_expression(expr)?;
28221        }
28222        self.write(")");
28223        Ok(())
28224    }
28225
28226    fn generate_encode(&mut self, e: &Encode) -> Result<()> {
28227        // ENCODE(string, charset)
28228        self.write_keyword("ENCODE");
28229        self.write("(");
28230        self.generate_expression(&e.this)?;
28231        if let Some(charset) = &e.charset {
28232            self.write(", ");
28233            self.generate_expression(charset)?;
28234        }
28235        self.write(")");
28236        Ok(())
28237    }
28238
28239    fn generate_encode_property(&mut self, e: &EncodeProperty) -> Result<()> {
28240        // Python: [KEY ]ENCODE this [properties]
28241        if e.key.is_some() {
28242            self.write_keyword("KEY ");
28243        }
28244        self.write_keyword("ENCODE");
28245        self.write_space();
28246        self.generate_expression(&e.this)?;
28247        if !e.properties.is_empty() {
28248            self.write(" (");
28249            for (i, prop) in e.properties.iter().enumerate() {
28250                if i > 0 {
28251                    self.write(", ");
28252                }
28253                self.generate_expression(prop)?;
28254            }
28255            self.write(")");
28256        }
28257        Ok(())
28258    }
28259
28260    fn generate_encrypt(&mut self, e: &Encrypt) -> Result<()> {
28261        // ENCRYPT(value, passphrase [, aad [, algorithm]])
28262        self.write_keyword("ENCRYPT");
28263        self.write("(");
28264        self.generate_expression(&e.this)?;
28265        if let Some(passphrase) = &e.passphrase {
28266            self.write(", ");
28267            self.generate_expression(passphrase)?;
28268        }
28269        if let Some(aad) = &e.aad {
28270            self.write(", ");
28271            self.generate_expression(aad)?;
28272        }
28273        if let Some(method) = &e.encryption_method {
28274            self.write(", ");
28275            self.generate_expression(method)?;
28276        }
28277        self.write(")");
28278        Ok(())
28279    }
28280
28281    fn generate_encrypt_raw(&mut self, e: &EncryptRaw) -> Result<()> {
28282        // ENCRYPT_RAW(value, key [, iv [, aad [, algorithm]]])
28283        self.write_keyword("ENCRYPT_RAW");
28284        self.write("(");
28285        self.generate_expression(&e.this)?;
28286        if let Some(key) = &e.key {
28287            self.write(", ");
28288            self.generate_expression(key)?;
28289        }
28290        if let Some(iv) = &e.iv {
28291            self.write(", ");
28292            self.generate_expression(iv)?;
28293        }
28294        if let Some(aad) = &e.aad {
28295            self.write(", ");
28296            self.generate_expression(aad)?;
28297        }
28298        if let Some(method) = &e.encryption_method {
28299            self.write(", ");
28300            self.generate_expression(method)?;
28301        }
28302        self.write(")");
28303        Ok(())
28304    }
28305
28306    fn generate_engine_property(&mut self, e: &EngineProperty) -> Result<()> {
28307        // MySQL: ENGINE = InnoDB
28308        self.write_keyword("ENGINE");
28309        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
28310            self.write("=");
28311        } else {
28312            self.write(" = ");
28313        }
28314        self.generate_expression(&e.this)?;
28315        Ok(())
28316    }
28317
28318    fn generate_enviroment_property(&mut self, e: &EnviromentProperty) -> Result<()> {
28319        // ENVIRONMENT (expressions)
28320        self.write_keyword("ENVIRONMENT");
28321        self.write(" (");
28322        for (i, expr) in e.expressions.iter().enumerate() {
28323            if i > 0 {
28324                self.write(", ");
28325            }
28326            self.generate_expression(expr)?;
28327        }
28328        self.write(")");
28329        Ok(())
28330    }
28331
28332    fn generate_ephemeral_column_constraint(
28333        &mut self,
28334        e: &EphemeralColumnConstraint,
28335    ) -> Result<()> {
28336        // MySQL: EPHEMERAL [expr]
28337        self.write_keyword("EPHEMERAL");
28338        if let Some(this) = &e.this {
28339            self.write_space();
28340            self.generate_expression(this)?;
28341        }
28342        Ok(())
28343    }
28344
28345    fn generate_equal_null(&mut self, e: &EqualNull) -> Result<()> {
28346        // Snowflake: EQUAL_NULL(a, b)
28347        self.write_keyword("EQUAL_NULL");
28348        self.write("(");
28349        self.generate_expression(&e.this)?;
28350        self.write(", ");
28351        self.generate_expression(&e.expression)?;
28352        self.write(")");
28353        Ok(())
28354    }
28355
28356    fn generate_euclidean_distance(&mut self, e: &EuclideanDistance) -> Result<()> {
28357        use crate::dialects::DialectType;
28358
28359        // PostgreSQL uses <-> operator syntax
28360        match self.config.dialect {
28361            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift) => {
28362                self.generate_expression(&e.this)?;
28363                self.write(" <-> ");
28364                self.generate_expression(&e.expression)?;
28365            }
28366            _ => {
28367                // Other dialects use EUCLIDEAN_DISTANCE function
28368                self.write_keyword("EUCLIDEAN_DISTANCE");
28369                self.write("(");
28370                self.generate_expression(&e.this)?;
28371                self.write(", ");
28372                self.generate_expression(&e.expression)?;
28373                self.write(")");
28374            }
28375        }
28376        Ok(())
28377    }
28378
28379    fn generate_execute_as_property(&mut self, e: &ExecuteAsProperty) -> Result<()> {
28380        // EXECUTE AS CALLER|OWNER|user
28381        self.write_keyword("EXECUTE AS");
28382        self.write_space();
28383        self.generate_expression(&e.this)?;
28384        Ok(())
28385    }
28386
28387    fn generate_export(&mut self, e: &Export) -> Result<()> {
28388        // BigQuery: EXPORT DATA [WITH CONNECTION connection] OPTIONS (...) AS query
28389        self.write_keyword("EXPORT DATA");
28390        if let Some(connection) = &e.connection {
28391            self.write_space();
28392            self.write_keyword("WITH CONNECTION");
28393            self.write_space();
28394            self.generate_expression(connection)?;
28395        }
28396        if !e.options.is_empty() {
28397            self.write_space();
28398            self.generate_options_clause(&e.options)?;
28399        }
28400        self.write_space();
28401        self.write_keyword("AS");
28402        self.write_space();
28403        self.generate_expression(&e.this)?;
28404        Ok(())
28405    }
28406
28407    fn generate_external_property(&mut self, e: &ExternalProperty) -> Result<()> {
28408        // EXTERNAL [this]
28409        self.write_keyword("EXTERNAL");
28410        if let Some(this) = &e.this {
28411            self.write_space();
28412            self.generate_expression(this)?;
28413        }
28414        Ok(())
28415    }
28416
28417    fn generate_fallback_property(&mut self, e: &FallbackProperty) -> Result<()> {
28418        // Python: {no}FALLBACK{protection}
28419        if e.no.is_some() {
28420            self.write_keyword("NO ");
28421        }
28422        self.write_keyword("FALLBACK");
28423        if e.protection.is_some() {
28424            self.write_keyword(" PROTECTION");
28425        }
28426        Ok(())
28427    }
28428
28429    fn generate_farm_fingerprint(&mut self, e: &FarmFingerprint) -> Result<()> {
28430        // BigQuery: FARM_FINGERPRINT(value)
28431        self.write_keyword("FARM_FINGERPRINT");
28432        self.write("(");
28433        for (i, expr) in e.expressions.iter().enumerate() {
28434            if i > 0 {
28435                self.write(", ");
28436            }
28437            self.generate_expression(expr)?;
28438        }
28439        self.write(")");
28440        Ok(())
28441    }
28442
28443    fn generate_features_at_time(&mut self, e: &FeaturesAtTime) -> Result<()> {
28444        // BigQuery ML: FEATURES_AT_TIME(feature_view, time, [num_rows], [ignore_feature_nulls])
28445        self.write_keyword("FEATURES_AT_TIME");
28446        self.write("(");
28447        self.generate_expression(&e.this)?;
28448        if let Some(time) = &e.time {
28449            self.write(", ");
28450            self.generate_expression(time)?;
28451        }
28452        if let Some(num_rows) = &e.num_rows {
28453            self.write(", ");
28454            self.generate_expression(num_rows)?;
28455        }
28456        if let Some(ignore_nulls) = &e.ignore_feature_nulls {
28457            self.write(", ");
28458            self.generate_expression(ignore_nulls)?;
28459        }
28460        self.write(")");
28461        Ok(())
28462    }
28463
28464    fn generate_fetch(&mut self, e: &Fetch) -> Result<()> {
28465        // For dialects that prefer LIMIT, convert simple FETCH to LIMIT
28466        let use_limit = !e.percent
28467            && !e.with_ties
28468            && e.count.is_some()
28469            && matches!(
28470                self.config.dialect,
28471                Some(DialectType::Spark)
28472                    | Some(DialectType::Hive)
28473                    | Some(DialectType::DuckDB)
28474                    | Some(DialectType::SQLite)
28475                    | Some(DialectType::MySQL)
28476                    | Some(DialectType::BigQuery)
28477                    | Some(DialectType::Databricks)
28478                    | Some(DialectType::StarRocks)
28479                    | Some(DialectType::Doris)
28480                    | Some(DialectType::Athena)
28481                    | Some(DialectType::ClickHouse)
28482            );
28483
28484        if use_limit {
28485            self.write_keyword("LIMIT");
28486            self.write_space();
28487            self.generate_expression(e.count.as_ref().unwrap())?;
28488            return Ok(());
28489        }
28490
28491        // Python: FETCH direction count limit_options
28492        self.write_keyword("FETCH");
28493        if !e.direction.is_empty() {
28494            self.write_space();
28495            self.write_keyword(&e.direction);
28496        }
28497        if let Some(count) = &e.count {
28498            self.write_space();
28499            self.generate_expression(count)?;
28500        }
28501        // Generate PERCENT, ROWS, WITH TIES/ONLY
28502        if e.percent {
28503            self.write_keyword(" PERCENT");
28504        }
28505        if e.rows {
28506            self.write_keyword(" ROWS");
28507        }
28508        if e.with_ties {
28509            self.write_keyword(" WITH TIES");
28510        } else if e.rows {
28511            self.write_keyword(" ONLY");
28512        } else {
28513            self.write_keyword(" ROWS ONLY");
28514        }
28515        Ok(())
28516    }
28517
28518    fn generate_file_format_property(&mut self, e: &FileFormatProperty) -> Result<()> {
28519        // For Hive format: STORED AS this or STORED AS INPUTFORMAT x OUTPUTFORMAT y
28520        // For Spark/Databricks without hive_format: USING this
28521        // For Snowflake/others: FILE_FORMAT = this or FILE_FORMAT = (expressions)
28522        if e.hive_format.is_some() {
28523            // Hive format: STORED AS ...
28524            self.write_keyword("STORED AS");
28525            self.write_space();
28526            if let Some(this) = &e.this {
28527                // Uppercase the format name (e.g., parquet -> PARQUET)
28528                if let Expression::Identifier(id) = this.as_ref() {
28529                    self.write_keyword(&id.name.to_ascii_uppercase());
28530                } else {
28531                    self.generate_expression(this)?;
28532                }
28533            }
28534        } else if matches!(self.config.dialect, Some(DialectType::Hive)) {
28535            // Hive: STORED AS format
28536            self.write_keyword("STORED AS");
28537            self.write_space();
28538            if let Some(this) = &e.this {
28539                if let Expression::Identifier(id) = this.as_ref() {
28540                    self.write_keyword(&id.name.to_ascii_uppercase());
28541                } else {
28542                    self.generate_expression(this)?;
28543                }
28544            }
28545        } else if matches!(
28546            self.config.dialect,
28547            Some(DialectType::Spark) | Some(DialectType::Databricks)
28548        ) {
28549            // Spark/Databricks: USING format (e.g., USING DELTA)
28550            self.write_keyword("USING");
28551            self.write_space();
28552            if let Some(this) = &e.this {
28553                self.generate_expression(this)?;
28554            }
28555        } else {
28556            // Snowflake/standard format
28557            self.write_keyword("FILE_FORMAT");
28558            self.write(" = ");
28559            if let Some(this) = &e.this {
28560                self.generate_expression(this)?;
28561            } else if !e.expressions.is_empty() {
28562                self.write("(");
28563                for (i, expr) in e.expressions.iter().enumerate() {
28564                    if i > 0 {
28565                        self.write(", ");
28566                    }
28567                    self.generate_expression(expr)?;
28568                }
28569                self.write(")");
28570            }
28571        }
28572        Ok(())
28573    }
28574
28575    fn generate_filter(&mut self, e: &Filter) -> Result<()> {
28576        // agg_func FILTER(WHERE condition)
28577        self.generate_expression(&e.this)?;
28578        self.write_space();
28579        self.write_keyword("FILTER");
28580        self.write("(");
28581        self.write_keyword("WHERE");
28582        self.write_space();
28583        self.generate_expression(&e.expression)?;
28584        self.write(")");
28585        Ok(())
28586    }
28587
28588    fn generate_float64(&mut self, e: &Float64) -> Result<()> {
28589        // FLOAT64(this) or FLOAT64(this, expression)
28590        self.write_keyword("FLOAT64");
28591        self.write("(");
28592        self.generate_expression(&e.this)?;
28593        if let Some(expr) = &e.expression {
28594            self.write(", ");
28595            self.generate_expression(expr)?;
28596        }
28597        self.write(")");
28598        Ok(())
28599    }
28600
28601    fn generate_for_in(&mut self, e: &ForIn) -> Result<()> {
28602        // FOR this DO expression
28603        self.write_keyword("FOR");
28604        self.write_space();
28605        self.generate_expression(&e.this)?;
28606        self.write_space();
28607        self.write_keyword("DO");
28608        self.write_space();
28609        self.generate_expression(&e.expression)?;
28610        Ok(())
28611    }
28612
28613    fn generate_foreign_key(&mut self, e: &ForeignKey) -> Result<()> {
28614        // FOREIGN KEY (cols) REFERENCES table(cols) ON DELETE action ON UPDATE action
28615        self.write_keyword("FOREIGN KEY");
28616        if !e.expressions.is_empty() {
28617            self.write(" (");
28618            for (i, expr) in e.expressions.iter().enumerate() {
28619                if i > 0 {
28620                    self.write(", ");
28621                }
28622                self.generate_expression(expr)?;
28623            }
28624            self.write(")");
28625        }
28626        if let Some(reference) = &e.reference {
28627            self.write_space();
28628            self.generate_expression(reference)?;
28629        }
28630        if let Some(delete) = &e.delete {
28631            self.write_space();
28632            self.write_keyword("ON DELETE");
28633            self.write_space();
28634            self.generate_expression(delete)?;
28635        }
28636        if let Some(update) = &e.update {
28637            self.write_space();
28638            self.write_keyword("ON UPDATE");
28639            self.write_space();
28640            self.generate_expression(update)?;
28641        }
28642        if !e.options.is_empty() {
28643            self.write_space();
28644            for (i, opt) in e.options.iter().enumerate() {
28645                if i > 0 {
28646                    self.write_space();
28647                }
28648                self.generate_expression(opt)?;
28649            }
28650        }
28651        Ok(())
28652    }
28653
28654    fn generate_format(&mut self, e: &Format) -> Result<()> {
28655        // FORMAT(this, expressions...)
28656        self.write_keyword("FORMAT");
28657        self.write("(");
28658        self.generate_expression(&e.this)?;
28659        for expr in &e.expressions {
28660            self.write(", ");
28661            self.generate_expression(expr)?;
28662        }
28663        self.write(")");
28664        Ok(())
28665    }
28666
28667    fn generate_format_phrase(&mut self, e: &FormatPhrase) -> Result<()> {
28668        // Teradata: column (FORMAT 'format_string')
28669        self.generate_expression(&e.this)?;
28670        self.write(" (");
28671        self.write_keyword("FORMAT");
28672        self.write(" '");
28673        self.write(&e.format);
28674        self.write("')");
28675        Ok(())
28676    }
28677
28678    fn generate_freespace_property(&mut self, e: &FreespaceProperty) -> Result<()> {
28679        // Python: FREESPACE=this[PERCENT]
28680        self.write_keyword("FREESPACE");
28681        self.write("=");
28682        self.generate_expression(&e.this)?;
28683        if e.percent.is_some() {
28684            self.write_keyword(" PERCENT");
28685        }
28686        Ok(())
28687    }
28688
28689    fn generate_from(&mut self, e: &From) -> Result<()> {
28690        // Python: return f"{self.seg('FROM')} {self.sql(expression, 'this')}"
28691        self.write_keyword("FROM");
28692        self.write_space();
28693
28694        // BigQuery, Hive, Spark, Databricks, SQLite, and ClickHouse prefer explicit CROSS JOIN over comma syntax
28695        // But keep commas when TABLESAMPLE is present
28696        // Also keep commas when the source dialect is Generic/None and target is one of these dialects
28697        use crate::dialects::DialectType;
28698        let has_tablesample = e
28699            .expressions
28700            .iter()
28701            .any(|expr| matches!(expr, Expression::TableSample(_)));
28702        let is_cross_join_dialect = matches!(
28703            self.config.dialect,
28704            Some(DialectType::BigQuery)
28705                | Some(DialectType::Hive)
28706                | Some(DialectType::Spark)
28707                | Some(DialectType::Databricks)
28708                | Some(DialectType::SQLite)
28709                | Some(DialectType::ClickHouse)
28710        );
28711        let source_is_same_as_target2 = self.config.source_dialect.is_some()
28712            && self.config.source_dialect == self.config.dialect;
28713        let source_is_cross_join_dialect2 = matches!(
28714            self.config.source_dialect,
28715            Some(DialectType::BigQuery)
28716                | Some(DialectType::Hive)
28717                | Some(DialectType::Spark)
28718                | Some(DialectType::Databricks)
28719                | Some(DialectType::SQLite)
28720                | Some(DialectType::ClickHouse)
28721        );
28722        let use_cross_join = !has_tablesample
28723            && is_cross_join_dialect
28724            && (source_is_same_as_target2
28725                || source_is_cross_join_dialect2
28726                || self.config.source_dialect.is_none());
28727
28728        // Snowflake wraps standalone VALUES in FROM clause with parentheses
28729        let wrap_values_in_parens = matches!(self.config.dialect, Some(DialectType::Snowflake));
28730
28731        for (i, expr) in e.expressions.iter().enumerate() {
28732            if i > 0 {
28733                if use_cross_join {
28734                    self.write(" CROSS JOIN ");
28735                } else {
28736                    self.write(", ");
28737                }
28738            }
28739            if wrap_values_in_parens && matches!(expr, Expression::Values(_)) {
28740                self.write("(");
28741                self.generate_expression(expr)?;
28742                self.write(")");
28743            } else {
28744                self.generate_expression(expr)?;
28745            }
28746            // Output leading comments that were on the table name before FROM
28747            // (e.g., FROM \n/* comment */\n tbl PIVOT(...) -> ... PIVOT(...) /* comment */)
28748            let leading = Self::extract_table_leading_comments(expr);
28749            for comment in &leading {
28750                self.write_space();
28751                self.write_formatted_comment(comment);
28752            }
28753        }
28754        Ok(())
28755    }
28756
28757    /// Extract leading_comments from a table expression (possibly wrapped in PIVOT/UNPIVOT)
28758    fn extract_table_leading_comments(expr: &Expression) -> Vec<String> {
28759        match expr {
28760            Expression::Table(t) => t.leading_comments.clone(),
28761            Expression::Pivot(p) => {
28762                if let Expression::Table(t) = &p.this {
28763                    t.leading_comments.clone()
28764                } else {
28765                    Vec::new()
28766                }
28767            }
28768            _ => Vec::new(),
28769        }
28770    }
28771
28772    fn generate_from_base(&mut self, e: &FromBase) -> Result<()> {
28773        // FROM_BASE(this, expression) - convert from base N
28774        self.write_keyword("FROM_BASE");
28775        self.write("(");
28776        self.generate_expression(&e.this)?;
28777        self.write(", ");
28778        self.generate_expression(&e.expression)?;
28779        self.write(")");
28780        Ok(())
28781    }
28782
28783    fn generate_from_time_zone(&mut self, e: &FromTimeZone) -> Result<()> {
28784        // this AT TIME ZONE zone AT TIME ZONE 'UTC'
28785        self.generate_expression(&e.this)?;
28786        if let Some(zone) = &e.zone {
28787            self.write_space();
28788            self.write_keyword("AT TIME ZONE");
28789            self.write_space();
28790            self.generate_expression(zone)?;
28791            self.write_space();
28792            self.write_keyword("AT TIME ZONE");
28793            self.write(" 'UTC'");
28794        }
28795        Ok(())
28796    }
28797
28798    fn generate_gap_fill(&mut self, e: &GapFill) -> Result<()> {
28799        // GAP_FILL(this, ts_column, bucket_width, ...)
28800        self.write_keyword("GAP_FILL");
28801        self.write("(");
28802        self.generate_expression(&e.this)?;
28803        if let Some(ts_column) = &e.ts_column {
28804            self.write(", ");
28805            self.generate_expression(ts_column)?;
28806        }
28807        if let Some(bucket_width) = &e.bucket_width {
28808            self.write(", ");
28809            self.generate_expression(bucket_width)?;
28810        }
28811        if let Some(partitioning_columns) = &e.partitioning_columns {
28812            self.write(", ");
28813            self.generate_expression(partitioning_columns)?;
28814        }
28815        if let Some(value_columns) = &e.value_columns {
28816            self.write(", ");
28817            self.generate_expression(value_columns)?;
28818        }
28819        self.write(")");
28820        Ok(())
28821    }
28822
28823    fn generate_generate_date_array(&mut self, e: &GenerateDateArray) -> Result<()> {
28824        // GENERATE_DATE_ARRAY(start, end, step)
28825        self.write_keyword("GENERATE_DATE_ARRAY");
28826        self.write("(");
28827        let mut first = true;
28828        if let Some(start) = &e.start {
28829            self.generate_expression(start)?;
28830            first = false;
28831        }
28832        if let Some(end) = &e.end {
28833            if !first {
28834                self.write(", ");
28835            }
28836            self.generate_expression(end)?;
28837            first = false;
28838        }
28839        if let Some(step) = &e.step {
28840            if !first {
28841                self.write(", ");
28842            }
28843            self.generate_expression(step)?;
28844        }
28845        self.write(")");
28846        Ok(())
28847    }
28848
28849    fn generate_generate_embedding(&mut self, e: &GenerateEmbedding) -> Result<()> {
28850        // ML.GENERATE_EMBEDDING(model, content, params)
28851        self.write_keyword("ML.GENERATE_EMBEDDING");
28852        self.write("(");
28853        self.generate_expression(&e.this)?;
28854        self.write(", ");
28855        self.generate_expression(&e.expression)?;
28856        if let Some(params) = &e.params_struct {
28857            self.write(", ");
28858            self.generate_expression(params)?;
28859        }
28860        self.write(")");
28861        Ok(())
28862    }
28863
28864    fn generate_generate_series(&mut self, e: &GenerateSeries) -> Result<()> {
28865        // Dialect-specific function name
28866        let fn_name = match self.config.dialect {
28867            Some(DialectType::Presto)
28868            | Some(DialectType::Trino)
28869            | Some(DialectType::Athena)
28870            | Some(DialectType::Spark)
28871            | Some(DialectType::Databricks)
28872            | Some(DialectType::Hive) => "SEQUENCE",
28873            _ => "GENERATE_SERIES",
28874        };
28875        self.write_keyword(fn_name);
28876        self.write("(");
28877        let mut first = true;
28878        if let Some(start) = &e.start {
28879            self.generate_expression(start)?;
28880            first = false;
28881        }
28882        if let Some(end) = &e.end {
28883            if !first {
28884                self.write(", ");
28885            }
28886            self.generate_expression(end)?;
28887            first = false;
28888        }
28889        if let Some(step) = &e.step {
28890            if !first {
28891                self.write(", ");
28892            }
28893            // For Presto/Trino: convert WEEK intervals to DAY multiples
28894            // e.g., INTERVAL '1' WEEK -> (1 * INTERVAL '7' DAY)
28895            if matches!(
28896                self.config.dialect,
28897                Some(DialectType::Presto) | Some(DialectType::Trino) | Some(DialectType::Athena)
28898            ) {
28899                if let Some(converted) = self.convert_week_interval_to_day(step) {
28900                    self.generate_expression(&converted)?;
28901                } else {
28902                    self.generate_expression(step)?;
28903                }
28904            } else {
28905                self.generate_expression(step)?;
28906            }
28907        }
28908        self.write(")");
28909        Ok(())
28910    }
28911
28912    /// Convert a WEEK interval to a DAY-based multiplication expression for Presto/Trino.
28913    /// INTERVAL N WEEK -> (N * INTERVAL '7' DAY)
28914    fn convert_week_interval_to_day(&self, expr: &Expression) -> Option<Expression> {
28915        use crate::expressions::*;
28916        if let Expression::Interval(ref iv) = expr {
28917            // Check for structured WEEK unit
28918            let (is_week, count_str) = if let Some(IntervalUnitSpec::Simple {
28919                unit: IntervalUnit::Week,
28920                ..
28921            }) = &iv.unit
28922            {
28923                // Value is in iv.this
28924                let count = match &iv.this {
28925                    Some(Expression::Literal(lit)) => match lit.as_ref() {
28926                        Literal::String(s) | Literal::Number(s) => s.clone(),
28927                        _ => return None,
28928                    },
28929                    _ => return None,
28930                };
28931                (true, count)
28932            } else if iv.unit.is_none() {
28933                // Check for string-encoded interval like "1 WEEK"
28934                if let Some(Expression::Literal(lit)) = &iv.this {
28935                    if let Literal::String(s) = lit.as_ref() {
28936                        let parts: Vec<&str> = s.trim().splitn(2, char::is_whitespace).collect();
28937                        if parts.len() == 2 && parts[1].eq_ignore_ascii_case("WEEK") {
28938                            (true, parts[0].to_string())
28939                        } else {
28940                            (false, String::new())
28941                        }
28942                    } else {
28943                        (false, String::new())
28944                    }
28945                } else {
28946                    (false, String::new())
28947                }
28948            } else {
28949                (false, String::new())
28950            };
28951
28952            if is_week {
28953                // Build: (N * INTERVAL '7' DAY)
28954                let count_expr = Expression::Literal(Box::new(Literal::Number(count_str)));
28955                let day_interval = Expression::Interval(Box::new(Interval {
28956                    this: Some(Expression::Literal(Box::new(Literal::String(
28957                        "7".to_string(),
28958                    )))),
28959                    unit: Some(IntervalUnitSpec::Simple {
28960                        unit: IntervalUnit::Day,
28961                        use_plural: false,
28962                    }),
28963                }));
28964                let mul = Expression::Mul(Box::new(BinaryOp {
28965                    left: count_expr,
28966                    right: day_interval,
28967                    left_comments: vec![],
28968                    operator_comments: vec![],
28969                    trailing_comments: vec![],
28970                    inferred_type: None,
28971                }));
28972                return Some(Expression::Paren(Box::new(Paren {
28973                    this: mul,
28974                    trailing_comments: vec![],
28975                })));
28976            }
28977        }
28978        None
28979    }
28980
28981    fn generate_generate_timestamp_array(&mut self, e: &GenerateTimestampArray) -> Result<()> {
28982        // GENERATE_TIMESTAMP_ARRAY(start, end, step)
28983        self.write_keyword("GENERATE_TIMESTAMP_ARRAY");
28984        self.write("(");
28985        let mut first = true;
28986        if let Some(start) = &e.start {
28987            self.generate_expression(start)?;
28988            first = false;
28989        }
28990        if let Some(end) = &e.end {
28991            if !first {
28992                self.write(", ");
28993            }
28994            self.generate_expression(end)?;
28995            first = false;
28996        }
28997        if let Some(step) = &e.step {
28998            if !first {
28999                self.write(", ");
29000            }
29001            self.generate_expression(step)?;
29002        }
29003        self.write(")");
29004        Ok(())
29005    }
29006
29007    fn generate_generated_as_identity_column_constraint(
29008        &mut self,
29009        e: &GeneratedAsIdentityColumnConstraint,
29010    ) -> Result<()> {
29011        use crate::dialects::DialectType;
29012
29013        // For Snowflake, use AUTOINCREMENT START x INCREMENT y syntax
29014        if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
29015            self.write_keyword("AUTOINCREMENT");
29016            if let Some(start) = &e.start {
29017                self.write_keyword(" START ");
29018                self.generate_expression(start)?;
29019            }
29020            if let Some(increment) = &e.increment {
29021                self.write_keyword(" INCREMENT ");
29022                self.generate_expression(increment)?;
29023            }
29024            return Ok(());
29025        }
29026
29027        // Python: GENERATED [ALWAYS|BY DEFAULT [ON NULL]] AS IDENTITY [(start, increment, ...)]
29028        self.write_keyword("GENERATED");
29029        if let Some(this) = &e.this {
29030            // Check if it's a truthy boolean expression
29031            if let Expression::Boolean(b) = this.as_ref() {
29032                if b.value {
29033                    self.write_keyword(" ALWAYS");
29034                } else {
29035                    self.write_keyword(" BY DEFAULT");
29036                    if e.on_null.is_some() {
29037                        self.write_keyword(" ON NULL");
29038                    }
29039                }
29040            } else {
29041                self.write_keyword(" ALWAYS");
29042            }
29043        }
29044        self.write_keyword(" AS IDENTITY");
29045        // Add sequence options if any
29046        let has_options = e.start.is_some()
29047            || e.increment.is_some()
29048            || e.minvalue.is_some()
29049            || e.maxvalue.is_some();
29050        if has_options {
29051            self.write(" (");
29052            let mut first = true;
29053            if let Some(start) = &e.start {
29054                self.write_keyword("START WITH ");
29055                self.generate_expression(start)?;
29056                first = false;
29057            }
29058            if let Some(increment) = &e.increment {
29059                if !first {
29060                    self.write(" ");
29061                }
29062                self.write_keyword("INCREMENT BY ");
29063                self.generate_expression(increment)?;
29064                first = false;
29065            }
29066            if let Some(minvalue) = &e.minvalue {
29067                if !first {
29068                    self.write(" ");
29069                }
29070                self.write_keyword("MINVALUE ");
29071                self.generate_expression(minvalue)?;
29072                first = false;
29073            }
29074            if let Some(maxvalue) = &e.maxvalue {
29075                if !first {
29076                    self.write(" ");
29077                }
29078                self.write_keyword("MAXVALUE ");
29079                self.generate_expression(maxvalue)?;
29080            }
29081            self.write(")");
29082        }
29083        Ok(())
29084    }
29085
29086    fn generate_generated_as_row_column_constraint(
29087        &mut self,
29088        e: &GeneratedAsRowColumnConstraint,
29089    ) -> Result<()> {
29090        // Python: GENERATED ALWAYS AS ROW START|END [HIDDEN]
29091        self.write_keyword("GENERATED ALWAYS AS ROW ");
29092        if e.start.is_some() {
29093            self.write_keyword("START");
29094        } else {
29095            self.write_keyword("END");
29096        }
29097        if e.hidden.is_some() {
29098            self.write_keyword(" HIDDEN");
29099        }
29100        Ok(())
29101    }
29102
29103    fn generate_get(&mut self, e: &Get) -> Result<()> {
29104        // GET this target properties
29105        self.write_keyword("GET");
29106        self.write_space();
29107        self.generate_expression(&e.this)?;
29108        if let Some(target) = &e.target {
29109            self.write_space();
29110            self.generate_expression(target)?;
29111        }
29112        for prop in &e.properties {
29113            self.write_space();
29114            self.generate_expression(prop)?;
29115        }
29116        Ok(())
29117    }
29118
29119    fn generate_get_extract(&mut self, e: &GetExtract) -> Result<()> {
29120        // GetExtract generates bracket access: this[expression]
29121        self.generate_expression(&e.this)?;
29122        self.write("[");
29123        self.generate_expression(&e.expression)?;
29124        self.write("]");
29125        Ok(())
29126    }
29127
29128    fn generate_getbit(&mut self, e: &Getbit) -> Result<()> {
29129        // GETBIT(this, expression) or GET_BIT(this, expression)
29130        self.write_keyword("GETBIT");
29131        self.write("(");
29132        self.generate_expression(&e.this)?;
29133        self.write(", ");
29134        self.generate_expression(&e.expression)?;
29135        self.write(")");
29136        Ok(())
29137    }
29138
29139    fn generate_grant_principal(&mut self, e: &GrantPrincipal) -> Result<()> {
29140        // [ROLE|GROUP|SHARE] name (e.g., "ROLE admin", "GROUP qa_users", "SHARE s1", or just "user1")
29141        if e.is_role {
29142            self.write_keyword("ROLE");
29143            self.write_space();
29144        } else if e.is_group {
29145            self.write_keyword("GROUP");
29146            self.write_space();
29147        } else if e.is_share {
29148            self.write_keyword("SHARE");
29149            self.write_space();
29150        }
29151        self.write(&e.name.name);
29152        Ok(())
29153    }
29154
29155    fn generate_grant_privilege(&mut self, e: &GrantPrivilege) -> Result<()> {
29156        // privilege(columns) or just privilege
29157        self.generate_expression(&e.this)?;
29158        if !e.expressions.is_empty() {
29159            self.write("(");
29160            for (i, expr) in e.expressions.iter().enumerate() {
29161                if i > 0 {
29162                    self.write(", ");
29163                }
29164                self.generate_expression(expr)?;
29165            }
29166            self.write(")");
29167        }
29168        Ok(())
29169    }
29170
29171    fn generate_group(&mut self, e: &Group) -> Result<()> {
29172        // Python handles GROUP BY ALL/DISTINCT modifiers and grouping expressions
29173        self.write_keyword("GROUP BY");
29174        // Handle ALL/DISTINCT modifier: Some(true) = ALL, Some(false) = DISTINCT
29175        match e.all {
29176            Some(true) => {
29177                self.write_space();
29178                self.write_keyword("ALL");
29179            }
29180            Some(false) => {
29181                self.write_space();
29182                self.write_keyword("DISTINCT");
29183            }
29184            None => {}
29185        }
29186        if !e.expressions.is_empty() {
29187            self.write_space();
29188            for (i, expr) in e.expressions.iter().enumerate() {
29189                if i > 0 {
29190                    self.write(", ");
29191                }
29192                self.generate_expression(expr)?;
29193            }
29194        }
29195        // Handle CUBE, ROLLUP, GROUPING SETS
29196        if let Some(cube) = &e.cube {
29197            if !e.expressions.is_empty() {
29198                self.write(", ");
29199            } else {
29200                self.write_space();
29201            }
29202            self.generate_expression(cube)?;
29203        }
29204        if let Some(rollup) = &e.rollup {
29205            if !e.expressions.is_empty() || e.cube.is_some() {
29206                self.write(", ");
29207            } else {
29208                self.write_space();
29209            }
29210            self.generate_expression(rollup)?;
29211        }
29212        if let Some(grouping_sets) = &e.grouping_sets {
29213            if !e.expressions.is_empty() || e.cube.is_some() || e.rollup.is_some() {
29214                self.write(", ");
29215            } else {
29216                self.write_space();
29217            }
29218            self.generate_expression(grouping_sets)?;
29219        }
29220        if let Some(totals) = &e.totals {
29221            self.write_space();
29222            self.write_keyword("WITH TOTALS");
29223            self.generate_expression(totals)?;
29224        }
29225        Ok(())
29226    }
29227
29228    fn generate_group_by(&mut self, e: &GroupBy) -> Result<()> {
29229        // GROUP BY expressions
29230        self.write_keyword("GROUP BY");
29231        // Handle ALL/DISTINCT modifier: Some(true) = ALL, Some(false) = DISTINCT
29232        match e.all {
29233            Some(true) => {
29234                self.write_space();
29235                self.write_keyword("ALL");
29236            }
29237            Some(false) => {
29238                self.write_space();
29239                self.write_keyword("DISTINCT");
29240            }
29241            None => {}
29242        }
29243
29244        // Check for trailing WITH CUBE or WITH ROLLUP (Hive/MySQL syntax)
29245        // These are represented as Cube/Rollup expressions with empty expressions at the end
29246        let mut trailing_cube = false;
29247        let mut trailing_rollup = false;
29248        let mut regular_expressions: Vec<&Expression> = Vec::new();
29249
29250        for expr in &e.expressions {
29251            match expr {
29252                Expression::Cube(c) if c.expressions.is_empty() => {
29253                    trailing_cube = true;
29254                }
29255                Expression::Rollup(r) if r.expressions.is_empty() => {
29256                    trailing_rollup = true;
29257                }
29258                _ => {
29259                    regular_expressions.push(expr);
29260                }
29261            }
29262        }
29263
29264        // In pretty mode, put columns on separate lines
29265        if self.config.pretty {
29266            self.write_newline();
29267            self.indent_level += 1;
29268            for (i, expr) in regular_expressions.iter().enumerate() {
29269                if i > 0 {
29270                    self.write(",");
29271                    self.write_newline();
29272                }
29273                self.write_indent();
29274                self.generate_expression(expr)?;
29275            }
29276            self.indent_level -= 1;
29277        } else {
29278            self.write_space();
29279            for (i, expr) in regular_expressions.iter().enumerate() {
29280                if i > 0 {
29281                    self.write(", ");
29282                }
29283                self.generate_expression(expr)?;
29284            }
29285        }
29286
29287        // Output trailing WITH CUBE or WITH ROLLUP
29288        if trailing_cube {
29289            self.write_space();
29290            self.write_keyword("WITH CUBE");
29291        } else if trailing_rollup {
29292            self.write_space();
29293            self.write_keyword("WITH ROLLUP");
29294        }
29295
29296        // ClickHouse: WITH TOTALS
29297        if e.totals {
29298            self.write_space();
29299            self.write_keyword("WITH TOTALS");
29300        }
29301
29302        Ok(())
29303    }
29304
29305    fn generate_grouping(&mut self, e: &Grouping) -> Result<()> {
29306        // GROUPING(col1, col2, ...)
29307        self.write_keyword("GROUPING");
29308        self.write("(");
29309        for (i, expr) in e.expressions.iter().enumerate() {
29310            if i > 0 {
29311                self.write(", ");
29312            }
29313            self.generate_expression(expr)?;
29314        }
29315        self.write(")");
29316        Ok(())
29317    }
29318
29319    fn generate_grouping_id(&mut self, e: &GroupingId) -> Result<()> {
29320        // GROUPING_ID(col1, col2, ...)
29321        self.write_keyword("GROUPING_ID");
29322        self.write("(");
29323        for (i, expr) in e.expressions.iter().enumerate() {
29324            if i > 0 {
29325                self.write(", ");
29326            }
29327            self.generate_expression(expr)?;
29328        }
29329        self.write(")");
29330        Ok(())
29331    }
29332
29333    fn generate_grouping_sets(&mut self, e: &GroupingSets) -> Result<()> {
29334        // Python: return f"GROUPING SETS {self.wrap(grouping_sets)}"
29335        self.write_keyword("GROUPING SETS");
29336        self.write(" (");
29337        for (i, expr) in e.expressions.iter().enumerate() {
29338            if i > 0 {
29339                self.write(", ");
29340            }
29341            self.generate_expression(expr)?;
29342        }
29343        self.write(")");
29344        Ok(())
29345    }
29346
29347    fn generate_hash_agg(&mut self, e: &HashAgg) -> Result<()> {
29348        // HASH_AGG(this, expressions...)
29349        self.write_keyword("HASH_AGG");
29350        self.write("(");
29351        self.generate_expression(&e.this)?;
29352        for expr in &e.expressions {
29353            self.write(", ");
29354            self.generate_expression(expr)?;
29355        }
29356        self.write(")");
29357        Ok(())
29358    }
29359
29360    fn generate_having(&mut self, e: &Having) -> Result<()> {
29361        // Python: return f"{self.seg('HAVING')}{self.sep()}{this}"
29362        self.write_keyword("HAVING");
29363        self.write_space();
29364        self.generate_expression(&e.this)?;
29365        Ok(())
29366    }
29367
29368    fn generate_having_max(&mut self, e: &HavingMax) -> Result<()> {
29369        // Python: this HAVING MAX|MIN expression
29370        self.generate_expression(&e.this)?;
29371        self.write_space();
29372        self.write_keyword("HAVING");
29373        self.write_space();
29374        if e.max.is_some() {
29375            self.write_keyword("MAX");
29376        } else {
29377            self.write_keyword("MIN");
29378        }
29379        self.write_space();
29380        self.generate_expression(&e.expression)?;
29381        Ok(())
29382    }
29383
29384    fn generate_heredoc(&mut self, e: &Heredoc) -> Result<()> {
29385        use crate::dialects::DialectType;
29386        // DuckDB: convert dollar-tagged strings to single-quoted
29387        if matches!(self.config.dialect, Some(DialectType::DuckDB)) {
29388            // Extract the string content and output as single-quoted
29389            if let Expression::Literal(ref lit) = *e.this {
29390                if let Literal::String(ref s) = lit.as_ref() {
29391                    return self.generate_string_literal(s);
29392                }
29393            }
29394        }
29395        // PostgreSQL: preserve dollar-quoting
29396        if matches!(
29397            self.config.dialect,
29398            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
29399        ) {
29400            self.write("$");
29401            if let Some(tag) = &e.tag {
29402                self.generate_expression(tag)?;
29403            }
29404            self.write("$");
29405            self.generate_expression(&e.this)?;
29406            self.write("$");
29407            if let Some(tag) = &e.tag {
29408                self.generate_expression(tag)?;
29409            }
29410            self.write("$");
29411            return Ok(());
29412        }
29413        // Default: output as dollar-tagged
29414        self.write("$");
29415        if let Some(tag) = &e.tag {
29416            self.generate_expression(tag)?;
29417        }
29418        self.write("$");
29419        self.generate_expression(&e.this)?;
29420        self.write("$");
29421        if let Some(tag) = &e.tag {
29422            self.generate_expression(tag)?;
29423        }
29424        self.write("$");
29425        Ok(())
29426    }
29427
29428    fn generate_hex_encode(&mut self, e: &HexEncode) -> Result<()> {
29429        // HEX_ENCODE(this)
29430        self.write_keyword("HEX_ENCODE");
29431        self.write("(");
29432        self.generate_expression(&e.this)?;
29433        self.write(")");
29434        Ok(())
29435    }
29436
29437    fn generate_historical_data(&mut self, e: &HistoricalData) -> Result<()> {
29438        // Python: this (kind => expression)
29439        // Write the keyword (AT/BEFORE/END) directly to avoid quoting it as a reserved word
29440        match e.this.as_ref() {
29441            Expression::Identifier(id) => self.write(&id.name),
29442            other => self.generate_expression(other)?,
29443        }
29444        self.write(" (");
29445        self.write(&e.kind);
29446        self.write(" => ");
29447        self.generate_expression(&e.expression)?;
29448        self.write(")");
29449        Ok(())
29450    }
29451
29452    fn generate_hll(&mut self, e: &Hll) -> Result<()> {
29453        // HLL(this, expressions...)
29454        self.write_keyword("HLL");
29455        self.write("(");
29456        self.generate_expression(&e.this)?;
29457        for expr in &e.expressions {
29458            self.write(", ");
29459            self.generate_expression(expr)?;
29460        }
29461        self.write(")");
29462        Ok(())
29463    }
29464
29465    fn generate_in_out_column_constraint(&mut self, e: &InOutColumnConstraint) -> Result<()> {
29466        // Python: IN|OUT|IN OUT
29467        if e.input_.is_some() && e.output.is_some() {
29468            self.write_keyword("IN OUT");
29469        } else if e.input_.is_some() {
29470            self.write_keyword("IN");
29471        } else if e.output.is_some() {
29472            self.write_keyword("OUT");
29473        }
29474        Ok(())
29475    }
29476
29477    fn generate_include_property(&mut self, e: &IncludeProperty) -> Result<()> {
29478        // Python: INCLUDE this [column_def] [AS alias]
29479        self.write_keyword("INCLUDE");
29480        self.write_space();
29481        self.generate_expression(&e.this)?;
29482        if let Some(column_def) = &e.column_def {
29483            self.write_space();
29484            self.generate_expression(column_def)?;
29485        }
29486        if let Some(alias) = &e.alias {
29487            self.write_space();
29488            self.write_keyword("AS");
29489            self.write_space();
29490            self.write(alias);
29491        }
29492        Ok(())
29493    }
29494
29495    fn generate_index(&mut self, e: &Index) -> Result<()> {
29496        // [UNIQUE] [PRIMARY] [AMP] INDEX [name] [ON table] (params)
29497        if e.unique {
29498            self.write_keyword("UNIQUE");
29499            self.write_space();
29500        }
29501        if e.primary.is_some() {
29502            self.write_keyword("PRIMARY");
29503            self.write_space();
29504        }
29505        if e.amp.is_some() {
29506            self.write_keyword("AMP");
29507            self.write_space();
29508        }
29509        if e.table.is_none() {
29510            self.write_keyword("INDEX");
29511            self.write_space();
29512        }
29513        if let Some(name) = &e.this {
29514            self.generate_expression(name)?;
29515            self.write_space();
29516        }
29517        if let Some(table) = &e.table {
29518            self.write_keyword("ON");
29519            self.write_space();
29520            self.generate_expression(table)?;
29521        }
29522        if !e.params.is_empty() {
29523            self.write("(");
29524            for (i, param) in e.params.iter().enumerate() {
29525                if i > 0 {
29526                    self.write(", ");
29527                }
29528                self.generate_expression(param)?;
29529            }
29530            self.write(")");
29531        }
29532        Ok(())
29533    }
29534
29535    fn generate_index_column_constraint(&mut self, e: &IndexColumnConstraint) -> Result<()> {
29536        // Python: kind INDEX [this] [USING index_type] (expressions) [options]
29537        if let Some(kind) = &e.kind {
29538            self.write(kind);
29539            self.write_space();
29540        }
29541        self.write_keyword("INDEX");
29542        if let Some(this) = &e.this {
29543            self.write_space();
29544            self.generate_expression(this)?;
29545        }
29546        if let Some(index_type) = &e.index_type {
29547            self.write_space();
29548            self.write_keyword("USING");
29549            self.write_space();
29550            self.generate_expression(index_type)?;
29551        }
29552        if !e.expressions.is_empty() {
29553            self.write(" (");
29554            for (i, expr) in e.expressions.iter().enumerate() {
29555                if i > 0 {
29556                    self.write(", ");
29557                }
29558                self.generate_expression(expr)?;
29559            }
29560            self.write(")");
29561        }
29562        for opt in &e.options {
29563            self.write_space();
29564            self.generate_expression(opt)?;
29565        }
29566        Ok(())
29567    }
29568
29569    fn generate_index_constraint_option(&mut self, e: &IndexConstraintOption) -> Result<()> {
29570        // Python: KEY_BLOCK_SIZE = x | USING x | WITH PARSER x | COMMENT x | visible | engine_attr | secondary_engine_attr
29571        if let Some(key_block_size) = &e.key_block_size {
29572            self.write_keyword("KEY_BLOCK_SIZE");
29573            self.write(" = ");
29574            self.generate_expression(key_block_size)?;
29575        } else if let Some(using) = &e.using {
29576            self.write_keyword("USING");
29577            self.write_space();
29578            self.generate_expression(using)?;
29579        } else if let Some(parser) = &e.parser {
29580            self.write_keyword("WITH PARSER");
29581            self.write_space();
29582            self.generate_expression(parser)?;
29583        } else if let Some(comment) = &e.comment {
29584            self.write_keyword("COMMENT");
29585            self.write_space();
29586            self.generate_expression(comment)?;
29587        } else if let Some(visible) = &e.visible {
29588            self.generate_expression(visible)?;
29589        } else if let Some(engine_attr) = &e.engine_attr {
29590            self.write_keyword("ENGINE_ATTRIBUTE");
29591            self.write(" = ");
29592            self.generate_expression(engine_attr)?;
29593        } else if let Some(secondary_engine_attr) = &e.secondary_engine_attr {
29594            self.write_keyword("SECONDARY_ENGINE_ATTRIBUTE");
29595            self.write(" = ");
29596            self.generate_expression(secondary_engine_attr)?;
29597        }
29598        Ok(())
29599    }
29600
29601    fn generate_index_parameters(&mut self, e: &IndexParameters) -> Result<()> {
29602        // Python: [USING using] (columns) [PARTITION BY partition_by] [where] [INCLUDE (include)] [WITH (with_storage)] [USING INDEX TABLESPACE tablespace]
29603        if let Some(using) = &e.using {
29604            self.write_keyword("USING");
29605            self.write_space();
29606            self.generate_expression(using)?;
29607        }
29608        if !e.columns.is_empty() {
29609            self.write("(");
29610            for (i, col) in e.columns.iter().enumerate() {
29611                if i > 0 {
29612                    self.write(", ");
29613                }
29614                self.generate_expression(col)?;
29615            }
29616            self.write(")");
29617        }
29618        if let Some(partition_by) = &e.partition_by {
29619            self.write_space();
29620            self.write_keyword("PARTITION BY");
29621            self.write_space();
29622            self.generate_expression(partition_by)?;
29623        }
29624        if let Some(where_) = &e.where_ {
29625            self.write_space();
29626            self.generate_expression(where_)?;
29627        }
29628        if let Some(include) = &e.include {
29629            self.write_space();
29630            self.write_keyword("INCLUDE");
29631            self.write(" (");
29632            self.generate_expression(include)?;
29633            self.write(")");
29634        }
29635        if let Some(with_storage) = &e.with_storage {
29636            self.write_space();
29637            self.write_keyword("WITH");
29638            self.write(" (");
29639            self.generate_expression(with_storage)?;
29640            self.write(")");
29641        }
29642        if let Some(tablespace) = &e.tablespace {
29643            self.write_space();
29644            self.write_keyword("USING INDEX TABLESPACE");
29645            self.write_space();
29646            self.generate_expression(tablespace)?;
29647        }
29648        Ok(())
29649    }
29650
29651    fn generate_index_table_hint(&mut self, e: &IndexTableHint) -> Result<()> {
29652        // Python: this INDEX [FOR target] (expressions)
29653        // Write hint type (USE/IGNORE/FORCE) as keyword, not through generate_expression
29654        // to avoid quoting reserved keywords like IGNORE, FORCE, JOIN
29655        if let Expression::Identifier(id) = &*e.this {
29656            self.write_keyword(&id.name);
29657        } else {
29658            self.generate_expression(&e.this)?;
29659        }
29660        self.write_space();
29661        self.write_keyword("INDEX");
29662        if let Some(target) = &e.target {
29663            self.write_space();
29664            self.write_keyword("FOR");
29665            self.write_space();
29666            if let Expression::Identifier(id) = &**target {
29667                self.write_keyword(&id.name);
29668            } else {
29669                self.generate_expression(target)?;
29670            }
29671        }
29672        // Always output parentheses (even if empty, e.g. USE INDEX ())
29673        self.write(" (");
29674        for (i, expr) in e.expressions.iter().enumerate() {
29675            if i > 0 {
29676                self.write(", ");
29677            }
29678            self.generate_expression(expr)?;
29679        }
29680        self.write(")");
29681        Ok(())
29682    }
29683
29684    fn generate_inherits_property(&mut self, e: &InheritsProperty) -> Result<()> {
29685        // INHERITS (table1, table2, ...)
29686        self.write_keyword("INHERITS");
29687        self.write(" (");
29688        for (i, expr) in e.expressions.iter().enumerate() {
29689            if i > 0 {
29690                self.write(", ");
29691            }
29692            self.generate_expression(expr)?;
29693        }
29694        self.write(")");
29695        Ok(())
29696    }
29697
29698    fn generate_input_model_property(&mut self, e: &InputModelProperty) -> Result<()> {
29699        // INPUT(model)
29700        self.write_keyword("INPUT");
29701        self.write("(");
29702        self.generate_expression(&e.this)?;
29703        self.write(")");
29704        Ok(())
29705    }
29706
29707    fn generate_input_output_format(&mut self, e: &InputOutputFormat) -> Result<()> {
29708        // Python: INPUTFORMAT input_format OUTPUTFORMAT output_format
29709        if let Some(input_format) = &e.input_format {
29710            self.write_keyword("INPUTFORMAT");
29711            self.write_space();
29712            self.generate_expression(input_format)?;
29713        }
29714        if let Some(output_format) = &e.output_format {
29715            if e.input_format.is_some() {
29716                self.write(" ");
29717            }
29718            self.write_keyword("OUTPUTFORMAT");
29719            self.write_space();
29720            self.generate_expression(output_format)?;
29721        }
29722        Ok(())
29723    }
29724
29725    fn generate_install(&mut self, e: &Install) -> Result<()> {
29726        // [FORCE] INSTALL extension [FROM source]
29727        if e.force.is_some() {
29728            self.write_keyword("FORCE");
29729            self.write_space();
29730        }
29731        self.write_keyword("INSTALL");
29732        self.write_space();
29733        self.generate_expression(&e.this)?;
29734        if let Some(from) = &e.from_ {
29735            self.write_space();
29736            self.write_keyword("FROM");
29737            self.write_space();
29738            self.generate_expression(from)?;
29739        }
29740        Ok(())
29741    }
29742
29743    fn generate_interval_op(&mut self, e: &IntervalOp) -> Result<()> {
29744        // INTERVAL 'expression' unit
29745        self.write_keyword("INTERVAL");
29746        self.write_space();
29747        // When a unit is specified and the expression is a number,
29748        self.generate_expression(&e.expression)?;
29749        if let Some(unit) = &e.unit {
29750            self.write_space();
29751            self.write(unit);
29752        }
29753        Ok(())
29754    }
29755
29756    fn generate_interval_span(&mut self, e: &IntervalSpan) -> Result<()> {
29757        // unit TO unit (e.g., HOUR TO SECOND)
29758        self.write(&format!("{:?}", e.this).to_ascii_uppercase());
29759        self.write_space();
29760        self.write_keyword("TO");
29761        self.write_space();
29762        self.write(&format!("{:?}", e.expression).to_ascii_uppercase());
29763        Ok(())
29764    }
29765
29766    fn generate_into_clause(&mut self, e: &IntoClause) -> Result<()> {
29767        // INTO [TEMPORARY|UNLOGGED] table
29768        self.write_keyword("INTO");
29769        if e.temporary {
29770            self.write_keyword(" TEMPORARY");
29771        }
29772        if e.unlogged.is_some() {
29773            self.write_keyword(" UNLOGGED");
29774        }
29775        if let Some(this) = &e.this {
29776            self.write_space();
29777            self.generate_expression(this)?;
29778        }
29779        if !e.expressions.is_empty() {
29780            self.write(" (");
29781            for (i, expr) in e.expressions.iter().enumerate() {
29782                if i > 0 {
29783                    self.write(", ");
29784                }
29785                self.generate_expression(expr)?;
29786            }
29787            self.write(")");
29788        }
29789        Ok(())
29790    }
29791
29792    fn generate_introducer(&mut self, e: &Introducer) -> Result<()> {
29793        // Python: this expression (e.g., _utf8 'string')
29794        self.generate_expression(&e.this)?;
29795        self.write_space();
29796        self.generate_expression(&e.expression)?;
29797        Ok(())
29798    }
29799
29800    fn generate_isolated_loading_property(&mut self, e: &IsolatedLoadingProperty) -> Result<()> {
29801        // Python: WITH [NO] [CONCURRENT] ISOLATED LOADING [target]
29802        self.write_keyword("WITH");
29803        if e.no.is_some() {
29804            self.write_keyword(" NO");
29805        }
29806        if e.concurrent.is_some() {
29807            self.write_keyword(" CONCURRENT");
29808        }
29809        self.write_keyword(" ISOLATED LOADING");
29810        if let Some(target) = &e.target {
29811            self.write_space();
29812            self.generate_expression(target)?;
29813        }
29814        Ok(())
29815    }
29816
29817    fn generate_json(&mut self, e: &JSON) -> Result<()> {
29818        // Python: JSON [this] [WITHOUT|WITH] [UNIQUE KEYS]
29819        self.write_keyword("JSON");
29820        if let Some(this) = &e.this {
29821            self.write_space();
29822            self.generate_expression(this)?;
29823        }
29824        if let Some(with_) = &e.with_ {
29825            // Check if it's a truthy boolean
29826            if let Expression::Boolean(b) = with_.as_ref() {
29827                if b.value {
29828                    self.write_keyword(" WITH");
29829                } else {
29830                    self.write_keyword(" WITHOUT");
29831                }
29832            }
29833        }
29834        if e.unique {
29835            self.write_keyword(" UNIQUE KEYS");
29836        }
29837        Ok(())
29838    }
29839
29840    fn generate_json_array(&mut self, e: &JSONArray) -> Result<()> {
29841        // Python: return self.func("JSON_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})")
29842        self.write_keyword("JSON_ARRAY");
29843        self.write("(");
29844        for (i, expr) in e.expressions.iter().enumerate() {
29845            if i > 0 {
29846                self.write(", ");
29847            }
29848            self.generate_expression(expr)?;
29849        }
29850        if let Some(null_handling) = &e.null_handling {
29851            self.write_space();
29852            self.generate_expression(null_handling)?;
29853        }
29854        if let Some(return_type) = &e.return_type {
29855            self.write_space();
29856            self.write_keyword("RETURNING");
29857            self.write_space();
29858            self.generate_expression(return_type)?;
29859        }
29860        if e.strict.is_some() {
29861            self.write_space();
29862            self.write_keyword("STRICT");
29863        }
29864        self.write(")");
29865        Ok(())
29866    }
29867
29868    fn generate_json_array_agg_struct(&mut self, e: &JSONArrayAgg) -> Result<()> {
29869        // JSON_ARRAYAGG(this [ORDER BY ...] [NULL ON NULL | ABSENT ON NULL] [RETURNING type] [STRICT])
29870        self.write_keyword("JSON_ARRAYAGG");
29871        self.write("(");
29872        self.generate_expression(&e.this)?;
29873        if let Some(order) = &e.order {
29874            self.write_space();
29875            // Order is stored as an OrderBy expression
29876            if let Expression::OrderBy(ob) = order.as_ref() {
29877                self.write_keyword("ORDER BY");
29878                self.write_space();
29879                for (i, ord) in ob.expressions.iter().enumerate() {
29880                    if i > 0 {
29881                        self.write(", ");
29882                    }
29883                    self.generate_ordered(ord)?;
29884                }
29885            } else {
29886                // Fallback: generate the expression directly
29887                self.generate_expression(order)?;
29888            }
29889        }
29890        if let Some(null_handling) = &e.null_handling {
29891            self.write_space();
29892            self.generate_expression(null_handling)?;
29893        }
29894        if let Some(return_type) = &e.return_type {
29895            self.write_space();
29896            self.write_keyword("RETURNING");
29897            self.write_space();
29898            self.generate_expression(return_type)?;
29899        }
29900        if e.strict.is_some() {
29901            self.write_space();
29902            self.write_keyword("STRICT");
29903        }
29904        self.write(")");
29905        Ok(())
29906    }
29907
29908    fn generate_json_object_agg_struct(&mut self, e: &JSONObjectAgg) -> Result<()> {
29909        // JSON_OBJECTAGG(key: value [NULL ON NULL | ABSENT ON NULL] [WITH UNIQUE KEYS] [RETURNING type])
29910        self.write_keyword("JSON_OBJECTAGG");
29911        self.write("(");
29912        for (i, expr) in e.expressions.iter().enumerate() {
29913            if i > 0 {
29914                self.write(", ");
29915            }
29916            self.generate_expression(expr)?;
29917        }
29918        if let Some(null_handling) = &e.null_handling {
29919            self.write_space();
29920            self.generate_expression(null_handling)?;
29921        }
29922        if let Some(unique_keys) = &e.unique_keys {
29923            self.write_space();
29924            if let Expression::Boolean(b) = unique_keys.as_ref() {
29925                if b.value {
29926                    self.write_keyword("WITH UNIQUE KEYS");
29927                } else {
29928                    self.write_keyword("WITHOUT UNIQUE KEYS");
29929                }
29930            }
29931        }
29932        if let Some(return_type) = &e.return_type {
29933            self.write_space();
29934            self.write_keyword("RETURNING");
29935            self.write_space();
29936            self.generate_expression(return_type)?;
29937        }
29938        self.write(")");
29939        Ok(())
29940    }
29941
29942    fn generate_json_array_append(&mut self, e: &JSONArrayAppend) -> Result<()> {
29943        // JSON_ARRAY_APPEND(this, path, value, ...)
29944        self.write_keyword("JSON_ARRAY_APPEND");
29945        self.write("(");
29946        self.generate_expression(&e.this)?;
29947        for expr in &e.expressions {
29948            self.write(", ");
29949            self.generate_expression(expr)?;
29950        }
29951        self.write(")");
29952        Ok(())
29953    }
29954
29955    fn generate_json_array_contains(&mut self, e: &JSONArrayContains) -> Result<()> {
29956        // JSON_ARRAY_CONTAINS(this, expression)
29957        self.write_keyword("JSON_ARRAY_CONTAINS");
29958        self.write("(");
29959        self.generate_expression(&e.this)?;
29960        self.write(", ");
29961        self.generate_expression(&e.expression)?;
29962        self.write(")");
29963        Ok(())
29964    }
29965
29966    fn generate_json_array_insert(&mut self, e: &JSONArrayInsert) -> Result<()> {
29967        // JSON_ARRAY_INSERT(this, path, value, ...)
29968        self.write_keyword("JSON_ARRAY_INSERT");
29969        self.write("(");
29970        self.generate_expression(&e.this)?;
29971        for expr in &e.expressions {
29972            self.write(", ");
29973            self.generate_expression(expr)?;
29974        }
29975        self.write(")");
29976        Ok(())
29977    }
29978
29979    fn generate_jsonb_exists(&mut self, e: &JSONBExists) -> Result<()> {
29980        // JSONB_EXISTS(this, path)
29981        self.write_keyword("JSONB_EXISTS");
29982        self.write("(");
29983        self.generate_expression(&e.this)?;
29984        if let Some(path) = &e.path {
29985            self.write(", ");
29986            self.generate_expression(path)?;
29987        }
29988        self.write(")");
29989        Ok(())
29990    }
29991
29992    fn generate_jsonb_extract_scalar(&mut self, e: &JSONBExtractScalar) -> Result<()> {
29993        // JSONB_EXTRACT_SCALAR(this, expression)
29994        self.write_keyword("JSONB_EXTRACT_SCALAR");
29995        self.write("(");
29996        self.generate_expression(&e.this)?;
29997        self.write(", ");
29998        self.generate_expression(&e.expression)?;
29999        self.write(")");
30000        Ok(())
30001    }
30002
30003    fn generate_jsonb_object_agg(&mut self, e: &JSONBObjectAgg) -> Result<()> {
30004        // JSONB_OBJECT_AGG(this, expression)
30005        self.write_keyword("JSONB_OBJECT_AGG");
30006        self.write("(");
30007        self.generate_expression(&e.this)?;
30008        self.write(", ");
30009        self.generate_expression(&e.expression)?;
30010        self.write(")");
30011        Ok(())
30012    }
30013
30014    fn generate_json_column_def(&mut self, e: &JSONColumnDef) -> Result<()> {
30015        // Python: NESTED PATH path schema | this kind PATH path [FOR ORDINALITY]
30016        if let Some(nested_schema) = &e.nested_schema {
30017            self.write_keyword("NESTED");
30018            if let Some(path) = &e.path {
30019                self.write_space();
30020                self.write_keyword("PATH");
30021                self.write_space();
30022                self.generate_expression(path)?;
30023            }
30024            self.write_space();
30025            self.generate_expression(nested_schema)?;
30026        } else {
30027            if let Some(this) = &e.this {
30028                self.generate_expression(this)?;
30029            }
30030            if let Some(kind) = &e.kind {
30031                self.write_space();
30032                self.write(kind);
30033            }
30034            if e.format_json {
30035                self.write_space();
30036                self.write_keyword("FORMAT JSON");
30037            }
30038            if let Some(path) = &e.path {
30039                self.write_space();
30040                self.write_keyword("PATH");
30041                self.write_space();
30042                self.generate_expression(path)?;
30043            }
30044            if e.ordinality.is_some() {
30045                self.write_keyword(" FOR ORDINALITY");
30046            }
30047        }
30048        Ok(())
30049    }
30050
30051    fn generate_json_exists(&mut self, e: &JSONExists) -> Result<()> {
30052        // JSON_EXISTS(this, path PASSING vars ON ERROR/EMPTY condition)
30053        self.write_keyword("JSON_EXISTS");
30054        self.write("(");
30055        self.generate_expression(&e.this)?;
30056        if let Some(path) = &e.path {
30057            self.write(", ");
30058            self.generate_expression(path)?;
30059        }
30060        if let Some(passing) = &e.passing {
30061            self.write_space();
30062            self.write_keyword("PASSING");
30063            self.write_space();
30064            self.generate_expression(passing)?;
30065        }
30066        if let Some(on_condition) = &e.on_condition {
30067            self.write_space();
30068            self.generate_expression(on_condition)?;
30069        }
30070        self.write(")");
30071        Ok(())
30072    }
30073
30074    fn generate_json_cast(&mut self, e: &JSONCast) -> Result<()> {
30075        self.generate_expression(&e.this)?;
30076        self.write(".:");
30077        // If the data type has nested type parameters (like Array(JSON), Map(String, Int)),
30078        // wrap the entire type string in double quotes.
30079        // This matches Python sqlglot's ClickHouse _json_cast_sql behavior.
30080        if Self::data_type_has_nested_expressions(&e.to) {
30081            // Generate the data type to a temporary string buffer, then wrap in quotes
30082            let saved = std::mem::take(&mut self.output);
30083            self.generate_data_type(&e.to)?;
30084            let type_sql = std::mem::replace(&mut self.output, saved);
30085            self.write("\"");
30086            self.write(&type_sql);
30087            self.write("\"");
30088        } else {
30089            self.generate_data_type(&e.to)?;
30090        }
30091        Ok(())
30092    }
30093
30094    /// Check if a DataType has nested type expressions (sub-types).
30095    /// This corresponds to Python sqlglot's `to.expressions` being non-empty.
30096    fn data_type_has_nested_expressions(dt: &DataType) -> bool {
30097        matches!(
30098            dt,
30099            DataType::Array { .. } | DataType::Map { .. } | DataType::Struct { .. }
30100        )
30101    }
30102
30103    fn generate_json_extract_array(&mut self, e: &JSONExtractArray) -> Result<()> {
30104        // JSON_EXTRACT_ARRAY(this, expression)
30105        self.write_keyword("JSON_EXTRACT_ARRAY");
30106        self.write("(");
30107        self.generate_expression(&e.this)?;
30108        if let Some(expr) = &e.expression {
30109            self.write(", ");
30110            self.generate_expression(expr)?;
30111        }
30112        self.write(")");
30113        Ok(())
30114    }
30115
30116    fn generate_json_extract_quote(&mut self, e: &JSONExtractQuote) -> Result<()> {
30117        // Snowflake: KEEP [OMIT] QUOTES [SCALAR_ONLY] for JSON extraction
30118        if let Some(option) = &e.option {
30119            self.generate_expression(option)?;
30120            self.write_space();
30121        }
30122        self.write_keyword("QUOTES");
30123        if e.scalar.is_some() {
30124            self.write_keyword(" SCALAR_ONLY");
30125        }
30126        Ok(())
30127    }
30128
30129    fn generate_json_extract_scalar(&mut self, e: &JSONExtractScalar) -> Result<()> {
30130        // JSON_EXTRACT_SCALAR(this, expression)
30131        self.write_keyword("JSON_EXTRACT_SCALAR");
30132        self.write("(");
30133        self.generate_expression(&e.this)?;
30134        self.write(", ");
30135        self.generate_expression(&e.expression)?;
30136        self.write(")");
30137        Ok(())
30138    }
30139
30140    fn generate_json_extract_path(&mut self, e: &JSONExtract) -> Result<()> {
30141        // For variant_extract (Snowflake/Databricks colon syntax like a:field)
30142        // Databricks uses col:path syntax, Snowflake uses GET_PATH(col, 'path')
30143        // Otherwise output JSON_EXTRACT(this, expression)
30144        if e.variant_extract.is_some() {
30145            use crate::dialects::DialectType;
30146            if matches!(self.config.dialect, Some(DialectType::Databricks)) {
30147                // Databricks: output col:path syntax (e.g., c1:price, c1:price.foo, c1:price.bar[1])
30148                // Keys that are not safe identifiers (contain hyphens, spaces, etc.) must use
30149                // bracket notation: c:["x-y"] instead of c:x-y
30150                self.generate_expression(&e.this)?;
30151                self.write(":");
30152                match e.expression.as_ref() {
30153                    Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
30154                        let Literal::String(s) = lit.as_ref() else {
30155                            unreachable!()
30156                        };
30157                        self.write_databricks_json_path(s);
30158                    }
30159                    _ => {
30160                        // Fallback: generate as-is (shouldn't happen in typical cases)
30161                        self.generate_expression(&e.expression)?;
30162                    }
30163                }
30164            } else {
30165                // Snowflake and others: use GET_PATH(col, 'path')
30166                self.write_keyword("GET_PATH");
30167                self.write("(");
30168                self.generate_expression(&e.this)?;
30169                self.write(", ");
30170                self.generate_expression(&e.expression)?;
30171                self.write(")");
30172            }
30173        } else {
30174            self.write_keyword("JSON_EXTRACT");
30175            self.write("(");
30176            self.generate_expression(&e.this)?;
30177            self.write(", ");
30178            self.generate_expression(&e.expression)?;
30179            for expr in &e.expressions {
30180                self.write(", ");
30181                self.generate_expression(expr)?;
30182            }
30183            self.write(")");
30184        }
30185        Ok(())
30186    }
30187
30188    /// Write a Databricks JSON colon-path, using bracket notation for keys
30189    /// that are not safe identifiers (e.g., contain hyphens, spaces, etc.)
30190    /// Safe identifier regex: ^[_a-zA-Z]\w*$
30191    fn write_databricks_json_path(&mut self, path: &str) {
30192        // If the path already starts with bracket notation (e.g., '["fr\'uit"]'),
30193        // it was already formatted by the parser - output as-is
30194        if path.starts_with("[\"") || path.starts_with("['") {
30195            self.write(path);
30196            return;
30197        }
30198        // Split the path into segments at '.' boundaries, but preserve bracket subscripts
30199        // e.g., "price.items[0].name" -> ["price", "items[0]", "name"]
30200        // e.g., "x-y" -> ["x-y"]
30201        let mut first = true;
30202        for segment in path.split('.') {
30203            if !first {
30204                self.write(".");
30205            }
30206            first = false;
30207            // Check if there's a bracket subscript in this segment: "items[0]"
30208            if let Some(bracket_pos) = segment.find('[') {
30209                let key = &segment[..bracket_pos];
30210                let subscript = &segment[bracket_pos..];
30211                if key.is_empty() {
30212                    // Bracket notation at start of segment (e.g., already formatted)
30213                    self.write(segment);
30214                } else if Self::is_safe_json_path_key(key) {
30215                    self.write(key);
30216                    self.write(subscript);
30217                } else {
30218                    self.write("[\"");
30219                    self.write(key);
30220                    self.write("\"]");
30221                    self.write(subscript);
30222                }
30223            } else if Self::is_safe_json_path_key(segment) {
30224                self.write(segment);
30225            } else {
30226                self.write("[\"");
30227                self.write(segment);
30228                self.write("\"]");
30229            }
30230        }
30231    }
30232
30233    /// Check if a JSON path key is a safe identifier that doesn't need bracket quoting.
30234    /// Matches Python sqlglot's SAFE_IDENTIFIER_RE: ^[_a-zA-Z]\w*$
30235    fn is_safe_json_path_key(key: &str) -> bool {
30236        if key.is_empty() {
30237            return false;
30238        }
30239        let mut chars = key.chars();
30240        let first = chars.next().unwrap();
30241        if first != '_' && !first.is_ascii_alphabetic() {
30242            return false;
30243        }
30244        chars.all(|c| c == '_' || c.is_ascii_alphanumeric())
30245    }
30246
30247    fn generate_json_format(&mut self, e: &JSONFormat) -> Result<()> {
30248        // Output: {expr} FORMAT JSON
30249        // This wraps an expression with FORMAT JSON suffix (Oracle JSON function syntax)
30250        if let Some(this) = &e.this {
30251            self.generate_expression(this)?;
30252            self.write_space();
30253        }
30254        self.write_keyword("FORMAT JSON");
30255        Ok(())
30256    }
30257
30258    fn generate_json_key_value(&mut self, e: &JSONKeyValue) -> Result<()> {
30259        // key: value (for JSON objects)
30260        self.generate_expression(&e.this)?;
30261        self.write(": ");
30262        self.generate_expression(&e.expression)?;
30263        Ok(())
30264    }
30265
30266    fn generate_json_keys(&mut self, e: &JSONKeys) -> Result<()> {
30267        // JSON_KEYS(this, expression, expressions...)
30268        self.write_keyword("JSON_KEYS");
30269        self.write("(");
30270        self.generate_expression(&e.this)?;
30271        if let Some(expr) = &e.expression {
30272            self.write(", ");
30273            self.generate_expression(expr)?;
30274        }
30275        for expr in &e.expressions {
30276            self.write(", ");
30277            self.generate_expression(expr)?;
30278        }
30279        self.write(")");
30280        Ok(())
30281    }
30282
30283    fn generate_json_keys_at_depth(&mut self, e: &JSONKeysAtDepth) -> Result<()> {
30284        // JSON_KEYS(this, expression)
30285        self.write_keyword("JSON_KEYS");
30286        self.write("(");
30287        self.generate_expression(&e.this)?;
30288        if let Some(expr) = &e.expression {
30289            self.write(", ");
30290            self.generate_expression(expr)?;
30291        }
30292        self.write(")");
30293        Ok(())
30294    }
30295
30296    fn generate_json_path_expr(&mut self, e: &JSONPath) -> Result<()> {
30297        // JSONPath expression: generates a quoted path like '$.foo' or '$[0]'
30298        // The path components are concatenated without spaces
30299        let mut path_str = String::new();
30300        for expr in &e.expressions {
30301            match expr {
30302                Expression::JSONPathRoot(_) => {
30303                    path_str.push('$');
30304                }
30305                Expression::JSONPathKey(k) => {
30306                    // .key or ."key" (quote if key has special characters)
30307                    if let Expression::Literal(lit) = k.this.as_ref() {
30308                        if let crate::expressions::Literal::String(s) = lit.as_ref() {
30309                            path_str.push('.');
30310                            // Quote the key if it contains non-alphanumeric characters (hyphens, spaces, etc.)
30311                            let needs_quoting = s.chars().any(|c| !c.is_alphanumeric() && c != '_');
30312                            if needs_quoting {
30313                                path_str.push('"');
30314                                path_str.push_str(s);
30315                                path_str.push('"');
30316                            } else {
30317                                path_str.push_str(s);
30318                            }
30319                        }
30320                    }
30321                }
30322                Expression::JSONPathSubscript(s) => {
30323                    // [index]
30324                    if let Expression::Literal(lit) = s.this.as_ref() {
30325                        if let crate::expressions::Literal::Number(n) = lit.as_ref() {
30326                            path_str.push('[');
30327                            path_str.push_str(n);
30328                            path_str.push(']');
30329                        }
30330                    }
30331                }
30332                _ => {
30333                    // For other path parts, try to generate them
30334                    let mut temp_gen = Self::with_arc_config(self.config.clone());
30335                    temp_gen.generate_expression(expr)?;
30336                    path_str.push_str(&temp_gen.output);
30337                }
30338            }
30339        }
30340        // Output as quoted string
30341        self.write("'");
30342        self.write(&path_str);
30343        self.write("'");
30344        Ok(())
30345    }
30346
30347    fn generate_json_path_filter(&mut self, e: &JSONPathFilter) -> Result<()> {
30348        // JSON path filter: ?(predicate)
30349        self.write("?(");
30350        self.generate_expression(&e.this)?;
30351        self.write(")");
30352        Ok(())
30353    }
30354
30355    fn generate_json_path_key(&mut self, e: &JSONPathKey) -> Result<()> {
30356        // JSON path key: .key or ["key"]
30357        self.write(".");
30358        self.generate_expression(&e.this)?;
30359        Ok(())
30360    }
30361
30362    fn generate_json_path_recursive(&mut self, e: &JSONPathRecursive) -> Result<()> {
30363        // JSON path recursive descent: ..
30364        self.write("..");
30365        if let Some(this) = &e.this {
30366            self.generate_expression(this)?;
30367        }
30368        Ok(())
30369    }
30370
30371    fn generate_json_path_root(&mut self) -> Result<()> {
30372        // JSON path root: $
30373        self.write("$");
30374        Ok(())
30375    }
30376
30377    fn generate_json_path_script(&mut self, e: &JSONPathScript) -> Result<()> {
30378        // JSON path script: (expression)
30379        self.write("(");
30380        self.generate_expression(&e.this)?;
30381        self.write(")");
30382        Ok(())
30383    }
30384
30385    fn generate_json_path_selector(&mut self, e: &JSONPathSelector) -> Result<()> {
30386        // JSON path selector: *
30387        self.generate_expression(&e.this)?;
30388        Ok(())
30389    }
30390
30391    fn generate_json_path_slice(&mut self, e: &JSONPathSlice) -> Result<()> {
30392        // JSON path slice: [start:end:step]
30393        self.write("[");
30394        if let Some(start) = &e.start {
30395            self.generate_expression(start)?;
30396        }
30397        self.write(":");
30398        if let Some(end) = &e.end {
30399            self.generate_expression(end)?;
30400        }
30401        if let Some(step) = &e.step {
30402            self.write(":");
30403            self.generate_expression(step)?;
30404        }
30405        self.write("]");
30406        Ok(())
30407    }
30408
30409    fn generate_json_path_subscript(&mut self, e: &JSONPathSubscript) -> Result<()> {
30410        // JSON path subscript: [index] or [*]
30411        self.write("[");
30412        self.generate_expression(&e.this)?;
30413        self.write("]");
30414        Ok(())
30415    }
30416
30417    fn generate_json_path_union(&mut self, e: &JSONPathUnion) -> Result<()> {
30418        // JSON path union: [key1, key2, ...]
30419        self.write("[");
30420        for (i, expr) in e.expressions.iter().enumerate() {
30421            if i > 0 {
30422                self.write(", ");
30423            }
30424            self.generate_expression(expr)?;
30425        }
30426        self.write("]");
30427        Ok(())
30428    }
30429
30430    fn generate_json_remove(&mut self, e: &JSONRemove) -> Result<()> {
30431        // JSON_REMOVE(this, path1, path2, ...)
30432        self.write_keyword("JSON_REMOVE");
30433        self.write("(");
30434        self.generate_expression(&e.this)?;
30435        for expr in &e.expressions {
30436            self.write(", ");
30437            self.generate_expression(expr)?;
30438        }
30439        self.write(")");
30440        Ok(())
30441    }
30442
30443    fn generate_json_schema(&mut self, e: &JSONSchema) -> Result<()> {
30444        // COLUMNS(col1 type, col2 type, ...)
30445        // When pretty printing and content is too wide, format with each column on a separate line
30446        self.write_keyword("COLUMNS");
30447        self.write("(");
30448
30449        if self.config.pretty && !e.expressions.is_empty() {
30450            // First, generate all expressions into strings to check width
30451            let mut expr_strings: Vec<String> = Vec::with_capacity(e.expressions.len());
30452            for expr in &e.expressions {
30453                let mut temp_gen = Generator::with_arc_config(self.config.clone());
30454                temp_gen.generate_expression(expr)?;
30455                expr_strings.push(temp_gen.output);
30456            }
30457
30458            // Check if total width exceeds max_text_width
30459            if self.too_wide(&expr_strings) {
30460                // Pretty print: each column on its own line
30461                self.write_newline();
30462                self.indent_level += 1;
30463                for (i, expr_str) in expr_strings.iter().enumerate() {
30464                    if i > 0 {
30465                        self.write(",");
30466                        self.write_newline();
30467                    }
30468                    self.write_indent();
30469                    self.write(expr_str);
30470                }
30471                self.write_newline();
30472                self.indent_level -= 1;
30473                self.write_indent();
30474            } else {
30475                // Compact: all on one line
30476                for (i, expr_str) in expr_strings.iter().enumerate() {
30477                    if i > 0 {
30478                        self.write(", ");
30479                    }
30480                    self.write(expr_str);
30481                }
30482            }
30483        } else {
30484            // Non-pretty mode: compact format
30485            for (i, expr) in e.expressions.iter().enumerate() {
30486                if i > 0 {
30487                    self.write(", ");
30488                }
30489                self.generate_expression(expr)?;
30490            }
30491        }
30492        self.write(")");
30493        Ok(())
30494    }
30495
30496    fn generate_json_set(&mut self, e: &JSONSet) -> Result<()> {
30497        // JSON_SET(this, path, value, ...)
30498        self.write_keyword("JSON_SET");
30499        self.write("(");
30500        self.generate_expression(&e.this)?;
30501        for expr in &e.expressions {
30502            self.write(", ");
30503            self.generate_expression(expr)?;
30504        }
30505        self.write(")");
30506        Ok(())
30507    }
30508
30509    fn generate_json_strip_nulls(&mut self, e: &JSONStripNulls) -> Result<()> {
30510        // JSON_STRIP_NULLS(this, expression)
30511        self.write_keyword("JSON_STRIP_NULLS");
30512        self.write("(");
30513        self.generate_expression(&e.this)?;
30514        if let Some(expr) = &e.expression {
30515            self.write(", ");
30516            self.generate_expression(expr)?;
30517        }
30518        self.write(")");
30519        Ok(())
30520    }
30521
30522    fn generate_json_table(&mut self, e: &JSONTable) -> Result<()> {
30523        // JSON_TABLE(this, path [error_handling] [empty_handling] schema)
30524        self.write_keyword("JSON_TABLE");
30525        self.write("(");
30526        self.generate_expression(&e.this)?;
30527        if let Some(path) = &e.path {
30528            self.write(", ");
30529            self.generate_expression(path)?;
30530        }
30531        if let Some(error_handling) = &e.error_handling {
30532            self.write_space();
30533            self.generate_expression(error_handling)?;
30534        }
30535        if let Some(empty_handling) = &e.empty_handling {
30536            self.write_space();
30537            self.generate_expression(empty_handling)?;
30538        }
30539        if let Some(schema) = &e.schema {
30540            self.write_space();
30541            self.generate_expression(schema)?;
30542        }
30543        self.write(")");
30544        Ok(())
30545    }
30546
30547    fn generate_json_type(&mut self, e: &JSONType) -> Result<()> {
30548        // JSON_TYPE(this)
30549        self.write_keyword("JSON_TYPE");
30550        self.write("(");
30551        self.generate_expression(&e.this)?;
30552        self.write(")");
30553        Ok(())
30554    }
30555
30556    fn generate_json_value(&mut self, e: &JSONValue) -> Result<()> {
30557        // JSON_VALUE(this, path RETURNING type ON condition)
30558        self.write_keyword("JSON_VALUE");
30559        self.write("(");
30560        self.generate_expression(&e.this)?;
30561        if let Some(path) = &e.path {
30562            self.write(", ");
30563            self.generate_expression(path)?;
30564        }
30565        if let Some(returning) = &e.returning {
30566            self.write_space();
30567            self.write_keyword("RETURNING");
30568            self.write_space();
30569            self.generate_expression(returning)?;
30570        }
30571        if let Some(on_condition) = &e.on_condition {
30572            self.write_space();
30573            self.generate_expression(on_condition)?;
30574        }
30575        self.write(")");
30576        Ok(())
30577    }
30578
30579    fn generate_json_value_array(&mut self, e: &JSONValueArray) -> Result<()> {
30580        // JSON_VALUE_ARRAY(this)
30581        self.write_keyword("JSON_VALUE_ARRAY");
30582        self.write("(");
30583        self.generate_expression(&e.this)?;
30584        self.write(")");
30585        Ok(())
30586    }
30587
30588    fn generate_jarowinkler_similarity(&mut self, e: &JarowinklerSimilarity) -> Result<()> {
30589        // JAROWINKLER_SIMILARITY(str1, str2)
30590        self.write_keyword("JAROWINKLER_SIMILARITY");
30591        self.write("(");
30592        self.generate_expression(&e.this)?;
30593        self.write(", ");
30594        self.generate_expression(&e.expression)?;
30595        self.write(")");
30596        Ok(())
30597    }
30598
30599    fn generate_join_hint(&mut self, e: &JoinHint) -> Result<()> {
30600        // Python: this(expressions)
30601        self.generate_expression(&e.this)?;
30602        self.write("(");
30603        for (i, expr) in e.expressions.iter().enumerate() {
30604            if i > 0 {
30605                self.write(", ");
30606            }
30607            self.generate_expression(expr)?;
30608        }
30609        self.write(")");
30610        Ok(())
30611    }
30612
30613    fn generate_journal_property(&mut self, e: &JournalProperty) -> Result<()> {
30614        // Python: {no}{local}{dual}{before}{after}JOURNAL
30615        if e.no.is_some() {
30616            self.write_keyword("NO ");
30617        }
30618        if let Some(local) = &e.local {
30619            self.generate_expression(local)?;
30620            self.write_space();
30621        }
30622        if e.dual.is_some() {
30623            self.write_keyword("DUAL ");
30624        }
30625        if e.before.is_some() {
30626            self.write_keyword("BEFORE ");
30627        }
30628        if e.after.is_some() {
30629            self.write_keyword("AFTER ");
30630        }
30631        self.write_keyword("JOURNAL");
30632        Ok(())
30633    }
30634
30635    fn generate_language_property(&mut self, e: &LanguageProperty) -> Result<()> {
30636        // LANGUAGE language_name
30637        self.write_keyword("LANGUAGE");
30638        self.write_space();
30639        self.generate_expression(&e.this)?;
30640        Ok(())
30641    }
30642
30643    fn generate_lateral(&mut self, e: &Lateral) -> Result<()> {
30644        // Python: handles LATERAL VIEW (Hive/Spark) and regular LATERAL
30645        if e.view.is_some() {
30646            // LATERAL VIEW [OUTER] expression [alias] [AS columns]
30647            self.write_keyword("LATERAL VIEW");
30648            if e.outer.is_some() {
30649                self.write_space();
30650                self.write_keyword("OUTER");
30651            }
30652            self.write_space();
30653            self.generate_expression(&e.this)?;
30654            if let Some(alias) = &e.alias {
30655                self.write_space();
30656                self.write(alias);
30657            }
30658        } else {
30659            // LATERAL subquery/function [WITH ORDINALITY] [AS alias(columns)]
30660            self.write_keyword("LATERAL");
30661            self.write_space();
30662            self.generate_expression(&e.this)?;
30663            if e.ordinality.is_some() {
30664                self.write_space();
30665                self.write_keyword("WITH ORDINALITY");
30666            }
30667            if let Some(alias) = &e.alias {
30668                self.write_space();
30669                self.write_keyword("AS");
30670                self.write_space();
30671                self.write(alias);
30672                if !e.column_aliases.is_empty() {
30673                    self.write("(");
30674                    for (i, col) in e.column_aliases.iter().enumerate() {
30675                        if i > 0 {
30676                            self.write(", ");
30677                        }
30678                        self.write(col);
30679                    }
30680                    self.write(")");
30681                }
30682            }
30683        }
30684        Ok(())
30685    }
30686
30687    fn generate_like_property(&mut self, e: &LikeProperty) -> Result<()> {
30688        // Python: LIKE this [options]
30689        self.write_keyword("LIKE");
30690        self.write_space();
30691        self.generate_expression(&e.this)?;
30692        for expr in &e.expressions {
30693            self.write_space();
30694            self.generate_expression(expr)?;
30695        }
30696        Ok(())
30697    }
30698
30699    fn generate_limit(&mut self, e: &Limit) -> Result<()> {
30700        self.write_keyword("LIMIT");
30701        self.write_space();
30702        self.write_limit_expr(&e.this)?;
30703        if e.percent {
30704            self.write_space();
30705            self.write_keyword("PERCENT");
30706        }
30707        // Emit any comments that were captured from before the LIMIT keyword
30708        for comment in &e.comments {
30709            self.write(" ");
30710            self.write_formatted_comment(comment);
30711        }
30712        Ok(())
30713    }
30714
30715    fn generate_limit_options(&mut self, e: &LimitOptions) -> Result<()> {
30716        // Python: [PERCENT][ROWS][WITH TIES|ONLY]
30717        if e.percent.is_some() {
30718            self.write_keyword(" PERCENT");
30719        }
30720        if e.rows.is_some() {
30721            self.write_keyword(" ROWS");
30722        }
30723        if e.with_ties.is_some() {
30724            self.write_keyword(" WITH TIES");
30725        } else if e.rows.is_some() {
30726            self.write_keyword(" ONLY");
30727        }
30728        Ok(())
30729    }
30730
30731    fn generate_list(&mut self, e: &List) -> Result<()> {
30732        use crate::dialects::DialectType;
30733        let is_materialize = matches!(self.config.dialect, Some(DialectType::Materialize));
30734
30735        // Check if this is a subquery-based list (LIST(SELECT ...))
30736        if e.expressions.len() == 1 {
30737            if let Expression::Select(_) = &e.expressions[0] {
30738                self.write_keyword("LIST");
30739                self.write("(");
30740                self.generate_expression(&e.expressions[0])?;
30741                self.write(")");
30742                return Ok(());
30743            }
30744        }
30745
30746        // For Materialize, output as LIST[expr, expr, ...]
30747        if is_materialize {
30748            self.write_keyword("LIST");
30749            self.write("[");
30750            for (i, expr) in e.expressions.iter().enumerate() {
30751                if i > 0 {
30752                    self.write(", ");
30753                }
30754                self.generate_expression(expr)?;
30755            }
30756            self.write("]");
30757        } else {
30758            // For other dialects, output as LIST(expr, expr, ...)
30759            self.write_keyword("LIST");
30760            self.write("(");
30761            for (i, expr) in e.expressions.iter().enumerate() {
30762                if i > 0 {
30763                    self.write(", ");
30764                }
30765                self.generate_expression(expr)?;
30766            }
30767            self.write(")");
30768        }
30769        Ok(())
30770    }
30771
30772    fn generate_tomap(&mut self, e: &ToMap) -> Result<()> {
30773        // Check if this is a subquery-based map (MAP(SELECT ...))
30774        if let Expression::Select(_) = &*e.this {
30775            self.write_keyword("MAP");
30776            self.write("(");
30777            self.generate_expression(&e.this)?;
30778            self.write(")");
30779            return Ok(());
30780        }
30781
30782        let is_duckdb = matches!(self.config.dialect, Some(DialectType::DuckDB));
30783
30784        // For Struct-based map: DuckDB uses MAP {'key': value}, Materialize uses MAP['key' => value]
30785        self.write_keyword("MAP");
30786        if is_duckdb {
30787            self.write(" {");
30788        } else {
30789            self.write("[");
30790        }
30791        if let Expression::Struct(s) = &*e.this {
30792            for (i, (_, expr)) in s.fields.iter().enumerate() {
30793                if i > 0 {
30794                    self.write(", ");
30795                }
30796                if let Expression::PropertyEQ(op) = expr {
30797                    self.generate_expression(&op.left)?;
30798                    if is_duckdb {
30799                        self.write(": ");
30800                    } else {
30801                        self.write(" => ");
30802                    }
30803                    self.generate_expression(&op.right)?;
30804                } else {
30805                    self.generate_expression(expr)?;
30806                }
30807            }
30808        }
30809        if is_duckdb {
30810            self.write("}");
30811        } else {
30812            self.write("]");
30813        }
30814        Ok(())
30815    }
30816
30817    fn generate_localtime(&mut self, e: &Localtime) -> Result<()> {
30818        // Python: LOCALTIME or LOCALTIME(precision)
30819        self.write_keyword("LOCALTIME");
30820        if let Some(precision) = &e.this {
30821            self.write("(");
30822            self.generate_expression(precision)?;
30823            self.write(")");
30824        }
30825        Ok(())
30826    }
30827
30828    fn generate_localtimestamp(&mut self, e: &Localtimestamp) -> Result<()> {
30829        // Python: LOCALTIMESTAMP or LOCALTIMESTAMP(precision)
30830        self.write_keyword("LOCALTIMESTAMP");
30831        if let Some(precision) = &e.this {
30832            self.write("(");
30833            self.generate_expression(precision)?;
30834            self.write(")");
30835        }
30836        Ok(())
30837    }
30838
30839    fn generate_location_property(&mut self, e: &LocationProperty) -> Result<()> {
30840        // LOCATION 'path'
30841        self.write_keyword("LOCATION");
30842        self.write_space();
30843        self.generate_expression(&e.this)?;
30844        Ok(())
30845    }
30846
30847    fn generate_lock(&mut self, e: &Lock) -> Result<()> {
30848        // Python: FOR UPDATE|FOR SHARE [OF tables] [NOWAIT|WAIT n]
30849        if e.update.is_some() {
30850            if e.key.is_some() {
30851                self.write_keyword("FOR NO KEY UPDATE");
30852            } else {
30853                self.write_keyword("FOR UPDATE");
30854            }
30855        } else {
30856            if e.key.is_some() {
30857                self.write_keyword("FOR KEY SHARE");
30858            } else {
30859                self.write_keyword("FOR SHARE");
30860            }
30861        }
30862        if !e.expressions.is_empty() {
30863            self.write_keyword(" OF ");
30864            for (i, expr) in e.expressions.iter().enumerate() {
30865                if i > 0 {
30866                    self.write(", ");
30867                }
30868                self.generate_expression(expr)?;
30869            }
30870        }
30871        // Handle wait option following Python sqlglot convention:
30872        // - Boolean(true) -> NOWAIT
30873        // - Boolean(false) -> SKIP LOCKED
30874        // - Literal (number) -> WAIT n
30875        if let Some(wait) = &e.wait {
30876            match wait.as_ref() {
30877                Expression::Boolean(b) => {
30878                    if b.value {
30879                        self.write_keyword(" NOWAIT");
30880                    } else {
30881                        self.write_keyword(" SKIP LOCKED");
30882                    }
30883                }
30884                _ => {
30885                    // It's a literal (number), output WAIT n
30886                    self.write_keyword(" WAIT ");
30887                    self.generate_expression(wait)?;
30888                }
30889            }
30890        }
30891        Ok(())
30892    }
30893
30894    fn generate_lock_property(&mut self, e: &LockProperty) -> Result<()> {
30895        // LOCK property
30896        self.write_keyword("LOCK");
30897        self.write_space();
30898        self.generate_expression(&e.this)?;
30899        Ok(())
30900    }
30901
30902    fn generate_locking_property(&mut self, e: &LockingProperty) -> Result<()> {
30903        // Python: LOCKING kind [this] [for_or_in] lock_type [OVERRIDE]
30904        self.write_keyword("LOCKING");
30905        self.write_space();
30906        self.write(&e.kind);
30907        if let Some(this) = &e.this {
30908            self.write_space();
30909            self.generate_expression(this)?;
30910        }
30911        if let Some(for_or_in) = &e.for_or_in {
30912            self.write_space();
30913            self.generate_expression(for_or_in)?;
30914        }
30915        if let Some(lock_type) = &e.lock_type {
30916            self.write_space();
30917            self.generate_expression(lock_type)?;
30918        }
30919        if e.override_.is_some() {
30920            self.write_keyword(" OVERRIDE");
30921        }
30922        Ok(())
30923    }
30924
30925    fn generate_locking_statement(&mut self, e: &LockingStatement) -> Result<()> {
30926        // this expression
30927        self.generate_expression(&e.this)?;
30928        self.write_space();
30929        self.generate_expression(&e.expression)?;
30930        Ok(())
30931    }
30932
30933    fn generate_log_property(&mut self, e: &LogProperty) -> Result<()> {
30934        // [NO] LOG
30935        if e.no.is_some() {
30936            self.write_keyword("NO ");
30937        }
30938        self.write_keyword("LOG");
30939        Ok(())
30940    }
30941
30942    fn generate_md5_digest(&mut self, e: &MD5Digest) -> Result<()> {
30943        // MD5(this, expressions...)
30944        self.write_keyword("MD5");
30945        self.write("(");
30946        self.generate_expression(&e.this)?;
30947        for expr in &e.expressions {
30948            self.write(", ");
30949            self.generate_expression(expr)?;
30950        }
30951        self.write(")");
30952        Ok(())
30953    }
30954
30955    fn generate_ml_forecast(&mut self, e: &MLForecast) -> Result<()> {
30956        // ML.FORECAST(model, [params])
30957        self.write_keyword("ML.FORECAST");
30958        self.write("(");
30959        self.generate_expression(&e.this)?;
30960        if let Some(expression) = &e.expression {
30961            self.write(", ");
30962            self.generate_expression(expression)?;
30963        }
30964        if let Some(params) = &e.params_struct {
30965            self.write(", ");
30966            self.generate_expression(params)?;
30967        }
30968        self.write(")");
30969        Ok(())
30970    }
30971
30972    fn generate_ml_translate(&mut self, e: &MLTranslate) -> Result<()> {
30973        // ML.TRANSLATE(model, input, [params])
30974        self.write_keyword("ML.TRANSLATE");
30975        self.write("(");
30976        self.generate_expression(&e.this)?;
30977        self.write(", ");
30978        self.generate_expression(&e.expression)?;
30979        if let Some(params) = &e.params_struct {
30980            self.write(", ");
30981            self.generate_expression(params)?;
30982        }
30983        self.write(")");
30984        Ok(())
30985    }
30986
30987    fn generate_make_interval(&mut self, e: &MakeInterval) -> Result<()> {
30988        // MAKE_INTERVAL(years => x, months => y, ...)
30989        self.write_keyword("MAKE_INTERVAL");
30990        self.write("(");
30991        let mut first = true;
30992        if let Some(year) = &e.year {
30993            self.write("years => ");
30994            self.generate_expression(year)?;
30995            first = false;
30996        }
30997        if let Some(month) = &e.month {
30998            if !first {
30999                self.write(", ");
31000            }
31001            self.write("months => ");
31002            self.generate_expression(month)?;
31003            first = false;
31004        }
31005        if let Some(week) = &e.week {
31006            if !first {
31007                self.write(", ");
31008            }
31009            self.write("weeks => ");
31010            self.generate_expression(week)?;
31011            first = false;
31012        }
31013        if let Some(day) = &e.day {
31014            if !first {
31015                self.write(", ");
31016            }
31017            self.write("days => ");
31018            self.generate_expression(day)?;
31019            first = false;
31020        }
31021        if let Some(hour) = &e.hour {
31022            if !first {
31023                self.write(", ");
31024            }
31025            self.write("hours => ");
31026            self.generate_expression(hour)?;
31027            first = false;
31028        }
31029        if let Some(minute) = &e.minute {
31030            if !first {
31031                self.write(", ");
31032            }
31033            self.write("mins => ");
31034            self.generate_expression(minute)?;
31035            first = false;
31036        }
31037        if let Some(second) = &e.second {
31038            if !first {
31039                self.write(", ");
31040            }
31041            self.write("secs => ");
31042            self.generate_expression(second)?;
31043        }
31044        self.write(")");
31045        Ok(())
31046    }
31047
31048    fn generate_manhattan_distance(&mut self, e: &ManhattanDistance) -> Result<()> {
31049        // MANHATTAN_DISTANCE(vector1, vector2)
31050        self.write_keyword("MANHATTAN_DISTANCE");
31051        self.write("(");
31052        self.generate_expression(&e.this)?;
31053        self.write(", ");
31054        self.generate_expression(&e.expression)?;
31055        self.write(")");
31056        Ok(())
31057    }
31058
31059    fn generate_map(&mut self, e: &Map) -> Result<()> {
31060        // MAP(key1, value1, key2, value2, ...)
31061        self.write_keyword("MAP");
31062        self.write("(");
31063        for (i, (key, value)) in e.keys.iter().zip(e.values.iter()).enumerate() {
31064            if i > 0 {
31065                self.write(", ");
31066            }
31067            self.generate_expression(key)?;
31068            self.write(", ");
31069            self.generate_expression(value)?;
31070        }
31071        self.write(")");
31072        Ok(())
31073    }
31074
31075    fn generate_map_cat(&mut self, e: &MapCat) -> Result<()> {
31076        // MAP_CAT(map1, map2)
31077        self.write_keyword("MAP_CAT");
31078        self.write("(");
31079        self.generate_expression(&e.this)?;
31080        self.write(", ");
31081        self.generate_expression(&e.expression)?;
31082        self.write(")");
31083        Ok(())
31084    }
31085
31086    fn generate_map_delete(&mut self, e: &MapDelete) -> Result<()> {
31087        // MAP_DELETE(map, key1, key2, ...)
31088        self.write_keyword("MAP_DELETE");
31089        self.write("(");
31090        self.generate_expression(&e.this)?;
31091        for expr in &e.expressions {
31092            self.write(", ");
31093            self.generate_expression(expr)?;
31094        }
31095        self.write(")");
31096        Ok(())
31097    }
31098
31099    fn generate_map_insert(&mut self, e: &MapInsert) -> Result<()> {
31100        // MAP_INSERT(map, key, value, [update_flag])
31101        self.write_keyword("MAP_INSERT");
31102        self.write("(");
31103        self.generate_expression(&e.this)?;
31104        if let Some(key) = &e.key {
31105            self.write(", ");
31106            self.generate_expression(key)?;
31107        }
31108        if let Some(value) = &e.value {
31109            self.write(", ");
31110            self.generate_expression(value)?;
31111        }
31112        if let Some(update_flag) = &e.update_flag {
31113            self.write(", ");
31114            self.generate_expression(update_flag)?;
31115        }
31116        self.write(")");
31117        Ok(())
31118    }
31119
31120    fn generate_map_pick(&mut self, e: &MapPick) -> Result<()> {
31121        // MAP_PICK(map, key1, key2, ...)
31122        self.write_keyword("MAP_PICK");
31123        self.write("(");
31124        self.generate_expression(&e.this)?;
31125        for expr in &e.expressions {
31126            self.write(", ");
31127            self.generate_expression(expr)?;
31128        }
31129        self.write(")");
31130        Ok(())
31131    }
31132
31133    fn generate_masking_policy_column_constraint(
31134        &mut self,
31135        e: &MaskingPolicyColumnConstraint,
31136    ) -> Result<()> {
31137        // Python: MASKING POLICY name [USING (cols)]
31138        self.write_keyword("MASKING POLICY");
31139        self.write_space();
31140        self.generate_expression(&e.this)?;
31141        if !e.expressions.is_empty() {
31142            self.write_keyword(" USING");
31143            self.write(" (");
31144            for (i, expr) in e.expressions.iter().enumerate() {
31145                if i > 0 {
31146                    self.write(", ");
31147                }
31148                self.generate_expression(expr)?;
31149            }
31150            self.write(")");
31151        }
31152        Ok(())
31153    }
31154
31155    fn generate_match_against(&mut self, e: &MatchAgainst) -> Result<()> {
31156        if matches!(
31157            self.config.dialect,
31158            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
31159        ) {
31160            if e.expressions.len() > 1 {
31161                self.write("(");
31162            }
31163            for (i, expr) in e.expressions.iter().enumerate() {
31164                if i > 0 {
31165                    self.write_keyword(" OR ");
31166                }
31167                self.generate_expression(expr)?;
31168                self.write_space();
31169                self.write("@@");
31170                self.write_space();
31171                self.generate_expression(&e.this)?;
31172            }
31173            if e.expressions.len() > 1 {
31174                self.write(")");
31175            }
31176            return Ok(());
31177        }
31178
31179        // MATCH(columns) AGAINST(expr [modifier])
31180        self.write_keyword("MATCH");
31181        self.write("(");
31182        for (i, expr) in e.expressions.iter().enumerate() {
31183            if i > 0 {
31184                self.write(", ");
31185            }
31186            self.generate_expression(expr)?;
31187        }
31188        self.write(")");
31189        self.write_keyword(" AGAINST");
31190        self.write("(");
31191        self.generate_expression(&e.this)?;
31192        if let Some(modifier) = &e.modifier {
31193            self.write_space();
31194            self.generate_expression(modifier)?;
31195        }
31196        self.write(")");
31197        Ok(())
31198    }
31199
31200    fn generate_match_recognize_measure(&mut self, e: &MatchRecognizeMeasure) -> Result<()> {
31201        // Python: [window_frame] this
31202        if let Some(window_frame) = &e.window_frame {
31203            self.write(&format!("{:?}", window_frame).to_ascii_uppercase());
31204            self.write_space();
31205        }
31206        self.generate_expression(&e.this)?;
31207        Ok(())
31208    }
31209
31210    fn generate_materialized_property(&mut self, e: &MaterializedProperty) -> Result<()> {
31211        // MATERIALIZED [this]
31212        self.write_keyword("MATERIALIZED");
31213        if let Some(this) = &e.this {
31214            self.write_space();
31215            self.generate_expression(this)?;
31216        }
31217        Ok(())
31218    }
31219
31220    fn generate_merge(&mut self, e: &Merge) -> Result<()> {
31221        // MERGE INTO target USING source ON condition WHEN ...
31222        // DuckDB variant: MERGE INTO target USING source USING (key_columns) WHEN ...
31223        if let Some(with_) = &e.with_ {
31224            if let Expression::With(with_clause) = with_.as_ref() {
31225                self.generate_with(with_clause)?;
31226                self.write_space();
31227            } else {
31228                self.generate_expression(with_)?;
31229                self.write_space();
31230            }
31231        }
31232        self.write_keyword("MERGE INTO");
31233        self.write_space();
31234        if matches!(self.config.dialect, Some(crate::DialectType::Oracle)) {
31235            if let Expression::Alias(alias) = e.this.as_ref() {
31236                self.generate_expression(&alias.this)?;
31237                self.write_space();
31238                self.generate_identifier(&alias.alias)?;
31239            } else {
31240                self.generate_expression(&e.this)?;
31241            }
31242        } else {
31243            self.generate_expression(&e.this)?;
31244        }
31245
31246        // USING clause - newline before in pretty mode
31247        if self.config.pretty {
31248            self.write_newline();
31249            self.write_indent();
31250        } else {
31251            self.write_space();
31252        }
31253        self.write_keyword("USING");
31254        self.write_space();
31255        self.generate_expression(&e.using)?;
31256
31257        // ON clause - newline before in pretty mode
31258        if let Some(on) = &e.on {
31259            if self.config.pretty {
31260                self.write_newline();
31261                self.write_indent();
31262            } else {
31263                self.write_space();
31264            }
31265            self.write_keyword("ON");
31266            self.write_space();
31267            self.generate_expression(on)?;
31268        }
31269        // DuckDB USING (key_columns) clause
31270        if let Some(using_cond) = &e.using_cond {
31271            self.write_space();
31272            self.write_keyword("USING");
31273            self.write_space();
31274            self.write("(");
31275            // using_cond is a Tuple containing the column identifiers
31276            if let Expression::Tuple(tuple) = using_cond.as_ref() {
31277                for (i, col) in tuple.expressions.iter().enumerate() {
31278                    if i > 0 {
31279                        self.write(", ");
31280                    }
31281                    self.generate_expression(col)?;
31282                }
31283            } else {
31284                self.generate_expression(using_cond)?;
31285            }
31286            self.write(")");
31287        }
31288        // For PostgreSQL dialect, extract target table name/alias to strip from UPDATE SET
31289        let saved_merge_strip = std::mem::take(&mut self.merge_strip_qualifiers);
31290        if matches!(
31291            self.config.dialect,
31292            Some(crate::DialectType::PostgreSQL)
31293                | Some(crate::DialectType::Redshift)
31294                | Some(crate::DialectType::Trino)
31295                | Some(crate::DialectType::Presto)
31296                | Some(crate::DialectType::Athena)
31297        ) {
31298            let mut names = Vec::new();
31299            match e.this.as_ref() {
31300                Expression::Alias(a) => {
31301                    // e.g., "x AS z" -> strip both "x" and "z"
31302                    if let Expression::Table(t) = &a.this {
31303                        names.push(t.name.name.clone());
31304                    } else if let Expression::Identifier(id) = &a.this {
31305                        names.push(id.name.clone());
31306                    }
31307                    names.push(a.alias.name.clone());
31308                }
31309                Expression::Table(t) => {
31310                    names.push(t.name.name.clone());
31311                }
31312                Expression::Identifier(id) => {
31313                    names.push(id.name.clone());
31314                }
31315                _ => {}
31316            }
31317            self.merge_strip_qualifiers = names;
31318        }
31319
31320        // WHEN clauses - newline before each in pretty mode
31321        if let Some(whens) = &e.whens {
31322            if self.config.pretty {
31323                self.write_newline();
31324                self.write_indent();
31325            } else {
31326                self.write_space();
31327            }
31328            self.generate_expression(whens)?;
31329        }
31330
31331        // Restore merge_strip_qualifiers
31332        self.merge_strip_qualifiers = saved_merge_strip;
31333
31334        // OUTPUT/RETURNING clause - newline before in pretty mode
31335        if let Some(returning) = &e.returning {
31336            if self.config.pretty {
31337                self.write_newline();
31338                self.write_indent();
31339            } else {
31340                self.write_space();
31341            }
31342            self.generate_expression(returning)?;
31343        }
31344        Ok(())
31345    }
31346
31347    fn generate_merge_block_ratio_property(&mut self, e: &MergeBlockRatioProperty) -> Result<()> {
31348        // Python: NO MERGEBLOCKRATIO | DEFAULT MERGEBLOCKRATIO | MERGEBLOCKRATIO=this [PERCENT]
31349        if e.no.is_some() {
31350            self.write_keyword("NO MERGEBLOCKRATIO");
31351        } else if e.default.is_some() {
31352            self.write_keyword("DEFAULT MERGEBLOCKRATIO");
31353        } else {
31354            self.write_keyword("MERGEBLOCKRATIO");
31355            self.write("=");
31356            if let Some(this) = &e.this {
31357                self.generate_expression(this)?;
31358            }
31359            if e.percent.is_some() {
31360                self.write_keyword(" PERCENT");
31361            }
31362        }
31363        Ok(())
31364    }
31365
31366    fn generate_merge_tree_ttl(&mut self, e: &MergeTreeTTL) -> Result<()> {
31367        // TTL expressions [WHERE where] [GROUP BY group] [SET aggregates]
31368        self.write_keyword("TTL");
31369        let pretty_clickhouse = self.config.pretty
31370            && matches!(
31371                self.config.dialect,
31372                Some(crate::dialects::DialectType::ClickHouse)
31373            );
31374
31375        if pretty_clickhouse {
31376            self.write_newline();
31377            self.indent_level += 1;
31378            for (i, expr) in e.expressions.iter().enumerate() {
31379                if i > 0 {
31380                    self.write(",");
31381                    self.write_newline();
31382                }
31383                self.write_indent();
31384                self.generate_expression(expr)?;
31385            }
31386            self.indent_level -= 1;
31387        } else {
31388            self.write_space();
31389            for (i, expr) in e.expressions.iter().enumerate() {
31390                if i > 0 {
31391                    self.write(", ");
31392                }
31393                self.generate_expression(expr)?;
31394            }
31395        }
31396
31397        if let Some(where_) = &e.where_ {
31398            if pretty_clickhouse {
31399                self.write_newline();
31400                if let Expression::Where(w) = where_.as_ref() {
31401                    self.write_indent();
31402                    self.write_keyword("WHERE");
31403                    self.write_newline();
31404                    self.indent_level += 1;
31405                    self.write_indent();
31406                    self.generate_expression(&w.this)?;
31407                    self.indent_level -= 1;
31408                } else {
31409                    self.write_indent();
31410                    self.generate_expression(where_)?;
31411                }
31412            } else {
31413                self.write_space();
31414                self.generate_expression(where_)?;
31415            }
31416        }
31417        if let Some(group) = &e.group {
31418            if pretty_clickhouse {
31419                self.write_newline();
31420                if let Expression::Group(g) = group.as_ref() {
31421                    self.write_indent();
31422                    self.write_keyword("GROUP BY");
31423                    self.write_newline();
31424                    self.indent_level += 1;
31425                    for (i, expr) in g.expressions.iter().enumerate() {
31426                        if i > 0 {
31427                            self.write(",");
31428                            self.write_newline();
31429                        }
31430                        self.write_indent();
31431                        self.generate_expression(expr)?;
31432                    }
31433                    self.indent_level -= 1;
31434                } else {
31435                    self.write_indent();
31436                    self.generate_expression(group)?;
31437                }
31438            } else {
31439                self.write_space();
31440                self.generate_expression(group)?;
31441            }
31442        }
31443        if let Some(aggregates) = &e.aggregates {
31444            if pretty_clickhouse {
31445                self.write_newline();
31446                self.write_indent();
31447                self.write_keyword("SET");
31448                self.write_newline();
31449                self.indent_level += 1;
31450                if let Expression::Tuple(t) = aggregates.as_ref() {
31451                    for (i, agg) in t.expressions.iter().enumerate() {
31452                        if i > 0 {
31453                            self.write(",");
31454                            self.write_newline();
31455                        }
31456                        self.write_indent();
31457                        self.generate_expression(agg)?;
31458                    }
31459                } else {
31460                    self.write_indent();
31461                    self.generate_expression(aggregates)?;
31462                }
31463                self.indent_level -= 1;
31464            } else {
31465                self.write_space();
31466                self.write_keyword("SET");
31467                self.write_space();
31468                self.generate_expression(aggregates)?;
31469            }
31470        }
31471        Ok(())
31472    }
31473
31474    fn generate_merge_tree_ttl_action(&mut self, e: &MergeTreeTTLAction) -> Result<()> {
31475        // Python: this [DELETE] [RECOMPRESS codec] [TO DISK disk] [TO VOLUME volume]
31476        self.generate_expression(&e.this)?;
31477        if e.delete.is_some() {
31478            self.write_keyword(" DELETE");
31479        }
31480        if let Some(recompress) = &e.recompress {
31481            self.write_keyword(" RECOMPRESS ");
31482            self.generate_expression(recompress)?;
31483        }
31484        if let Some(to_disk) = &e.to_disk {
31485            self.write_keyword(" TO DISK ");
31486            self.generate_expression(to_disk)?;
31487        }
31488        if let Some(to_volume) = &e.to_volume {
31489            self.write_keyword(" TO VOLUME ");
31490            self.generate_expression(to_volume)?;
31491        }
31492        Ok(())
31493    }
31494
31495    fn generate_minhash(&mut self, e: &Minhash) -> Result<()> {
31496        // MINHASH(this, expressions...)
31497        self.write_keyword("MINHASH");
31498        self.write("(");
31499        self.generate_expression(&e.this)?;
31500        for expr in &e.expressions {
31501            self.write(", ");
31502            self.generate_expression(expr)?;
31503        }
31504        self.write(")");
31505        Ok(())
31506    }
31507
31508    fn generate_model_attribute(&mut self, e: &ModelAttribute) -> Result<()> {
31509        // model!attribute - Snowflake syntax
31510        self.generate_expression(&e.this)?;
31511        self.write("!");
31512        self.generate_expression(&e.expression)?;
31513        Ok(())
31514    }
31515
31516    fn generate_monthname(&mut self, e: &Monthname) -> Result<()> {
31517        // MONTHNAME(this)
31518        self.write_keyword("MONTHNAME");
31519        self.write("(");
31520        self.generate_expression(&e.this)?;
31521        self.write(")");
31522        Ok(())
31523    }
31524
31525    fn generate_multitable_inserts(&mut self, e: &MultitableInserts) -> Result<()> {
31526        // Output leading comments
31527        for comment in &e.leading_comments {
31528            self.write_formatted_comment(comment);
31529            if self.config.pretty {
31530                self.write_newline();
31531                self.write_indent();
31532            } else {
31533                self.write_space();
31534            }
31535        }
31536        // Python: INSERT [OVERWRITE] kind expressions source
31537        self.write_keyword("INSERT");
31538        if e.overwrite {
31539            self.write_space();
31540            self.write_keyword("OVERWRITE");
31541        }
31542        self.write_space();
31543        self.write(&e.kind);
31544        if self.config.pretty {
31545            self.indent_level += 1;
31546            for expr in &e.expressions {
31547                self.write_newline();
31548                self.write_indent();
31549                self.generate_expression(expr)?;
31550            }
31551            self.indent_level -= 1;
31552        } else {
31553            for expr in &e.expressions {
31554                self.write_space();
31555                self.generate_expression(expr)?;
31556            }
31557        }
31558        if let Some(source) = &e.source {
31559            if self.config.pretty {
31560                self.write_newline();
31561                self.write_indent();
31562            } else {
31563                self.write_space();
31564            }
31565            self.generate_expression(source)?;
31566        }
31567        Ok(())
31568    }
31569
31570    fn generate_next_value_for(&mut self, e: &NextValueFor) -> Result<()> {
31571        // Python: NEXT VALUE FOR this [OVER (order)]
31572        self.write_keyword("NEXT VALUE FOR");
31573        self.write_space();
31574        self.generate_expression(&e.this)?;
31575        if let Some(order) = &e.order {
31576            self.write_space();
31577            self.write_keyword("OVER");
31578            self.write(" (");
31579            self.generate_expression(order)?;
31580            self.write(")");
31581        }
31582        Ok(())
31583    }
31584
31585    fn generate_normal(&mut self, e: &Normal) -> Result<()> {
31586        // NORMAL(mean, stddev, gen)
31587        self.write_keyword("NORMAL");
31588        self.write("(");
31589        self.generate_expression(&e.this)?;
31590        if let Some(stddev) = &e.stddev {
31591            self.write(", ");
31592            self.generate_expression(stddev)?;
31593        }
31594        if let Some(gen) = &e.gen {
31595            self.write(", ");
31596            self.generate_expression(gen)?;
31597        }
31598        self.write(")");
31599        Ok(())
31600    }
31601
31602    fn generate_normalize(&mut self, e: &Normalize) -> Result<()> {
31603        // NORMALIZE(this, form) or CASEFOLD version
31604        if e.is_casefold.is_some() {
31605            self.write_keyword("NORMALIZE_AND_CASEFOLD");
31606        } else {
31607            self.write_keyword("NORMALIZE");
31608        }
31609        self.write("(");
31610        self.generate_expression(&e.this)?;
31611        if let Some(form) = &e.form {
31612            self.write(", ");
31613            self.generate_expression(form)?;
31614        }
31615        self.write(")");
31616        Ok(())
31617    }
31618
31619    fn generate_not_null_column_constraint(&mut self, e: &NotNullColumnConstraint) -> Result<()> {
31620        // Python: [NOT ]NULL
31621        if e.allow_null.is_none() {
31622            self.write_keyword("NOT ");
31623        }
31624        self.write_keyword("NULL");
31625        Ok(())
31626    }
31627
31628    fn generate_nullif(&mut self, e: &Nullif) -> Result<()> {
31629        // NULLIF(this, expression)
31630        self.write_keyword("NULLIF");
31631        self.write("(");
31632        self.generate_expression(&e.this)?;
31633        self.write(", ");
31634        self.generate_expression(&e.expression)?;
31635        self.write(")");
31636        Ok(())
31637    }
31638
31639    fn generate_number_to_str(&mut self, e: &NumberToStr) -> Result<()> {
31640        // FORMAT(this, format, culture)
31641        self.write_keyword("FORMAT");
31642        self.write("(");
31643        self.generate_expression(&e.this)?;
31644        self.write(", '");
31645        self.write(&e.format);
31646        self.write("'");
31647        if let Some(culture) = &e.culture {
31648            self.write(", ");
31649            self.generate_expression(culture)?;
31650        }
31651        self.write(")");
31652        Ok(())
31653    }
31654
31655    fn generate_object_agg(&mut self, e: &ObjectAgg) -> Result<()> {
31656        // OBJECT_AGG(key, value)
31657        self.write_keyword("OBJECT_AGG");
31658        self.write("(");
31659        self.generate_expression(&e.this)?;
31660        self.write(", ");
31661        self.generate_expression(&e.expression)?;
31662        self.write(")");
31663        Ok(())
31664    }
31665
31666    fn generate_object_identifier(&mut self, e: &ObjectIdentifier) -> Result<()> {
31667        // Python: Just returns the name
31668        self.generate_expression(&e.this)?;
31669        Ok(())
31670    }
31671
31672    fn generate_object_insert(&mut self, e: &ObjectInsert) -> Result<()> {
31673        // OBJECT_INSERT(obj, key, value, [update_flag])
31674        self.write_keyword("OBJECT_INSERT");
31675        self.write("(");
31676        self.generate_expression(&e.this)?;
31677        if let Some(key) = &e.key {
31678            self.write(", ");
31679            self.generate_expression(key)?;
31680        }
31681        if let Some(value) = &e.value {
31682            self.write(", ");
31683            self.generate_expression(value)?;
31684        }
31685        if let Some(update_flag) = &e.update_flag {
31686            self.write(", ");
31687            self.generate_expression(update_flag)?;
31688        }
31689        self.write(")");
31690        Ok(())
31691    }
31692
31693    fn generate_offset(&mut self, e: &Offset) -> Result<()> {
31694        // OFFSET value [ROW|ROWS]
31695        self.write_keyword("OFFSET");
31696        self.write_space();
31697        self.generate_expression(&e.this)?;
31698        // Output ROWS keyword only for TSQL/Oracle targets
31699        if e.rows == Some(true)
31700            && matches!(
31701                self.config.dialect,
31702                Some(crate::dialects::DialectType::TSQL)
31703                    | Some(crate::dialects::DialectType::Oracle)
31704            )
31705        {
31706            self.write_space();
31707            self.write_keyword("ROWS");
31708        }
31709        Ok(())
31710    }
31711
31712    fn generate_qualify(&mut self, e: &Qualify) -> Result<()> {
31713        // QUALIFY condition (Snowflake/BigQuery)
31714        self.write_keyword("QUALIFY");
31715        self.write_space();
31716        self.generate_expression(&e.this)?;
31717        Ok(())
31718    }
31719
31720    fn generate_on_cluster(&mut self, e: &OnCluster) -> Result<()> {
31721        // ON CLUSTER cluster_name
31722        self.write_keyword("ON CLUSTER");
31723        self.write_space();
31724        self.generate_expression(&e.this)?;
31725        Ok(())
31726    }
31727
31728    fn generate_on_commit_property(&mut self, e: &OnCommitProperty) -> Result<()> {
31729        // ON COMMIT [DELETE ROWS | PRESERVE ROWS]
31730        self.write_keyword("ON COMMIT");
31731        if e.delete.is_some() {
31732            self.write_keyword(" DELETE ROWS");
31733        } else {
31734            self.write_keyword(" PRESERVE ROWS");
31735        }
31736        Ok(())
31737    }
31738
31739    fn generate_on_condition(&mut self, e: &OnCondition) -> Result<()> {
31740        // Python: error/empty/null handling
31741        if let Some(empty) = &e.empty {
31742            self.generate_expression(empty)?;
31743            self.write_keyword(" ON EMPTY");
31744        }
31745        if let Some(error) = &e.error {
31746            if e.empty.is_some() {
31747                self.write_space();
31748            }
31749            self.generate_expression(error)?;
31750            self.write_keyword(" ON ERROR");
31751        }
31752        if let Some(null) = &e.null {
31753            if e.empty.is_some() || e.error.is_some() {
31754                self.write_space();
31755            }
31756            self.generate_expression(null)?;
31757            self.write_keyword(" ON NULL");
31758        }
31759        Ok(())
31760    }
31761
31762    fn generate_on_conflict(&mut self, e: &OnConflict) -> Result<()> {
31763        // Materialize doesn't support ON CONFLICT - skip entirely
31764        if matches!(self.config.dialect, Some(DialectType::Materialize)) {
31765            return Ok(());
31766        }
31767        // Python: ON CONFLICT|ON DUPLICATE KEY [ON CONSTRAINT constraint] [conflict_keys] action
31768        if e.duplicate.is_some() {
31769            // MySQL: ON DUPLICATE KEY UPDATE col = val, ...
31770            self.write_keyword("ON DUPLICATE KEY UPDATE");
31771            for (i, expr) in e.expressions.iter().enumerate() {
31772                if i > 0 {
31773                    self.write(",");
31774                }
31775                self.write_space();
31776                self.generate_expression(expr)?;
31777            }
31778            return Ok(());
31779        } else {
31780            self.write_keyword("ON CONFLICT");
31781        }
31782        if let Some(constraint) = &e.constraint {
31783            self.write_keyword(" ON CONSTRAINT ");
31784            self.generate_expression(constraint)?;
31785        }
31786        if let Some(conflict_keys) = &e.conflict_keys {
31787            // conflict_keys can be a Tuple containing expressions
31788            if let Expression::Tuple(t) = conflict_keys.as_ref() {
31789                self.write("(");
31790                for (i, expr) in t.expressions.iter().enumerate() {
31791                    if i > 0 {
31792                        self.write(", ");
31793                    }
31794                    self.generate_expression(expr)?;
31795                }
31796                self.write(")");
31797            } else {
31798                self.write("(");
31799                self.generate_expression(conflict_keys)?;
31800                self.write(")");
31801            }
31802        }
31803        if let Some(index_predicate) = &e.index_predicate {
31804            self.write_keyword(" WHERE ");
31805            self.generate_expression(index_predicate)?;
31806        }
31807        if let Some(action) = &e.action {
31808            // Check if action is "NOTHING" or an UPDATE set
31809            if let Expression::Identifier(id) = action.as_ref() {
31810                if id.name.eq_ignore_ascii_case("NOTHING") {
31811                    self.write_keyword(" DO NOTHING");
31812                } else {
31813                    self.write_keyword(" DO ");
31814                    self.generate_expression(action)?;
31815                }
31816            } else if let Expression::Tuple(t) = action.as_ref() {
31817                // DO UPDATE SET col1 = val1, col2 = val2
31818                self.write_keyword(" DO UPDATE SET ");
31819                for (i, expr) in t.expressions.iter().enumerate() {
31820                    if i > 0 {
31821                        self.write(", ");
31822                    }
31823                    self.generate_expression(expr)?;
31824                }
31825            } else {
31826                self.write_keyword(" DO ");
31827                self.generate_expression(action)?;
31828            }
31829        }
31830        // WHERE clause for the UPDATE action
31831        if let Some(where_) = &e.where_ {
31832            self.write_keyword(" WHERE ");
31833            self.generate_expression(where_)?;
31834        }
31835        Ok(())
31836    }
31837
31838    fn generate_on_property(&mut self, e: &OnProperty) -> Result<()> {
31839        // ON property_value
31840        self.write_keyword("ON");
31841        self.write_space();
31842        self.generate_expression(&e.this)?;
31843        Ok(())
31844    }
31845
31846    fn generate_opclass(&mut self, e: &Opclass) -> Result<()> {
31847        // Python: this expression (e.g., column opclass)
31848        self.generate_expression(&e.this)?;
31849        self.write_space();
31850        self.generate_expression(&e.expression)?;
31851        Ok(())
31852    }
31853
31854    fn generate_open_json(&mut self, e: &OpenJSON) -> Result<()> {
31855        // Python: OPENJSON(this[, path]) [WITH (columns)]
31856        self.write_keyword("OPENJSON");
31857        self.write("(");
31858        self.generate_expression(&e.this)?;
31859        if let Some(path) = &e.path {
31860            self.write(", ");
31861            self.generate_expression(path)?;
31862        }
31863        self.write(")");
31864        if !e.expressions.is_empty() {
31865            self.write_keyword(" WITH");
31866            if self.config.pretty {
31867                self.write(" (\n");
31868                self.indent_level += 2;
31869                for (i, expr) in e.expressions.iter().enumerate() {
31870                    if i > 0 {
31871                        self.write(",\n");
31872                    }
31873                    self.write_indent();
31874                    self.generate_expression(expr)?;
31875                }
31876                self.write("\n");
31877                self.indent_level -= 2;
31878                self.write(")");
31879            } else {
31880                self.write(" (");
31881                for (i, expr) in e.expressions.iter().enumerate() {
31882                    if i > 0 {
31883                        self.write(", ");
31884                    }
31885                    self.generate_expression(expr)?;
31886                }
31887                self.write(")");
31888            }
31889        }
31890        Ok(())
31891    }
31892
31893    fn generate_open_json_column_def(&mut self, e: &OpenJSONColumnDef) -> Result<()> {
31894        // Python: this kind [path] [AS JSON]
31895        self.generate_expression(&e.this)?;
31896        self.write_space();
31897        // Use parsed data_type if available, otherwise fall back to kind string
31898        if let Some(ref dt) = e.data_type {
31899            self.generate_data_type(dt)?;
31900        } else if !e.kind.is_empty() {
31901            self.write(&e.kind);
31902        }
31903        if let Some(path) = &e.path {
31904            self.write_space();
31905            self.generate_expression(path)?;
31906        }
31907        if e.as_json.is_some() {
31908            self.write_keyword(" AS JSON");
31909        }
31910        Ok(())
31911    }
31912
31913    fn generate_operator(&mut self, e: &Operator) -> Result<()> {
31914        // this OPERATOR(op) expression
31915        self.generate_expression(&e.this)?;
31916        self.write_space();
31917        if let Some(op) = &e.operator {
31918            self.write_keyword("OPERATOR");
31919            self.write("(");
31920            self.generate_expression(op)?;
31921            self.write(")");
31922        }
31923        // Emit inline comments between OPERATOR() and the RHS
31924        for comment in &e.comments {
31925            self.write_space();
31926            self.write_formatted_comment(comment);
31927        }
31928        self.write_space();
31929        self.generate_expression(&e.expression)?;
31930        Ok(())
31931    }
31932
31933    fn generate_order_by(&mut self, e: &OrderBy) -> Result<()> {
31934        // ORDER BY expr1 [ASC|DESC] [NULLS FIRST|LAST], expr2 ...
31935        self.write_keyword("ORDER BY");
31936        let pretty_clickhouse_single_paren = self.config.pretty
31937            && matches!(self.config.dialect, Some(DialectType::ClickHouse))
31938            && e.expressions.len() == 1
31939            && matches!(e.expressions[0].this, Expression::Paren(ref p) if !matches!(p.this, Expression::Tuple(_)));
31940        let clickhouse_single_tuple = matches!(self.config.dialect, Some(DialectType::ClickHouse))
31941            && e.expressions.len() == 1
31942            && matches!(e.expressions[0].this, Expression::Tuple(_))
31943            && !e.expressions[0].desc
31944            && e.expressions[0].nulls_first.is_none();
31945
31946        if pretty_clickhouse_single_paren {
31947            self.write_space();
31948            if let Expression::Paren(p) = &e.expressions[0].this {
31949                self.write("(");
31950                self.write_newline();
31951                self.indent_level += 1;
31952                self.write_indent();
31953                self.generate_expression(&p.this)?;
31954                self.indent_level -= 1;
31955                self.write_newline();
31956                self.write(")");
31957            }
31958            return Ok(());
31959        }
31960
31961        if clickhouse_single_tuple {
31962            self.write_space();
31963            if let Expression::Tuple(t) = &e.expressions[0].this {
31964                self.write("(");
31965                for (i, expr) in t.expressions.iter().enumerate() {
31966                    if i > 0 {
31967                        self.write(", ");
31968                    }
31969                    self.generate_expression(expr)?;
31970                }
31971                self.write(")");
31972            }
31973            return Ok(());
31974        }
31975
31976        self.write_space();
31977        for (i, ordered) in e.expressions.iter().enumerate() {
31978            if i > 0 {
31979                self.write(", ");
31980            }
31981            self.generate_expression(&ordered.this)?;
31982            if ordered.desc {
31983                self.write_space();
31984                self.write_keyword("DESC");
31985            } else if ordered.explicit_asc {
31986                self.write_space();
31987                self.write_keyword("ASC");
31988            }
31989            if let Some(nulls_first) = ordered.nulls_first {
31990                // In Dremio, NULLS LAST is the default, so skip generating it
31991                let skip_nulls_last =
31992                    !nulls_first && matches!(self.config.dialect, Some(DialectType::Dremio));
31993                if !skip_nulls_last {
31994                    self.write_space();
31995                    self.write_keyword("NULLS");
31996                    self.write_space();
31997                    if nulls_first {
31998                        self.write_keyword("FIRST");
31999                    } else {
32000                        self.write_keyword("LAST");
32001                    }
32002                }
32003            }
32004        }
32005        Ok(())
32006    }
32007
32008    fn generate_output_model_property(&mut self, e: &OutputModelProperty) -> Result<()> {
32009        // OUTPUT(model)
32010        self.write_keyword("OUTPUT");
32011        self.write("(");
32012        if self.config.pretty {
32013            self.indent_level += 1;
32014            self.write_newline();
32015            self.write_indent();
32016            self.generate_expression(&e.this)?;
32017            self.indent_level -= 1;
32018            self.write_newline();
32019        } else {
32020            self.generate_expression(&e.this)?;
32021        }
32022        self.write(")");
32023        Ok(())
32024    }
32025
32026    fn generate_overflow_truncate_behavior(&mut self, e: &OverflowTruncateBehavior) -> Result<()> {
32027        // Python: TRUNCATE [filler] WITH|WITHOUT COUNT
32028        self.write_keyword("TRUNCATE");
32029        if let Some(this) = &e.this {
32030            self.write_space();
32031            self.generate_expression(this)?;
32032        }
32033        if e.with_count.is_some() {
32034            self.write_keyword(" WITH COUNT");
32035        } else {
32036            self.write_keyword(" WITHOUT COUNT");
32037        }
32038        Ok(())
32039    }
32040
32041    fn generate_parameterized_agg(&mut self, e: &ParameterizedAgg) -> Result<()> {
32042        // Python: name(expressions)(params)
32043        self.generate_expression(&e.this)?;
32044        self.write("(");
32045        for (i, expr) in e.expressions.iter().enumerate() {
32046            if i > 0 {
32047                self.write(", ");
32048            }
32049            self.generate_expression(expr)?;
32050        }
32051        self.write(")(");
32052        for (i, param) in e.params.iter().enumerate() {
32053            if i > 0 {
32054                self.write(", ");
32055            }
32056            self.generate_expression(param)?;
32057        }
32058        self.write(")");
32059        Ok(())
32060    }
32061
32062    fn generate_parse_datetime(&mut self, e: &ParseDatetime) -> Result<()> {
32063        // PARSE_DATETIME(format, this) or similar
32064        self.write_keyword("PARSE_DATETIME");
32065        self.write("(");
32066        if let Some(format) = &e.format {
32067            self.write("'");
32068            self.write(format);
32069            self.write("', ");
32070        }
32071        self.generate_expression(&e.this)?;
32072        if let Some(zone) = &e.zone {
32073            self.write(", ");
32074            self.generate_expression(zone)?;
32075        }
32076        self.write(")");
32077        Ok(())
32078    }
32079
32080    fn generate_parse_ip(&mut self, e: &ParseIp) -> Result<()> {
32081        // PARSE_IP(this, type, permissive)
32082        self.write_keyword("PARSE_IP");
32083        self.write("(");
32084        self.generate_expression(&e.this)?;
32085        if let Some(type_) = &e.type_ {
32086            self.write(", ");
32087            self.generate_expression(type_)?;
32088        }
32089        if let Some(permissive) = &e.permissive {
32090            self.write(", ");
32091            self.generate_expression(permissive)?;
32092        }
32093        self.write(")");
32094        Ok(())
32095    }
32096
32097    fn generate_parse_json(&mut self, e: &ParseJSON) -> Result<()> {
32098        // PARSE_JSON(this, [expression])
32099        self.write_keyword("PARSE_JSON");
32100        self.write("(");
32101        self.generate_expression(&e.this)?;
32102        if let Some(expression) = &e.expression {
32103            self.write(", ");
32104            self.generate_expression(expression)?;
32105        }
32106        self.write(")");
32107        Ok(())
32108    }
32109
32110    fn generate_parse_time(&mut self, e: &ParseTime) -> Result<()> {
32111        // PARSE_TIME(format, this) or STR_TO_TIME(this, format)
32112        self.write_keyword("PARSE_TIME");
32113        self.write("(");
32114        self.write(&format!("'{}'", e.format));
32115        self.write(", ");
32116        self.generate_expression(&e.this)?;
32117        self.write(")");
32118        Ok(())
32119    }
32120
32121    fn generate_parse_url(&mut self, e: &ParseUrl) -> Result<()> {
32122        // PARSE_URL(this, [part_to_extract], [key], [permissive])
32123        self.write_keyword("PARSE_URL");
32124        self.write("(");
32125        self.generate_expression(&e.this)?;
32126        if let Some(part) = &e.part_to_extract {
32127            self.write(", ");
32128            self.generate_expression(part)?;
32129        }
32130        if let Some(key) = &e.key {
32131            self.write(", ");
32132            self.generate_expression(key)?;
32133        }
32134        if let Some(permissive) = &e.permissive {
32135            self.write(", ");
32136            self.generate_expression(permissive)?;
32137        }
32138        self.write(")");
32139        Ok(())
32140    }
32141
32142    fn generate_partition_expr(&mut self, e: &Partition) -> Result<()> {
32143        // PARTITION(expr1, expr2, ...) or SUBPARTITION(expr1, expr2, ...)
32144        if e.subpartition {
32145            self.write_keyword("SUBPARTITION");
32146        } else {
32147            self.write_keyword("PARTITION");
32148        }
32149        self.write("(");
32150        for (i, expr) in e.expressions.iter().enumerate() {
32151            if i > 0 {
32152                self.write(", ");
32153            }
32154            self.generate_expression(expr)?;
32155        }
32156        self.write(")");
32157        Ok(())
32158    }
32159
32160    fn generate_partition_bound_spec(&mut self, e: &PartitionBoundSpec) -> Result<()> {
32161        // IN (values) or WITH (MODULUS this, REMAINDER expression) or FROM (from) TO (to)
32162        if let Some(this) = &e.this {
32163            if let Some(expression) = &e.expression {
32164                // WITH (MODULUS this, REMAINDER expression)
32165                self.write_keyword("WITH");
32166                self.write(" (");
32167                self.write_keyword("MODULUS");
32168                self.write_space();
32169                self.generate_expression(this)?;
32170                self.write(", ");
32171                self.write_keyword("REMAINDER");
32172                self.write_space();
32173                self.generate_expression(expression)?;
32174                self.write(")");
32175            } else {
32176                // IN (this) - this could be a list
32177                self.write_keyword("IN");
32178                self.write(" (");
32179                self.generate_partition_bound_values(this)?;
32180                self.write(")");
32181            }
32182        } else if let (Some(from), Some(to)) = (&e.from_expressions, &e.to_expressions) {
32183            // FROM (from_expressions) TO (to_expressions)
32184            self.write_keyword("FROM");
32185            self.write(" (");
32186            self.generate_partition_bound_values(from)?;
32187            self.write(") ");
32188            self.write_keyword("TO");
32189            self.write(" (");
32190            self.generate_partition_bound_values(to)?;
32191            self.write(")");
32192        }
32193        Ok(())
32194    }
32195
32196    /// Generate partition bound values - handles Tuple expressions by outputting
32197    /// contents without wrapping parens (since caller provides the parens)
32198    fn generate_partition_bound_values(&mut self, expr: &Expression) -> Result<()> {
32199        if let Expression::Tuple(t) = expr {
32200            for (i, e) in t.expressions.iter().enumerate() {
32201                if i > 0 {
32202                    self.write(", ");
32203                }
32204                self.generate_expression(e)?;
32205            }
32206            Ok(())
32207        } else {
32208            self.generate_expression(expr)
32209        }
32210    }
32211
32212    fn generate_partition_by_list_property(&mut self, e: &PartitionByListProperty) -> Result<()> {
32213        // PARTITION BY LIST (partition_expressions) (create_expressions)
32214        self.write_keyword("PARTITION BY LIST");
32215        if let Some(partition_exprs) = &e.partition_expressions {
32216            self.write(" (");
32217            // Unwrap Tuple for partition columns (don't generate outer parens from Tuple)
32218            self.generate_doris_partition_expressions(partition_exprs)?;
32219            self.write(")");
32220        }
32221        if let Some(create_exprs) = &e.create_expressions {
32222            self.write(" (");
32223            // Unwrap Tuple for partition definitions
32224            self.generate_doris_partition_definitions(create_exprs)?;
32225            self.write(")");
32226        }
32227        Ok(())
32228    }
32229
32230    fn generate_partition_by_range_property(&mut self, e: &PartitionByRangeProperty) -> Result<()> {
32231        // PARTITION BY RANGE (partition_expressions) (create_expressions)
32232        self.write_keyword("PARTITION BY RANGE");
32233        if let Some(partition_exprs) = &e.partition_expressions {
32234            self.write(" (");
32235            // Unwrap Tuple for partition columns (don't generate outer parens from Tuple)
32236            self.generate_doris_partition_expressions(partition_exprs)?;
32237            self.write(")");
32238        }
32239        if let Some(create_exprs) = &e.create_expressions {
32240            self.write(" (");
32241            // Check for dynamic partition (PartitionByRangePropertyDynamic) or static (Tuple of Partition)
32242            self.generate_doris_partition_definitions(create_exprs)?;
32243            self.write(")");
32244        }
32245        Ok(())
32246    }
32247
32248    /// Generate Doris partition column expressions (unwrap Tuple)
32249    fn generate_doris_partition_expressions(&mut self, expr: &Expression) -> Result<()> {
32250        if let Expression::Tuple(t) = expr {
32251            for (i, e) in t.expressions.iter().enumerate() {
32252                if i > 0 {
32253                    self.write(", ");
32254                }
32255                self.generate_expression(e)?;
32256            }
32257        } else {
32258            self.generate_expression(expr)?;
32259        }
32260        Ok(())
32261    }
32262
32263    /// Generate Doris partition definitions (comma-separated Partition expressions)
32264    fn generate_doris_partition_definitions(&mut self, expr: &Expression) -> Result<()> {
32265        match expr {
32266            Expression::Tuple(t) => {
32267                // Multiple partitions, comma-separated
32268                for (i, part) in t.expressions.iter().enumerate() {
32269                    if i > 0 {
32270                        self.write(", ");
32271                    }
32272                    // For Partition expressions, generate the inner PartitionRange/PartitionList directly
32273                    if let Expression::Partition(p) = part {
32274                        for (j, inner) in p.expressions.iter().enumerate() {
32275                            if j > 0 {
32276                                self.write(", ");
32277                            }
32278                            self.generate_expression(inner)?;
32279                        }
32280                    } else {
32281                        self.generate_expression(part)?;
32282                    }
32283                }
32284            }
32285            Expression::PartitionByRangePropertyDynamic(_) => {
32286                // Dynamic partition - FROM/TO/INTERVAL
32287                self.generate_expression(expr)?;
32288            }
32289            _ => {
32290                self.generate_expression(expr)?;
32291            }
32292        }
32293        Ok(())
32294    }
32295
32296    fn generate_partition_by_range_property_dynamic(
32297        &mut self,
32298        e: &PartitionByRangePropertyDynamic,
32299    ) -> Result<()> {
32300        if e.use_start_end {
32301            // StarRocks: START ('val') END ('val') EVERY (expr)
32302            if let Some(start) = &e.start {
32303                self.write_keyword("START");
32304                self.write(" (");
32305                self.generate_expression(start)?;
32306                self.write(")");
32307            }
32308            if let Some(end) = &e.end {
32309                self.write_space();
32310                self.write_keyword("END");
32311                self.write(" (");
32312                self.generate_expression(end)?;
32313                self.write(")");
32314            }
32315            if let Some(every) = &e.every {
32316                self.write_space();
32317                self.write_keyword("EVERY");
32318                self.write(" (");
32319                // Use unquoted interval format for StarRocks
32320                self.generate_doris_interval(every)?;
32321                self.write(")");
32322            }
32323        } else {
32324            // Doris: FROM (start) TO (end) INTERVAL n UNIT
32325            if let Some(start) = &e.start {
32326                self.write_keyword("FROM");
32327                self.write(" (");
32328                self.generate_expression(start)?;
32329                self.write(")");
32330            }
32331            if let Some(end) = &e.end {
32332                self.write_space();
32333                self.write_keyword("TO");
32334                self.write(" (");
32335                self.generate_expression(end)?;
32336                self.write(")");
32337            }
32338            if let Some(every) = &e.every {
32339                self.write_space();
32340                // Generate INTERVAL n UNIT (not quoted, for Doris dynamic partition)
32341                self.generate_doris_interval(every)?;
32342            }
32343        }
32344        Ok(())
32345    }
32346
32347    /// Generate Doris-style interval without quoting numbers: INTERVAL n UNIT
32348    fn generate_doris_interval(&mut self, expr: &Expression) -> Result<()> {
32349        if let Expression::Interval(interval) = expr {
32350            self.write_keyword("INTERVAL");
32351            if let Some(ref value) = interval.this {
32352                self.write_space();
32353                // If the value is a string literal that looks like a number,
32354                // output it without quotes (matching Python sqlglot's
32355                // partitionbyrangepropertydynamic_sql which converts back to number)
32356                match value {
32357                    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()) => {
32358                        if let Literal::String(s) = lit.as_ref() {
32359                            self.write(s);
32360                        }
32361                    }
32362                    _ => {
32363                        self.generate_expression(value)?;
32364                    }
32365                }
32366            }
32367            if let Some(ref unit_spec) = interval.unit {
32368                self.write_space();
32369                self.write_interval_unit_spec(unit_spec)?;
32370            }
32371            Ok(())
32372        } else {
32373            self.generate_expression(expr)
32374        }
32375    }
32376
32377    fn generate_partition_by_truncate(&mut self, e: &PartitionByTruncate) -> Result<()> {
32378        // TRUNCATE(expression, this)
32379        self.write_keyword("TRUNCATE");
32380        self.write("(");
32381        self.generate_expression(&e.expression)?;
32382        self.write(", ");
32383        self.generate_expression(&e.this)?;
32384        self.write(")");
32385        Ok(())
32386    }
32387
32388    fn generate_partition_list(&mut self, e: &PartitionList) -> Result<()> {
32389        // Doris: PARTITION name VALUES IN (val1, val2)
32390        self.write_keyword("PARTITION");
32391        self.write_space();
32392        self.generate_expression(&e.this)?;
32393        self.write_space();
32394        self.write_keyword("VALUES IN");
32395        self.write(" (");
32396        for (i, expr) in e.expressions.iter().enumerate() {
32397            if i > 0 {
32398                self.write(", ");
32399            }
32400            self.generate_expression(expr)?;
32401        }
32402        self.write(")");
32403        Ok(())
32404    }
32405
32406    fn generate_partition_range(&mut self, e: &PartitionRange) -> Result<()> {
32407        // Check if this is a TSQL-style simple range (e.g., "2 TO 5")
32408        // TSQL ranges have no expressions and just use `this TO expression`
32409        if e.expressions.is_empty() && e.expression.is_some() {
32410            // TSQL: simple range like "2 TO 5" - no PARTITION keyword
32411            self.generate_expression(&e.this)?;
32412            self.write_space();
32413            self.write_keyword("TO");
32414            self.write_space();
32415            self.generate_expression(e.expression.as_ref().unwrap())?;
32416            return Ok(());
32417        }
32418
32419        // Doris: PARTITION name VALUES LESS THAN (val) or PARTITION name VALUES [(val1), (val2))
32420        self.write_keyword("PARTITION");
32421        self.write_space();
32422        self.generate_expression(&e.this)?;
32423        self.write_space();
32424
32425        // Check if expressions contain Tuple (bracket notation) or single values (LESS THAN)
32426        if e.expressions.len() == 1 {
32427            // Single value: VALUES LESS THAN (val)
32428            self.write_keyword("VALUES LESS THAN");
32429            self.write(" (");
32430            self.generate_expression(&e.expressions[0])?;
32431            self.write(")");
32432        } else if !e.expressions.is_empty() {
32433            // Multiple values with Tuple: VALUES [(val1), (val2))
32434            self.write_keyword("VALUES");
32435            self.write(" [");
32436            for (i, expr) in e.expressions.iter().enumerate() {
32437                if i > 0 {
32438                    self.write(", ");
32439                }
32440                // If the expr is a Tuple, generate its contents wrapped in parens
32441                if let Expression::Tuple(t) = expr {
32442                    self.write("(");
32443                    for (j, inner) in t.expressions.iter().enumerate() {
32444                        if j > 0 {
32445                            self.write(", ");
32446                        }
32447                        self.generate_expression(inner)?;
32448                    }
32449                    self.write(")");
32450                } else {
32451                    self.write("(");
32452                    self.generate_expression(expr)?;
32453                    self.write(")");
32454                }
32455            }
32456            self.write(")");
32457        }
32458        Ok(())
32459    }
32460
32461    fn generate_partitioned_by_bucket(&mut self, e: &PartitionedByBucket) -> Result<()> {
32462        // BUCKET(this, expression)
32463        self.write_keyword("BUCKET");
32464        self.write("(");
32465        self.generate_expression(&e.this)?;
32466        self.write(", ");
32467        self.generate_expression(&e.expression)?;
32468        self.write(")");
32469        Ok(())
32470    }
32471
32472    fn generate_partition_by_property(&mut self, e: &PartitionByProperty) -> Result<()> {
32473        // BigQuery table property: PARTITION BY expression [, expression ...]
32474        self.write_keyword("PARTITION BY");
32475        self.write_space();
32476        for (i, expr) in e.expressions.iter().enumerate() {
32477            if i > 0 {
32478                self.write(", ");
32479            }
32480            self.generate_expression(expr)?;
32481        }
32482        Ok(())
32483    }
32484
32485    fn generate_partitioned_by_property(&mut self, e: &PartitionedByProperty) -> Result<()> {
32486        // PARTITIONED BY this (Teradata/ClickHouse use PARTITION BY)
32487        if matches!(
32488            self.config.dialect,
32489            Some(crate::dialects::DialectType::Teradata)
32490                | Some(crate::dialects::DialectType::ClickHouse)
32491        ) {
32492            self.write_keyword("PARTITION BY");
32493        } else {
32494            self.write_keyword("PARTITIONED BY");
32495        }
32496        self.write_space();
32497        // In pretty mode, always use multiline tuple format for PARTITIONED BY
32498        if self.config.pretty {
32499            if let Expression::Tuple(ref tuple) = *e.this {
32500                self.write("(");
32501                self.write_newline();
32502                self.indent_level += 1;
32503                for (i, expr) in tuple.expressions.iter().enumerate() {
32504                    if i > 0 {
32505                        self.write(",");
32506                        self.write_newline();
32507                    }
32508                    self.write_indent();
32509                    self.generate_expression(expr)?;
32510                }
32511                self.indent_level -= 1;
32512                self.write_newline();
32513                self.write(")");
32514            } else {
32515                self.generate_expression(&e.this)?;
32516            }
32517        } else {
32518            self.generate_expression(&e.this)?;
32519        }
32520        Ok(())
32521    }
32522
32523    fn generate_partitioned_of_property(&mut self, e: &PartitionedOfProperty) -> Result<()> {
32524        // PARTITION OF this FOR VALUES expression or PARTITION OF this DEFAULT
32525        self.write_keyword("PARTITION OF");
32526        self.write_space();
32527        self.generate_expression(&e.this)?;
32528        // Check if expression is a PartitionBoundSpec
32529        if let Expression::PartitionBoundSpec(_) = e.expression.as_ref() {
32530            self.write_space();
32531            self.write_keyword("FOR VALUES");
32532            self.write_space();
32533            self.generate_expression(&e.expression)?;
32534        } else {
32535            self.write_space();
32536            self.write_keyword("DEFAULT");
32537        }
32538        Ok(())
32539    }
32540
32541    fn generate_period_for_system_time_constraint(
32542        &mut self,
32543        e: &PeriodForSystemTimeConstraint,
32544    ) -> Result<()> {
32545        // PERIOD FOR SYSTEM_TIME (this, expression)
32546        self.write_keyword("PERIOD FOR SYSTEM_TIME");
32547        self.write(" (");
32548        self.generate_expression(&e.this)?;
32549        self.write(", ");
32550        self.generate_expression(&e.expression)?;
32551        self.write(")");
32552        Ok(())
32553    }
32554
32555    fn generate_pivot_alias(&mut self, e: &PivotAlias) -> Result<()> {
32556        // value AS alias
32557        // The alias can be an identifier or an expression (e.g., string concatenation)
32558        self.generate_expression(&e.this)?;
32559        self.write_space();
32560        self.write_keyword("AS");
32561        self.write_space();
32562        // When target dialect uses identifiers for UNPIVOT aliases, convert literals to identifiers
32563        if self.config.unpivot_aliases_are_identifiers {
32564            match &e.alias {
32565                Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
32566                    let Literal::String(s) = lit.as_ref() else {
32567                        unreachable!()
32568                    };
32569                    // Convert string literal to identifier
32570                    self.generate_identifier(&Identifier::new(s.clone()))?;
32571                }
32572                Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(_)) => {
32573                    let Literal::Number(n) = lit.as_ref() else {
32574                        unreachable!()
32575                    };
32576                    // Convert number literal to quoted identifier
32577                    let mut id = Identifier::new(n.clone());
32578                    id.quoted = true;
32579                    self.generate_identifier(&id)?;
32580                }
32581                other => {
32582                    self.generate_expression(other)?;
32583                }
32584            }
32585        } else {
32586            self.generate_expression(&e.alias)?;
32587        }
32588        Ok(())
32589    }
32590
32591    fn generate_pivot_any(&mut self, e: &PivotAny) -> Result<()> {
32592        // ANY or ANY [expression]
32593        self.write_keyword("ANY");
32594        if let Some(this) = &e.this {
32595            self.write_space();
32596            self.generate_expression(this)?;
32597        }
32598        Ok(())
32599    }
32600
32601    fn generate_predict(&mut self, e: &Predict) -> Result<()> {
32602        // ML.PREDICT(MODEL this, expression, [params_struct])
32603        self.write_keyword("ML.PREDICT");
32604        self.write("(");
32605        self.write_keyword("MODEL");
32606        self.write_space();
32607        self.generate_expression(&e.this)?;
32608        self.write(", ");
32609        self.generate_expression(&e.expression)?;
32610        if let Some(params) = &e.params_struct {
32611            self.write(", ");
32612            self.generate_expression(params)?;
32613        }
32614        self.write(")");
32615        Ok(())
32616    }
32617
32618    fn generate_previous_day(&mut self, e: &PreviousDay) -> Result<()> {
32619        // PREVIOUS_DAY(this, expression)
32620        self.write_keyword("PREVIOUS_DAY");
32621        self.write("(");
32622        self.generate_expression(&e.this)?;
32623        self.write(", ");
32624        self.generate_expression(&e.expression)?;
32625        self.write(")");
32626        Ok(())
32627    }
32628
32629    fn generate_primary_key(&mut self, e: &PrimaryKey) -> Result<()> {
32630        // PRIMARY KEY [name] (columns) [INCLUDE (...)] [options]
32631        self.write_keyword("PRIMARY KEY");
32632        if let Some(name) = &e.this {
32633            self.write_space();
32634            self.generate_expression(name)?;
32635        }
32636        if !e.expressions.is_empty() {
32637            self.write(" (");
32638            for (i, expr) in e.expressions.iter().enumerate() {
32639                if i > 0 {
32640                    self.write(", ");
32641                }
32642                self.generate_expression(expr)?;
32643            }
32644            self.write(")");
32645        }
32646        if let Some(include) = &e.include {
32647            self.write_space();
32648            self.generate_expression(include)?;
32649        }
32650        if !e.options.is_empty() {
32651            self.write_space();
32652            for (i, opt) in e.options.iter().enumerate() {
32653                if i > 0 {
32654                    self.write_space();
32655                }
32656                self.generate_expression(opt)?;
32657            }
32658        }
32659        Ok(())
32660    }
32661
32662    fn generate_primary_key_column_constraint(
32663        &mut self,
32664        _e: &PrimaryKeyColumnConstraint,
32665    ) -> Result<()> {
32666        // PRIMARY KEY constraint at column level
32667        self.write_keyword("PRIMARY KEY");
32668        Ok(())
32669    }
32670
32671    fn generate_path_column_constraint(&mut self, e: &PathColumnConstraint) -> Result<()> {
32672        // PATH 'xpath' constraint for XMLTABLE/JSON_TABLE columns
32673        self.write_keyword("PATH");
32674        self.write_space();
32675        self.generate_expression(&e.this)?;
32676        Ok(())
32677    }
32678
32679    fn generate_projection_def(&mut self, e: &ProjectionDef) -> Result<()> {
32680        // PROJECTION this (expression)
32681        self.write_keyword("PROJECTION");
32682        self.write_space();
32683        self.generate_expression(&e.this)?;
32684        self.write(" (");
32685        self.generate_expression(&e.expression)?;
32686        self.write(")");
32687        Ok(())
32688    }
32689
32690    fn generate_properties(&mut self, e: &Properties) -> Result<()> {
32691        // Properties list
32692        for (i, prop) in e.expressions.iter().enumerate() {
32693            if i > 0 {
32694                self.write(", ");
32695            }
32696            self.generate_expression(prop)?;
32697        }
32698        Ok(())
32699    }
32700
32701    fn generate_property(&mut self, e: &Property) -> Result<()> {
32702        // name=value
32703        self.generate_expression(&e.this)?;
32704        if let Some(value) = &e.value {
32705            self.write("=");
32706            self.generate_expression(value)?;
32707        }
32708        Ok(())
32709    }
32710
32711    fn generate_options_property(&mut self, e: &OptionsProperty) -> Result<()> {
32712        self.write_keyword("OPTIONS");
32713        if e.entries.is_empty() {
32714            self.write(" ()");
32715            return Ok(());
32716        }
32717
32718        if self.config.pretty {
32719            self.write(" (");
32720            self.write_newline();
32721            self.indent_level += 1;
32722            for (i, entry) in e.entries.iter().enumerate() {
32723                if i > 0 {
32724                    self.write(",");
32725                    self.write_newline();
32726                }
32727                self.write_indent();
32728                self.generate_identifier(&entry.key)?;
32729                self.write("=");
32730                self.generate_expression(&entry.value)?;
32731            }
32732            self.indent_level -= 1;
32733            self.write_newline();
32734            self.write(")");
32735        } else {
32736            self.write(" (");
32737            for (i, entry) in e.entries.iter().enumerate() {
32738                if i > 0 {
32739                    self.write(", ");
32740                }
32741                self.generate_identifier(&entry.key)?;
32742                self.write("=");
32743                self.generate_expression(&entry.value)?;
32744            }
32745            self.write(")");
32746        }
32747        Ok(())
32748    }
32749
32750    /// Generate BigQuery-style OPTIONS clause: OPTIONS (key=value, key=value, ...)
32751    fn generate_options_clause(&mut self, options: &[Expression]) -> Result<()> {
32752        self.write_keyword("OPTIONS");
32753        self.write(" (");
32754        for (i, opt) in options.iter().enumerate() {
32755            if i > 0 {
32756                self.write(", ");
32757            }
32758            self.generate_option_expression(opt)?;
32759        }
32760        self.write(")");
32761        Ok(())
32762    }
32763
32764    /// Generate Doris/StarRocks-style PROPERTIES clause: PROPERTIES ('key'='value', 'key'='value', ...)
32765    fn generate_properties_clause(&mut self, properties: &[Expression]) -> Result<()> {
32766        self.write_keyword("PROPERTIES");
32767        self.write(" (");
32768        for (i, prop) in properties.iter().enumerate() {
32769            if i > 0 {
32770                self.write(", ");
32771            }
32772            self.generate_option_expression(prop)?;
32773        }
32774        self.write(")");
32775        Ok(())
32776    }
32777
32778    /// Generate Databricks-style ENVIRONMENT clause: ENVIRONMENT (key = 'value', key = 'value', ...)
32779    fn generate_environment_clause(&mut self, environment: &[Expression]) -> Result<()> {
32780        self.write_keyword("ENVIRONMENT");
32781        self.write(" (");
32782        for (i, env_item) in environment.iter().enumerate() {
32783            if i > 0 {
32784                self.write(", ");
32785            }
32786            self.generate_environment_expression(env_item)?;
32787        }
32788        self.write(")");
32789        Ok(())
32790    }
32791
32792    /// Generate an environment expression with spaces around =
32793    fn generate_environment_expression(&mut self, expr: &Expression) -> Result<()> {
32794        match expr {
32795            Expression::Eq(eq) => {
32796                // Generate key = value with spaces (Databricks ENVIRONMENT style)
32797                self.generate_expression(&eq.left)?;
32798                self.write(" = ");
32799                self.generate_expression(&eq.right)?;
32800                Ok(())
32801            }
32802            _ => self.generate_expression(expr),
32803        }
32804    }
32805
32806    /// Generate Hive-style TBLPROPERTIES clause: TBLPROPERTIES ('key'='value', ...)
32807    fn generate_tblproperties_clause(&mut self, options: &[Expression]) -> Result<()> {
32808        self.write_keyword("TBLPROPERTIES");
32809        if self.config.pretty {
32810            self.write(" (");
32811            self.write_newline();
32812            self.indent_level += 1;
32813            for (i, opt) in options.iter().enumerate() {
32814                if i > 0 {
32815                    self.write(",");
32816                    self.write_newline();
32817                }
32818                self.write_indent();
32819                self.generate_option_expression(opt)?;
32820            }
32821            self.indent_level -= 1;
32822            self.write_newline();
32823            self.write(")");
32824        } else {
32825            self.write(" (");
32826            for (i, opt) in options.iter().enumerate() {
32827                if i > 0 {
32828                    self.write(", ");
32829                }
32830                self.generate_option_expression(opt)?;
32831            }
32832            self.write(")");
32833        }
32834        Ok(())
32835    }
32836
32837    /// Generate an option expression without spaces around =
32838    fn generate_option_expression(&mut self, expr: &Expression) -> Result<()> {
32839        match expr {
32840            Expression::Eq(eq) => {
32841                // Generate key=value without spaces
32842                self.generate_expression(&eq.left)?;
32843                self.write("=");
32844                self.generate_expression(&eq.right)?;
32845                Ok(())
32846            }
32847            _ => self.generate_expression(expr),
32848        }
32849    }
32850
32851    fn generate_pseudo_type(&mut self, e: &PseudoType) -> Result<()> {
32852        // Just output the name
32853        self.generate_expression(&e.this)?;
32854        Ok(())
32855    }
32856
32857    fn generate_put(&mut self, e: &PutStmt) -> Result<()> {
32858        // PUT source_file @stage [options]
32859        self.write_keyword("PUT");
32860        self.write_space();
32861
32862        // Source file path - preserve original quoting
32863        if e.source_quoted {
32864            self.write("'");
32865            self.write(&e.source);
32866            self.write("'");
32867        } else {
32868            self.write(&e.source);
32869        }
32870
32871        self.write_space();
32872
32873        // Target stage reference - output the string directly (includes @)
32874        if let Expression::Literal(lit) = &e.target {
32875            if let Literal::String(s) = lit.as_ref() {
32876                self.write(s);
32877            }
32878        } else {
32879            self.generate_expression(&e.target)?;
32880        }
32881
32882        // Optional parameters: KEY=VALUE
32883        for param in &e.params {
32884            self.write_space();
32885            self.write(&param.name);
32886            if let Some(ref value) = param.value {
32887                self.write("=");
32888                self.generate_expression(value)?;
32889            }
32890        }
32891
32892        Ok(())
32893    }
32894
32895    fn generate_quantile(&mut self, e: &Quantile) -> Result<()> {
32896        // QUANTILE(this, quantile)
32897        self.write_keyword("QUANTILE");
32898        self.write("(");
32899        self.generate_expression(&e.this)?;
32900        if let Some(quantile) = &e.quantile {
32901            self.write(", ");
32902            self.generate_expression(quantile)?;
32903        }
32904        self.write(")");
32905        Ok(())
32906    }
32907
32908    fn generate_query_band(&mut self, e: &QueryBand) -> Result<()> {
32909        // QUERY_BAND = this [UPDATE] [FOR scope]
32910        if matches!(
32911            self.config.dialect,
32912            Some(crate::dialects::DialectType::Teradata)
32913        ) {
32914            self.write_keyword("SET");
32915            self.write_space();
32916        }
32917        self.write_keyword("QUERY_BAND");
32918        self.write(" = ");
32919        self.generate_expression(&e.this)?;
32920        if e.update.is_some() {
32921            self.write_space();
32922            self.write_keyword("UPDATE");
32923        }
32924        if let Some(scope) = &e.scope {
32925            self.write_space();
32926            self.write_keyword("FOR");
32927            self.write_space();
32928            self.generate_expression(scope)?;
32929        }
32930        Ok(())
32931    }
32932
32933    fn generate_query_option(&mut self, e: &QueryOption) -> Result<()> {
32934        // this = expression
32935        self.generate_expression(&e.this)?;
32936        if let Some(expression) = &e.expression {
32937            self.write(" = ");
32938            self.generate_expression(expression)?;
32939        }
32940        Ok(())
32941    }
32942
32943    fn generate_query_transform(&mut self, e: &QueryTransform) -> Result<()> {
32944        // TRANSFORM (expressions) [row_format_before] [RECORDWRITER record_writer] USING command_script [AS schema] [row_format_after] [RECORDREADER record_reader]
32945        self.write_keyword("TRANSFORM");
32946        self.write("(");
32947        for (i, expr) in e.expressions.iter().enumerate() {
32948            if i > 0 {
32949                self.write(", ");
32950            }
32951            self.generate_expression(expr)?;
32952        }
32953        self.write(")");
32954        if let Some(row_format_before) = &e.row_format_before {
32955            self.write_space();
32956            self.generate_expression(row_format_before)?;
32957        }
32958        if let Some(record_writer) = &e.record_writer {
32959            self.write_space();
32960            self.write_keyword("RECORDWRITER");
32961            self.write_space();
32962            self.generate_expression(record_writer)?;
32963        }
32964        if let Some(command_script) = &e.command_script {
32965            self.write_space();
32966            self.write_keyword("USING");
32967            self.write_space();
32968            self.generate_expression(command_script)?;
32969        }
32970        if let Some(schema) = &e.schema {
32971            self.write_space();
32972            self.write_keyword("AS");
32973            self.write_space();
32974            self.generate_expression(schema)?;
32975        }
32976        if let Some(row_format_after) = &e.row_format_after {
32977            self.write_space();
32978            self.generate_expression(row_format_after)?;
32979        }
32980        if let Some(record_reader) = &e.record_reader {
32981            self.write_space();
32982            self.write_keyword("RECORDREADER");
32983            self.write_space();
32984            self.generate_expression(record_reader)?;
32985        }
32986        Ok(())
32987    }
32988
32989    fn generate_randn(&mut self, e: &Randn) -> Result<()> {
32990        // RANDN([seed])
32991        self.write_keyword("RANDN");
32992        self.write("(");
32993        if let Some(this) = &e.this {
32994            self.generate_expression(this)?;
32995        }
32996        self.write(")");
32997        Ok(())
32998    }
32999
33000    fn generate_randstr(&mut self, e: &Randstr) -> Result<()> {
33001        // RANDSTR(this, [generator])
33002        self.write_keyword("RANDSTR");
33003        self.write("(");
33004        self.generate_expression(&e.this)?;
33005        if let Some(generator) = &e.generator {
33006            self.write(", ");
33007            self.generate_expression(generator)?;
33008        }
33009        self.write(")");
33010        Ok(())
33011    }
33012
33013    fn generate_range_bucket(&mut self, e: &RangeBucket) -> Result<()> {
33014        // RANGE_BUCKET(this, expression)
33015        self.write_keyword("RANGE_BUCKET");
33016        self.write("(");
33017        self.generate_expression(&e.this)?;
33018        self.write(", ");
33019        self.generate_expression(&e.expression)?;
33020        self.write(")");
33021        Ok(())
33022    }
33023
33024    fn generate_range_n(&mut self, e: &RangeN) -> Result<()> {
33025        // RANGE_N(this BETWEEN expressions [EACH each])
33026        self.write_keyword("RANGE_N");
33027        self.write("(");
33028        self.generate_expression(&e.this)?;
33029        self.write_space();
33030        self.write_keyword("BETWEEN");
33031        self.write_space();
33032        for (i, expr) in e.expressions.iter().enumerate() {
33033            if i > 0 {
33034                self.write(", ");
33035            }
33036            self.generate_expression(expr)?;
33037        }
33038        if let Some(each) = &e.each {
33039            self.write_space();
33040            self.write_keyword("EACH");
33041            self.write_space();
33042            self.generate_expression(each)?;
33043        }
33044        self.write(")");
33045        Ok(())
33046    }
33047
33048    fn generate_read_csv(&mut self, e: &ReadCSV) -> Result<()> {
33049        // READ_CSV(this, expressions...)
33050        self.write_keyword("READ_CSV");
33051        self.write("(");
33052        self.generate_expression(&e.this)?;
33053        for expr in &e.expressions {
33054            self.write(", ");
33055            self.generate_expression(expr)?;
33056        }
33057        self.write(")");
33058        Ok(())
33059    }
33060
33061    fn generate_read_parquet(&mut self, e: &ReadParquet) -> Result<()> {
33062        // READ_PARQUET(expressions...)
33063        self.write_keyword("READ_PARQUET");
33064        self.write("(");
33065        for (i, expr) in e.expressions.iter().enumerate() {
33066            if i > 0 {
33067                self.write(", ");
33068            }
33069            self.generate_expression(expr)?;
33070        }
33071        self.write(")");
33072        Ok(())
33073    }
33074
33075    fn generate_recursive_with_search(&mut self, e: &RecursiveWithSearch) -> Result<()> {
33076        // SEARCH kind FIRST BY this SET expression [USING using]
33077        // or CYCLE this SET expression [USING using]
33078        if e.kind == "CYCLE" {
33079            self.write_keyword("CYCLE");
33080        } else {
33081            self.write_keyword("SEARCH");
33082            self.write_space();
33083            self.write(&e.kind);
33084            self.write_space();
33085            self.write_keyword("FIRST BY");
33086        }
33087        self.write_space();
33088        self.generate_expression(&e.this)?;
33089        self.write_space();
33090        self.write_keyword("SET");
33091        self.write_space();
33092        self.generate_expression(&e.expression)?;
33093        if let Some(using) = &e.using {
33094            self.write_space();
33095            self.write_keyword("USING");
33096            self.write_space();
33097            self.generate_expression(using)?;
33098        }
33099        Ok(())
33100    }
33101
33102    fn generate_reduce(&mut self, e: &Reduce) -> Result<()> {
33103        // REDUCE(this, initial, merge, [finish])
33104        self.write_keyword("REDUCE");
33105        self.write("(");
33106        self.generate_expression(&e.this)?;
33107        if let Some(initial) = &e.initial {
33108            self.write(", ");
33109            self.generate_expression(initial)?;
33110        }
33111        if let Some(merge) = &e.merge {
33112            self.write(", ");
33113            self.generate_expression(merge)?;
33114        }
33115        if let Some(finish) = &e.finish {
33116            self.write(", ");
33117            self.generate_expression(finish)?;
33118        }
33119        self.write(")");
33120        Ok(())
33121    }
33122
33123    fn generate_reference(&mut self, e: &Reference) -> Result<()> {
33124        // REFERENCES this (expressions) [options]
33125        self.write_keyword("REFERENCES");
33126        self.write_space();
33127        self.generate_expression(&e.this)?;
33128        if !e.expressions.is_empty() {
33129            self.write(" (");
33130            for (i, expr) in e.expressions.iter().enumerate() {
33131                if i > 0 {
33132                    self.write(", ");
33133                }
33134                self.generate_expression(expr)?;
33135            }
33136            self.write(")");
33137        }
33138        for opt in &e.options {
33139            self.write_space();
33140            self.generate_expression(opt)?;
33141        }
33142        Ok(())
33143    }
33144
33145    fn generate_refresh(&mut self, e: &Refresh) -> Result<()> {
33146        // REFRESH [kind] this
33147        self.write_keyword("REFRESH");
33148        if !e.kind.is_empty() {
33149            self.write_space();
33150            self.write_keyword(&e.kind);
33151        }
33152        self.write_space();
33153        self.generate_expression(&e.this)?;
33154        Ok(())
33155    }
33156
33157    fn generate_refresh_trigger_property(&mut self, e: &RefreshTriggerProperty) -> Result<()> {
33158        // Doris REFRESH clause: REFRESH method ON kind [EVERY n UNIT] [STARTS 'datetime']
33159        self.write_keyword("REFRESH");
33160        self.write_space();
33161        self.write_keyword(&e.method);
33162
33163        if let Some(ref kind) = e.kind {
33164            self.write_space();
33165            self.write_keyword("ON");
33166            self.write_space();
33167            self.write_keyword(kind);
33168
33169            // EVERY n UNIT
33170            if let Some(ref every) = e.every {
33171                self.write_space();
33172                self.write_keyword("EVERY");
33173                self.write_space();
33174                self.generate_expression(every)?;
33175                if let Some(ref unit) = e.unit {
33176                    self.write_space();
33177                    self.write_keyword(unit);
33178                }
33179            }
33180
33181            // STARTS 'datetime'
33182            if let Some(ref starts) = e.starts {
33183                self.write_space();
33184                self.write_keyword("STARTS");
33185                self.write_space();
33186                self.generate_expression(starts)?;
33187            }
33188        }
33189        Ok(())
33190    }
33191
33192    fn generate_regexp_count(&mut self, e: &RegexpCount) -> Result<()> {
33193        // REGEXP_COUNT(this, expression, position, parameters)
33194        self.write_keyword("REGEXP_COUNT");
33195        self.write("(");
33196        self.generate_expression(&e.this)?;
33197        self.write(", ");
33198        self.generate_expression(&e.expression)?;
33199        if let Some(position) = &e.position {
33200            self.write(", ");
33201            self.generate_expression(position)?;
33202        }
33203        if let Some(parameters) = &e.parameters {
33204            self.write(", ");
33205            self.generate_expression(parameters)?;
33206        }
33207        self.write(")");
33208        Ok(())
33209    }
33210
33211    fn generate_regexp_extract_all(&mut self, e: &RegexpExtractAll) -> Result<()> {
33212        // REGEXP_EXTRACT_ALL(this, expression, group, parameters, position, occurrence)
33213        self.write_keyword("REGEXP_EXTRACT_ALL");
33214        self.write("(");
33215        self.generate_expression(&e.this)?;
33216        self.write(", ");
33217        self.generate_expression(&e.expression)?;
33218        if let Some(group) = &e.group {
33219            self.write(", ");
33220            self.generate_expression(group)?;
33221        }
33222        self.write(")");
33223        Ok(())
33224    }
33225
33226    fn generate_regexp_full_match(&mut self, e: &RegexpFullMatch) -> Result<()> {
33227        // REGEXP_FULL_MATCH(this, expression)
33228        self.write_keyword("REGEXP_FULL_MATCH");
33229        self.write("(");
33230        self.generate_expression(&e.this)?;
33231        self.write(", ");
33232        self.generate_expression(&e.expression)?;
33233        self.write(")");
33234        Ok(())
33235    }
33236
33237    fn generate_regexp_i_like(&mut self, e: &RegexpILike) -> Result<()> {
33238        use crate::dialects::DialectType;
33239        // PostgreSQL/Redshift uses ~* operator for case-insensitive regex matching
33240        if matches!(
33241            self.config.dialect,
33242            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
33243        ) && e.flag.is_none()
33244        {
33245            self.generate_expression(&e.this)?;
33246            self.write(" ~* ");
33247            self.generate_expression(&e.expression)?;
33248        } else if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
33249            // Snowflake uses REGEXP_LIKE(x, pattern, 'i')
33250            self.write_keyword("REGEXP_LIKE");
33251            self.write("(");
33252            self.generate_expression(&e.this)?;
33253            self.write(", ");
33254            self.generate_expression(&e.expression)?;
33255            self.write(", ");
33256            if let Some(flag) = &e.flag {
33257                self.generate_expression(flag)?;
33258            } else {
33259                self.write("'i'");
33260            }
33261            self.write(")");
33262        } else {
33263            // this REGEXP_ILIKE expression or REGEXP_ILIKE(this, expression, flag)
33264            self.generate_expression(&e.this)?;
33265            self.write_space();
33266            self.write_keyword("REGEXP_ILIKE");
33267            self.write_space();
33268            self.generate_expression(&e.expression)?;
33269            if let Some(flag) = &e.flag {
33270                self.write(", ");
33271                self.generate_expression(flag)?;
33272            }
33273        }
33274        Ok(())
33275    }
33276
33277    fn generate_regexp_instr(&mut self, e: &RegexpInstr) -> Result<()> {
33278        // REGEXP_INSTR(this, expression, position, occurrence, option, parameters, group)
33279        self.write_keyword("REGEXP_INSTR");
33280        self.write("(");
33281        self.generate_expression(&e.this)?;
33282        self.write(", ");
33283        self.generate_expression(&e.expression)?;
33284        if let Some(position) = &e.position {
33285            self.write(", ");
33286            self.generate_expression(position)?;
33287        }
33288        if let Some(occurrence) = &e.occurrence {
33289            self.write(", ");
33290            self.generate_expression(occurrence)?;
33291        }
33292        if let Some(option) = &e.option {
33293            self.write(", ");
33294            self.generate_expression(option)?;
33295        }
33296        if let Some(parameters) = &e.parameters {
33297            self.write(", ");
33298            self.generate_expression(parameters)?;
33299        }
33300        if let Some(group) = &e.group {
33301            self.write(", ");
33302            self.generate_expression(group)?;
33303        }
33304        self.write(")");
33305        Ok(())
33306    }
33307
33308    fn generate_regexp_split(&mut self, e: &RegexpSplit) -> Result<()> {
33309        // REGEXP_SPLIT(this, expression, limit)
33310        self.write_keyword("REGEXP_SPLIT");
33311        self.write("(");
33312        self.generate_expression(&e.this)?;
33313        self.write(", ");
33314        self.generate_expression(&e.expression)?;
33315        if let Some(limit) = &e.limit {
33316            self.write(", ");
33317            self.generate_expression(limit)?;
33318        }
33319        self.write(")");
33320        Ok(())
33321    }
33322
33323    fn generate_regr_avgx(&mut self, e: &RegrAvgx) -> Result<()> {
33324        // REGR_AVGX(this, expression)
33325        self.write_keyword("REGR_AVGX");
33326        self.write("(");
33327        self.generate_expression(&e.this)?;
33328        self.write(", ");
33329        self.generate_expression(&e.expression)?;
33330        self.write(")");
33331        Ok(())
33332    }
33333
33334    fn generate_regr_avgy(&mut self, e: &RegrAvgy) -> Result<()> {
33335        // REGR_AVGY(this, expression)
33336        self.write_keyword("REGR_AVGY");
33337        self.write("(");
33338        self.generate_expression(&e.this)?;
33339        self.write(", ");
33340        self.generate_expression(&e.expression)?;
33341        self.write(")");
33342        Ok(())
33343    }
33344
33345    fn generate_regr_count(&mut self, e: &RegrCount) -> Result<()> {
33346        // REGR_COUNT(this, expression)
33347        self.write_keyword("REGR_COUNT");
33348        self.write("(");
33349        self.generate_expression(&e.this)?;
33350        self.write(", ");
33351        self.generate_expression(&e.expression)?;
33352        self.write(")");
33353        Ok(())
33354    }
33355
33356    fn generate_regr_intercept(&mut self, e: &RegrIntercept) -> Result<()> {
33357        // REGR_INTERCEPT(this, expression)
33358        self.write_keyword("REGR_INTERCEPT");
33359        self.write("(");
33360        self.generate_expression(&e.this)?;
33361        self.write(", ");
33362        self.generate_expression(&e.expression)?;
33363        self.write(")");
33364        Ok(())
33365    }
33366
33367    fn generate_regr_r2(&mut self, e: &RegrR2) -> Result<()> {
33368        // REGR_R2(this, expression)
33369        self.write_keyword("REGR_R2");
33370        self.write("(");
33371        self.generate_expression(&e.this)?;
33372        self.write(", ");
33373        self.generate_expression(&e.expression)?;
33374        self.write(")");
33375        Ok(())
33376    }
33377
33378    fn generate_regr_slope(&mut self, e: &RegrSlope) -> Result<()> {
33379        // REGR_SLOPE(this, expression)
33380        self.write_keyword("REGR_SLOPE");
33381        self.write("(");
33382        self.generate_expression(&e.this)?;
33383        self.write(", ");
33384        self.generate_expression(&e.expression)?;
33385        self.write(")");
33386        Ok(())
33387    }
33388
33389    fn generate_regr_sxx(&mut self, e: &RegrSxx) -> Result<()> {
33390        // REGR_SXX(this, expression)
33391        self.write_keyword("REGR_SXX");
33392        self.write("(");
33393        self.generate_expression(&e.this)?;
33394        self.write(", ");
33395        self.generate_expression(&e.expression)?;
33396        self.write(")");
33397        Ok(())
33398    }
33399
33400    fn generate_regr_sxy(&mut self, e: &RegrSxy) -> Result<()> {
33401        // REGR_SXY(this, expression)
33402        self.write_keyword("REGR_SXY");
33403        self.write("(");
33404        self.generate_expression(&e.this)?;
33405        self.write(", ");
33406        self.generate_expression(&e.expression)?;
33407        self.write(")");
33408        Ok(())
33409    }
33410
33411    fn generate_regr_syy(&mut self, e: &RegrSyy) -> Result<()> {
33412        // REGR_SYY(this, expression)
33413        self.write_keyword("REGR_SYY");
33414        self.write("(");
33415        self.generate_expression(&e.this)?;
33416        self.write(", ");
33417        self.generate_expression(&e.expression)?;
33418        self.write(")");
33419        Ok(())
33420    }
33421
33422    fn generate_regr_valx(&mut self, e: &RegrValx) -> Result<()> {
33423        // REGR_VALX(this, expression)
33424        self.write_keyword("REGR_VALX");
33425        self.write("(");
33426        self.generate_expression(&e.this)?;
33427        self.write(", ");
33428        self.generate_expression(&e.expression)?;
33429        self.write(")");
33430        Ok(())
33431    }
33432
33433    fn generate_regr_valy(&mut self, e: &RegrValy) -> Result<()> {
33434        // REGR_VALY(this, expression)
33435        self.write_keyword("REGR_VALY");
33436        self.write("(");
33437        self.generate_expression(&e.this)?;
33438        self.write(", ");
33439        self.generate_expression(&e.expression)?;
33440        self.write(")");
33441        Ok(())
33442    }
33443
33444    fn generate_remote_with_connection_model_property(
33445        &mut self,
33446        e: &RemoteWithConnectionModelProperty,
33447    ) -> Result<()> {
33448        // REMOTE WITH CONNECTION this
33449        self.write_keyword("REMOTE WITH CONNECTION");
33450        self.write_space();
33451        self.generate_expression(&e.this)?;
33452        Ok(())
33453    }
33454
33455    fn generate_rename_column(&mut self, e: &RenameColumn) -> Result<()> {
33456        // RENAME COLUMN [IF EXISTS] this TO new_name
33457        self.write_keyword("RENAME COLUMN");
33458        if e.exists {
33459            self.write_space();
33460            self.write_keyword("IF EXISTS");
33461        }
33462        self.write_space();
33463        self.generate_expression(&e.this)?;
33464        if let Some(to) = &e.to {
33465            self.write_space();
33466            self.write_keyword("TO");
33467            self.write_space();
33468            self.generate_expression(to)?;
33469        }
33470        Ok(())
33471    }
33472
33473    fn generate_replace_partition(&mut self, e: &ReplacePartition) -> Result<()> {
33474        // REPLACE PARTITION expression [FROM source]
33475        self.write_keyword("REPLACE PARTITION");
33476        self.write_space();
33477        self.generate_expression(&e.expression)?;
33478        if let Some(source) = &e.source {
33479            self.write_space();
33480            self.write_keyword("FROM");
33481            self.write_space();
33482            self.generate_expression(source)?;
33483        }
33484        Ok(())
33485    }
33486
33487    fn generate_returning(&mut self, e: &Returning) -> Result<()> {
33488        // RETURNING expressions [INTO into]
33489        // TSQL and Fabric use OUTPUT instead of RETURNING
33490        let keyword = match self.config.dialect {
33491            Some(DialectType::TSQL) | Some(DialectType::Fabric) => "OUTPUT",
33492            _ => "RETURNING",
33493        };
33494        self.write_keyword(keyword);
33495        self.write_space();
33496        for (i, expr) in e.expressions.iter().enumerate() {
33497            if i > 0 {
33498                self.write(", ");
33499            }
33500            self.generate_expression(expr)?;
33501        }
33502        if let Some(into) = &e.into {
33503            self.write_space();
33504            self.write_keyword("INTO");
33505            self.write_space();
33506            self.generate_expression(into)?;
33507        }
33508        Ok(())
33509    }
33510
33511    fn generate_output_clause(&mut self, output: &OutputClause) -> Result<()> {
33512        // OUTPUT expressions [INTO into_table]
33513        self.write_space();
33514        self.write_keyword("OUTPUT");
33515        self.write_space();
33516        for (i, expr) in output.columns.iter().enumerate() {
33517            if i > 0 {
33518                self.write(", ");
33519            }
33520            self.generate_expression(expr)?;
33521        }
33522        if let Some(into_table) = &output.into_table {
33523            self.write_space();
33524            self.write_keyword("INTO");
33525            self.write_space();
33526            self.generate_expression(into_table)?;
33527        }
33528        Ok(())
33529    }
33530
33531    fn generate_returns_property(&mut self, e: &ReturnsProperty) -> Result<()> {
33532        // RETURNS [TABLE] this [NULL ON NULL INPUT | CALLED ON NULL INPUT]
33533        self.write_keyword("RETURNS");
33534        if e.is_table.is_some() {
33535            self.write_space();
33536            self.write_keyword("TABLE");
33537        }
33538        if let Some(table) = &e.table {
33539            self.write_space();
33540            self.generate_expression(table)?;
33541        } else if let Some(this) = &e.this {
33542            self.write_space();
33543            self.generate_expression(this)?;
33544        }
33545        if e.null.is_some() {
33546            self.write_space();
33547            self.write_keyword("NULL ON NULL INPUT");
33548        }
33549        Ok(())
33550    }
33551
33552    fn generate_rollback(&mut self, e: &Rollback) -> Result<()> {
33553        // ROLLBACK [TRANSACTION [transaction_name]] [TO savepoint]
33554        self.write_keyword("ROLLBACK");
33555
33556        // TSQL always uses ROLLBACK TRANSACTION
33557        if e.this.is_none()
33558            && matches!(
33559                self.config.dialect,
33560                Some(DialectType::TSQL) | Some(DialectType::Fabric)
33561            )
33562        {
33563            self.write_space();
33564            self.write_keyword("TRANSACTION");
33565        }
33566
33567        // Check if this has TRANSACTION keyword or transaction name
33568        if let Some(this) = &e.this {
33569            // Check if it's just the "TRANSACTION" marker or an actual transaction name
33570            let is_transaction_marker = matches!(
33571                this.as_ref(),
33572                Expression::Identifier(id) if id.name == "TRANSACTION"
33573            );
33574
33575            self.write_space();
33576            self.write_keyword("TRANSACTION");
33577
33578            // If it's a real transaction name, output it
33579            if !is_transaction_marker {
33580                self.write_space();
33581                self.generate_expression(this)?;
33582            }
33583        }
33584
33585        // Output TO savepoint
33586        if let Some(savepoint) = &e.savepoint {
33587            self.write_space();
33588            self.write_keyword("TO");
33589            self.write_space();
33590            self.generate_expression(savepoint)?;
33591        }
33592        Ok(())
33593    }
33594
33595    fn generate_rollup(&mut self, e: &Rollup) -> Result<()> {
33596        // Python: return f"ROLLUP {self.wrap(expressions)}" if expressions else "WITH ROLLUP"
33597        if e.expressions.is_empty() {
33598            self.write_keyword("WITH ROLLUP");
33599        } else {
33600            self.write_keyword("ROLLUP");
33601            self.write("(");
33602            for (i, expr) in e.expressions.iter().enumerate() {
33603                if i > 0 {
33604                    self.write(", ");
33605                }
33606                self.generate_expression(expr)?;
33607            }
33608            self.write(")");
33609        }
33610        Ok(())
33611    }
33612
33613    fn generate_row_format_delimited_property(
33614        &mut self,
33615        e: &RowFormatDelimitedProperty,
33616    ) -> Result<()> {
33617        // ROW FORMAT DELIMITED [FIELDS TERMINATED BY ...] [ESCAPED BY ...] [COLLECTION ITEMS TERMINATED BY ...] [MAP KEYS TERMINATED BY ...] [LINES TERMINATED BY ...] [NULL DEFINED AS ...]
33618        self.write_keyword("ROW FORMAT DELIMITED");
33619        if let Some(fields) = &e.fields {
33620            self.write_space();
33621            self.write_keyword("FIELDS TERMINATED BY");
33622            self.write_space();
33623            self.generate_expression(fields)?;
33624        }
33625        if let Some(escaped) = &e.escaped {
33626            self.write_space();
33627            self.write_keyword("ESCAPED BY");
33628            self.write_space();
33629            self.generate_expression(escaped)?;
33630        }
33631        if let Some(items) = &e.collection_items {
33632            self.write_space();
33633            self.write_keyword("COLLECTION ITEMS TERMINATED BY");
33634            self.write_space();
33635            self.generate_expression(items)?;
33636        }
33637        if let Some(keys) = &e.map_keys {
33638            self.write_space();
33639            self.write_keyword("MAP KEYS TERMINATED BY");
33640            self.write_space();
33641            self.generate_expression(keys)?;
33642        }
33643        if let Some(lines) = &e.lines {
33644            self.write_space();
33645            self.write_keyword("LINES TERMINATED BY");
33646            self.write_space();
33647            self.generate_expression(lines)?;
33648        }
33649        if let Some(null) = &e.null {
33650            self.write_space();
33651            self.write_keyword("NULL DEFINED AS");
33652            self.write_space();
33653            self.generate_expression(null)?;
33654        }
33655        if let Some(serde) = &e.serde {
33656            self.write_space();
33657            self.generate_expression(serde)?;
33658        }
33659        Ok(())
33660    }
33661
33662    fn generate_row_format_property(&mut self, e: &RowFormatProperty) -> Result<()> {
33663        // ROW FORMAT this
33664        self.write_keyword("ROW FORMAT");
33665        self.write_space();
33666        self.generate_expression(&e.this)?;
33667        Ok(())
33668    }
33669
33670    fn generate_row_format_serde_property(&mut self, e: &RowFormatSerdeProperty) -> Result<()> {
33671        // ROW FORMAT SERDE this [WITH SERDEPROPERTIES (...)]
33672        self.write_keyword("ROW FORMAT SERDE");
33673        self.write_space();
33674        self.generate_expression(&e.this)?;
33675        if let Some(props) = &e.serde_properties {
33676            self.write_space();
33677            // SerdeProperties generates its own "[WITH] SERDEPROPERTIES (...)"
33678            self.generate_expression(props)?;
33679        }
33680        Ok(())
33681    }
33682
33683    fn generate_sha2(&mut self, e: &SHA2) -> Result<()> {
33684        // SHA2(this, length)
33685        self.write_keyword("SHA2");
33686        self.write("(");
33687        self.generate_expression(&e.this)?;
33688        if let Some(length) = e.length {
33689            self.write(", ");
33690            self.write(&length.to_string());
33691        }
33692        self.write(")");
33693        Ok(())
33694    }
33695
33696    fn generate_sha2_digest(&mut self, e: &SHA2Digest) -> Result<()> {
33697        // SHA2_DIGEST(this, length)
33698        self.write_keyword("SHA2_DIGEST");
33699        self.write("(");
33700        self.generate_expression(&e.this)?;
33701        if let Some(length) = e.length {
33702            self.write(", ");
33703            self.write(&length.to_string());
33704        }
33705        self.write(")");
33706        Ok(())
33707    }
33708
33709    fn generate_safe_add(&mut self, e: &SafeAdd) -> Result<()> {
33710        let name = if matches!(
33711            self.config.dialect,
33712            Some(crate::dialects::DialectType::Spark)
33713                | Some(crate::dialects::DialectType::Databricks)
33714        ) {
33715            "TRY_ADD"
33716        } else {
33717            "SAFE_ADD"
33718        };
33719        self.write_keyword(name);
33720        self.write("(");
33721        self.generate_expression(&e.this)?;
33722        self.write(", ");
33723        self.generate_expression(&e.expression)?;
33724        self.write(")");
33725        Ok(())
33726    }
33727
33728    fn generate_safe_divide(&mut self, e: &SafeDivide) -> Result<()> {
33729        // SAFE_DIVIDE(this, expression)
33730        self.write_keyword("SAFE_DIVIDE");
33731        self.write("(");
33732        self.generate_expression(&e.this)?;
33733        self.write(", ");
33734        self.generate_expression(&e.expression)?;
33735        self.write(")");
33736        Ok(())
33737    }
33738
33739    fn generate_safe_multiply(&mut self, e: &SafeMultiply) -> Result<()> {
33740        let name = if matches!(
33741            self.config.dialect,
33742            Some(crate::dialects::DialectType::Spark)
33743                | Some(crate::dialects::DialectType::Databricks)
33744        ) {
33745            "TRY_MULTIPLY"
33746        } else {
33747            "SAFE_MULTIPLY"
33748        };
33749        self.write_keyword(name);
33750        self.write("(");
33751        self.generate_expression(&e.this)?;
33752        self.write(", ");
33753        self.generate_expression(&e.expression)?;
33754        self.write(")");
33755        Ok(())
33756    }
33757
33758    fn generate_safe_subtract(&mut self, e: &SafeSubtract) -> Result<()> {
33759        let name = if matches!(
33760            self.config.dialect,
33761            Some(crate::dialects::DialectType::Spark)
33762                | Some(crate::dialects::DialectType::Databricks)
33763        ) {
33764            "TRY_SUBTRACT"
33765        } else {
33766            "SAFE_SUBTRACT"
33767        };
33768        self.write_keyword(name);
33769        self.write("(");
33770        self.generate_expression(&e.this)?;
33771        self.write(", ");
33772        self.generate_expression(&e.expression)?;
33773        self.write(")");
33774        Ok(())
33775    }
33776
33777    /// Generate the body of a USING SAMPLE or TABLESAMPLE clause:
33778    /// METHOD (size UNIT) [REPEATABLE (seed)]
33779    fn generate_sample_body(&mut self, sample: &Sample) -> Result<()> {
33780        // Handle BUCKET sampling: TABLESAMPLE (BUCKET n OUT OF m [ON col])
33781        if matches!(sample.method, SampleMethod::Bucket) {
33782            self.write(" (");
33783            self.write_keyword("BUCKET");
33784            self.write_space();
33785            if let Some(ref num) = sample.bucket_numerator {
33786                self.generate_expression(num)?;
33787            }
33788            self.write_space();
33789            self.write_keyword("OUT OF");
33790            self.write_space();
33791            if let Some(ref denom) = sample.bucket_denominator {
33792                self.generate_expression(denom)?;
33793            }
33794            if let Some(ref field) = sample.bucket_field {
33795                self.write_space();
33796                self.write_keyword("ON");
33797                self.write_space();
33798                self.generate_expression(field)?;
33799            }
33800            self.write(")");
33801            return Ok(());
33802        }
33803
33804        // Output method name if explicitly specified, or for dialects that always require it
33805        let is_snowflake = matches!(
33806            self.config.dialect,
33807            Some(crate::dialects::DialectType::Snowflake)
33808        );
33809        let is_postgres = matches!(
33810            self.config.dialect,
33811            Some(crate::dialects::DialectType::PostgreSQL)
33812                | Some(crate::dialects::DialectType::Redshift)
33813        );
33814        // Databricks and Spark don't output method names
33815        let is_databricks = matches!(
33816            self.config.dialect,
33817            Some(crate::dialects::DialectType::Databricks)
33818        );
33819        let is_spark = matches!(
33820            self.config.dialect,
33821            Some(crate::dialects::DialectType::Spark)
33822        );
33823        let suppress_method = is_databricks || is_spark || sample.suppress_method_output;
33824        // PostgreSQL always outputs BERNOULLI for BERNOULLI samples
33825        let force_method = is_postgres && matches!(sample.method, SampleMethod::Bernoulli);
33826        if !suppress_method && (sample.explicit_method || is_snowflake || force_method) {
33827            self.write_space();
33828            if !sample.explicit_method && (is_snowflake || force_method) {
33829                // Snowflake/PostgreSQL defaults to BERNOULLI when no method is specified
33830                self.write_keyword("BERNOULLI");
33831            } else {
33832                match sample.method {
33833                    SampleMethod::Bernoulli => self.write_keyword("BERNOULLI"),
33834                    SampleMethod::System => self.write_keyword("SYSTEM"),
33835                    SampleMethod::Block => self.write_keyword("BLOCK"),
33836                    SampleMethod::Row => self.write_keyword("ROW"),
33837                    SampleMethod::Reservoir => self.write_keyword("RESERVOIR"),
33838                    SampleMethod::Percent => self.write_keyword("SYSTEM"),
33839                    SampleMethod::Bucket => {} // handled above
33840                }
33841            }
33842        }
33843
33844        // Output size, with or without parentheses depending on dialect
33845        let emit_size_no_parens = !self.config.tablesample_requires_parens;
33846        if emit_size_no_parens {
33847            self.write_space();
33848            match &sample.size {
33849                Expression::Tuple(tuple) => {
33850                    for (i, expr) in tuple.expressions.iter().enumerate() {
33851                        if i > 0 {
33852                            self.write(", ");
33853                        }
33854                        self.generate_expression(expr)?;
33855                    }
33856                }
33857                expr => self.generate_expression(expr)?,
33858            }
33859        } else {
33860            self.write(" (");
33861            self.generate_expression(&sample.size)?;
33862        }
33863
33864        // Determine unit
33865        let is_rows_method = matches!(
33866            sample.method,
33867            SampleMethod::Reservoir | SampleMethod::Row | SampleMethod::Bucket
33868        );
33869        let is_percent = matches!(
33870            sample.method,
33871            SampleMethod::Percent
33872                | SampleMethod::System
33873                | SampleMethod::Bernoulli
33874                | SampleMethod::Block
33875        );
33876
33877        // For Snowflake, PostgreSQL, and Presto/Trino, only output ROWS/PERCENT when the user explicitly wrote it (unit_after_size).
33878        // These dialects use bare numbers for percentage by default in TABLESAMPLE METHOD(size) syntax.
33879        // For Databricks and Spark, always output PERCENT for percentage samples.
33880        let is_presto = matches!(
33881            self.config.dialect,
33882            Some(crate::dialects::DialectType::Presto)
33883                | Some(crate::dialects::DialectType::Trino)
33884                | Some(crate::dialects::DialectType::Athena)
33885        );
33886        let should_output_unit = if is_databricks || is_spark {
33887            // Always output PERCENT for percentage-based methods, or ROWS for row-based methods
33888            is_percent || is_rows_method || sample.unit_after_size
33889        } else if is_snowflake || is_postgres || is_presto {
33890            sample.unit_after_size
33891        } else {
33892            sample.unit_after_size || (sample.explicit_method && (is_rows_method || is_percent))
33893        };
33894
33895        if should_output_unit {
33896            self.write_space();
33897            if sample.is_percent {
33898                self.write_keyword("PERCENT");
33899            } else if is_rows_method && !sample.unit_after_size {
33900                self.write_keyword("ROWS");
33901            } else if sample.unit_after_size {
33902                match sample.method {
33903                    SampleMethod::Percent
33904                    | SampleMethod::System
33905                    | SampleMethod::Bernoulli
33906                    | SampleMethod::Block => {
33907                        self.write_keyword("PERCENT");
33908                    }
33909                    SampleMethod::Row | SampleMethod::Reservoir => {
33910                        self.write_keyword("ROWS");
33911                    }
33912                    _ => self.write_keyword("ROWS"),
33913                }
33914            } else {
33915                self.write_keyword("PERCENT");
33916            }
33917        }
33918
33919        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
33920            if let Some(ref offset) = sample.offset {
33921                self.write_space();
33922                self.write_keyword("OFFSET");
33923                self.write_space();
33924                self.generate_expression(offset)?;
33925            }
33926        }
33927        if !emit_size_no_parens {
33928            self.write(")");
33929        }
33930
33931        Ok(())
33932    }
33933
33934    fn generate_sample_property(&mut self, e: &SampleProperty) -> Result<()> {
33935        // SAMPLE this (ClickHouse uses SAMPLE BY)
33936        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
33937            self.write_keyword("SAMPLE BY");
33938        } else {
33939            self.write_keyword("SAMPLE");
33940        }
33941        self.write_space();
33942        self.generate_expression(&e.this)?;
33943        Ok(())
33944    }
33945
33946    fn generate_schema(&mut self, e: &Schema) -> Result<()> {
33947        // this (expressions...)
33948        if let Some(this) = &e.this {
33949            self.generate_expression(this)?;
33950        }
33951        if !e.expressions.is_empty() {
33952            // Add space before column list if there's a preceding expression
33953            if e.this.is_some() {
33954                self.write_space();
33955            }
33956            self.write("(");
33957            for (i, expr) in e.expressions.iter().enumerate() {
33958                if i > 0 {
33959                    self.write(", ");
33960                }
33961                self.generate_expression(expr)?;
33962            }
33963            self.write(")");
33964        }
33965        Ok(())
33966    }
33967
33968    fn generate_schema_comment_property(&mut self, e: &SchemaCommentProperty) -> Result<()> {
33969        // COMMENT this
33970        self.write_keyword("COMMENT");
33971        self.write_space();
33972        self.generate_expression(&e.this)?;
33973        Ok(())
33974    }
33975
33976    fn generate_scope_resolution(&mut self, e: &ScopeResolution) -> Result<()> {
33977        // [this::]expression
33978        if let Some(this) = &e.this {
33979            self.generate_expression(this)?;
33980            self.write("::");
33981        }
33982        self.generate_expression(&e.expression)?;
33983        Ok(())
33984    }
33985
33986    fn generate_search(&mut self, e: &Search) -> Result<()> {
33987        // SEARCH(this, expression, [json_scope], [analyzer], [analyzer_options], [search_mode])
33988        self.write_keyword("SEARCH");
33989        self.write("(");
33990        self.generate_expression(&e.this)?;
33991        self.write(", ");
33992        self.generate_expression(&e.expression)?;
33993        if let Some(json_scope) = &e.json_scope {
33994            self.write(", ");
33995            self.generate_expression(json_scope)?;
33996        }
33997        if let Some(analyzer) = &e.analyzer {
33998            self.write(", ");
33999            self.generate_expression(analyzer)?;
34000        }
34001        if let Some(analyzer_options) = &e.analyzer_options {
34002            self.write(", ");
34003            self.generate_expression(analyzer_options)?;
34004        }
34005        if let Some(search_mode) = &e.search_mode {
34006            self.write(", ");
34007            self.generate_expression(search_mode)?;
34008        }
34009        self.write(")");
34010        Ok(())
34011    }
34012
34013    fn generate_search_ip(&mut self, e: &SearchIp) -> Result<()> {
34014        // SEARCH_IP(this, expression)
34015        self.write_keyword("SEARCH_IP");
34016        self.write("(");
34017        self.generate_expression(&e.this)?;
34018        self.write(", ");
34019        self.generate_expression(&e.expression)?;
34020        self.write(")");
34021        Ok(())
34022    }
34023
34024    fn generate_security_property(&mut self, e: &SecurityProperty) -> Result<()> {
34025        // SECURITY this
34026        self.write_keyword("SECURITY");
34027        self.write_space();
34028        self.generate_expression(&e.this)?;
34029        Ok(())
34030    }
34031
34032    fn generate_semantic_view(&mut self, e: &SemanticView) -> Result<()> {
34033        // SEMANTIC_VIEW(this [METRICS ...] [DIMENSIONS ...] [FACTS ...] [WHERE ...])
34034        self.write("SEMANTIC_VIEW(");
34035
34036        if self.config.pretty {
34037            // Pretty print: each clause on its own line
34038            self.write_newline();
34039            self.indent_level += 1;
34040            self.write_indent();
34041            self.generate_expression(&e.this)?;
34042
34043            if let Some(metrics) = &e.metrics {
34044                self.write_newline();
34045                self.write_indent();
34046                self.write_keyword("METRICS");
34047                self.write_space();
34048                self.generate_semantic_view_tuple(metrics)?;
34049            }
34050            if let Some(dimensions) = &e.dimensions {
34051                self.write_newline();
34052                self.write_indent();
34053                self.write_keyword("DIMENSIONS");
34054                self.write_space();
34055                self.generate_semantic_view_tuple(dimensions)?;
34056            }
34057            if let Some(facts) = &e.facts {
34058                self.write_newline();
34059                self.write_indent();
34060                self.write_keyword("FACTS");
34061                self.write_space();
34062                self.generate_semantic_view_tuple(facts)?;
34063            }
34064            if let Some(where_) = &e.where_ {
34065                self.write_newline();
34066                self.write_indent();
34067                self.write_keyword("WHERE");
34068                self.write_space();
34069                self.generate_expression(where_)?;
34070            }
34071            self.write_newline();
34072            self.indent_level -= 1;
34073            self.write_indent();
34074        } else {
34075            // Compact: all on one line
34076            self.generate_expression(&e.this)?;
34077            if let Some(metrics) = &e.metrics {
34078                self.write_space();
34079                self.write_keyword("METRICS");
34080                self.write_space();
34081                self.generate_semantic_view_tuple(metrics)?;
34082            }
34083            if let Some(dimensions) = &e.dimensions {
34084                self.write_space();
34085                self.write_keyword("DIMENSIONS");
34086                self.write_space();
34087                self.generate_semantic_view_tuple(dimensions)?;
34088            }
34089            if let Some(facts) = &e.facts {
34090                self.write_space();
34091                self.write_keyword("FACTS");
34092                self.write_space();
34093                self.generate_semantic_view_tuple(facts)?;
34094            }
34095            if let Some(where_) = &e.where_ {
34096                self.write_space();
34097                self.write_keyword("WHERE");
34098                self.write_space();
34099                self.generate_expression(where_)?;
34100            }
34101        }
34102        self.write(")");
34103        Ok(())
34104    }
34105
34106    /// Helper for SEMANTIC_VIEW tuple contents (without parentheses)
34107    fn generate_semantic_view_tuple(&mut self, expr: &Expression) -> Result<()> {
34108        if let Expression::Tuple(t) = expr {
34109            for (i, e) in t.expressions.iter().enumerate() {
34110                if i > 0 {
34111                    self.write(", ");
34112                }
34113                self.generate_expression(e)?;
34114            }
34115        } else {
34116            self.generate_expression(expr)?;
34117        }
34118        Ok(())
34119    }
34120
34121    fn generate_sequence_properties(&mut self, e: &SequenceProperties) -> Result<()> {
34122        // [START WITH start] [INCREMENT BY increment] [MINVALUE minvalue] [MAXVALUE maxvalue] [CACHE cache] [OWNED BY owned]
34123        if let Some(start) = &e.start {
34124            self.write_keyword("START WITH");
34125            self.write_space();
34126            self.generate_expression(start)?;
34127        }
34128        if let Some(increment) = &e.increment {
34129            self.write_space();
34130            self.write_keyword("INCREMENT BY");
34131            self.write_space();
34132            self.generate_expression(increment)?;
34133        }
34134        if let Some(minvalue) = &e.minvalue {
34135            self.write_space();
34136            self.write_keyword("MINVALUE");
34137            self.write_space();
34138            self.generate_expression(minvalue)?;
34139        }
34140        if let Some(maxvalue) = &e.maxvalue {
34141            self.write_space();
34142            self.write_keyword("MAXVALUE");
34143            self.write_space();
34144            self.generate_expression(maxvalue)?;
34145        }
34146        if let Some(cache) = &e.cache {
34147            self.write_space();
34148            self.write_keyword("CACHE");
34149            self.write_space();
34150            self.generate_expression(cache)?;
34151        }
34152        if let Some(owned) = &e.owned {
34153            self.write_space();
34154            self.write_keyword("OWNED BY");
34155            self.write_space();
34156            self.generate_expression(owned)?;
34157        }
34158        for opt in &e.options {
34159            self.write_space();
34160            self.generate_expression(opt)?;
34161        }
34162        Ok(())
34163    }
34164
34165    fn generate_serde_properties(&mut self, e: &SerdeProperties) -> Result<()> {
34166        // [WITH] SERDEPROPERTIES (expressions)
34167        if e.with_.is_some() {
34168            self.write_keyword("WITH");
34169            self.write_space();
34170        }
34171        self.write_keyword("SERDEPROPERTIES");
34172        self.write(" (");
34173        for (i, expr) in e.expressions.iter().enumerate() {
34174            if i > 0 {
34175                self.write(", ");
34176            }
34177            // Generate key=value without spaces around =
34178            match expr {
34179                Expression::Eq(eq) => {
34180                    self.generate_expression(&eq.left)?;
34181                    self.write("=");
34182                    self.generate_expression(&eq.right)?;
34183                }
34184                _ => self.generate_expression(expr)?,
34185            }
34186        }
34187        self.write(")");
34188        Ok(())
34189    }
34190
34191    fn generate_session_parameter(&mut self, e: &SessionParameter) -> Result<()> {
34192        // @@[kind.]this
34193        self.write("@@");
34194        if let Some(kind) = &e.kind {
34195            self.write(kind);
34196            self.write(".");
34197        }
34198        self.generate_expression(&e.this)?;
34199        Ok(())
34200    }
34201
34202    fn generate_set(&mut self, e: &Set) -> Result<()> {
34203        // SET/UNSET [TAG] expressions
34204        if e.unset.is_some() {
34205            self.write_keyword("UNSET");
34206        } else {
34207            self.write_keyword("SET");
34208        }
34209        if e.tag.is_some() {
34210            self.write_space();
34211            self.write_keyword("TAG");
34212        }
34213        if !e.expressions.is_empty() {
34214            self.write_space();
34215            for (i, expr) in e.expressions.iter().enumerate() {
34216                if i > 0 {
34217                    self.write(", ");
34218                }
34219                self.generate_expression(expr)?;
34220            }
34221        }
34222        Ok(())
34223    }
34224
34225    fn generate_set_config_property(&mut self, e: &SetConfigProperty) -> Result<()> {
34226        // SET this or SETCONFIG this
34227        self.write_keyword("SET");
34228        self.write_space();
34229        self.generate_expression(&e.this)?;
34230        Ok(())
34231    }
34232
34233    fn generate_set_item(&mut self, e: &SetItem) -> Result<()> {
34234        // [kind] name = value
34235        if let Some(kind) = &e.kind {
34236            self.write_keyword(kind);
34237            self.write_space();
34238        }
34239        self.generate_expression(&e.name)?;
34240        self.write(" = ");
34241        self.generate_expression(&e.value)?;
34242        Ok(())
34243    }
34244
34245    fn generate_set_operation(&mut self, e: &SetOperation) -> Result<()> {
34246        // [WITH ...] this UNION|INTERSECT|EXCEPT [ALL|DISTINCT] [BY NAME] expression
34247        if let Some(with_) = &e.with_ {
34248            self.generate_expression(with_)?;
34249            self.write_space();
34250        }
34251        self.generate_expression(&e.this)?;
34252        self.write_space();
34253        // kind should be UNION, INTERSECT, EXCEPT, etc.
34254        if let Some(kind) = &e.kind {
34255            self.write_keyword(kind);
34256        }
34257        if e.distinct {
34258            self.write_space();
34259            self.write_keyword("DISTINCT");
34260        } else {
34261            self.write_space();
34262            self.write_keyword("ALL");
34263        }
34264        if e.by_name.is_some() {
34265            self.write_space();
34266            self.write_keyword("BY NAME");
34267        }
34268        self.write_space();
34269        self.generate_expression(&e.expression)?;
34270        Ok(())
34271    }
34272
34273    fn generate_set_property(&mut self, e: &SetProperty) -> Result<()> {
34274        // SET or MULTISET
34275        if e.multi.is_some() {
34276            self.write_keyword("MULTISET");
34277        } else {
34278            self.write_keyword("SET");
34279        }
34280        Ok(())
34281    }
34282
34283    fn generate_settings_property(&mut self, e: &SettingsProperty) -> Result<()> {
34284        // SETTINGS expressions
34285        self.write_keyword("SETTINGS");
34286        if self.config.pretty && e.expressions.len() > 1 {
34287            // Pretty print: each setting on its own line, indented
34288            self.indent_level += 1;
34289            for (i, expr) in e.expressions.iter().enumerate() {
34290                if i > 0 {
34291                    self.write(",");
34292                }
34293                self.write_newline();
34294                self.write_indent();
34295                self.generate_expression(expr)?;
34296            }
34297            self.indent_level -= 1;
34298        } else {
34299            self.write_space();
34300            for (i, expr) in e.expressions.iter().enumerate() {
34301                if i > 0 {
34302                    self.write(", ");
34303                }
34304                self.generate_expression(expr)?;
34305            }
34306        }
34307        Ok(())
34308    }
34309
34310    fn generate_sharing_property(&mut self, e: &SharingProperty) -> Result<()> {
34311        // SHARING = this
34312        self.write_keyword("SHARING");
34313        if let Some(this) = &e.this {
34314            self.write(" = ");
34315            self.generate_expression(this)?;
34316        }
34317        Ok(())
34318    }
34319
34320    fn generate_slice(&mut self, e: &Slice) -> Result<()> {
34321        // Python array slicing: begin:end:step
34322        if let Some(begin) = &e.this {
34323            self.generate_expression(begin)?;
34324        }
34325        self.write(":");
34326        if let Some(end) = &e.expression {
34327            self.generate_expression(end)?;
34328        }
34329        if let Some(step) = &e.step {
34330            self.write(":");
34331            self.generate_expression(step)?;
34332        }
34333        Ok(())
34334    }
34335
34336    fn generate_sort_array(&mut self, e: &SortArray) -> Result<()> {
34337        // SORT_ARRAY(this, asc)
34338        self.write_keyword("SORT_ARRAY");
34339        self.write("(");
34340        self.generate_expression(&e.this)?;
34341        if let Some(asc) = &e.asc {
34342            self.write(", ");
34343            self.generate_expression(asc)?;
34344        }
34345        self.write(")");
34346        Ok(())
34347    }
34348
34349    fn generate_sort_by(&mut self, e: &SortBy) -> Result<()> {
34350        // SORT BY expressions
34351        self.write_keyword("SORT BY");
34352        self.write_space();
34353        for (i, expr) in e.expressions.iter().enumerate() {
34354            if i > 0 {
34355                self.write(", ");
34356            }
34357            self.generate_ordered(expr)?;
34358        }
34359        Ok(())
34360    }
34361
34362    fn generate_sort_key_property(&mut self, e: &SortKeyProperty) -> Result<()> {
34363        // [COMPOUND] SORTKEY(col1, col2, ...) - no space before paren
34364        if e.compound.is_some() {
34365            self.write_keyword("COMPOUND");
34366            self.write_space();
34367        }
34368        self.write_keyword("SORTKEY");
34369        self.write("(");
34370        // If this is a Tuple, unwrap its contents to avoid double parentheses
34371        if let Expression::Tuple(t) = e.this.as_ref() {
34372            for (i, expr) in t.expressions.iter().enumerate() {
34373                if i > 0 {
34374                    self.write(", ");
34375                }
34376                self.generate_expression(expr)?;
34377            }
34378        } else {
34379            self.generate_expression(&e.this)?;
34380        }
34381        self.write(")");
34382        Ok(())
34383    }
34384
34385    fn generate_split_part(&mut self, e: &SplitPart) -> Result<()> {
34386        // SPLIT_PART(this, delimiter, part_index)
34387        self.write_keyword("SPLIT_PART");
34388        self.write("(");
34389        self.generate_expression(&e.this)?;
34390        if let Some(delimiter) = &e.delimiter {
34391            self.write(", ");
34392            self.generate_expression(delimiter)?;
34393        }
34394        if let Some(part_index) = &e.part_index {
34395            self.write(", ");
34396            self.generate_expression(part_index)?;
34397        }
34398        self.write(")");
34399        Ok(())
34400    }
34401
34402    fn generate_sql_read_write_property(&mut self, e: &SqlReadWriteProperty) -> Result<()> {
34403        // READS SQL DATA or MODIFIES SQL DATA, etc.
34404        self.generate_expression(&e.this)?;
34405        Ok(())
34406    }
34407
34408    fn generate_sql_security_property(&mut self, e: &SqlSecurityProperty) -> Result<()> {
34409        // SQL SECURITY DEFINER or SQL SECURITY INVOKER
34410        self.write_keyword("SQL SECURITY");
34411        self.write_space();
34412        self.generate_expression(&e.this)?;
34413        Ok(())
34414    }
34415
34416    fn generate_st_distance(&mut self, e: &StDistance) -> Result<()> {
34417        // ST_DISTANCE(this, expression, [use_spheroid])
34418        self.write_keyword("ST_DISTANCE");
34419        self.write("(");
34420        self.generate_expression(&e.this)?;
34421        self.write(", ");
34422        self.generate_expression(&e.expression)?;
34423        if let Some(use_spheroid) = &e.use_spheroid {
34424            self.write(", ");
34425            self.generate_expression(use_spheroid)?;
34426        }
34427        self.write(")");
34428        Ok(())
34429    }
34430
34431    fn generate_st_point(&mut self, e: &StPoint) -> Result<()> {
34432        // ST_POINT(this, expression)
34433        self.write_keyword("ST_POINT");
34434        self.write("(");
34435        self.generate_expression(&e.this)?;
34436        self.write(", ");
34437        self.generate_expression(&e.expression)?;
34438        self.write(")");
34439        Ok(())
34440    }
34441
34442    fn generate_stability_property(&mut self, e: &StabilityProperty) -> Result<()> {
34443        // IMMUTABLE, STABLE, VOLATILE
34444        self.generate_expression(&e.this)?;
34445        Ok(())
34446    }
34447
34448    fn generate_standard_hash(&mut self, e: &StandardHash) -> Result<()> {
34449        // STANDARD_HASH(this, [expression])
34450        self.write_keyword("STANDARD_HASH");
34451        self.write("(");
34452        self.generate_expression(&e.this)?;
34453        if let Some(expression) = &e.expression {
34454            self.write(", ");
34455            self.generate_expression(expression)?;
34456        }
34457        self.write(")");
34458        Ok(())
34459    }
34460
34461    fn generate_storage_handler_property(&mut self, e: &StorageHandlerProperty) -> Result<()> {
34462        // STORED BY this
34463        self.write_keyword("STORED BY");
34464        self.write_space();
34465        self.generate_expression(&e.this)?;
34466        Ok(())
34467    }
34468
34469    fn generate_str_position(&mut self, e: &StrPosition) -> Result<()> {
34470        // STRPOS(this, substr) or STRPOS(this, substr, position)
34471        // Different dialects have different function names
34472        use crate::dialects::DialectType;
34473        if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
34474            // Snowflake: CHARINDEX(substr, str[, position])
34475            self.write_keyword("CHARINDEX");
34476            self.write("(");
34477            if let Some(substr) = &e.substr {
34478                self.generate_expression(substr)?;
34479                self.write(", ");
34480            }
34481            self.generate_expression(&e.this)?;
34482            if let Some(position) = &e.position {
34483                self.write(", ");
34484                self.generate_expression(position)?;
34485            }
34486            self.write(")");
34487        } else if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
34488            self.write_keyword("POSITION");
34489            self.write("(");
34490            self.generate_expression(&e.this)?;
34491            if let Some(substr) = &e.substr {
34492                self.write(", ");
34493                self.generate_expression(substr)?;
34494            }
34495            if let Some(position) = &e.position {
34496                self.write(", ");
34497                self.generate_expression(position)?;
34498            }
34499            if let Some(occurrence) = &e.occurrence {
34500                self.write(", ");
34501                self.generate_expression(occurrence)?;
34502            }
34503            self.write(")");
34504        } else if matches!(
34505            self.config.dialect,
34506            Some(DialectType::SQLite)
34507                | Some(DialectType::Oracle)
34508                | Some(DialectType::BigQuery)
34509                | Some(DialectType::Teradata)
34510        ) {
34511            self.write_keyword("INSTR");
34512            self.write("(");
34513            self.generate_expression(&e.this)?;
34514            if let Some(substr) = &e.substr {
34515                self.write(", ");
34516                self.generate_expression(substr)?;
34517            }
34518            if let Some(position) = &e.position {
34519                self.write(", ");
34520                self.generate_expression(position)?;
34521            } else if e.occurrence.is_some() {
34522                // INSTR requires a position arg before occurrence: INSTR(str, substr, start, nth)
34523                // Default start position is 1
34524                self.write(", 1");
34525            }
34526            if let Some(occurrence) = &e.occurrence {
34527                self.write(", ");
34528                self.generate_expression(occurrence)?;
34529            }
34530            self.write(")");
34531        } else if matches!(
34532            self.config.dialect,
34533            Some(DialectType::MySQL)
34534                | Some(DialectType::SingleStore)
34535                | Some(DialectType::Doris)
34536                | Some(DialectType::StarRocks)
34537                | Some(DialectType::Hive)
34538                | Some(DialectType::Spark)
34539                | Some(DialectType::Databricks)
34540        ) {
34541            // LOCATE(substr, str[, position]) - substr first
34542            self.write_keyword("LOCATE");
34543            self.write("(");
34544            if let Some(substr) = &e.substr {
34545                self.generate_expression(substr)?;
34546                self.write(", ");
34547            }
34548            self.generate_expression(&e.this)?;
34549            if let Some(position) = &e.position {
34550                self.write(", ");
34551                self.generate_expression(position)?;
34552            }
34553            self.write(")");
34554        } else if matches!(self.config.dialect, Some(DialectType::TSQL)) {
34555            // CHARINDEX(substr, str[, position])
34556            self.write_keyword("CHARINDEX");
34557            self.write("(");
34558            if let Some(substr) = &e.substr {
34559                self.generate_expression(substr)?;
34560                self.write(", ");
34561            }
34562            self.generate_expression(&e.this)?;
34563            if let Some(position) = &e.position {
34564                self.write(", ");
34565                self.generate_expression(position)?;
34566            }
34567            self.write(")");
34568        } else if matches!(
34569            self.config.dialect,
34570            Some(DialectType::PostgreSQL)
34571                | Some(DialectType::Materialize)
34572                | Some(DialectType::RisingWave)
34573                | Some(DialectType::Redshift)
34574        ) {
34575            // POSITION(substr IN str) syntax
34576            self.write_keyword("POSITION");
34577            self.write("(");
34578            if let Some(substr) = &e.substr {
34579                self.generate_expression(substr)?;
34580                self.write(" IN ");
34581            }
34582            self.generate_expression(&e.this)?;
34583            self.write(")");
34584        } else {
34585            self.write_keyword("STRPOS");
34586            self.write("(");
34587            self.generate_expression(&e.this)?;
34588            if let Some(substr) = &e.substr {
34589                self.write(", ");
34590                self.generate_expression(substr)?;
34591            }
34592            if let Some(position) = &e.position {
34593                self.write(", ");
34594                self.generate_expression(position)?;
34595            }
34596            if let Some(occurrence) = &e.occurrence {
34597                self.write(", ");
34598                self.generate_expression(occurrence)?;
34599            }
34600            self.write(")");
34601        }
34602        Ok(())
34603    }
34604
34605    fn generate_str_to_date(&mut self, e: &StrToDate) -> Result<()> {
34606        match self.config.dialect {
34607            Some(DialectType::Spark) | Some(DialectType::Databricks) | Some(DialectType::Hive) => {
34608                // TO_DATE(this, java_format)
34609                self.write_keyword("TO_DATE");
34610                self.write("(");
34611                self.generate_expression(&e.this)?;
34612                if let Some(format) = &e.format {
34613                    self.write(", '");
34614                    self.write(&Self::strftime_to_java_format(format));
34615                    self.write("'");
34616                }
34617                self.write(")");
34618            }
34619            Some(DialectType::DuckDB) => {
34620                // CAST(STRPTIME(this, format) AS DATE)
34621                self.write_keyword("CAST");
34622                self.write("(");
34623                self.write_keyword("STRPTIME");
34624                self.write("(");
34625                self.generate_expression(&e.this)?;
34626                if let Some(format) = &e.format {
34627                    self.write(", '");
34628                    self.write(format);
34629                    self.write("'");
34630                }
34631                self.write(")");
34632                self.write_keyword(" AS ");
34633                self.write_keyword("DATE");
34634                self.write(")");
34635            }
34636            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift) => {
34637                // TO_DATE(this, pg_format)
34638                self.write_keyword("TO_DATE");
34639                self.write("(");
34640                self.generate_expression(&e.this)?;
34641                if let Some(format) = &e.format {
34642                    self.write(", '");
34643                    self.write(&Self::strftime_to_postgres_format(format));
34644                    self.write("'");
34645                }
34646                self.write(")");
34647            }
34648            Some(DialectType::BigQuery) => {
34649                // PARSE_DATE(format, this) - note: format comes first for BigQuery
34650                self.write_keyword("PARSE_DATE");
34651                self.write("(");
34652                if let Some(format) = &e.format {
34653                    self.write("'");
34654                    self.write(format);
34655                    self.write("'");
34656                    self.write(", ");
34657                }
34658                self.generate_expression(&e.this)?;
34659                self.write(")");
34660            }
34661            Some(DialectType::Teradata) => {
34662                // CAST(this AS DATE FORMAT 'teradata_fmt')
34663                self.write_keyword("CAST");
34664                self.write("(");
34665                self.generate_expression(&e.this)?;
34666                self.write_keyword(" AS ");
34667                self.write_keyword("DATE");
34668                if let Some(format) = &e.format {
34669                    self.write_keyword(" FORMAT ");
34670                    self.write("'");
34671                    self.write(&Self::strftime_to_teradata_format(format));
34672                    self.write("'");
34673                }
34674                self.write(")");
34675            }
34676            _ => {
34677                // STR_TO_DATE(this, format) - MySQL default
34678                self.write_keyword("STR_TO_DATE");
34679                self.write("(");
34680                self.generate_expression(&e.this)?;
34681                if let Some(format) = &e.format {
34682                    self.write(", '");
34683                    self.write(format);
34684                    self.write("'");
34685                }
34686                self.write(")");
34687            }
34688        }
34689        Ok(())
34690    }
34691
34692    /// Convert strftime format to Teradata date format (YYYY, DD, MM, etc.)
34693    fn strftime_to_teradata_format(fmt: &str) -> String {
34694        let mut result = String::with_capacity(fmt.len() * 2);
34695        let bytes = fmt.as_bytes();
34696        let len = bytes.len();
34697        let mut i = 0;
34698        while i < len {
34699            if bytes[i] == b'%' && i + 1 < len {
34700                let replacement = match bytes[i + 1] {
34701                    b'Y' => "YYYY",
34702                    b'y' => "YY",
34703                    b'm' => "MM",
34704                    b'B' => "MMMM",
34705                    b'b' => "MMM",
34706                    b'd' => "DD",
34707                    b'j' => "DDD",
34708                    b'H' => "HH",
34709                    b'M' => "MI",
34710                    b'S' => "SS",
34711                    b'f' => "SSSSSS",
34712                    b'A' => "EEEE",
34713                    b'a' => "EEE",
34714                    _ => {
34715                        result.push('%');
34716                        i += 1;
34717                        continue;
34718                    }
34719                };
34720                result.push_str(replacement);
34721                i += 2;
34722            } else {
34723                result.push(bytes[i] as char);
34724                i += 1;
34725            }
34726        }
34727        result
34728    }
34729
34730    /// Convert strftime format (%Y, %m, %d, etc.) to Java date format (yyyy, MM, dd, etc.)
34731    /// Public static version for use by other modules
34732    pub fn strftime_to_java_format_static(fmt: &str) -> String {
34733        Self::strftime_to_java_format(fmt)
34734    }
34735
34736    /// Convert strftime format (%Y, %m, %d, etc.) to Java date format (yyyy, MM, dd, etc.)
34737    fn strftime_to_java_format(fmt: &str) -> String {
34738        let mut result = String::with_capacity(fmt.len() * 2);
34739        let bytes = fmt.as_bytes();
34740        let len = bytes.len();
34741        let mut i = 0;
34742        while i < len {
34743            if bytes[i] == b'%' && i + 1 < len {
34744                // Check for non-padded variants (%-X)
34745                if bytes[i + 1] == b'-' && i + 2 < len {
34746                    let replacement = match bytes[i + 2] {
34747                        b'd' => "d",
34748                        b'm' => "M",
34749                        b'H' => "H",
34750                        b'M' => "m",
34751                        b'S' => "s",
34752                        _ => {
34753                            result.push('%');
34754                            i += 1;
34755                            continue;
34756                        }
34757                    };
34758                    result.push_str(replacement);
34759                    i += 3;
34760                } else {
34761                    let replacement = match bytes[i + 1] {
34762                        b'Y' => "yyyy",
34763                        b'y' => "yy",
34764                        b'm' => "MM",
34765                        b'B' => "MMMM",
34766                        b'b' => "MMM",
34767                        b'd' => "dd",
34768                        b'j' => "DDD",
34769                        b'H' => "HH",
34770                        b'M' => "mm",
34771                        b'S' => "ss",
34772                        b'f' => "SSSSSS",
34773                        b'A' => "EEEE",
34774                        b'a' => "EEE",
34775                        _ => {
34776                            result.push('%');
34777                            i += 1;
34778                            continue;
34779                        }
34780                    };
34781                    result.push_str(replacement);
34782                    i += 2;
34783                }
34784            } else {
34785                result.push(bytes[i] as char);
34786                i += 1;
34787            }
34788        }
34789        result
34790    }
34791
34792    /// Convert strftime format (%Y, %m, %d, etc.) to .NET date format for TSQL FORMAT()
34793    /// Similar to Java but uses ffffff for microseconds instead of SSSSSS
34794    fn strftime_to_tsql_format(fmt: &str) -> String {
34795        let mut result = String::with_capacity(fmt.len() * 2);
34796        let bytes = fmt.as_bytes();
34797        let len = bytes.len();
34798        let mut i = 0;
34799        while i < len {
34800            if bytes[i] == b'%' && i + 1 < len {
34801                // Check for non-padded variants (%-X)
34802                if bytes[i + 1] == b'-' && i + 2 < len {
34803                    let replacement = match bytes[i + 2] {
34804                        b'd' => "d",
34805                        b'm' => "M",
34806                        b'H' => "H",
34807                        b'M' => "m",
34808                        b'S' => "s",
34809                        _ => {
34810                            result.push('%');
34811                            i += 1;
34812                            continue;
34813                        }
34814                    };
34815                    result.push_str(replacement);
34816                    i += 3;
34817                } else {
34818                    let replacement = match bytes[i + 1] {
34819                        b'Y' => "yyyy",
34820                        b'y' => "yy",
34821                        b'm' => "MM",
34822                        b'B' => "MMMM",
34823                        b'b' => "MMM",
34824                        b'd' => "dd",
34825                        b'j' => "DDD",
34826                        b'H' => "HH",
34827                        b'M' => "mm",
34828                        b'S' => "ss",
34829                        b'f' => "ffffff",
34830                        b'A' => "dddd",
34831                        b'a' => "ddd",
34832                        _ => {
34833                            result.push('%');
34834                            i += 1;
34835                            continue;
34836                        }
34837                    };
34838                    result.push_str(replacement);
34839                    i += 2;
34840                }
34841            } else {
34842                result.push(bytes[i] as char);
34843                i += 1;
34844            }
34845        }
34846        result
34847    }
34848
34849    /// Decompose a JSON path string like "$.y[0].z" into individual parts: ["y", "0", "z"]
34850    /// This is used for PostgreSQL/Redshift JSON_EXTRACT_PATH / JSON_EXTRACT_PATH_TEXT
34851    fn decompose_json_path(path: &str) -> Vec<String> {
34852        let mut parts = Vec::new();
34853        // Strip leading $ and optional .
34854        let path = if path.starts_with("$.") {
34855            &path[2..]
34856        } else if path.starts_with('$') {
34857            &path[1..]
34858        } else {
34859            path
34860        };
34861        if path.is_empty() {
34862            return parts;
34863        }
34864        let mut current = String::new();
34865        let chars: Vec<char> = path.chars().collect();
34866        let mut i = 0;
34867        while i < chars.len() {
34868            match chars[i] {
34869                '.' => {
34870                    if !current.is_empty() {
34871                        parts.push(current.clone());
34872                        current.clear();
34873                    }
34874                    i += 1;
34875                }
34876                '[' => {
34877                    if !current.is_empty() {
34878                        parts.push(current.clone());
34879                        current.clear();
34880                    }
34881                    i += 1;
34882                    // Read the content inside brackets
34883                    let mut bracket_content = String::new();
34884                    while i < chars.len() && chars[i] != ']' {
34885                        // Skip quotes inside brackets
34886                        if chars[i] == '"' || chars[i] == '\'' {
34887                            let quote = chars[i];
34888                            i += 1;
34889                            while i < chars.len() && chars[i] != quote {
34890                                bracket_content.push(chars[i]);
34891                                i += 1;
34892                            }
34893                            if i < chars.len() {
34894                                i += 1;
34895                            } // skip closing quote
34896                        } else {
34897                            bracket_content.push(chars[i]);
34898                            i += 1;
34899                        }
34900                    }
34901                    if i < chars.len() {
34902                        i += 1;
34903                    } // skip ]
34904                      // Skip wildcard [*] - don't add as a part
34905                    if bracket_content != "*" {
34906                        parts.push(bracket_content);
34907                    }
34908                }
34909                _ => {
34910                    current.push(chars[i]);
34911                    i += 1;
34912                }
34913            }
34914        }
34915        if !current.is_empty() {
34916            parts.push(current);
34917        }
34918        parts
34919    }
34920
34921    /// Convert strftime format to PostgreSQL date format (YYYY, MM, DD, etc.)
34922    fn strftime_to_postgres_format(fmt: &str) -> String {
34923        let mut result = String::with_capacity(fmt.len() * 2);
34924        let bytes = fmt.as_bytes();
34925        let len = bytes.len();
34926        let mut i = 0;
34927        while i < len {
34928            if bytes[i] == b'%' && i + 1 < len {
34929                // Check for non-padded variants (%-X)
34930                if bytes[i + 1] == b'-' && i + 2 < len {
34931                    let replacement = match bytes[i + 2] {
34932                        b'd' => "FMDD",
34933                        b'm' => "FMMM",
34934                        b'H' => "FMHH24",
34935                        b'M' => "FMMI",
34936                        b'S' => "FMSS",
34937                        _ => {
34938                            result.push('%');
34939                            i += 1;
34940                            continue;
34941                        }
34942                    };
34943                    result.push_str(replacement);
34944                    i += 3;
34945                } else {
34946                    let replacement = match bytes[i + 1] {
34947                        b'Y' => "YYYY",
34948                        b'y' => "YY",
34949                        b'm' => "MM",
34950                        b'B' => "Month",
34951                        b'b' => "Mon",
34952                        b'd' => "DD",
34953                        b'j' => "DDD",
34954                        b'H' => "HH24",
34955                        b'M' => "MI",
34956                        b'S' => "SS",
34957                        b'f' => "US",
34958                        b'A' => "Day",
34959                        b'a' => "Dy",
34960                        _ => {
34961                            result.push('%');
34962                            i += 1;
34963                            continue;
34964                        }
34965                    };
34966                    result.push_str(replacement);
34967                    i += 2;
34968                }
34969            } else {
34970                result.push(bytes[i] as char);
34971                i += 1;
34972            }
34973        }
34974        result
34975    }
34976
34977    /// Convert strftime format to Snowflake date format (yyyy, mm, DD, etc.)
34978    fn strftime_to_snowflake_format(fmt: &str) -> String {
34979        let mut result = String::with_capacity(fmt.len() * 2);
34980        let bytes = fmt.as_bytes();
34981        let len = bytes.len();
34982        let mut i = 0;
34983        while i < len {
34984            if bytes[i] == b'%' && i + 1 < len {
34985                // Check for non-padded variants (%-X)
34986                if bytes[i + 1] == b'-' && i + 2 < len {
34987                    let replacement = match bytes[i + 2] {
34988                        b'd' => "dd",
34989                        b'm' => "mm",
34990                        _ => {
34991                            result.push('%');
34992                            i += 1;
34993                            continue;
34994                        }
34995                    };
34996                    result.push_str(replacement);
34997                    i += 3;
34998                } else {
34999                    let replacement = match bytes[i + 1] {
35000                        b'Y' => "yyyy",
35001                        b'y' => "yy",
35002                        b'm' => "mm",
35003                        b'd' => "DD",
35004                        b'H' => "hh24",
35005                        b'M' => "mi",
35006                        b'S' => "ss",
35007                        b'f' => "ff",
35008                        _ => {
35009                            result.push('%');
35010                            i += 1;
35011                            continue;
35012                        }
35013                    };
35014                    result.push_str(replacement);
35015                    i += 2;
35016                }
35017            } else {
35018                result.push(bytes[i] as char);
35019                i += 1;
35020            }
35021        }
35022        result
35023    }
35024
35025    fn generate_str_to_map(&mut self, e: &StrToMap) -> Result<()> {
35026        // STR_TO_MAP(this, pair_delim, key_value_delim)
35027        self.write_keyword("STR_TO_MAP");
35028        self.write("(");
35029        self.generate_expression(&e.this)?;
35030        // Spark/Hive: STR_TO_MAP needs explicit default delimiters
35031        let needs_defaults = matches!(
35032            self.config.dialect,
35033            Some(DialectType::Spark) | Some(DialectType::Hive) | Some(DialectType::Databricks)
35034        );
35035        if let Some(pair_delim) = &e.pair_delim {
35036            self.write(", ");
35037            self.generate_expression(pair_delim)?;
35038        } else if needs_defaults {
35039            self.write(", ','");
35040        }
35041        if let Some(key_value_delim) = &e.key_value_delim {
35042            self.write(", ");
35043            self.generate_expression(key_value_delim)?;
35044        } else if needs_defaults {
35045            self.write(", ':'");
35046        }
35047        self.write(")");
35048        Ok(())
35049    }
35050
35051    fn generate_str_to_time(&mut self, e: &StrToTime) -> Result<()> {
35052        // Detect format style: strftime (starts with %) vs Snowflake/Java
35053        let is_strftime = e.format.contains('%');
35054        // Helper: get strftime format from whatever style is stored
35055        let to_strftime = |f: &str| -> String {
35056            if is_strftime {
35057                f.to_string()
35058            } else {
35059                Self::snowflake_format_to_strftime(f)
35060            }
35061        };
35062        // Helper: get Java format
35063        let to_java = |f: &str| -> String {
35064            if is_strftime {
35065                Self::strftime_to_java_format(f)
35066            } else {
35067                Self::snowflake_format_to_spark(f)
35068            }
35069        };
35070        // Helper: get PG format
35071        let to_pg = |f: &str| -> String {
35072            if is_strftime {
35073                Self::strftime_to_postgres_format(f)
35074            } else {
35075                Self::convert_strptime_to_postgres_format(f)
35076            }
35077        };
35078
35079        match self.config.dialect {
35080            Some(DialectType::Exasol) => {
35081                self.write_keyword("TO_DATE");
35082                self.write("(");
35083                self.generate_expression(&e.this)?;
35084                self.write(", '");
35085                self.write(&Self::convert_strptime_to_exasol_format(&e.format));
35086                self.write("'");
35087                self.write(")");
35088            }
35089            Some(DialectType::BigQuery) => {
35090                // BigQuery: PARSE_TIMESTAMP(format, value) - note swapped args
35091                let fmt = to_strftime(&e.format);
35092                // BigQuery normalizes: %Y-%m-%d -> %F, %H:%M:%S -> %T
35093                let fmt = fmt.replace("%Y-%m-%d", "%F").replace("%H:%M:%S", "%T");
35094                self.write_keyword("PARSE_TIMESTAMP");
35095                self.write("('");
35096                self.write(&fmt);
35097                self.write("', ");
35098                self.generate_expression(&e.this)?;
35099                self.write(")");
35100            }
35101            Some(DialectType::Hive) => {
35102                // Hive: CAST(x AS TIMESTAMP) for simple date formats
35103                // Check both the raw format and the converted format (in case it's already Java)
35104                let java_fmt = to_java(&e.format);
35105                if java_fmt == "yyyy-MM-dd HH:mm:ss"
35106                    || java_fmt == "yyyy-MM-dd"
35107                    || e.format == "yyyy-MM-dd HH:mm:ss"
35108                    || e.format == "yyyy-MM-dd"
35109                {
35110                    self.write_keyword("CAST");
35111                    self.write("(");
35112                    self.generate_expression(&e.this)?;
35113                    self.write(" ");
35114                    self.write_keyword("AS TIMESTAMP");
35115                    self.write(")");
35116                } else {
35117                    // CAST(FROM_UNIXTIME(UNIX_TIMESTAMP(x, java_fmt)) AS TIMESTAMP)
35118                    self.write_keyword("CAST");
35119                    self.write("(");
35120                    self.write_keyword("FROM_UNIXTIME");
35121                    self.write("(");
35122                    self.write_keyword("UNIX_TIMESTAMP");
35123                    self.write("(");
35124                    self.generate_expression(&e.this)?;
35125                    self.write(", '");
35126                    self.write(&java_fmt);
35127                    self.write("')");
35128                    self.write(") ");
35129                    self.write_keyword("AS TIMESTAMP");
35130                    self.write(")");
35131                }
35132            }
35133            Some(DialectType::Spark) | Some(DialectType::Databricks) => {
35134                // Spark: TO_TIMESTAMP(value, java_format)
35135                let java_fmt = to_java(&e.format);
35136                self.write_keyword("TO_TIMESTAMP");
35137                self.write("(");
35138                self.generate_expression(&e.this)?;
35139                self.write(", '");
35140                self.write(&java_fmt);
35141                self.write("')");
35142            }
35143            Some(DialectType::MySQL) => {
35144                // MySQL: STR_TO_DATE(value, format)
35145                let mut fmt = to_strftime(&e.format);
35146                // MySQL uses %e for non-padded day, %T for %H:%M:%S
35147                fmt = fmt.replace("%-d", "%e");
35148                fmt = fmt.replace("%-m", "%c");
35149                fmt = fmt.replace("%H:%M:%S", "%T");
35150                self.write_keyword("STR_TO_DATE");
35151                self.write("(");
35152                self.generate_expression(&e.this)?;
35153                self.write(", '");
35154                self.write(&fmt);
35155                self.write("')");
35156            }
35157            Some(DialectType::Drill) => {
35158                // Drill: TO_TIMESTAMP(value, java_format) with T quoted in single quotes
35159                let java_fmt = to_java(&e.format);
35160                // Drill quotes literal T character: T -> ''T'' (double-quoted within SQL string literal)
35161                let java_fmt = java_fmt.replace('T', "''T''");
35162                self.write_keyword("TO_TIMESTAMP");
35163                self.write("(");
35164                self.generate_expression(&e.this)?;
35165                self.write(", '");
35166                self.write(&java_fmt);
35167                self.write("')");
35168            }
35169            Some(DialectType::Presto) | Some(DialectType::Trino) | Some(DialectType::Athena) => {
35170                // Presto: DATE_PARSE(value, strftime_format)
35171                let mut fmt = to_strftime(&e.format);
35172                // Presto uses %e for non-padded day, %T for %H:%M:%S
35173                fmt = fmt.replace("%-d", "%e");
35174                fmt = fmt.replace("%-m", "%c");
35175                fmt = fmt.replace("%H:%M:%S", "%T");
35176                self.write_keyword("DATE_PARSE");
35177                self.write("(");
35178                self.generate_expression(&e.this)?;
35179                self.write(", '");
35180                self.write(&fmt);
35181                self.write("')");
35182            }
35183            Some(DialectType::DuckDB) => {
35184                // DuckDB: STRPTIME(value, strftime_format)
35185                let fmt = to_strftime(&e.format);
35186                self.write_keyword("STRPTIME");
35187                self.write("(");
35188                self.generate_expression(&e.this)?;
35189                self.write(", '");
35190                self.write(&fmt);
35191                self.write("')");
35192            }
35193            Some(DialectType::PostgreSQL)
35194            | Some(DialectType::Redshift)
35195            | Some(DialectType::Materialize) => {
35196                // PostgreSQL/Redshift/Materialize: TO_TIMESTAMP(value, pg_format)
35197                let pg_fmt = to_pg(&e.format);
35198                self.write_keyword("TO_TIMESTAMP");
35199                self.write("(");
35200                self.generate_expression(&e.this)?;
35201                self.write(", '");
35202                self.write(&pg_fmt);
35203                self.write("')");
35204            }
35205            Some(DialectType::Oracle) => {
35206                // Oracle: TO_TIMESTAMP(value, pg_format)
35207                let pg_fmt = to_pg(&e.format);
35208                self.write_keyword("TO_TIMESTAMP");
35209                self.write("(");
35210                self.generate_expression(&e.this)?;
35211                self.write(", '");
35212                self.write(&pg_fmt);
35213                self.write("')");
35214            }
35215            Some(DialectType::Snowflake) => {
35216                // Snowflake: TO_TIMESTAMP(value, format) - native format
35217                self.write_keyword("TO_TIMESTAMP");
35218                self.write("(");
35219                self.generate_expression(&e.this)?;
35220                self.write(", '");
35221                self.write(&e.format);
35222                self.write("')");
35223            }
35224            _ => {
35225                // Default: STR_TO_TIME(this, format)
35226                self.write_keyword("STR_TO_TIME");
35227                self.write("(");
35228                self.generate_expression(&e.this)?;
35229                self.write(", '");
35230                self.write(&e.format);
35231                self.write("'");
35232                self.write(")");
35233            }
35234        }
35235        Ok(())
35236    }
35237
35238    /// Convert Snowflake normalized format to strftime-style (%Y, %m, etc.)
35239    fn snowflake_format_to_strftime(format: &str) -> String {
35240        let mut result = String::new();
35241        let chars: Vec<char> = format.chars().collect();
35242        let mut i = 0;
35243        while i < chars.len() {
35244            let remaining = &format[i..];
35245            if remaining.starts_with("yyyy") {
35246                result.push_str("%Y");
35247                i += 4;
35248            } else if remaining.starts_with("yy") {
35249                result.push_str("%y");
35250                i += 2;
35251            } else if remaining.starts_with("mmmm") {
35252                result.push_str("%B"); // full month name
35253                i += 4;
35254            } else if remaining.starts_with("mon") {
35255                result.push_str("%b"); // abbreviated month
35256                i += 3;
35257            } else if remaining.starts_with("mm") {
35258                result.push_str("%m");
35259                i += 2;
35260            } else if remaining.starts_with("DD") {
35261                result.push_str("%d");
35262                i += 2;
35263            } else if remaining.starts_with("dy") {
35264                result.push_str("%a"); // abbreviated day name
35265                i += 2;
35266            } else if remaining.starts_with("hh24") {
35267                result.push_str("%H");
35268                i += 4;
35269            } else if remaining.starts_with("hh12") {
35270                result.push_str("%I");
35271                i += 4;
35272            } else if remaining.starts_with("hh") {
35273                result.push_str("%H");
35274                i += 2;
35275            } else if remaining.starts_with("mi") {
35276                result.push_str("%M");
35277                i += 2;
35278            } else if remaining.starts_with("ss") {
35279                result.push_str("%S");
35280                i += 2;
35281            } else if remaining.starts_with("ff") {
35282                // Fractional seconds
35283                result.push_str("%f");
35284                i += 2;
35285                // Skip digits after ff (ff3, ff6, ff9)
35286                while i < chars.len() && chars[i].is_ascii_digit() {
35287                    i += 1;
35288                }
35289            } else if remaining.starts_with("am") || remaining.starts_with("pm") {
35290                result.push_str("%p");
35291                i += 2;
35292            } else if remaining.starts_with("tz") {
35293                result.push_str("%Z");
35294                i += 2;
35295            } else {
35296                result.push(chars[i]);
35297                i += 1;
35298            }
35299        }
35300        result
35301    }
35302
35303    /// Convert Snowflake normalized format to Spark format (Java-style)
35304    fn snowflake_format_to_spark(format: &str) -> String {
35305        let mut result = String::new();
35306        let chars: Vec<char> = format.chars().collect();
35307        let mut i = 0;
35308        while i < chars.len() {
35309            let remaining = &format[i..];
35310            if remaining.starts_with("yyyy") {
35311                result.push_str("yyyy");
35312                i += 4;
35313            } else if remaining.starts_with("yy") {
35314                result.push_str("yy");
35315                i += 2;
35316            } else if remaining.starts_with("mmmm") {
35317                result.push_str("MMMM"); // full month name
35318                i += 4;
35319            } else if remaining.starts_with("mon") {
35320                result.push_str("MMM"); // abbreviated month
35321                i += 3;
35322            } else if remaining.starts_with("mm") {
35323                result.push_str("MM");
35324                i += 2;
35325            } else if remaining.starts_with("DD") {
35326                result.push_str("dd");
35327                i += 2;
35328            } else if remaining.starts_with("dy") {
35329                result.push_str("EEE"); // abbreviated day name
35330                i += 2;
35331            } else if remaining.starts_with("hh24") {
35332                result.push_str("HH");
35333                i += 4;
35334            } else if remaining.starts_with("hh12") {
35335                result.push_str("hh");
35336                i += 4;
35337            } else if remaining.starts_with("hh") {
35338                result.push_str("HH");
35339                i += 2;
35340            } else if remaining.starts_with("mi") {
35341                result.push_str("mm");
35342                i += 2;
35343            } else if remaining.starts_with("ss") {
35344                result.push_str("ss");
35345                i += 2;
35346            } else if remaining.starts_with("ff") {
35347                result.push_str("SSS"); // milliseconds
35348                i += 2;
35349                // Skip digits after ff
35350                while i < chars.len() && chars[i].is_ascii_digit() {
35351                    i += 1;
35352                }
35353            } else if remaining.starts_with("am") || remaining.starts_with("pm") {
35354                result.push_str("a");
35355                i += 2;
35356            } else if remaining.starts_with("tz") {
35357                result.push_str("z");
35358                i += 2;
35359            } else {
35360                result.push(chars[i]);
35361                i += 1;
35362            }
35363        }
35364        result
35365    }
35366
35367    fn generate_str_to_unix(&mut self, e: &StrToUnix) -> Result<()> {
35368        match self.config.dialect {
35369            Some(DialectType::DuckDB) => {
35370                // DuckDB: EPOCH(STRPTIME(value, format))
35371                self.write_keyword("EPOCH");
35372                self.write("(");
35373                self.write_keyword("STRPTIME");
35374                self.write("(");
35375                if let Some(this) = &e.this {
35376                    self.generate_expression(this)?;
35377                }
35378                if let Some(format) = &e.format {
35379                    self.write(", '");
35380                    self.write(format);
35381                    self.write("'");
35382                }
35383                self.write("))");
35384            }
35385            Some(DialectType::Hive) => {
35386                // Hive: UNIX_TIMESTAMP(value, java_format) - convert C fmt to Java
35387                self.write_keyword("UNIX_TIMESTAMP");
35388                self.write("(");
35389                if let Some(this) = &e.this {
35390                    self.generate_expression(this)?;
35391                }
35392                if let Some(format) = &e.format {
35393                    let java_fmt = Self::strftime_to_java_format(format);
35394                    if java_fmt != "yyyy-MM-dd HH:mm:ss" {
35395                        self.write(", '");
35396                        self.write(&java_fmt);
35397                        self.write("'");
35398                    }
35399                }
35400                self.write(")");
35401            }
35402            Some(DialectType::Doris) | Some(DialectType::StarRocks) => {
35403                // Doris/StarRocks: UNIX_TIMESTAMP(value, format) - C format
35404                self.write_keyword("UNIX_TIMESTAMP");
35405                self.write("(");
35406                if let Some(this) = &e.this {
35407                    self.generate_expression(this)?;
35408                }
35409                if let Some(format) = &e.format {
35410                    self.write(", '");
35411                    self.write(format);
35412                    self.write("'");
35413                }
35414                self.write(")");
35415            }
35416            Some(DialectType::Presto) | Some(DialectType::Trino) => {
35417                // Presto: TO_UNIXTIME(COALESCE(TRY(DATE_PARSE(CAST(value AS VARCHAR), c_format)),
35418                //   PARSE_DATETIME(DATE_FORMAT(CAST(value AS TIMESTAMP), c_format), java_format)))
35419                let c_fmt = e.format.as_deref().unwrap_or("%Y-%m-%d %T");
35420                let java_fmt = Self::strftime_to_java_format(c_fmt);
35421                self.write_keyword("TO_UNIXTIME");
35422                self.write("(");
35423                self.write_keyword("COALESCE");
35424                self.write("(");
35425                self.write_keyword("TRY");
35426                self.write("(");
35427                self.write_keyword("DATE_PARSE");
35428                self.write("(");
35429                self.write_keyword("CAST");
35430                self.write("(");
35431                if let Some(this) = &e.this {
35432                    self.generate_expression(this)?;
35433                }
35434                self.write(" ");
35435                self.write_keyword("AS VARCHAR");
35436                self.write("), '");
35437                self.write(c_fmt);
35438                self.write("')), ");
35439                self.write_keyword("PARSE_DATETIME");
35440                self.write("(");
35441                self.write_keyword("DATE_FORMAT");
35442                self.write("(");
35443                self.write_keyword("CAST");
35444                self.write("(");
35445                if let Some(this) = &e.this {
35446                    self.generate_expression(this)?;
35447                }
35448                self.write(" ");
35449                self.write_keyword("AS TIMESTAMP");
35450                self.write("), '");
35451                self.write(c_fmt);
35452                self.write("'), '");
35453                self.write(&java_fmt);
35454                self.write("')))");
35455            }
35456            Some(DialectType::Spark) | Some(DialectType::Databricks) => {
35457                // Spark: UNIX_TIMESTAMP(value, java_format)
35458                self.write_keyword("UNIX_TIMESTAMP");
35459                self.write("(");
35460                if let Some(this) = &e.this {
35461                    self.generate_expression(this)?;
35462                }
35463                if let Some(format) = &e.format {
35464                    let java_fmt = Self::strftime_to_java_format(format);
35465                    self.write(", '");
35466                    self.write(&java_fmt);
35467                    self.write("'");
35468                }
35469                self.write(")");
35470            }
35471            _ => {
35472                // Default: STR_TO_UNIX(this, format)
35473                self.write_keyword("STR_TO_UNIX");
35474                self.write("(");
35475                if let Some(this) = &e.this {
35476                    self.generate_expression(this)?;
35477                }
35478                if let Some(format) = &e.format {
35479                    self.write(", '");
35480                    self.write(format);
35481                    self.write("'");
35482                }
35483                self.write(")");
35484            }
35485        }
35486        Ok(())
35487    }
35488
35489    fn generate_string_to_array(&mut self, e: &StringToArray) -> Result<()> {
35490        // STRING_TO_ARRAY(this, delimiter, null_string)
35491        self.write_keyword("STRING_TO_ARRAY");
35492        self.write("(");
35493        self.generate_expression(&e.this)?;
35494        if let Some(expression) = &e.expression {
35495            self.write(", ");
35496            self.generate_expression(expression)?;
35497        }
35498        if let Some(null_val) = &e.null {
35499            self.write(", ");
35500            self.generate_expression(null_val)?;
35501        }
35502        self.write(")");
35503        Ok(())
35504    }
35505
35506    fn generate_struct(&mut self, e: &Struct) -> Result<()> {
35507        if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
35508            // Snowflake: OBJECT_CONSTRUCT('key', value, 'key', value, ...)
35509            self.write_keyword("OBJECT_CONSTRUCT");
35510            self.write("(");
35511            for (i, (name, expr)) in e.fields.iter().enumerate() {
35512                if i > 0 {
35513                    self.write(", ");
35514                }
35515                if let Some(name) = name {
35516                    self.write("'");
35517                    self.write(name);
35518                    self.write("'");
35519                    self.write(", ");
35520                } else {
35521                    self.write("'_");
35522                    self.write(&i.to_string());
35523                    self.write("'");
35524                    self.write(", ");
35525                }
35526                self.generate_expression(expr)?;
35527            }
35528            self.write(")");
35529        } else if self.config.struct_curly_brace_notation {
35530            // DuckDB-style: {'key': value, ...}
35531            self.write("{");
35532            for (i, (name, expr)) in e.fields.iter().enumerate() {
35533                if i > 0 {
35534                    self.write(", ");
35535                }
35536                if let Some(name) = name {
35537                    // Quote the key as a string literal
35538                    self.write("'");
35539                    self.write(name);
35540                    self.write("'");
35541                    self.write(": ");
35542                } else {
35543                    // Unnamed field: use positional key
35544                    self.write("'_");
35545                    self.write(&i.to_string());
35546                    self.write("'");
35547                    self.write(": ");
35548                }
35549                self.generate_expression(expr)?;
35550            }
35551            self.write("}");
35552        } else {
35553            // Standard SQL struct notation
35554            // BigQuery/Spark/Databricks use: STRUCT(value AS name, ...)
35555            // Others (Presto etc.) use: STRUCT(name AS value, ...) or ROW(value, ...)
35556            let value_as_name = matches!(
35557                self.config.dialect,
35558                Some(DialectType::BigQuery)
35559                    | Some(DialectType::Spark)
35560                    | Some(DialectType::Databricks)
35561                    | Some(DialectType::Hive)
35562            );
35563            self.write_keyword("STRUCT");
35564            self.write("(");
35565            for (i, (name, expr)) in e.fields.iter().enumerate() {
35566                if i > 0 {
35567                    self.write(", ");
35568                }
35569                if let Some(name) = name {
35570                    if value_as_name {
35571                        // STRUCT(value AS name)
35572                        self.generate_expression(expr)?;
35573                        self.write_space();
35574                        self.write_keyword("AS");
35575                        self.write_space();
35576                        // Quote name if it contains spaces or special chars
35577                        let needs_quoting = name.contains(' ') || name.contains('-');
35578                        if needs_quoting {
35579                            if matches!(
35580                                self.config.dialect,
35581                                Some(DialectType::Spark)
35582                                    | Some(DialectType::Databricks)
35583                                    | Some(DialectType::Hive)
35584                            ) {
35585                                self.write("`");
35586                                self.write(name);
35587                                self.write("`");
35588                            } else {
35589                                self.write(name);
35590                            }
35591                        } else {
35592                            self.write(name);
35593                        }
35594                    } else {
35595                        // STRUCT(name AS value)
35596                        self.write(name);
35597                        self.write_space();
35598                        self.write_keyword("AS");
35599                        self.write_space();
35600                        self.generate_expression(expr)?;
35601                    }
35602                } else {
35603                    self.generate_expression(expr)?;
35604                }
35605            }
35606            self.write(")");
35607        }
35608        Ok(())
35609    }
35610
35611    fn generate_stuff(&mut self, e: &Stuff) -> Result<()> {
35612        // STUFF(this, start, length, expression)
35613        self.write_keyword("STUFF");
35614        self.write("(");
35615        self.generate_expression(&e.this)?;
35616        if let Some(start) = &e.start {
35617            self.write(", ");
35618            self.generate_expression(start)?;
35619        }
35620        if let Some(length) = e.length {
35621            self.write(", ");
35622            self.write(&length.to_string());
35623        }
35624        self.write(", ");
35625        self.generate_expression(&e.expression)?;
35626        self.write(")");
35627        Ok(())
35628    }
35629
35630    fn generate_substring_index(&mut self, e: &SubstringIndex) -> Result<()> {
35631        // SUBSTRING_INDEX(this, delimiter, count)
35632        self.write_keyword("SUBSTRING_INDEX");
35633        self.write("(");
35634        self.generate_expression(&e.this)?;
35635        if let Some(delimiter) = &e.delimiter {
35636            self.write(", ");
35637            self.generate_expression(delimiter)?;
35638        }
35639        if let Some(count) = &e.count {
35640            self.write(", ");
35641            self.generate_expression(count)?;
35642        }
35643        self.write(")");
35644        Ok(())
35645    }
35646
35647    fn generate_summarize(&mut self, e: &Summarize) -> Result<()> {
35648        // SUMMARIZE [TABLE] this
35649        self.write_keyword("SUMMARIZE");
35650        if e.table.is_some() {
35651            self.write_space();
35652            self.write_keyword("TABLE");
35653        }
35654        self.write_space();
35655        self.generate_expression(&e.this)?;
35656        Ok(())
35657    }
35658
35659    fn generate_systimestamp(&mut self, _e: &Systimestamp) -> Result<()> {
35660        // SYSTIMESTAMP
35661        self.write_keyword("SYSTIMESTAMP");
35662        Ok(())
35663    }
35664
35665    fn generate_table_alias(&mut self, e: &TableAlias) -> Result<()> {
35666        // alias (columns...)
35667        if let Some(this) = &e.this {
35668            self.generate_expression(this)?;
35669        }
35670        if !e.columns.is_empty() {
35671            self.write("(");
35672            for (i, col) in e.columns.iter().enumerate() {
35673                if i > 0 {
35674                    self.write(", ");
35675                }
35676                self.generate_expression(col)?;
35677            }
35678            self.write(")");
35679        }
35680        Ok(())
35681    }
35682
35683    fn generate_table_from_rows(&mut self, e: &TableFromRows) -> Result<()> {
35684        // TABLE(this) [AS alias]
35685        self.write_keyword("TABLE");
35686        self.write("(");
35687        self.generate_expression(&e.this)?;
35688        self.write(")");
35689        if let Some(alias) = &e.alias {
35690            self.write_space();
35691            self.write_keyword("AS");
35692            self.write_space();
35693            self.write(alias);
35694        }
35695        Ok(())
35696    }
35697
35698    fn generate_rows_from(&mut self, e: &RowsFrom) -> Result<()> {
35699        // ROWS FROM (func1(...) AS alias1(...), func2(...) AS alias2(...)) [WITH ORDINALITY] [AS alias(...)]
35700        self.write_keyword("ROWS FROM");
35701        self.write(" (");
35702        for (i, expr) in e.expressions.iter().enumerate() {
35703            if i > 0 {
35704                self.write(", ");
35705            }
35706            // Each expression is either:
35707            // - A plain function (no alias)
35708            // - A Tuple(function, TableAlias) for: FUNC() AS alias(col type, ...)
35709            match expr {
35710                Expression::Tuple(tuple) if tuple.expressions.len() == 2 => {
35711                    // First element is the function, second is the TableAlias
35712                    self.generate_expression(&tuple.expressions[0])?;
35713                    self.write_space();
35714                    self.write_keyword("AS");
35715                    self.write_space();
35716                    self.generate_expression(&tuple.expressions[1])?;
35717                }
35718                _ => {
35719                    self.generate_expression(expr)?;
35720                }
35721            }
35722        }
35723        self.write(")");
35724        if e.ordinality {
35725            self.write_space();
35726            self.write_keyword("WITH ORDINALITY");
35727        }
35728        if let Some(alias) = &e.alias {
35729            self.write_space();
35730            self.write_keyword("AS");
35731            self.write_space();
35732            self.generate_expression(alias)?;
35733        }
35734        Ok(())
35735    }
35736
35737    fn generate_table_sample(&mut self, e: &TableSample) -> Result<()> {
35738        use crate::dialects::DialectType;
35739
35740        // New wrapper pattern: expression + Sample struct
35741        if let (Some(this), Some(sample)) = (&e.this, &e.sample) {
35742            // For alias_post_tablesample dialects (Spark, Hive, Oracle): output base expr, TABLESAMPLE, then alias
35743            if self.config.alias_post_tablesample {
35744                // Handle Subquery with alias and Alias wrapper
35745                if let Expression::Subquery(ref s) = **this {
35746                    if let Some(ref alias) = s.alias {
35747                        // Create a clone without alias for output
35748                        let mut subquery_no_alias = (**s).clone();
35749                        subquery_no_alias.alias = None;
35750                        subquery_no_alias.column_aliases = Vec::new();
35751                        self.generate_expression(&Expression::Subquery(Box::new(
35752                            subquery_no_alias,
35753                        )))?;
35754                        self.write_space();
35755                        self.write_keyword("TABLESAMPLE");
35756                        self.generate_sample_body(sample)?;
35757                        if let Some(ref seed) = sample.seed {
35758                            self.write_space();
35759                            let use_seed = sample.use_seed_keyword
35760                                && !matches!(
35761                                    self.config.dialect,
35762                                    Some(crate::dialects::DialectType::Databricks)
35763                                        | Some(crate::dialects::DialectType::Spark)
35764                                );
35765                            if use_seed {
35766                                self.write_keyword("SEED");
35767                            } else {
35768                                self.write_keyword("REPEATABLE");
35769                            }
35770                            self.write(" (");
35771                            self.generate_expression(seed)?;
35772                            self.write(")");
35773                        }
35774                        self.write_space();
35775                        self.write_keyword("AS");
35776                        self.write_space();
35777                        self.generate_identifier(alias)?;
35778                        return Ok(());
35779                    }
35780                } else if let Expression::Alias(ref a) = **this {
35781                    // Output the base expression without alias
35782                    self.generate_expression(&a.this)?;
35783                    self.write_space();
35784                    self.write_keyword("TABLESAMPLE");
35785                    self.generate_sample_body(sample)?;
35786                    if let Some(ref seed) = sample.seed {
35787                        self.write_space();
35788                        let use_seed = sample.use_seed_keyword
35789                            && !matches!(
35790                                self.config.dialect,
35791                                Some(crate::dialects::DialectType::Databricks)
35792                                    | Some(crate::dialects::DialectType::Spark)
35793                            );
35794                        if use_seed {
35795                            self.write_keyword("SEED");
35796                        } else {
35797                            self.write_keyword("REPEATABLE");
35798                        }
35799                        self.write(" (");
35800                        self.generate_expression(seed)?;
35801                        self.write(")");
35802                    }
35803                    // Output alias after TABLESAMPLE
35804                    self.write_space();
35805                    self.write_keyword("AS");
35806                    self.write_space();
35807                    self.generate_identifier(&a.alias)?;
35808                    return Ok(());
35809                }
35810            }
35811            // Default: generate wrapped expression first, then TABLESAMPLE
35812            self.generate_expression(this)?;
35813            self.write_space();
35814            self.write_keyword("TABLESAMPLE");
35815            self.generate_sample_body(sample)?;
35816            // Seed for table-level sample
35817            if let Some(ref seed) = sample.seed {
35818                self.write_space();
35819                // Databricks uses REPEATABLE, not SEED
35820                let use_seed = sample.use_seed_keyword
35821                    && !matches!(
35822                        self.config.dialect,
35823                        Some(crate::dialects::DialectType::Databricks)
35824                            | Some(crate::dialects::DialectType::Spark)
35825                    );
35826                if use_seed {
35827                    self.write_keyword("SEED");
35828                } else {
35829                    self.write_keyword("REPEATABLE");
35830                }
35831                self.write(" (");
35832                self.generate_expression(seed)?;
35833                self.write(")");
35834            }
35835            return Ok(());
35836        }
35837
35838        // Legacy pattern: TABLESAMPLE [method] (expressions) or TABLESAMPLE method BUCKET numerator OUT OF denominator
35839        self.write_keyword("TABLESAMPLE");
35840        if let Some(method) = &e.method {
35841            self.write_space();
35842            self.write_keyword(method);
35843        } else if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
35844            // Snowflake defaults to BERNOULLI when no method is specified
35845            self.write_space();
35846            self.write_keyword("BERNOULLI");
35847        }
35848        if let (Some(numerator), Some(denominator)) = (&e.bucket_numerator, &e.bucket_denominator) {
35849            self.write_space();
35850            self.write_keyword("BUCKET");
35851            self.write_space();
35852            self.generate_expression(numerator)?;
35853            self.write_space();
35854            self.write_keyword("OUT OF");
35855            self.write_space();
35856            self.generate_expression(denominator)?;
35857            if let Some(field) = &e.bucket_field {
35858                self.write_space();
35859                self.write_keyword("ON");
35860                self.write_space();
35861                self.generate_expression(field)?;
35862            }
35863        } else if !e.expressions.is_empty() {
35864            self.write(" (");
35865            for (i, expr) in e.expressions.iter().enumerate() {
35866                if i > 0 {
35867                    self.write(", ");
35868                }
35869                self.generate_expression(expr)?;
35870            }
35871            self.write(")");
35872        } else if let Some(percent) = &e.percent {
35873            self.write(" (");
35874            self.generate_expression(percent)?;
35875            self.write_space();
35876            self.write_keyword("PERCENT");
35877            self.write(")");
35878        }
35879        Ok(())
35880    }
35881
35882    fn generate_tag(&mut self, e: &Tag) -> Result<()> {
35883        // [prefix]this[postfix]
35884        if let Some(prefix) = &e.prefix {
35885            self.generate_expression(prefix)?;
35886        }
35887        if let Some(this) = &e.this {
35888            self.generate_expression(this)?;
35889        }
35890        if let Some(postfix) = &e.postfix {
35891            self.generate_expression(postfix)?;
35892        }
35893        Ok(())
35894    }
35895
35896    fn generate_tags(&mut self, e: &Tags) -> Result<()> {
35897        // TAG (expressions)
35898        self.write_keyword("TAG");
35899        self.write(" (");
35900        for (i, expr) in e.expressions.iter().enumerate() {
35901            if i > 0 {
35902                self.write(", ");
35903            }
35904            self.generate_expression(expr)?;
35905        }
35906        self.write(")");
35907        Ok(())
35908    }
35909
35910    fn generate_temporary_property(&mut self, e: &TemporaryProperty) -> Result<()> {
35911        // TEMPORARY or TEMP or [this] TEMPORARY
35912        if let Some(this) = &e.this {
35913            self.generate_expression(this)?;
35914            self.write_space();
35915        }
35916        self.write_keyword("TEMPORARY");
35917        Ok(())
35918    }
35919
35920    /// Generate a Time function expression
35921    /// For most dialects: TIME('value')
35922    fn generate_time_func(&mut self, e: &UnaryFunc) -> Result<()> {
35923        // Standard: TIME(value)
35924        self.write_keyword("TIME");
35925        self.write("(");
35926        self.generate_expression(&e.this)?;
35927        self.write(")");
35928        Ok(())
35929    }
35930
35931    fn generate_time_add(&mut self, e: &TimeAdd) -> Result<()> {
35932        // TIME_ADD(this, expression, unit)
35933        self.write_keyword("TIME_ADD");
35934        self.write("(");
35935        self.generate_expression(&e.this)?;
35936        self.write(", ");
35937        self.generate_expression(&e.expression)?;
35938        if let Some(unit) = &e.unit {
35939            self.write(", ");
35940            self.write_keyword(unit);
35941        }
35942        self.write(")");
35943        Ok(())
35944    }
35945
35946    fn generate_time_diff(&mut self, e: &TimeDiff) -> Result<()> {
35947        // TIME_DIFF(this, expression, unit)
35948        self.write_keyword("TIME_DIFF");
35949        self.write("(");
35950        self.generate_expression(&e.this)?;
35951        self.write(", ");
35952        self.generate_expression(&e.expression)?;
35953        if let Some(unit) = &e.unit {
35954            self.write(", ");
35955            self.write_keyword(unit);
35956        }
35957        self.write(")");
35958        Ok(())
35959    }
35960
35961    fn generate_time_from_parts(&mut self, e: &TimeFromParts) -> Result<()> {
35962        // TIME_FROM_PARTS(hour, minute, second, nanosecond)
35963        self.write_keyword("TIME_FROM_PARTS");
35964        self.write("(");
35965        let mut first = true;
35966        if let Some(hour) = &e.hour {
35967            self.generate_expression(hour)?;
35968            first = false;
35969        }
35970        if let Some(minute) = &e.min {
35971            if !first {
35972                self.write(", ");
35973            }
35974            self.generate_expression(minute)?;
35975            first = false;
35976        }
35977        if let Some(second) = &e.sec {
35978            if !first {
35979                self.write(", ");
35980            }
35981            self.generate_expression(second)?;
35982            first = false;
35983        }
35984        if let Some(ns) = &e.nano {
35985            if !first {
35986                self.write(", ");
35987            }
35988            self.generate_expression(ns)?;
35989        }
35990        self.write(")");
35991        Ok(())
35992    }
35993
35994    fn generate_time_slice(&mut self, e: &TimeSlice) -> Result<()> {
35995        // TIME_SLICE(this, expression, unit)
35996        self.write_keyword("TIME_SLICE");
35997        self.write("(");
35998        self.generate_expression(&e.this)?;
35999        self.write(", ");
36000        self.generate_expression(&e.expression)?;
36001        self.write(", ");
36002        self.write_keyword(&e.unit);
36003        self.write(")");
36004        Ok(())
36005    }
36006
36007    fn generate_time_str_to_time(&mut self, e: &TimeStrToTime) -> Result<()> {
36008        // TIME_STR_TO_TIME(this)
36009        self.write_keyword("TIME_STR_TO_TIME");
36010        self.write("(");
36011        self.generate_expression(&e.this)?;
36012        self.write(")");
36013        Ok(())
36014    }
36015
36016    fn generate_time_sub(&mut self, e: &TimeSub) -> Result<()> {
36017        // TIME_SUB(this, expression, unit)
36018        self.write_keyword("TIME_SUB");
36019        self.write("(");
36020        self.generate_expression(&e.this)?;
36021        self.write(", ");
36022        self.generate_expression(&e.expression)?;
36023        if let Some(unit) = &e.unit {
36024            self.write(", ");
36025            self.write_keyword(unit);
36026        }
36027        self.write(")");
36028        Ok(())
36029    }
36030
36031    fn generate_time_to_str(&mut self, e: &TimeToStr) -> Result<()> {
36032        match self.config.dialect {
36033            Some(DialectType::Exasol) => {
36034                // Exasol uses TO_CHAR with Exasol-specific format
36035                self.write_keyword("TO_CHAR");
36036                self.write("(");
36037                self.generate_expression(&e.this)?;
36038                self.write(", '");
36039                self.write(&Self::convert_strptime_to_exasol_format(&e.format));
36040                self.write("'");
36041                self.write(")");
36042            }
36043            Some(DialectType::PostgreSQL)
36044            | Some(DialectType::Redshift)
36045            | Some(DialectType::Materialize) => {
36046                // PostgreSQL/Redshift/Materialize uses TO_CHAR with PG-specific format
36047                self.write_keyword("TO_CHAR");
36048                self.write("(");
36049                self.generate_expression(&e.this)?;
36050                self.write(", '");
36051                self.write(&Self::convert_strptime_to_postgres_format(&e.format));
36052                self.write("'");
36053                self.write(")");
36054            }
36055            Some(DialectType::Oracle) => {
36056                // Oracle uses TO_CHAR with PG-like format
36057                self.write_keyword("TO_CHAR");
36058                self.write("(");
36059                self.generate_expression(&e.this)?;
36060                self.write(", '");
36061                self.write(&Self::convert_strptime_to_postgres_format(&e.format));
36062                self.write("'");
36063                self.write(")");
36064            }
36065            Some(DialectType::Drill) => {
36066                // Drill: TO_CHAR with Java format
36067                self.write_keyword("TO_CHAR");
36068                self.write("(");
36069                self.generate_expression(&e.this)?;
36070                self.write(", '");
36071                self.write(&Self::strftime_to_java_format(&e.format));
36072                self.write("'");
36073                self.write(")");
36074            }
36075            Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
36076                // TSQL: FORMAT(value, format) with .NET-style format
36077                self.write_keyword("FORMAT");
36078                self.write("(");
36079                self.generate_expression(&e.this)?;
36080                self.write(", '");
36081                self.write(&Self::strftime_to_tsql_format(&e.format));
36082                self.write("'");
36083                self.write(")");
36084            }
36085            Some(DialectType::DuckDB) => {
36086                // DuckDB: STRFTIME(value, format) - keeps C format
36087                self.write_keyword("STRFTIME");
36088                self.write("(");
36089                self.generate_expression(&e.this)?;
36090                self.write(", '");
36091                self.write(&e.format);
36092                self.write("'");
36093                self.write(")");
36094            }
36095            Some(DialectType::BigQuery) => {
36096                // BigQuery: FORMAT_DATE(format, value) - note swapped arg order
36097                // Normalize: %Y-%m-%d -> %F, %H:%M:%S -> %T
36098                let fmt = e.format.replace("%Y-%m-%d", "%F").replace("%H:%M:%S", "%T");
36099                self.write_keyword("FORMAT_DATE");
36100                self.write("('");
36101                self.write(&fmt);
36102                self.write("', ");
36103                self.generate_expression(&e.this)?;
36104                self.write(")");
36105            }
36106            Some(DialectType::Hive) | Some(DialectType::Spark) | Some(DialectType::Databricks) => {
36107                // Hive/Spark: DATE_FORMAT(value, java_format)
36108                self.write_keyword("DATE_FORMAT");
36109                self.write("(");
36110                self.generate_expression(&e.this)?;
36111                self.write(", '");
36112                self.write(&Self::strftime_to_java_format(&e.format));
36113                self.write("'");
36114                self.write(")");
36115            }
36116            Some(DialectType::Presto) | Some(DialectType::Trino) | Some(DialectType::Athena) => {
36117                // Presto/Trino: DATE_FORMAT(value, format) - keeps C format
36118                self.write_keyword("DATE_FORMAT");
36119                self.write("(");
36120                self.generate_expression(&e.this)?;
36121                self.write(", '");
36122                self.write(&e.format);
36123                self.write("'");
36124                self.write(")");
36125            }
36126            Some(DialectType::Doris) | Some(DialectType::StarRocks) => {
36127                // Doris/StarRocks: DATE_FORMAT(value, format) - keeps C format
36128                self.write_keyword("DATE_FORMAT");
36129                self.write("(");
36130                self.generate_expression(&e.this)?;
36131                self.write(", '");
36132                self.write(&e.format);
36133                self.write("'");
36134                self.write(")");
36135            }
36136            _ => {
36137                // Default: TIME_TO_STR(this, format)
36138                self.write_keyword("TIME_TO_STR");
36139                self.write("(");
36140                self.generate_expression(&e.this)?;
36141                self.write(", '");
36142                self.write(&e.format);
36143                self.write("'");
36144                self.write(")");
36145            }
36146        }
36147        Ok(())
36148    }
36149
36150    fn generate_time_to_unix(&mut self, e: &crate::expressions::UnaryFunc) -> Result<()> {
36151        match self.config.dialect {
36152            Some(DialectType::DuckDB) => {
36153                // DuckDB: EPOCH(x)
36154                self.write_keyword("EPOCH");
36155                self.write("(");
36156                self.generate_expression(&e.this)?;
36157                self.write(")");
36158            }
36159            Some(DialectType::Hive)
36160            | Some(DialectType::Spark)
36161            | Some(DialectType::Databricks)
36162            | Some(DialectType::Doris)
36163            | Some(DialectType::StarRocks)
36164            | Some(DialectType::Drill) => {
36165                // Hive/Spark/Doris/StarRocks/Drill: UNIX_TIMESTAMP(x)
36166                self.write_keyword("UNIX_TIMESTAMP");
36167                self.write("(");
36168                self.generate_expression(&e.this)?;
36169                self.write(")");
36170            }
36171            Some(DialectType::Presto) | Some(DialectType::Trino) => {
36172                // Presto: TO_UNIXTIME(x)
36173                self.write_keyword("TO_UNIXTIME");
36174                self.write("(");
36175                self.generate_expression(&e.this)?;
36176                self.write(")");
36177            }
36178            _ => {
36179                // Default: TIME_TO_UNIX(x)
36180                self.write_keyword("TIME_TO_UNIX");
36181                self.write("(");
36182                self.generate_expression(&e.this)?;
36183                self.write(")");
36184            }
36185        }
36186        Ok(())
36187    }
36188
36189    fn generate_time_str_to_date(&mut self, e: &crate::expressions::UnaryFunc) -> Result<()> {
36190        match self.config.dialect {
36191            Some(DialectType::Hive) => {
36192                // Hive: TO_DATE(x)
36193                self.write_keyword("TO_DATE");
36194                self.write("(");
36195                self.generate_expression(&e.this)?;
36196                self.write(")");
36197            }
36198            _ => {
36199                // Default: TIME_STR_TO_DATE(x)
36200                self.write_keyword("TIME_STR_TO_DATE");
36201                self.write("(");
36202                self.generate_expression(&e.this)?;
36203                self.write(")");
36204            }
36205        }
36206        Ok(())
36207    }
36208
36209    fn generate_time_trunc(&mut self, e: &TimeTrunc) -> Result<()> {
36210        // TIME_TRUNC(this, unit)
36211        self.write_keyword("TIME_TRUNC");
36212        self.write("(");
36213        self.generate_expression(&e.this)?;
36214        self.write(", ");
36215        self.write_keyword(&e.unit);
36216        self.write(")");
36217        Ok(())
36218    }
36219
36220    fn generate_time_unit(&mut self, e: &TimeUnit) -> Result<()> {
36221        // Just output the unit name
36222        if let Some(unit) = &e.unit {
36223            self.write_keyword(unit);
36224        }
36225        Ok(())
36226    }
36227
36228    /// Generate a Timestamp function expression
36229    /// For Exasol: {ts'value'} -> TO_TIMESTAMP('value')
36230    /// For other dialects: TIMESTAMP('value')
36231    fn generate_timestamp_func(&mut self, e: &TimestampFunc) -> Result<()> {
36232        use crate::dialects::DialectType;
36233        use crate::expressions::Literal;
36234
36235        match self.config.dialect {
36236            // Exasol uses TO_TIMESTAMP for Timestamp expressions
36237            Some(DialectType::Exasol) => {
36238                self.write_keyword("TO_TIMESTAMP");
36239                self.write("(");
36240                // Extract the string value from the expression if it's a string literal
36241                if let Some(this) = &e.this {
36242                    match this.as_ref() {
36243                        Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
36244                            let Literal::String(s) = lit.as_ref() else {
36245                                unreachable!()
36246                            };
36247                            self.write("'");
36248                            self.write(s);
36249                            self.write("'");
36250                        }
36251                        _ => {
36252                            self.generate_expression(this)?;
36253                        }
36254                    }
36255                }
36256                self.write(")");
36257            }
36258            // Standard: TIMESTAMP(value) or TIMESTAMP(value, zone)
36259            _ => {
36260                self.write_keyword("TIMESTAMP");
36261                self.write("(");
36262                if let Some(this) = &e.this {
36263                    self.generate_expression(this)?;
36264                }
36265                if let Some(zone) = &e.zone {
36266                    self.write(", ");
36267                    self.generate_expression(zone)?;
36268                }
36269                self.write(")");
36270            }
36271        }
36272        Ok(())
36273    }
36274
36275    fn generate_timestamp_add(&mut self, e: &TimestampAdd) -> Result<()> {
36276        // TIMESTAMP_ADD(this, expression, unit)
36277        self.write_keyword("TIMESTAMP_ADD");
36278        self.write("(");
36279        self.generate_expression(&e.this)?;
36280        self.write(", ");
36281        self.generate_expression(&e.expression)?;
36282        if let Some(unit) = &e.unit {
36283            self.write(", ");
36284            self.write_keyword(unit);
36285        }
36286        self.write(")");
36287        Ok(())
36288    }
36289
36290    fn generate_timestamp_diff(&mut self, e: &TimestampDiff) -> Result<()> {
36291        // TIMESTAMP_DIFF(this, expression, unit)
36292        self.write_keyword("TIMESTAMP_DIFF");
36293        self.write("(");
36294        self.generate_expression(&e.this)?;
36295        self.write(", ");
36296        self.generate_expression(&e.expression)?;
36297        if let Some(unit) = &e.unit {
36298            self.write(", ");
36299            self.write_keyword(unit);
36300        }
36301        self.write(")");
36302        Ok(())
36303    }
36304
36305    fn generate_timestamp_from_parts(&mut self, e: &TimestampFromParts) -> Result<()> {
36306        // TIMESTAMP_FROM_PARTS(this, expression)
36307        self.write_keyword("TIMESTAMP_FROM_PARTS");
36308        self.write("(");
36309        if let Some(this) = &e.this {
36310            self.generate_expression(this)?;
36311        }
36312        if let Some(expression) = &e.expression {
36313            self.write(", ");
36314            self.generate_expression(expression)?;
36315        }
36316        if let Some(zone) = &e.zone {
36317            self.write(", ");
36318            self.generate_expression(zone)?;
36319        }
36320        if let Some(milli) = &e.milli {
36321            self.write(", ");
36322            self.generate_expression(milli)?;
36323        }
36324        self.write(")");
36325        Ok(())
36326    }
36327
36328    fn generate_timestamp_sub(&mut self, e: &TimestampSub) -> Result<()> {
36329        // TIMESTAMP_SUB(this, INTERVAL expression unit)
36330        self.write_keyword("TIMESTAMP_SUB");
36331        self.write("(");
36332        self.generate_expression(&e.this)?;
36333        self.write(", ");
36334        self.write_keyword("INTERVAL");
36335        self.write_space();
36336        self.generate_expression(&e.expression)?;
36337        if let Some(unit) = &e.unit {
36338            self.write_space();
36339            self.write_keyword(unit);
36340        }
36341        self.write(")");
36342        Ok(())
36343    }
36344
36345    fn generate_timestamp_tz_from_parts(&mut self, e: &TimestampTzFromParts) -> Result<()> {
36346        // TIMESTAMP_TZ_FROM_PARTS(...)
36347        self.write_keyword("TIMESTAMP_TZ_FROM_PARTS");
36348        self.write("(");
36349        if let Some(zone) = &e.zone {
36350            self.generate_expression(zone)?;
36351        }
36352        self.write(")");
36353        Ok(())
36354    }
36355
36356    fn generate_to_binary(&mut self, e: &ToBinary) -> Result<()> {
36357        // TO_BINARY(this, [format])
36358        self.write_keyword("TO_BINARY");
36359        self.write("(");
36360        self.generate_expression(&e.this)?;
36361        if let Some(format) = &e.format {
36362            self.write(", '");
36363            self.write(format);
36364            self.write("'");
36365        }
36366        self.write(")");
36367        Ok(())
36368    }
36369
36370    fn generate_to_boolean(&mut self, e: &ToBoolean) -> Result<()> {
36371        // TO_BOOLEAN(this)
36372        self.write_keyword("TO_BOOLEAN");
36373        self.write("(");
36374        self.generate_expression(&e.this)?;
36375        self.write(")");
36376        Ok(())
36377    }
36378
36379    fn generate_to_char(&mut self, e: &ToChar) -> Result<()> {
36380        // TO_CHAR(this, [format], [nlsparam])
36381        self.write_keyword("TO_CHAR");
36382        self.write("(");
36383        self.generate_expression(&e.this)?;
36384        if let Some(format) = &e.format {
36385            self.write(", '");
36386            self.write(format);
36387            self.write("'");
36388        }
36389        if let Some(nlsparam) = &e.nlsparam {
36390            self.write(", ");
36391            self.generate_expression(nlsparam)?;
36392        }
36393        self.write(")");
36394        Ok(())
36395    }
36396
36397    fn generate_to_decfloat(&mut self, e: &ToDecfloat) -> Result<()> {
36398        // TO_DECFLOAT(this, [format])
36399        self.write_keyword("TO_DECFLOAT");
36400        self.write("(");
36401        self.generate_expression(&e.this)?;
36402        if let Some(format) = &e.format {
36403            self.write(", '");
36404            self.write(format);
36405            self.write("'");
36406        }
36407        self.write(")");
36408        Ok(())
36409    }
36410
36411    fn generate_to_double(&mut self, e: &ToDouble) -> Result<()> {
36412        // TO_DOUBLE(this, [format])
36413        self.write_keyword("TO_DOUBLE");
36414        self.write("(");
36415        self.generate_expression(&e.this)?;
36416        if let Some(format) = &e.format {
36417            self.write(", '");
36418            self.write(format);
36419            self.write("'");
36420        }
36421        self.write(")");
36422        Ok(())
36423    }
36424
36425    fn generate_to_file(&mut self, e: &ToFile) -> Result<()> {
36426        // TO_FILE(this, path)
36427        self.write_keyword("TO_FILE");
36428        self.write("(");
36429        self.generate_expression(&e.this)?;
36430        if let Some(path) = &e.path {
36431            self.write(", ");
36432            self.generate_expression(path)?;
36433        }
36434        self.write(")");
36435        Ok(())
36436    }
36437
36438    fn generate_to_number(&mut self, e: &ToNumber) -> Result<()> {
36439        // TO_NUMBER or TRY_TO_NUMBER (this, [format], [precision], [scale])
36440        // If safe flag is set, output TRY_TO_NUMBER
36441        let is_safe = e.safe.is_some();
36442        if is_safe {
36443            self.write_keyword("TRY_TO_NUMBER");
36444        } else {
36445            self.write_keyword("TO_NUMBER");
36446        }
36447        self.write("(");
36448        self.generate_expression(&e.this)?;
36449        let precision_is_snowflake_default = e.precision.is_none()
36450            || matches!(
36451                e.precision.as_deref(),
36452                Some(Expression::Literal(lit))
36453                    if matches!(lit.as_ref(), Literal::Number(n) if n == "0")
36454            );
36455        let is_snowflake_default_precision =
36456            matches!(self.config.dialect, Some(DialectType::Snowflake))
36457                && e.nlsparam.is_none()
36458                && e.scale.is_none()
36459                && matches!(
36460                    e.format.as_deref(),
36461                    Some(Expression::Literal(lit))
36462                        if matches!(lit.as_ref(), Literal::Number(n) if n == "38")
36463                )
36464                && precision_is_snowflake_default;
36465
36466        if !is_snowflake_default_precision {
36467            if let Some(format) = &e.format {
36468                self.write(", ");
36469                self.generate_expression(format)?;
36470            }
36471            if let Some(nlsparam) = &e.nlsparam {
36472                self.write(", ");
36473                self.generate_expression(nlsparam)?;
36474            }
36475            if let Some(precision) = &e.precision {
36476                self.write(", ");
36477                self.generate_expression(precision)?;
36478            }
36479            if let Some(scale) = &e.scale {
36480                self.write(", ");
36481                self.generate_expression(scale)?;
36482            }
36483        }
36484        self.write(")");
36485        Ok(())
36486    }
36487
36488    fn generate_to_table_property(&mut self, e: &ToTableProperty) -> Result<()> {
36489        // TO_TABLE this
36490        self.write_keyword("TO_TABLE");
36491        self.write_space();
36492        self.generate_expression(&e.this)?;
36493        Ok(())
36494    }
36495
36496    fn generate_transaction(&mut self, e: &Transaction) -> Result<()> {
36497        // Check mark to determine the format
36498        let mark_text = e.mark.as_ref().map(|m| match m.as_ref() {
36499            Expression::Identifier(id) => id.name.clone(),
36500            Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
36501                let Literal::String(s) = lit.as_ref() else {
36502                    unreachable!()
36503                };
36504                s.clone()
36505            }
36506            _ => String::new(),
36507        });
36508
36509        let is_start = mark_text.as_ref().map_or(false, |s| s == "START");
36510        let has_transaction_keyword = mark_text.as_ref().map_or(false, |s| s == "TRANSACTION");
36511        let has_with_mark = e.mark.as_ref().map_or(false, |m| {
36512            matches!(m.as_ref(), Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)))
36513        });
36514
36515        // For Presto/Trino: always use START TRANSACTION
36516        let use_start_transaction = matches!(
36517            self.config.dialect,
36518            Some(DialectType::Presto) | Some(DialectType::Trino) | Some(DialectType::Athena)
36519        );
36520        // For most dialects: strip TRANSACTION keyword
36521        let strip_transaction = matches!(
36522            self.config.dialect,
36523            Some(DialectType::Snowflake)
36524                | Some(DialectType::PostgreSQL)
36525                | Some(DialectType::Redshift)
36526                | Some(DialectType::MySQL)
36527                | Some(DialectType::Hive)
36528                | Some(DialectType::Spark)
36529                | Some(DialectType::Databricks)
36530                | Some(DialectType::DuckDB)
36531                | Some(DialectType::Oracle)
36532                | Some(DialectType::Doris)
36533                | Some(DialectType::StarRocks)
36534                | Some(DialectType::Materialize)
36535                | Some(DialectType::ClickHouse)
36536        );
36537
36538        if is_start || use_start_transaction {
36539            // START TRANSACTION [modes]
36540            self.write_keyword("START TRANSACTION");
36541            if let Some(modes) = &e.modes {
36542                self.write_space();
36543                self.generate_expression(modes)?;
36544            }
36545        } else {
36546            // BEGIN [DEFERRED|IMMEDIATE|EXCLUSIVE] [TRANSACTION] [transaction_name] [WITH MARK 'desc']
36547            self.write_keyword("BEGIN");
36548
36549            // Check if `this` is a transaction kind (DEFERRED/IMMEDIATE/EXCLUSIVE)
36550            let is_kind = e.this.as_ref().map_or(false, |t| {
36551                if let Expression::Identifier(id) = t.as_ref() {
36552                    id.name.eq_ignore_ascii_case("DEFERRED")
36553                        || id.name.eq_ignore_ascii_case("IMMEDIATE")
36554                        || id.name.eq_ignore_ascii_case("EXCLUSIVE")
36555                } else {
36556                    false
36557                }
36558            });
36559
36560            // Output kind before TRANSACTION keyword
36561            if is_kind {
36562                if let Some(this) = &e.this {
36563                    self.write_space();
36564                    if let Expression::Identifier(id) = this.as_ref() {
36565                        self.write_keyword(&id.name);
36566                    }
36567                }
36568            }
36569
36570            // Output TRANSACTION keyword if it was present and target supports it
36571            if (has_transaction_keyword || has_with_mark) && !strip_transaction {
36572                self.write_space();
36573                self.write_keyword("TRANSACTION");
36574            }
36575
36576            // Output transaction name (not kind)
36577            if !is_kind {
36578                if let Some(this) = &e.this {
36579                    self.write_space();
36580                    self.generate_expression(this)?;
36581                }
36582            }
36583
36584            // Output WITH MARK 'description' for TSQL
36585            if has_with_mark {
36586                self.write_space();
36587                self.write_keyword("WITH MARK");
36588                if let Some(Expression::Literal(lit)) = e.mark.as_deref() {
36589                    if let Literal::String(desc) = lit.as_ref() {
36590                        if !desc.is_empty() {
36591                            self.write_space();
36592                            self.write(&format!("'{}'", desc));
36593                        }
36594                    }
36595                }
36596            }
36597
36598            // Output modes (isolation levels, etc.)
36599            if let Some(modes) = &e.modes {
36600                self.write_space();
36601                self.generate_expression(modes)?;
36602            }
36603        }
36604        Ok(())
36605    }
36606
36607    fn generate_transform(&mut self, e: &Transform) -> Result<()> {
36608        // TRANSFORM(this, expression)
36609        self.write_keyword("TRANSFORM");
36610        self.write("(");
36611        self.generate_expression(&e.this)?;
36612        self.write(", ");
36613        self.generate_expression(&e.expression)?;
36614        self.write(")");
36615        Ok(())
36616    }
36617
36618    fn generate_transform_model_property(&mut self, e: &TransformModelProperty) -> Result<()> {
36619        // TRANSFORM(expressions)
36620        self.write_keyword("TRANSFORM");
36621        self.write("(");
36622        if self.config.pretty && !e.expressions.is_empty() {
36623            self.indent_level += 1;
36624            for (i, expr) in e.expressions.iter().enumerate() {
36625                if i > 0 {
36626                    self.write(",");
36627                }
36628                self.write_newline();
36629                self.write_indent();
36630                self.generate_expression(expr)?;
36631            }
36632            self.indent_level -= 1;
36633            self.write_newline();
36634            self.write(")");
36635        } else {
36636            for (i, expr) in e.expressions.iter().enumerate() {
36637                if i > 0 {
36638                    self.write(", ");
36639                }
36640                self.generate_expression(expr)?;
36641            }
36642            self.write(")");
36643        }
36644        Ok(())
36645    }
36646
36647    fn generate_transient_property(&mut self, e: &TransientProperty) -> Result<()> {
36648        use crate::dialects::DialectType;
36649        // TRANSIENT is Snowflake-specific; skip for other dialects
36650        if let Some(this) = &e.this {
36651            self.generate_expression(this)?;
36652            if matches!(self.config.dialect, Some(DialectType::Snowflake) | None) {
36653                self.write_space();
36654            }
36655        }
36656        if matches!(self.config.dialect, Some(DialectType::Snowflake) | None) {
36657            self.write_keyword("TRANSIENT");
36658        }
36659        Ok(())
36660    }
36661
36662    fn generate_translate(&mut self, e: &Translate) -> Result<()> {
36663        // TRANSLATE(this, from_, to)
36664        self.write_keyword("TRANSLATE");
36665        self.write("(");
36666        self.generate_expression(&e.this)?;
36667        if let Some(from) = &e.from_ {
36668            self.write(", ");
36669            self.generate_expression(from)?;
36670        }
36671        if let Some(to) = &e.to {
36672            self.write(", ");
36673            self.generate_expression(to)?;
36674        }
36675        self.write(")");
36676        Ok(())
36677    }
36678
36679    fn generate_translate_characters(&mut self, e: &TranslateCharacters) -> Result<()> {
36680        // TRANSLATE(this USING expression)
36681        self.write_keyword("TRANSLATE");
36682        self.write("(");
36683        self.generate_expression(&e.this)?;
36684        self.write_space();
36685        self.write_keyword("USING");
36686        self.write_space();
36687        self.generate_expression(&e.expression)?;
36688        if e.with_error.is_some() {
36689            self.write_space();
36690            self.write_keyword("WITH ERROR");
36691        }
36692        self.write(")");
36693        Ok(())
36694    }
36695
36696    fn generate_truncate_table(&mut self, e: &TruncateTable) -> Result<()> {
36697        // TRUNCATE TABLE table1, table2, ...
36698        self.write_keyword("TRUNCATE TABLE");
36699        self.write_space();
36700        for (i, expr) in e.expressions.iter().enumerate() {
36701            if i > 0 {
36702                self.write(", ");
36703            }
36704            self.generate_expression(expr)?;
36705        }
36706        Ok(())
36707    }
36708
36709    fn generate_try_base64_decode_binary(&mut self, e: &TryBase64DecodeBinary) -> Result<()> {
36710        // TRY_BASE64_DECODE_BINARY(this, [alphabet])
36711        self.write_keyword("TRY_BASE64_DECODE_BINARY");
36712        self.write("(");
36713        self.generate_expression(&e.this)?;
36714        if let Some(alphabet) = &e.alphabet {
36715            self.write(", ");
36716            self.generate_expression(alphabet)?;
36717        }
36718        self.write(")");
36719        Ok(())
36720    }
36721
36722    fn generate_try_base64_decode_string(&mut self, e: &TryBase64DecodeString) -> Result<()> {
36723        // TRY_BASE64_DECODE_STRING(this, [alphabet])
36724        self.write_keyword("TRY_BASE64_DECODE_STRING");
36725        self.write("(");
36726        self.generate_expression(&e.this)?;
36727        if let Some(alphabet) = &e.alphabet {
36728            self.write(", ");
36729            self.generate_expression(alphabet)?;
36730        }
36731        self.write(")");
36732        Ok(())
36733    }
36734
36735    fn generate_try_to_decfloat(&mut self, e: &TryToDecfloat) -> Result<()> {
36736        // TRY_TO_DECFLOAT(this, [format])
36737        self.write_keyword("TRY_TO_DECFLOAT");
36738        self.write("(");
36739        self.generate_expression(&e.this)?;
36740        if let Some(format) = &e.format {
36741            self.write(", '");
36742            self.write(format);
36743            self.write("'");
36744        }
36745        self.write(")");
36746        Ok(())
36747    }
36748
36749    fn generate_ts_or_ds_add(&mut self, e: &TsOrDsAdd) -> Result<()> {
36750        // TS_OR_DS_ADD(this, expression, [unit], [return_type])
36751        self.write_keyword("TS_OR_DS_ADD");
36752        self.write("(");
36753        self.generate_expression(&e.this)?;
36754        self.write(", ");
36755        self.generate_expression(&e.expression)?;
36756        if let Some(unit) = &e.unit {
36757            self.write(", ");
36758            self.write_keyword(unit);
36759        }
36760        if let Some(return_type) = &e.return_type {
36761            self.write(", ");
36762            self.generate_expression(return_type)?;
36763        }
36764        self.write(")");
36765        Ok(())
36766    }
36767
36768    fn generate_ts_or_ds_diff(&mut self, e: &TsOrDsDiff) -> Result<()> {
36769        // TS_OR_DS_DIFF(this, expression, [unit])
36770        self.write_keyword("TS_OR_DS_DIFF");
36771        self.write("(");
36772        self.generate_expression(&e.this)?;
36773        self.write(", ");
36774        self.generate_expression(&e.expression)?;
36775        if let Some(unit) = &e.unit {
36776            self.write(", ");
36777            self.write_keyword(unit);
36778        }
36779        self.write(")");
36780        Ok(())
36781    }
36782
36783    fn generate_ts_or_ds_to_date(&mut self, e: &TsOrDsToDate) -> Result<()> {
36784        let default_time_format = "%Y-%m-%d %H:%M:%S";
36785        let default_date_format = "%Y-%m-%d";
36786        let has_non_default_format = e.format.as_ref().map_or(false, |f| {
36787            f != default_time_format && f != default_date_format
36788        });
36789
36790        if has_non_default_format {
36791            // With non-default format: dialect-specific handling
36792            let fmt = e.format.as_ref().unwrap();
36793            match self.config.dialect {
36794                Some(DialectType::MySQL) | Some(DialectType::StarRocks) => {
36795                    // MySQL/StarRocks: STR_TO_DATE(x, fmt) - no CAST wrapper
36796                    // STR_TO_DATE is the MySQL-native form of StrToTime
36797                    let str_to_time = crate::expressions::StrToTime {
36798                        this: Box::new((*e.this).clone()),
36799                        format: fmt.clone(),
36800                        zone: None,
36801                        safe: None,
36802                        target_type: None,
36803                    };
36804                    self.generate_str_to_time(&str_to_time)?;
36805                }
36806                Some(DialectType::Hive)
36807                | Some(DialectType::Spark)
36808                | Some(DialectType::Databricks) => {
36809                    // Hive/Spark: TO_DATE(x, java_fmt)
36810                    self.write_keyword("TO_DATE");
36811                    self.write("(");
36812                    self.generate_expression(&e.this)?;
36813                    self.write(", '");
36814                    self.write(&Self::strftime_to_java_format(fmt));
36815                    self.write("')");
36816                }
36817                Some(DialectType::Snowflake) => {
36818                    // Snowflake: TO_DATE(x, snowflake_fmt)
36819                    self.write_keyword("TO_DATE");
36820                    self.write("(");
36821                    self.generate_expression(&e.this)?;
36822                    self.write(", '");
36823                    self.write(&Self::strftime_to_snowflake_format(fmt));
36824                    self.write("')");
36825                }
36826                Some(DialectType::Doris) => {
36827                    // Doris: TO_DATE(x) - ignores format
36828                    self.write_keyword("TO_DATE");
36829                    self.write("(");
36830                    self.generate_expression(&e.this)?;
36831                    self.write(")");
36832                }
36833                _ => {
36834                    // Default: CAST(STR_TO_TIME(x, fmt) AS DATE)
36835                    self.write_keyword("CAST");
36836                    self.write("(");
36837                    let str_to_time = crate::expressions::StrToTime {
36838                        this: Box::new((*e.this).clone()),
36839                        format: fmt.clone(),
36840                        zone: None,
36841                        safe: None,
36842                        target_type: None,
36843                    };
36844                    self.generate_str_to_time(&str_to_time)?;
36845                    self.write_keyword(" AS ");
36846                    self.write_keyword("DATE");
36847                    self.write(")");
36848                }
36849            }
36850        } else {
36851            // Without format (or default format): simple date conversion
36852            match self.config.dialect {
36853                Some(DialectType::MySQL)
36854                | Some(DialectType::SQLite)
36855                | Some(DialectType::StarRocks) => {
36856                    // MySQL/SQLite/StarRocks: DATE(x)
36857                    self.write_keyword("DATE");
36858                    self.write("(");
36859                    self.generate_expression(&e.this)?;
36860                    self.write(")");
36861                }
36862                Some(DialectType::Hive)
36863                | Some(DialectType::Spark)
36864                | Some(DialectType::Databricks)
36865                | Some(DialectType::Snowflake)
36866                | Some(DialectType::Doris) => {
36867                    // Hive/Spark/Databricks/Snowflake/Doris: TO_DATE(x)
36868                    self.write_keyword("TO_DATE");
36869                    self.write("(");
36870                    self.generate_expression(&e.this)?;
36871                    self.write(")");
36872                }
36873                Some(DialectType::Presto)
36874                | Some(DialectType::Trino)
36875                | Some(DialectType::Athena) => {
36876                    // Presto/Trino: CAST(CAST(x AS TIMESTAMP) AS DATE)
36877                    self.write_keyword("CAST");
36878                    self.write("(");
36879                    self.write_keyword("CAST");
36880                    self.write("(");
36881                    self.generate_expression(&e.this)?;
36882                    self.write_keyword(" AS ");
36883                    self.write_keyword("TIMESTAMP");
36884                    self.write(")");
36885                    self.write_keyword(" AS ");
36886                    self.write_keyword("DATE");
36887                    self.write(")");
36888                }
36889                Some(DialectType::ClickHouse) => {
36890                    // ClickHouse: CAST(x AS Nullable(DATE))
36891                    self.write_keyword("CAST");
36892                    self.write("(");
36893                    self.generate_expression(&e.this)?;
36894                    self.write_keyword(" AS ");
36895                    self.write("Nullable(DATE)");
36896                    self.write(")");
36897                }
36898                _ => {
36899                    // Default: CAST(x AS DATE)
36900                    self.write_keyword("CAST");
36901                    self.write("(");
36902                    self.generate_expression(&e.this)?;
36903                    self.write_keyword(" AS ");
36904                    self.write_keyword("DATE");
36905                    self.write(")");
36906                }
36907            }
36908        }
36909        Ok(())
36910    }
36911
36912    fn generate_ts_or_ds_to_time(&mut self, e: &TsOrDsToTime) -> Result<()> {
36913        // TS_OR_DS_TO_TIME(this, [format])
36914        self.write_keyword("TS_OR_DS_TO_TIME");
36915        self.write("(");
36916        self.generate_expression(&e.this)?;
36917        if let Some(format) = &e.format {
36918            self.write(", '");
36919            self.write(format);
36920            self.write("'");
36921        }
36922        self.write(")");
36923        Ok(())
36924    }
36925
36926    fn generate_unhex(&mut self, e: &Unhex) -> Result<()> {
36927        // UNHEX(this, [expression])
36928        self.write_keyword("UNHEX");
36929        self.write("(");
36930        self.generate_expression(&e.this)?;
36931        if let Some(expression) = &e.expression {
36932            self.write(", ");
36933            self.generate_expression(expression)?;
36934        }
36935        self.write(")");
36936        Ok(())
36937    }
36938
36939    fn generate_unicode_string(&mut self, e: &UnicodeString) -> Result<()> {
36940        // U&this [UESCAPE escape]
36941        self.write("U&");
36942        self.generate_expression(&e.this)?;
36943        if let Some(escape) = &e.escape {
36944            self.write_space();
36945            self.write_keyword("UESCAPE");
36946            self.write_space();
36947            self.generate_expression(escape)?;
36948        }
36949        Ok(())
36950    }
36951
36952    fn generate_uniform(&mut self, e: &Uniform) -> Result<()> {
36953        // UNIFORM(this, expression, [gen], [seed])
36954        self.write_keyword("UNIFORM");
36955        self.write("(");
36956        self.generate_expression(&e.this)?;
36957        self.write(", ");
36958        self.generate_expression(&e.expression)?;
36959        if let Some(gen) = &e.gen {
36960            self.write(", ");
36961            self.generate_expression(gen)?;
36962        }
36963        if let Some(seed) = &e.seed {
36964            self.write(", ");
36965            self.generate_expression(seed)?;
36966        }
36967        self.write(")");
36968        Ok(())
36969    }
36970
36971    fn generate_unique_column_constraint(&mut self, e: &UniqueColumnConstraint) -> Result<()> {
36972        // UNIQUE [NULLS NOT DISTINCT] [this] [index_type] [on_conflict] [options]
36973        self.write_keyword("UNIQUE");
36974        // Output NULLS NOT DISTINCT if nulls is set (PostgreSQL 15+ feature)
36975        if e.nulls.is_some() {
36976            self.write(" NULLS NOT DISTINCT");
36977        }
36978        if let Some(this) = &e.this {
36979            self.write_space();
36980            self.generate_expression(this)?;
36981        }
36982        if let Some(index_type) = &e.index_type {
36983            self.write(" USING ");
36984            self.generate_expression(index_type)?;
36985        }
36986        if let Some(on_conflict) = &e.on_conflict {
36987            self.write_space();
36988            self.generate_expression(on_conflict)?;
36989        }
36990        for opt in &e.options {
36991            self.write_space();
36992            self.generate_expression(opt)?;
36993        }
36994        Ok(())
36995    }
36996
36997    fn generate_unique_key_property(&mut self, e: &UniqueKeyProperty) -> Result<()> {
36998        // UNIQUE KEY (expressions)
36999        self.write_keyword("UNIQUE KEY");
37000        self.write(" (");
37001        for (i, expr) in e.expressions.iter().enumerate() {
37002            if i > 0 {
37003                self.write(", ");
37004            }
37005            self.generate_expression(expr)?;
37006        }
37007        self.write(")");
37008        Ok(())
37009    }
37010
37011    fn generate_rollup_property(&mut self, e: &RollupProperty) -> Result<()> {
37012        // ROLLUP (r1(col1, col2), r2(col1))
37013        self.write_keyword("ROLLUP");
37014        self.write(" (");
37015        for (i, index) in e.expressions.iter().enumerate() {
37016            if i > 0 {
37017                self.write(", ");
37018            }
37019            self.generate_identifier(&index.name)?;
37020            self.write("(");
37021            for (j, col) in index.expressions.iter().enumerate() {
37022                if j > 0 {
37023                    self.write(", ");
37024                }
37025                self.generate_identifier(col)?;
37026            }
37027            self.write(")");
37028        }
37029        self.write(")");
37030        Ok(())
37031    }
37032
37033    fn generate_unix_to_str(&mut self, e: &UnixToStr) -> Result<()> {
37034        match self.config.dialect {
37035            Some(DialectType::DuckDB) => {
37036                // DuckDB: STRFTIME(TO_TIMESTAMP(value), format)
37037                self.write_keyword("STRFTIME");
37038                self.write("(");
37039                self.write_keyword("TO_TIMESTAMP");
37040                self.write("(");
37041                self.generate_expression(&e.this)?;
37042                self.write("), '");
37043                if let Some(format) = &e.format {
37044                    self.write(format);
37045                }
37046                self.write("')");
37047            }
37048            Some(DialectType::Hive) => {
37049                // Hive: FROM_UNIXTIME(value, format) - elide format when it's the default
37050                self.write_keyword("FROM_UNIXTIME");
37051                self.write("(");
37052                self.generate_expression(&e.this)?;
37053                if let Some(format) = &e.format {
37054                    if format != "yyyy-MM-dd HH:mm:ss" {
37055                        self.write(", '");
37056                        self.write(format);
37057                        self.write("'");
37058                    }
37059                }
37060                self.write(")");
37061            }
37062            Some(DialectType::Presto) | Some(DialectType::Trino) => {
37063                // Presto: DATE_FORMAT(FROM_UNIXTIME(value), format)
37064                self.write_keyword("DATE_FORMAT");
37065                self.write("(");
37066                self.write_keyword("FROM_UNIXTIME");
37067                self.write("(");
37068                self.generate_expression(&e.this)?;
37069                self.write("), '");
37070                if let Some(format) = &e.format {
37071                    self.write(format);
37072                }
37073                self.write("')");
37074            }
37075            Some(DialectType::Spark) | Some(DialectType::Databricks) => {
37076                // Spark: FROM_UNIXTIME(value, format)
37077                self.write_keyword("FROM_UNIXTIME");
37078                self.write("(");
37079                self.generate_expression(&e.this)?;
37080                if let Some(format) = &e.format {
37081                    self.write(", '");
37082                    self.write(format);
37083                    self.write("'");
37084                }
37085                self.write(")");
37086            }
37087            _ => {
37088                // Default: UNIX_TO_STR(this, [format])
37089                self.write_keyword("UNIX_TO_STR");
37090                self.write("(");
37091                self.generate_expression(&e.this)?;
37092                if let Some(format) = &e.format {
37093                    self.write(", '");
37094                    self.write(format);
37095                    self.write("'");
37096                }
37097                self.write(")");
37098            }
37099        }
37100        Ok(())
37101    }
37102
37103    fn generate_unix_to_time(&mut self, e: &UnixToTime) -> Result<()> {
37104        use crate::dialects::DialectType;
37105        let scale = e.scale.unwrap_or(0); // 0 = seconds
37106
37107        match self.config.dialect {
37108            Some(DialectType::Snowflake) => {
37109                // Snowflake: TO_TIMESTAMP(value[, scale]) - skip scale for seconds (0)
37110                self.write_keyword("TO_TIMESTAMP");
37111                self.write("(");
37112                self.generate_expression(&e.this)?;
37113                if let Some(s) = e.scale {
37114                    if s > 0 {
37115                        self.write(", ");
37116                        self.write(&s.to_string());
37117                    }
37118                }
37119                self.write(")");
37120            }
37121            Some(DialectType::BigQuery) => {
37122                // BigQuery: TIMESTAMP_SECONDS(value) / TIMESTAMP_MILLIS(value)
37123                // or TIMESTAMP_SECONDS(CAST(value / POWER(10, scale) AS INT64)) for other scales
37124                match scale {
37125                    0 => {
37126                        self.write_keyword("TIMESTAMP_SECONDS");
37127                        self.write("(");
37128                        self.generate_expression(&e.this)?;
37129                        self.write(")");
37130                    }
37131                    3 => {
37132                        self.write_keyword("TIMESTAMP_MILLIS");
37133                        self.write("(");
37134                        self.generate_expression(&e.this)?;
37135                        self.write(")");
37136                    }
37137                    6 => {
37138                        self.write_keyword("TIMESTAMP_MICROS");
37139                        self.write("(");
37140                        self.generate_expression(&e.this)?;
37141                        self.write(")");
37142                    }
37143                    _ => {
37144                        // TIMESTAMP_SECONDS(CAST(value / POWER(10, scale) AS INT64))
37145                        self.write_keyword("TIMESTAMP_SECONDS");
37146                        self.write("(CAST(");
37147                        self.generate_expression(&e.this)?;
37148                        self.write(&format!(" / POWER(10, {}) AS INT64))", scale));
37149                    }
37150                }
37151            }
37152            Some(DialectType::Spark) => {
37153                // Spark: CAST(FROM_UNIXTIME(value) AS TIMESTAMP) for scale=0
37154                // TIMESTAMP_MILLIS(value) for scale=3
37155                // TIMESTAMP_MICROS(value) for scale=6
37156                // TIMESTAMP_SECONDS(value / POWER(10, scale)) for other scales
37157                match scale {
37158                    0 => {
37159                        self.write_keyword("CAST");
37160                        self.write("(");
37161                        self.write_keyword("FROM_UNIXTIME");
37162                        self.write("(");
37163                        self.generate_expression(&e.this)?;
37164                        self.write(") ");
37165                        self.write_keyword("AS TIMESTAMP");
37166                        self.write(")");
37167                    }
37168                    3 => {
37169                        self.write_keyword("TIMESTAMP_MILLIS");
37170                        self.write("(");
37171                        self.generate_expression(&e.this)?;
37172                        self.write(")");
37173                    }
37174                    6 => {
37175                        self.write_keyword("TIMESTAMP_MICROS");
37176                        self.write("(");
37177                        self.generate_expression(&e.this)?;
37178                        self.write(")");
37179                    }
37180                    _ => {
37181                        self.write_keyword("TIMESTAMP_SECONDS");
37182                        self.write("(");
37183                        self.generate_expression(&e.this)?;
37184                        self.write(&format!(" / POWER(10, {}))", scale));
37185                    }
37186                }
37187            }
37188            Some(DialectType::Databricks) => {
37189                // Databricks: CAST(FROM_UNIXTIME(value) AS TIMESTAMP) for scale=0
37190                // TIMESTAMP_MILLIS(value) for scale=3
37191                // TIMESTAMP_MICROS(value) for scale=6
37192                match scale {
37193                    0 => {
37194                        self.write_keyword("CAST");
37195                        self.write("(");
37196                        self.write_keyword("FROM_UNIXTIME");
37197                        self.write("(");
37198                        self.generate_expression(&e.this)?;
37199                        self.write(") ");
37200                        self.write_keyword("AS TIMESTAMP");
37201                        self.write(")");
37202                    }
37203                    3 => {
37204                        self.write_keyword("TIMESTAMP_MILLIS");
37205                        self.write("(");
37206                        self.generate_expression(&e.this)?;
37207                        self.write(")");
37208                    }
37209                    6 => {
37210                        self.write_keyword("TIMESTAMP_MICROS");
37211                        self.write("(");
37212                        self.generate_expression(&e.this)?;
37213                        self.write(")");
37214                    }
37215                    _ => {
37216                        self.write_keyword("TIMESTAMP_SECONDS");
37217                        self.write("(");
37218                        self.generate_expression(&e.this)?;
37219                        self.write(&format!(" / POWER(10, {}))", scale));
37220                    }
37221                }
37222            }
37223            Some(DialectType::Hive) => {
37224                // Hive: FROM_UNIXTIME(value)
37225                if scale == 0 {
37226                    self.write_keyword("FROM_UNIXTIME");
37227                    self.write("(");
37228                    self.generate_expression(&e.this)?;
37229                    self.write(")");
37230                } else {
37231                    self.write_keyword("FROM_UNIXTIME");
37232                    self.write("(");
37233                    self.generate_expression(&e.this)?;
37234                    self.write(&format!(" / POWER(10, {})", scale));
37235                    self.write(")");
37236                }
37237            }
37238            Some(DialectType::Presto) | Some(DialectType::Trino) => {
37239                // Presto: FROM_UNIXTIME(CAST(value AS DOUBLE) / POW(10, scale)) for scale > 0
37240                // FROM_UNIXTIME(value) for scale=0
37241                if scale == 0 {
37242                    self.write_keyword("FROM_UNIXTIME");
37243                    self.write("(");
37244                    self.generate_expression(&e.this)?;
37245                    self.write(")");
37246                } else {
37247                    self.write_keyword("FROM_UNIXTIME");
37248                    self.write("(CAST(");
37249                    self.generate_expression(&e.this)?;
37250                    self.write(&format!(" AS DOUBLE) / POW(10, {}))", scale));
37251                }
37252            }
37253            Some(DialectType::DuckDB) => {
37254                // DuckDB: TO_TIMESTAMP(value) for scale=0
37255                // EPOCH_MS(value) for scale=3
37256                // MAKE_TIMESTAMP(value) for scale=6
37257                match scale {
37258                    0 => {
37259                        self.write_keyword("TO_TIMESTAMP");
37260                        self.write("(");
37261                        self.generate_expression(&e.this)?;
37262                        self.write(")");
37263                    }
37264                    3 => {
37265                        self.write_keyword("EPOCH_MS");
37266                        self.write("(");
37267                        self.generate_expression(&e.this)?;
37268                        self.write(")");
37269                    }
37270                    6 => {
37271                        self.write_keyword("MAKE_TIMESTAMP");
37272                        self.write("(");
37273                        self.generate_expression(&e.this)?;
37274                        self.write(")");
37275                    }
37276                    _ => {
37277                        self.write_keyword("TO_TIMESTAMP");
37278                        self.write("(");
37279                        self.generate_expression(&e.this)?;
37280                        self.write(&format!(" / POWER(10, {}))", scale));
37281                        self.write_keyword(" AT TIME ZONE");
37282                        self.write(" 'UTC'");
37283                    }
37284                }
37285            }
37286            Some(DialectType::Doris) | Some(DialectType::StarRocks) => {
37287                // Doris/StarRocks: FROM_UNIXTIME(value)
37288                self.write_keyword("FROM_UNIXTIME");
37289                self.write("(");
37290                self.generate_expression(&e.this)?;
37291                self.write(")");
37292            }
37293            Some(DialectType::Oracle) => {
37294                // Oracle: TO_DATE('1970-01-01', 'YYYY-MM-DD') + (x / 86400)
37295                self.write("TO_DATE('1970-01-01', 'YYYY-MM-DD') + (");
37296                self.generate_expression(&e.this)?;
37297                self.write(" / 86400)");
37298            }
37299            Some(DialectType::Redshift) => {
37300                // Redshift: (TIMESTAMP 'epoch' + value * INTERVAL '1 SECOND') for scale=0
37301                // (TIMESTAMP 'epoch' + (value / POWER(10, scale)) * INTERVAL '1 SECOND') for scale > 0
37302                self.write("(TIMESTAMP 'epoch' + ");
37303                if scale == 0 {
37304                    self.generate_expression(&e.this)?;
37305                } else {
37306                    self.write("(");
37307                    self.generate_expression(&e.this)?;
37308                    self.write(&format!(" / POWER(10, {}))", scale));
37309                }
37310                self.write(" * INTERVAL '1 SECOND')");
37311            }
37312            Some(DialectType::Exasol) => {
37313                // Exasol: FROM_POSIX_TIME(value)
37314                self.write_keyword("FROM_POSIX_TIME");
37315                self.write("(");
37316                self.generate_expression(&e.this)?;
37317                self.write(")");
37318            }
37319            _ => {
37320                // Default: TO_TIMESTAMP(value[, scale])
37321                self.write_keyword("TO_TIMESTAMP");
37322                self.write("(");
37323                self.generate_expression(&e.this)?;
37324                if let Some(s) = e.scale {
37325                    self.write(", ");
37326                    self.write(&s.to_string());
37327                }
37328                self.write(")");
37329            }
37330        }
37331        Ok(())
37332    }
37333
37334    fn generate_unpivot_columns(&mut self, e: &UnpivotColumns) -> Result<()> {
37335        // NAME col VALUE col1, col2, ...
37336        if !matches!(&*e.this, Expression::Null(_)) {
37337            self.write_keyword("NAME");
37338            self.write_space();
37339            self.generate_expression(&e.this)?;
37340        }
37341        if !e.expressions.is_empty() {
37342            self.write_space();
37343            self.write_keyword("VALUE");
37344            self.write_space();
37345            for (i, expr) in e.expressions.iter().enumerate() {
37346                if i > 0 {
37347                    self.write(", ");
37348                }
37349                self.generate_expression(expr)?;
37350            }
37351        }
37352        Ok(())
37353    }
37354
37355    fn generate_user_defined_function(&mut self, e: &UserDefinedFunction) -> Result<()> {
37356        // this(expressions) or (this)(expressions)
37357        if e.wrapped.is_some() {
37358            self.write("(");
37359        }
37360        self.generate_expression(&e.this)?;
37361        if e.wrapped.is_some() {
37362            self.write(")");
37363        }
37364        self.write("(");
37365        for (i, expr) in e.expressions.iter().enumerate() {
37366            if i > 0 {
37367                self.write(", ");
37368            }
37369            self.generate_expression(expr)?;
37370        }
37371        self.write(")");
37372        Ok(())
37373    }
37374
37375    fn generate_using_template_property(&mut self, e: &UsingTemplateProperty) -> Result<()> {
37376        // USING TEMPLATE this
37377        self.write_keyword("USING TEMPLATE");
37378        self.write_space();
37379        self.generate_expression(&e.this)?;
37380        Ok(())
37381    }
37382
37383    fn generate_utc_time(&mut self, _e: &UtcTime) -> Result<()> {
37384        // UTC_TIME
37385        self.write_keyword("UTC_TIME");
37386        Ok(())
37387    }
37388
37389    fn generate_utc_timestamp(&mut self, _e: &UtcTimestamp) -> Result<()> {
37390        if matches!(
37391            self.config.dialect,
37392            Some(crate::dialects::DialectType::ClickHouse)
37393        ) {
37394            self.write_keyword("CURRENT_TIMESTAMP");
37395            self.write("('UTC')");
37396        } else {
37397            self.write_keyword("UTC_TIMESTAMP");
37398        }
37399        Ok(())
37400    }
37401
37402    fn generate_uuid(&mut self, e: &Uuid) -> Result<()> {
37403        use crate::dialects::DialectType;
37404        // Choose UUID function name based on target dialect
37405        let func_name = match self.config.dialect {
37406            Some(DialectType::Snowflake) => "UUID_STRING",
37407            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift) => "GEN_RANDOM_UUID",
37408            Some(DialectType::BigQuery) => "GENERATE_UUID",
37409            _ => {
37410                if let Some(name) = &e.name {
37411                    name.as_str()
37412                } else {
37413                    "UUID"
37414                }
37415            }
37416        };
37417        self.write_keyword(func_name);
37418        self.write("(");
37419        if let Some(this) = &e.this {
37420            self.generate_expression(this)?;
37421        }
37422        self.write(")");
37423        Ok(())
37424    }
37425
37426    fn generate_var_map(&mut self, e: &VarMap) -> Result<()> {
37427        // MAP(key1, value1, key2, value2, ...)
37428        self.write_keyword("MAP");
37429        self.write("(");
37430        let mut first = true;
37431        for (k, v) in e.keys.iter().zip(e.values.iter()) {
37432            if !first {
37433                self.write(", ");
37434            }
37435            self.generate_expression(k)?;
37436            self.write(", ");
37437            self.generate_expression(v)?;
37438            first = false;
37439        }
37440        self.write(")");
37441        Ok(())
37442    }
37443
37444    fn generate_vector_search(&mut self, e: &VectorSearch) -> Result<()> {
37445        // VECTOR_SEARCH(this, column_to_search, query_table, query_column_to_search, top_k, distance_type, ...)
37446        self.write_keyword("VECTOR_SEARCH");
37447        self.write("(");
37448        self.generate_expression(&e.this)?;
37449        if let Some(col) = &e.column_to_search {
37450            self.write(", ");
37451            self.generate_expression(col)?;
37452        }
37453        if let Some(query_table) = &e.query_table {
37454            self.write(", ");
37455            self.generate_expression(query_table)?;
37456        }
37457        if let Some(query_col) = &e.query_column_to_search {
37458            self.write(", ");
37459            self.generate_expression(query_col)?;
37460        }
37461        if let Some(top_k) = &e.top_k {
37462            self.write(", ");
37463            self.generate_expression(top_k)?;
37464        }
37465        if let Some(dist_type) = &e.distance_type {
37466            self.write(", ");
37467            self.generate_expression(dist_type)?;
37468        }
37469        self.write(")");
37470        Ok(())
37471    }
37472
37473    fn generate_version(&mut self, e: &Version) -> Result<()> {
37474        // Python: f"FOR {expression.name} {kind} {expr}"
37475        // e.this = Identifier("TIMESTAMP" or "VERSION")
37476        // e.kind = "AS OF" (or "BETWEEN", etc.)
37477        // e.expression = the value expression
37478        // Hive does NOT use the FOR prefix for time travel
37479        use crate::dialects::DialectType;
37480        let skip_for = matches!(
37481            self.config.dialect,
37482            Some(DialectType::Hive) | Some(DialectType::Spark) | Some(DialectType::Databricks)
37483        );
37484        if !skip_for {
37485            self.write_keyword("FOR");
37486            self.write_space();
37487        }
37488        // Extract the name from this (which is an Identifier expression)
37489        match e.this.as_ref() {
37490            Expression::Identifier(ident) => {
37491                self.write_keyword(&ident.name);
37492            }
37493            _ => {
37494                self.generate_expression(&e.this)?;
37495            }
37496        }
37497        self.write_space();
37498        self.write_keyword(&e.kind);
37499        if let Some(expression) = &e.expression {
37500            self.write_space();
37501            self.generate_expression(expression)?;
37502        }
37503        Ok(())
37504    }
37505
37506    fn generate_view_attribute_property(&mut self, e: &ViewAttributeProperty) -> Result<()> {
37507        // Python: return self.sql(expression, "this")
37508        self.generate_expression(&e.this)?;
37509        Ok(())
37510    }
37511
37512    fn generate_volatile_property(&mut self, e: &VolatileProperty) -> Result<()> {
37513        // Python: return "VOLATILE" if expression.args.get("this") is None else "NOT VOLATILE"
37514        if e.this.is_some() {
37515            self.write_keyword("NOT VOLATILE");
37516        } else {
37517            self.write_keyword("VOLATILE");
37518        }
37519        Ok(())
37520    }
37521
37522    fn generate_watermark_column_constraint(
37523        &mut self,
37524        e: &WatermarkColumnConstraint,
37525    ) -> Result<()> {
37526        // Python: f"WATERMARK FOR {self.sql(expression, 'this')} AS {self.sql(expression, 'expression')}"
37527        self.write_keyword("WATERMARK FOR");
37528        self.write_space();
37529        self.generate_expression(&e.this)?;
37530        self.write_space();
37531        self.write_keyword("AS");
37532        self.write_space();
37533        self.generate_expression(&e.expression)?;
37534        Ok(())
37535    }
37536
37537    fn generate_week(&mut self, e: &Week) -> Result<()> {
37538        // Python: return self.func("WEEK", expression.this, expression.args.get("mode"))
37539        self.write_keyword("WEEK");
37540        self.write("(");
37541        self.generate_expression(&e.this)?;
37542        if let Some(mode) = &e.mode {
37543            self.write(", ");
37544            self.generate_expression(mode)?;
37545        }
37546        self.write(")");
37547        Ok(())
37548    }
37549
37550    fn generate_when(&mut self, e: &When) -> Result<()> {
37551        // Python: WHEN {matched}{source}{condition} THEN {then}
37552        // matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED"
37553        // source = " BY SOURCE" if MATCHED_BY_SOURCE and expression.args.get("source") else ""
37554        self.write_keyword("WHEN");
37555        self.write_space();
37556
37557        // Check if matched
37558        if let Some(matched) = &e.matched {
37559            // Check the expression - if it's a boolean true, use MATCHED, otherwise NOT MATCHED
37560            match matched.as_ref() {
37561                Expression::Boolean(b) if b.value => {
37562                    self.write_keyword("MATCHED");
37563                }
37564                _ => {
37565                    self.write_keyword("NOT MATCHED");
37566                }
37567            }
37568        } else {
37569            self.write_keyword("NOT MATCHED");
37570        }
37571
37572        // BY SOURCE / BY TARGET
37573        // source = Boolean(true) means BY SOURCE, Boolean(false) means BY TARGET
37574        // BY TARGET is the default and typically omitted in output
37575        // Only emit if the dialect supports BY SOURCE syntax
37576        if self.config.matched_by_source {
37577            if let Some(source) = &e.source {
37578                if let Expression::Boolean(b) = source.as_ref() {
37579                    if b.value {
37580                        // BY SOURCE
37581                        self.write_space();
37582                        self.write_keyword("BY SOURCE");
37583                    }
37584                    // BY TARGET (b.value == false) is omitted as it's the default
37585                } else {
37586                    // For non-boolean source, output as BY SOURCE (legacy behavior)
37587                    self.write_space();
37588                    self.write_keyword("BY SOURCE");
37589                }
37590            }
37591        }
37592
37593        // Condition
37594        if let Some(condition) = &e.condition {
37595            self.write_space();
37596            self.write_keyword("AND");
37597            self.write_space();
37598            self.generate_expression(condition)?;
37599        }
37600
37601        self.write_space();
37602        self.write_keyword("THEN");
37603        self.write_space();
37604
37605        // Generate the then expression (could be INSERT, UPDATE, DELETE)
37606        // MERGE actions are stored as Tuples with the action keyword as first element
37607        self.generate_merge_action(&e.then)?;
37608
37609        Ok(())
37610    }
37611
37612    fn generate_merge_action(&mut self, action: &Expression) -> Result<()> {
37613        match action {
37614            Expression::Tuple(tuple) => {
37615                let elements = &tuple.expressions;
37616                if elements.is_empty() {
37617                    return self.generate_expression(action);
37618                }
37619                // Check if first element is a Var (INSERT, UPDATE, DELETE, etc.)
37620                match &elements[0] {
37621                    Expression::Var(v) if v.this == "INSERT" => {
37622                        self.write_keyword("INSERT");
37623                        // Spark: INSERT * (insert all columns)
37624                        if elements.len() > 1 && matches!(&elements[1], Expression::Star(_)) {
37625                            self.write(" *");
37626                            if let Some(Expression::Where(w)) = elements.get(2) {
37627                                self.write_space();
37628                                self.generate_where(w)?;
37629                            }
37630                        } else {
37631                            let mut values_idx = 1;
37632                            // Check if second element is column list (Tuple)
37633                            if elements.len() > 1 {
37634                                if let Expression::Tuple(cols) = &elements[1] {
37635                                    // Could be columns or values - if there's a third element, second is columns
37636                                    if elements.len() > 2 {
37637                                        // Second is columns, third is values
37638                                        self.write(" (");
37639                                        for (i, col) in cols.expressions.iter().enumerate() {
37640                                            if i > 0 {
37641                                                self.write(", ");
37642                                            }
37643                                            // Strip MERGE target qualifiers from INSERT column list
37644                                            if !self.merge_strip_qualifiers.is_empty() {
37645                                                let stripped = self.strip_merge_qualifier(col);
37646                                                self.generate_expression(&stripped)?;
37647                                            } else {
37648                                                self.generate_expression(col)?;
37649                                            }
37650                                        }
37651                                        self.write(")");
37652                                        values_idx = 2;
37653                                    } else {
37654                                        // Only two elements: INSERT + values (no explicit columns)
37655                                        values_idx = 1;
37656                                    }
37657                                }
37658                            }
37659                            let mut next_idx = values_idx;
37660                            // Generate VALUES clause
37661                            if values_idx < elements.len()
37662                                && !matches!(&elements[values_idx], Expression::Where(_))
37663                            {
37664                                // Check if it's INSERT ROW (BigQuery) — no VALUES keyword needed
37665                                let is_row = matches!(&elements[values_idx], Expression::Var(v) if v.this == "ROW");
37666                                if !is_row {
37667                                    self.write_space();
37668                                    self.write_keyword("VALUES");
37669                                }
37670                                self.write(" ");
37671                                if let Expression::Tuple(vals) = &elements[values_idx] {
37672                                    self.write("(");
37673                                    for (i, val) in vals.expressions.iter().enumerate() {
37674                                        if i > 0 {
37675                                            self.write(", ");
37676                                        }
37677                                        self.generate_expression(val)?;
37678                                    }
37679                                    self.write(")");
37680                                } else {
37681                                    self.generate_expression(&elements[values_idx])?;
37682                                }
37683                                next_idx += 1;
37684                            }
37685                            if let Some(Expression::Where(w)) = elements.get(next_idx) {
37686                                self.write_space();
37687                                self.generate_where(w)?;
37688                            }
37689                        } // close else for INSERT * check
37690                    }
37691                    Expression::Var(v) if v.this == "UPDATE" => {
37692                        self.write_keyword("UPDATE");
37693                        // Spark: UPDATE * (update all columns)
37694                        if elements.len() > 1 && matches!(&elements[1], Expression::Star(_)) {
37695                            self.write(" *");
37696                            if let Some(Expression::Where(w)) = elements.get(2) {
37697                                self.write_space();
37698                                self.generate_where(w)?;
37699                            }
37700                        } else if elements.len() > 1 {
37701                            self.write_space();
37702                            self.write_keyword("SET");
37703                            // In pretty mode, put assignments on next line with extra indent
37704                            if self.config.pretty {
37705                                self.write_newline();
37706                                self.indent_level += 1;
37707                                self.write_indent();
37708                            } else {
37709                                self.write_space();
37710                            }
37711                            if let Expression::Tuple(assignments) = &elements[1] {
37712                                for (i, assignment) in assignments.expressions.iter().enumerate() {
37713                                    if i > 0 {
37714                                        if self.config.pretty {
37715                                            self.write(",");
37716                                            self.write_newline();
37717                                            self.write_indent();
37718                                        } else {
37719                                            self.write(", ");
37720                                        }
37721                                    }
37722                                    // Strip MERGE target qualifiers from left side of UPDATE SET
37723                                    if !self.merge_strip_qualifiers.is_empty() {
37724                                        self.generate_merge_set_assignment(assignment)?;
37725                                    } else {
37726                                        self.generate_expression(assignment)?;
37727                                    }
37728                                }
37729                            } else {
37730                                self.generate_expression(&elements[1])?;
37731                            }
37732                            if self.config.pretty {
37733                                self.indent_level -= 1;
37734                            }
37735                            if let Some(Expression::Where(w)) = elements.get(2) {
37736                                self.write_space();
37737                                self.generate_where(w)?;
37738                            }
37739                        }
37740                    }
37741                    Expression::Var(v) if v.this == "DELETE" => {
37742                        self.write_keyword("DELETE");
37743                        if let Some(Expression::Where(w)) = elements.get(1) {
37744                            self.write_space();
37745                            self.generate_where(w)?;
37746                        }
37747                    }
37748                    _ => {
37749                        // Fallback: generic tuple generation
37750                        self.generate_expression(action)?;
37751                    }
37752                }
37753            }
37754            Expression::Var(v)
37755                if v.this == "INSERT"
37756                    || v.this == "UPDATE"
37757                    || v.this == "DELETE"
37758                    || v.this == "DO NOTHING" =>
37759            {
37760                self.write_keyword(&v.this);
37761            }
37762            _ => {
37763                self.generate_expression(action)?;
37764            }
37765        }
37766        Ok(())
37767    }
37768
37769    /// Generate a MERGE UPDATE SET assignment, stripping target table qualifier from left side
37770    fn generate_merge_set_assignment(&mut self, assignment: &Expression) -> Result<()> {
37771        match assignment {
37772            Expression::Eq(eq) => {
37773                // Strip qualifier from the left side if it matches a MERGE target name
37774                let stripped_left = self.strip_merge_qualifier(&eq.left);
37775                self.generate_expression(&stripped_left)?;
37776                self.write(" = ");
37777                self.generate_expression(&eq.right)?;
37778                Ok(())
37779            }
37780            other => self.generate_expression(other),
37781        }
37782    }
37783
37784    /// Strip table qualifier from a column reference if it matches a MERGE target name
37785    fn strip_merge_qualifier(&self, expr: &Expression) -> Expression {
37786        match expr {
37787            Expression::Column(col) => {
37788                if let Some(ref table_ident) = col.table {
37789                    if self
37790                        .merge_strip_qualifiers
37791                        .iter()
37792                        .any(|n| n.eq_ignore_ascii_case(&table_ident.name))
37793                    {
37794                        // Strip the table qualifier
37795                        let mut col = col.clone();
37796                        col.table = None;
37797                        return Expression::Column(col);
37798                    }
37799                }
37800                expr.clone()
37801            }
37802            Expression::Dot(dot) => {
37803                // table.column -> column (strip qualifier)
37804                if let Expression::Identifier(id) = &dot.this {
37805                    if self
37806                        .merge_strip_qualifiers
37807                        .iter()
37808                        .any(|n| n.eq_ignore_ascii_case(&id.name))
37809                    {
37810                        return Expression::Identifier(dot.field.clone());
37811                    }
37812                }
37813                expr.clone()
37814            }
37815            _ => expr.clone(),
37816        }
37817    }
37818
37819    fn generate_whens(&mut self, e: &Whens) -> Result<()> {
37820        // Python: return self.expressions(expression, sep=" ", indent=False)
37821        for (i, expr) in e.expressions.iter().enumerate() {
37822            if i > 0 {
37823                // In pretty mode, each WHEN clause on its own line
37824                if self.config.pretty {
37825                    self.write_newline();
37826                    self.write_indent();
37827                } else {
37828                    self.write_space();
37829                }
37830            }
37831            self.generate_expression(expr)?;
37832        }
37833        Ok(())
37834    }
37835
37836    fn generate_where(&mut self, e: &Where) -> Result<()> {
37837        // Python: return f"{self.seg('WHERE')}{self.sep()}{this}"
37838        self.write_keyword("WHERE");
37839        self.write_space();
37840        self.generate_expression(&e.this)?;
37841        Ok(())
37842    }
37843
37844    fn generate_width_bucket(&mut self, e: &WidthBucket) -> Result<()> {
37845        // Python: return self.func("WIDTH_BUCKET", expression.this, ...)
37846        self.write_keyword("WIDTH_BUCKET");
37847        self.write("(");
37848        self.generate_expression(&e.this)?;
37849        if let Some(min_value) = &e.min_value {
37850            self.write(", ");
37851            self.generate_expression(min_value)?;
37852        }
37853        if let Some(max_value) = &e.max_value {
37854            self.write(", ");
37855            self.generate_expression(max_value)?;
37856        }
37857        if let Some(num_buckets) = &e.num_buckets {
37858            self.write(", ");
37859            self.generate_expression(num_buckets)?;
37860        }
37861        self.write(")");
37862        Ok(())
37863    }
37864
37865    fn generate_window(&mut self, e: &WindowSpec) -> Result<()> {
37866        // Window specification: PARTITION BY ... ORDER BY ... frame
37867        self.generate_window_spec(e)
37868    }
37869
37870    fn generate_window_spec(&mut self, e: &WindowSpec) -> Result<()> {
37871        // Window specification: PARTITION BY ... ORDER BY ... frame
37872        let mut has_content = false;
37873
37874        // PARTITION BY
37875        if !e.partition_by.is_empty() {
37876            self.write_keyword("PARTITION BY");
37877            self.write_space();
37878            for (i, expr) in e.partition_by.iter().enumerate() {
37879                if i > 0 {
37880                    self.write(", ");
37881                }
37882                self.generate_expression(expr)?;
37883            }
37884            has_content = true;
37885        }
37886
37887        // ORDER BY
37888        if !e.order_by.is_empty() {
37889            if has_content {
37890                self.write_space();
37891            }
37892            self.write_keyword("ORDER BY");
37893            self.write_space();
37894            for (i, ordered) in e.order_by.iter().enumerate() {
37895                if i > 0 {
37896                    self.write(", ");
37897                }
37898                self.generate_expression(&ordered.this)?;
37899                if ordered.desc {
37900                    self.write_space();
37901                    self.write_keyword("DESC");
37902                } else if ordered.explicit_asc {
37903                    self.write_space();
37904                    self.write_keyword("ASC");
37905                }
37906                if let Some(nulls_first) = ordered.nulls_first {
37907                    self.write_space();
37908                    self.write_keyword("NULLS");
37909                    self.write_space();
37910                    if nulls_first {
37911                        self.write_keyword("FIRST");
37912                    } else {
37913                        self.write_keyword("LAST");
37914                    }
37915                }
37916            }
37917            has_content = true;
37918        }
37919
37920        // Frame specification
37921        if let Some(frame) = &e.frame {
37922            if has_content {
37923                self.write_space();
37924            }
37925            self.generate_window_frame(frame)?;
37926        }
37927
37928        Ok(())
37929    }
37930
37931    fn generate_with_data_property(&mut self, e: &WithDataProperty) -> Result<()> {
37932        // Python: f"WITH {'NO ' if expression.args.get('no') else ''}DATA"
37933        self.write_keyword("WITH");
37934        self.write_space();
37935        if e.no.is_some() {
37936            self.write_keyword("NO");
37937            self.write_space();
37938        }
37939        self.write_keyword("DATA");
37940
37941        // statistics
37942        if let Some(statistics) = &e.statistics {
37943            self.write_space();
37944            self.write_keyword("AND");
37945            self.write_space();
37946            // Check if statistics is true or false
37947            match statistics.as_ref() {
37948                Expression::Boolean(b) if !b.value => {
37949                    self.write_keyword("NO");
37950                    self.write_space();
37951                }
37952                _ => {}
37953            }
37954            self.write_keyword("STATISTICS");
37955        }
37956        Ok(())
37957    }
37958
37959    fn generate_with_fill(&mut self, e: &WithFill) -> Result<()> {
37960        // Python: f"WITH FILL{from_sql}{to_sql}{step_sql}{interpolate}"
37961        self.write_keyword("WITH FILL");
37962
37963        if let Some(from_) = &e.from_ {
37964            self.write_space();
37965            self.write_keyword("FROM");
37966            self.write_space();
37967            self.generate_expression(from_)?;
37968        }
37969
37970        if let Some(to) = &e.to {
37971            self.write_space();
37972            self.write_keyword("TO");
37973            self.write_space();
37974            self.generate_expression(to)?;
37975        }
37976
37977        if let Some(step) = &e.step {
37978            self.write_space();
37979            self.write_keyword("STEP");
37980            self.write_space();
37981            self.generate_expression(step)?;
37982        }
37983
37984        if let Some(staleness) = &e.staleness {
37985            self.write_space();
37986            self.write_keyword("STALENESS");
37987            self.write_space();
37988            self.generate_expression(staleness)?;
37989        }
37990
37991        if let Some(interpolate) = &e.interpolate {
37992            self.write_space();
37993            self.write_keyword("INTERPOLATE");
37994            self.write(" (");
37995            // INTERPOLATE items use reversed alias format: name AS expression
37996            self.generate_interpolate_item(interpolate)?;
37997            self.write(")");
37998        }
37999
38000        Ok(())
38001    }
38002
38003    /// Generate INTERPOLATE items with reversed alias format (name AS expression)
38004    fn generate_interpolate_item(&mut self, expr: &Expression) -> Result<()> {
38005        match expr {
38006            Expression::Alias(alias) => {
38007                // Output as: alias_name AS expression
38008                self.generate_identifier(&alias.alias)?;
38009                self.write_space();
38010                self.write_keyword("AS");
38011                self.write_space();
38012                self.generate_expression(&alias.this)?;
38013            }
38014            Expression::Tuple(tuple) => {
38015                for (i, item) in tuple.expressions.iter().enumerate() {
38016                    if i > 0 {
38017                        self.write(", ");
38018                    }
38019                    self.generate_interpolate_item(item)?;
38020                }
38021            }
38022            other => {
38023                self.generate_expression(other)?;
38024            }
38025        }
38026        Ok(())
38027    }
38028
38029    fn generate_with_journal_table_property(&mut self, e: &WithJournalTableProperty) -> Result<()> {
38030        // Python: return f"WITH JOURNAL TABLE={self.sql(expression, 'this')}"
38031        self.write_keyword("WITH JOURNAL TABLE");
38032        self.write("=");
38033        self.generate_expression(&e.this)?;
38034        Ok(())
38035    }
38036
38037    fn generate_with_operator(&mut self, e: &WithOperator) -> Result<()> {
38038        // Python: return f"{self.sql(expression, 'this')} WITH {self.sql(expression, 'op')}"
38039        self.generate_expression(&e.this)?;
38040        self.write_space();
38041        self.write_keyword("WITH");
38042        self.write_space();
38043        self.write_keyword(&e.op);
38044        Ok(())
38045    }
38046
38047    fn generate_with_procedure_options(&mut self, e: &WithProcedureOptions) -> Result<()> {
38048        // Python: return f"WITH {self.expressions(expression, flat=True)}"
38049        self.write_keyword("WITH");
38050        self.write_space();
38051        for (i, expr) in e.expressions.iter().enumerate() {
38052            if i > 0 {
38053                self.write(", ");
38054            }
38055            self.generate_expression(expr)?;
38056        }
38057        Ok(())
38058    }
38059
38060    fn generate_with_schema_binding_property(
38061        &mut self,
38062        e: &WithSchemaBindingProperty,
38063    ) -> Result<()> {
38064        // Python: return f"WITH {self.sql(expression, 'this')}"
38065        self.write_keyword("WITH");
38066        self.write_space();
38067        self.generate_expression(&e.this)?;
38068        Ok(())
38069    }
38070
38071    fn generate_with_system_versioning_property(
38072        &mut self,
38073        e: &WithSystemVersioningProperty,
38074    ) -> Result<()> {
38075        // Python: complex logic for SYSTEM_VERSIONING with options
38076        // SYSTEM_VERSIONING=ON(HISTORY_TABLE=..., DATA_CONSISTENCY_CHECK=..., HISTORY_RETENTION_PERIOD=...)
38077        // or SYSTEM_VERSIONING=ON/OFF
38078        // with WITH(...) wrapper if with_ is set
38079
38080        let mut parts = Vec::new();
38081
38082        if let Some(this) = &e.this {
38083            // HISTORY_TABLE=...
38084            let mut s = String::from("HISTORY_TABLE=");
38085            let mut gen = Generator::new();
38086            gen.generate_expression(this)?;
38087            s.push_str(&gen.output);
38088            parts.push(s);
38089        }
38090
38091        if let Some(data_consistency) = &e.data_consistency {
38092            let mut s = String::from("DATA_CONSISTENCY_CHECK=");
38093            let mut gen = Generator::new();
38094            gen.generate_expression(data_consistency)?;
38095            s.push_str(&gen.output);
38096            parts.push(s);
38097        }
38098
38099        if let Some(retention_period) = &e.retention_period {
38100            let mut s = String::from("HISTORY_RETENTION_PERIOD=");
38101            let mut gen = Generator::new();
38102            gen.generate_expression(retention_period)?;
38103            s.push_str(&gen.output);
38104            parts.push(s);
38105        }
38106
38107        self.write_keyword("SYSTEM_VERSIONING");
38108        self.write("=");
38109
38110        if !parts.is_empty() {
38111            self.write_keyword("ON");
38112            self.write("(");
38113            self.write(&parts.join(", "));
38114            self.write(")");
38115        } else if e.on.is_some() {
38116            self.write_keyword("ON");
38117        } else {
38118            self.write_keyword("OFF");
38119        }
38120
38121        // Wrap in WITH(...) if with_ is set
38122        if e.with_.is_some() {
38123            let inner = self.output.clone();
38124            self.output.clear();
38125            self.write("WITH(");
38126            self.write(&inner);
38127            self.write(")");
38128        }
38129
38130        Ok(())
38131    }
38132
38133    fn generate_with_table_hint(&mut self, e: &WithTableHint) -> Result<()> {
38134        // Python: f"WITH ({self.expressions(expression, flat=True)})"
38135        self.write_keyword("WITH");
38136        self.write(" (");
38137        for (i, expr) in e.expressions.iter().enumerate() {
38138            if i > 0 {
38139                self.write(", ");
38140            }
38141            self.generate_expression(expr)?;
38142        }
38143        self.write(")");
38144        Ok(())
38145    }
38146
38147    fn generate_xml_element(&mut self, e: &XMLElement) -> Result<()> {
38148        // Python: prefix = "EVALNAME" if expression.args.get("evalname") else "NAME"
38149        // return self.func("XMLELEMENT", name, *expression.expressions)
38150        self.write_keyword("XMLELEMENT");
38151        self.write("(");
38152
38153        if e.evalname.is_some() {
38154            self.write_keyword("EVALNAME");
38155        } else {
38156            self.write_keyword("NAME");
38157        }
38158        self.write_space();
38159        self.generate_expression(&e.this)?;
38160
38161        for expr in &e.expressions {
38162            self.write(", ");
38163            self.generate_expression(expr)?;
38164        }
38165        self.write(")");
38166        Ok(())
38167    }
38168
38169    fn generate_xml_get(&mut self, e: &XMLGet) -> Result<()> {
38170        // XMLGET(this, expression [, instance])
38171        self.write_keyword("XMLGET");
38172        self.write("(");
38173        self.generate_expression(&e.this)?;
38174        self.write(", ");
38175        self.generate_expression(&e.expression)?;
38176        if let Some(instance) = &e.instance {
38177            self.write(", ");
38178            self.generate_expression(instance)?;
38179        }
38180        self.write(")");
38181        Ok(())
38182    }
38183
38184    fn generate_xml_key_value_option(&mut self, e: &XMLKeyValueOption) -> Result<()> {
38185        // Python: this + optional (expr)
38186        self.generate_expression(&e.this)?;
38187        if let Some(expression) = &e.expression {
38188            self.write("(");
38189            self.generate_expression(expression)?;
38190            self.write(")");
38191        }
38192        Ok(())
38193    }
38194
38195    fn generate_xml_table(&mut self, e: &XMLTable) -> Result<()> {
38196        // Python: XMLTABLE(namespaces + this + passing + by_ref + columns)
38197        self.write_keyword("XMLTABLE");
38198        self.write("(");
38199
38200        if self.config.pretty {
38201            self.indent_level += 1;
38202            self.write_newline();
38203            self.write_indent();
38204            self.generate_expression(&e.this)?;
38205
38206            if let Some(passing) = &e.passing {
38207                self.write_newline();
38208                self.write_indent();
38209                self.write_keyword("PASSING");
38210                if let Expression::Tuple(tuple) = passing.as_ref() {
38211                    for expr in &tuple.expressions {
38212                        self.write_newline();
38213                        self.indent_level += 1;
38214                        self.write_indent();
38215                        self.generate_expression(expr)?;
38216                        self.indent_level -= 1;
38217                    }
38218                } else {
38219                    self.write_newline();
38220                    self.indent_level += 1;
38221                    self.write_indent();
38222                    self.generate_expression(passing)?;
38223                    self.indent_level -= 1;
38224                }
38225            }
38226
38227            if e.by_ref.is_some() {
38228                self.write_newline();
38229                self.write_indent();
38230                self.write_keyword("RETURNING SEQUENCE BY REF");
38231            }
38232
38233            if !e.columns.is_empty() {
38234                self.write_newline();
38235                self.write_indent();
38236                self.write_keyword("COLUMNS");
38237                for (i, col) in e.columns.iter().enumerate() {
38238                    self.write_newline();
38239                    self.indent_level += 1;
38240                    self.write_indent();
38241                    self.generate_expression(col)?;
38242                    self.indent_level -= 1;
38243                    if i < e.columns.len() - 1 {
38244                        self.write(",");
38245                    }
38246                }
38247            }
38248
38249            self.indent_level -= 1;
38250            self.write_newline();
38251            self.write_indent();
38252            self.write(")");
38253            return Ok(());
38254        }
38255
38256        // Namespaces - unwrap Tuple to generate comma-separated list without parentheses
38257        if let Some(namespaces) = &e.namespaces {
38258            self.write_keyword("XMLNAMESPACES");
38259            self.write("(");
38260            // Unwrap Tuple if present to avoid extra parentheses
38261            if let Expression::Tuple(tuple) = namespaces.as_ref() {
38262                for (i, expr) in tuple.expressions.iter().enumerate() {
38263                    if i > 0 {
38264                        self.write(", ");
38265                    }
38266                    // Python pattern: if it's an Alias, output as-is; otherwise prepend DEFAULT
38267                    // See xmlnamespace_sql in generator.py
38268                    if !matches!(expr, Expression::Alias(_)) {
38269                        self.write_keyword("DEFAULT");
38270                        self.write_space();
38271                    }
38272                    self.generate_expression(expr)?;
38273                }
38274            } else {
38275                // Single namespace - check if DEFAULT
38276                if !matches!(namespaces.as_ref(), Expression::Alias(_)) {
38277                    self.write_keyword("DEFAULT");
38278                    self.write_space();
38279                }
38280                self.generate_expression(namespaces)?;
38281            }
38282            self.write("), ");
38283        }
38284
38285        // XPath expression
38286        self.generate_expression(&e.this)?;
38287
38288        // PASSING clause - unwrap Tuple to generate comma-separated list without parentheses
38289        if let Some(passing) = &e.passing {
38290            self.write_space();
38291            self.write_keyword("PASSING");
38292            self.write_space();
38293            // Unwrap Tuple if present to avoid extra parentheses
38294            if let Expression::Tuple(tuple) = passing.as_ref() {
38295                for (i, expr) in tuple.expressions.iter().enumerate() {
38296                    if i > 0 {
38297                        self.write(", ");
38298                    }
38299                    self.generate_expression(expr)?;
38300                }
38301            } else {
38302                self.generate_expression(passing)?;
38303            }
38304        }
38305
38306        // RETURNING SEQUENCE BY REF
38307        if e.by_ref.is_some() {
38308            self.write_space();
38309            self.write_keyword("RETURNING SEQUENCE BY REF");
38310        }
38311
38312        // COLUMNS clause
38313        if !e.columns.is_empty() {
38314            self.write_space();
38315            self.write_keyword("COLUMNS");
38316            self.write_space();
38317            for (i, col) in e.columns.iter().enumerate() {
38318                if i > 0 {
38319                    self.write(", ");
38320                }
38321                self.generate_expression(col)?;
38322            }
38323        }
38324
38325        self.write(")");
38326        Ok(())
38327    }
38328
38329    fn generate_xor(&mut self, e: &Xor) -> Result<()> {
38330        // Python: return self.connector_sql(expression, "XOR", stack)
38331        // Handles: this XOR expression or expressions joined by XOR
38332        if let Some(this) = &e.this {
38333            self.generate_expression(this)?;
38334            if let Some(expression) = &e.expression {
38335                self.write_space();
38336                self.write_keyword("XOR");
38337                self.write_space();
38338                self.generate_expression(expression)?;
38339            }
38340        }
38341
38342        // Handle multiple expressions
38343        for (i, expr) in e.expressions.iter().enumerate() {
38344            if i > 0 || e.this.is_some() {
38345                self.write_space();
38346                self.write_keyword("XOR");
38347                self.write_space();
38348            }
38349            self.generate_expression(expr)?;
38350        }
38351        Ok(())
38352    }
38353
38354    fn generate_zipf(&mut self, e: &Zipf) -> Result<()> {
38355        // ZIPF(this, elementcount [, gen])
38356        self.write_keyword("ZIPF");
38357        self.write("(");
38358        self.generate_expression(&e.this)?;
38359        if let Some(elementcount) = &e.elementcount {
38360            self.write(", ");
38361            self.generate_expression(elementcount)?;
38362        }
38363        if let Some(gen) = &e.gen {
38364            self.write(", ");
38365            self.generate_expression(gen)?;
38366        }
38367        self.write(")");
38368        Ok(())
38369    }
38370}
38371
38372impl Default for Generator {
38373    fn default() -> Self {
38374        Self::new()
38375    }
38376}
38377
38378#[cfg(test)]
38379mod tests {
38380    use super::*;
38381    use crate::parser::Parser;
38382
38383    fn roundtrip(sql: &str) -> String {
38384        let ast = Parser::parse_sql(sql).unwrap();
38385        Generator::sql(&ast[0]).unwrap()
38386    }
38387
38388    #[test]
38389    fn test_simple_select() {
38390        let result = roundtrip("SELECT 1");
38391        assert_eq!(result, "SELECT 1");
38392    }
38393
38394    #[test]
38395    fn test_select_from() {
38396        let result = roundtrip("SELECT a, b FROM t");
38397        assert_eq!(result, "SELECT a, b FROM t");
38398    }
38399
38400    #[test]
38401    fn test_select_where() {
38402        let result = roundtrip("SELECT * FROM t WHERE x = 1");
38403        assert_eq!(result, "SELECT * FROM t WHERE x = 1");
38404    }
38405
38406    #[test]
38407    fn test_select_join() {
38408        let result = roundtrip("SELECT * FROM a JOIN b ON a.id = b.id");
38409        assert_eq!(result, "SELECT * FROM a JOIN b ON a.id = b.id");
38410    }
38411
38412    #[test]
38413    fn test_insert() {
38414        let result = roundtrip("INSERT INTO t (a, b) VALUES (1, 2)");
38415        assert_eq!(result, "INSERT INTO t (a, b) VALUES (1, 2)");
38416    }
38417
38418    #[test]
38419    fn test_pretty_print() {
38420        let ast = Parser::parse_sql("SELECT a, b FROM t WHERE x = 1").unwrap();
38421        let result = Generator::pretty_sql(&ast[0]).unwrap();
38422        assert!(result.contains('\n'));
38423    }
38424
38425    #[test]
38426    fn test_window_function() {
38427        let result = roundtrip("SELECT ROW_NUMBER() OVER (PARTITION BY category ORDER BY id)");
38428        assert_eq!(
38429            result,
38430            "SELECT ROW_NUMBER() OVER (PARTITION BY category ORDER BY id)"
38431        );
38432    }
38433
38434    #[test]
38435    fn test_window_function_with_frame() {
38436        let result = roundtrip("SELECT SUM(amount) OVER (ORDER BY order_date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)");
38437        assert_eq!(result, "SELECT SUM(amount) OVER (ORDER BY order_date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)");
38438    }
38439
38440    #[test]
38441    fn test_aggregate_with_filter() {
38442        let result = roundtrip("SELECT COUNT(*) FILTER (WHERE status = 1) FROM orders");
38443        assert_eq!(
38444            result,
38445            "SELECT COUNT(*) FILTER(WHERE status = 1) FROM orders"
38446        );
38447    }
38448
38449    #[test]
38450    fn test_subscript() {
38451        let result = roundtrip("SELECT arr[0]");
38452        assert_eq!(result, "SELECT arr[0]");
38453    }
38454
38455    // DDL tests
38456    #[test]
38457    fn test_create_table() {
38458        let result = roundtrip("CREATE TABLE users (id INT, name VARCHAR(100))");
38459        assert_eq!(result, "CREATE TABLE users (id INT, name VARCHAR(100))");
38460    }
38461
38462    #[test]
38463    fn test_create_table_with_constraints() {
38464        let result = roundtrip(
38465            "CREATE TABLE users (id INT PRIMARY KEY, email VARCHAR(255) UNIQUE NOT NULL)",
38466        );
38467        assert_eq!(
38468            result,
38469            "CREATE TABLE users (id INT PRIMARY KEY, email VARCHAR(255) UNIQUE NOT NULL)"
38470        );
38471    }
38472
38473    #[test]
38474    fn test_create_table_if_not_exists() {
38475        let result = roundtrip("CREATE TABLE IF NOT EXISTS t (id INT)");
38476        assert_eq!(result, "CREATE TABLE IF NOT EXISTS t (id INT)");
38477    }
38478
38479    #[test]
38480    fn test_drop_table() {
38481        let result = roundtrip("DROP TABLE users");
38482        assert_eq!(result, "DROP TABLE users");
38483    }
38484
38485    #[test]
38486    fn test_drop_table_if_exists_cascade() {
38487        let result = roundtrip("DROP TABLE IF EXISTS users CASCADE");
38488        assert_eq!(result, "DROP TABLE IF EXISTS users CASCADE");
38489    }
38490
38491    #[test]
38492    fn test_alter_table_add_column() {
38493        let result = roundtrip("ALTER TABLE users ADD COLUMN email VARCHAR(255)");
38494        assert_eq!(result, "ALTER TABLE users ADD COLUMN email VARCHAR(255)");
38495    }
38496
38497    #[test]
38498    fn test_alter_table_drop_column() {
38499        let result = roundtrip("ALTER TABLE users DROP COLUMN email");
38500        assert_eq!(result, "ALTER TABLE users DROP COLUMN email");
38501    }
38502
38503    #[test]
38504    fn test_create_index() {
38505        let result = roundtrip("CREATE INDEX idx_name ON users(name)");
38506        assert_eq!(result, "CREATE INDEX idx_name ON users(name)");
38507    }
38508
38509    #[test]
38510    fn test_create_unique_index() {
38511        let result = roundtrip("CREATE UNIQUE INDEX idx_email ON users(email)");
38512        assert_eq!(result, "CREATE UNIQUE INDEX idx_email ON users(email)");
38513    }
38514
38515    #[test]
38516    fn test_drop_index() {
38517        let result = roundtrip("DROP INDEX idx_name");
38518        assert_eq!(result, "DROP INDEX idx_name");
38519    }
38520
38521    #[test]
38522    fn test_create_view() {
38523        let result = roundtrip("CREATE VIEW active_users AS SELECT * FROM users WHERE active = 1");
38524        assert_eq!(
38525            result,
38526            "CREATE VIEW active_users AS SELECT * FROM users WHERE active = 1"
38527        );
38528    }
38529
38530    #[test]
38531    fn test_drop_view() {
38532        let result = roundtrip("DROP VIEW active_users");
38533        assert_eq!(result, "DROP VIEW active_users");
38534    }
38535
38536    #[test]
38537    fn test_truncate() {
38538        let result = roundtrip("TRUNCATE TABLE users");
38539        assert_eq!(result, "TRUNCATE TABLE users");
38540    }
38541
38542    #[test]
38543    fn test_string_literal_escaping_default() {
38544        // Default: double single quotes
38545        let result = roundtrip("SELECT 'hello'");
38546        assert_eq!(result, "SELECT 'hello'");
38547
38548        // Single quotes are doubled
38549        let result = roundtrip("SELECT 'it''s a test'");
38550        assert_eq!(result, "SELECT 'it''s a test'");
38551    }
38552
38553    #[test]
38554    fn test_not_in_style_prefix_default_generic() {
38555        let result = roundtrip("SELECT id FROM users WHERE status NOT IN ('deleted', 'banned')");
38556        assert_eq!(
38557            result,
38558            "SELECT id FROM users WHERE NOT status IN ('deleted', 'banned')"
38559        );
38560    }
38561
38562    #[test]
38563    fn test_not_in_style_infix_generic_override() {
38564        let ast =
38565            Parser::parse_sql("SELECT id FROM users WHERE status NOT IN ('deleted', 'banned')")
38566                .unwrap();
38567        let config = GeneratorConfig {
38568            not_in_style: NotInStyle::Infix,
38569            ..Default::default()
38570        };
38571        let mut gen = Generator::with_config(config);
38572        let result = gen.generate(&ast[0]).unwrap();
38573        assert_eq!(
38574            result,
38575            "SELECT id FROM users WHERE status NOT IN ('deleted', 'banned')"
38576        );
38577    }
38578
38579    #[test]
38580    fn test_string_literal_escaping_mysql() {
38581        use crate::dialects::DialectType;
38582
38583        let config = GeneratorConfig {
38584            dialect: Some(DialectType::MySQL),
38585            ..Default::default()
38586        };
38587
38588        let ast = Parser::parse_sql("SELECT 'hello'").unwrap();
38589        let mut gen = Generator::with_config(config.clone());
38590        let result = gen.generate(&ast[0]).unwrap();
38591        assert_eq!(result, "SELECT 'hello'");
38592
38593        // MySQL uses SQL standard quote doubling for escaping (matches Python sqlglot)
38594        let ast = Parser::parse_sql("SELECT 'it''s'").unwrap();
38595        let mut gen = Generator::with_config(config.clone());
38596        let result = gen.generate(&ast[0]).unwrap();
38597        assert_eq!(result, "SELECT 'it''s'");
38598    }
38599
38600    #[test]
38601    fn test_string_literal_escaping_postgres() {
38602        use crate::dialects::DialectType;
38603
38604        let config = GeneratorConfig {
38605            dialect: Some(DialectType::PostgreSQL),
38606            ..Default::default()
38607        };
38608
38609        let ast = Parser::parse_sql("SELECT 'hello'").unwrap();
38610        let mut gen = Generator::with_config(config.clone());
38611        let result = gen.generate(&ast[0]).unwrap();
38612        assert_eq!(result, "SELECT 'hello'");
38613
38614        // PostgreSQL uses doubled quotes for regular strings
38615        let ast = Parser::parse_sql("SELECT 'it''s'").unwrap();
38616        let mut gen = Generator::with_config(config.clone());
38617        let result = gen.generate(&ast[0]).unwrap();
38618        assert_eq!(result, "SELECT 'it''s'");
38619    }
38620
38621    #[test]
38622    fn test_string_literal_escaping_bigquery() {
38623        use crate::dialects::DialectType;
38624
38625        let config = GeneratorConfig {
38626            dialect: Some(DialectType::BigQuery),
38627            ..Default::default()
38628        };
38629
38630        let ast = Parser::parse_sql("SELECT 'hello'").unwrap();
38631        let mut gen = Generator::with_config(config.clone());
38632        let result = gen.generate(&ast[0]).unwrap();
38633        assert_eq!(result, "SELECT 'hello'");
38634
38635        // BigQuery escapes single quotes with backslash
38636        let ast = Parser::parse_sql("SELECT 'it''s'").unwrap();
38637        let mut gen = Generator::with_config(config.clone());
38638        let result = gen.generate(&ast[0]).unwrap();
38639        assert_eq!(result, "SELECT 'it\\'s'");
38640    }
38641
38642    #[test]
38643    fn test_generate_deep_and_chain_without_stack_growth() {
38644        let mut expr = Expression::Eq(Box::new(BinaryOp::new(
38645            Expression::column("c0"),
38646            Expression::number(0),
38647        )));
38648
38649        for i in 1..2500 {
38650            let predicate = Expression::Eq(Box::new(BinaryOp::new(
38651                Expression::column(format!("c{i}")),
38652                Expression::number(i as i64),
38653            )));
38654            expr = Expression::And(Box::new(BinaryOp::new(expr, predicate)));
38655        }
38656
38657        let sql = Generator::sql(&expr).expect("deep AND chain should generate");
38658        assert!(sql.contains("c2499 = 2499"), "{}", sql);
38659    }
38660
38661    #[test]
38662    fn test_generate_deep_or_chain_without_stack_growth() {
38663        let mut expr = Expression::Eq(Box::new(BinaryOp::new(
38664            Expression::column("c0"),
38665            Expression::number(0),
38666        )));
38667
38668        for i in 1..2500 {
38669            let predicate = Expression::Eq(Box::new(BinaryOp::new(
38670                Expression::column(format!("c{i}")),
38671                Expression::number(i as i64),
38672            )));
38673            expr = Expression::Or(Box::new(BinaryOp::new(expr, predicate)));
38674        }
38675
38676        let sql = Generator::sql(&expr).expect("deep OR chain should generate");
38677        assert!(sql.contains("c2499 = 2499"), "{}", sql);
38678    }
38679}