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("values");
873        set.remove("table");
874        set
875    });
876
877    /// MySQL-specific reserved keywords
878    pub static MYSQL_RESERVED: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
879        let mut set = SQL_RESERVED.clone();
880        set.extend([
881            "accessible",
882            "add",
883            "analyze",
884            "asensitive",
885            "before",
886            "bigint",
887            "binary",
888            "blob",
889            "call",
890            "cascade",
891            "change",
892            "char",
893            "character",
894            "condition",
895            "continue",
896            "convert",
897            "current_date",
898            "current_time",
899            "current_timestamp",
900            "current_user",
901            "cursor",
902            "database",
903            "databases",
904            "day_hour",
905            "day_microsecond",
906            "day_minute",
907            "day_second",
908            "dec",
909            "decimal",
910            "declare",
911            "delayed",
912            "describe",
913            "deterministic",
914            "distinctrow",
915            "div",
916            "double",
917            "dual",
918            "each",
919            "elseif",
920            "enclosed",
921            "escaped",
922            "exit",
923            "explain",
924            "float",
925            "float4",
926            "float8",
927            "force",
928            "get",
929            "high_priority",
930            "hour_microsecond",
931            "hour_minute",
932            "hour_second",
933            "ignore",
934            "infile",
935            "inout",
936            "insensitive",
937            "int",
938            "int1",
939            "int2",
940            "int3",
941            "int4",
942            "int8",
943            "integer",
944            "iterate",
945            "keys",
946            "kill",
947            "leave",
948            "linear",
949            "lines",
950            "load",
951            "lock",
952            "long",
953            "longblob",
954            "longtext",
955            "loop",
956            "low_priority",
957            "master_ssl_verify_server_cert",
958            "maxvalue",
959            "mediumblob",
960            "mediumint",
961            "mediumtext",
962            "middleint",
963            "minute_microsecond",
964            "minute_second",
965            "mod",
966            "modifies",
967            "no_write_to_binlog",
968            "numeric",
969            "optimize",
970            "option",
971            "optionally",
972            "out",
973            "outfile",
974            "precision",
975            "purge",
976            "read",
977            "reads",
978            "real",
979            "regexp",
980            "release",
981            "rename",
982            "repeat",
983            "replace",
984            "require",
985            "resignal",
986            "restrict",
987            "return",
988            "revoke",
989            "rlike",
990            "schema",
991            "schemas",
992            "second_microsecond",
993            "sensitive",
994            "separator",
995            "show",
996            "signal",
997            "smallint",
998            "spatial",
999            "specific",
1000            "sql",
1001            "sql_big_result",
1002            "sql_calc_found_rows",
1003            "sql_small_result",
1004            "sqlexception",
1005            "sqlstate",
1006            "sqlwarning",
1007            "ssl",
1008            "starting",
1009            "straight_join",
1010            "terminated",
1011            "text",
1012            "tinyblob",
1013            "tinyint",
1014            "tinytext",
1015            "trigger",
1016            "undo",
1017            "unlock",
1018            "unsigned",
1019            "usage",
1020            "utc_date",
1021            "utc_time",
1022            "utc_timestamp",
1023            "varbinary",
1024            "varchar",
1025            "varcharacter",
1026            "varying",
1027            "while",
1028            "write",
1029            "xor",
1030            "year_month",
1031            "zerofill",
1032        ]);
1033        set.remove("table");
1034        set
1035    });
1036
1037    /// Doris-specific reserved keywords
1038    /// Extends MySQL reserved with additional Doris-specific words
1039    pub static DORIS_RESERVED: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
1040        let mut set = MYSQL_RESERVED.clone();
1041        set.extend([
1042            "aggregate",
1043            "anti",
1044            "array",
1045            "backend",
1046            "backup",
1047            "begin",
1048            "bitmap",
1049            "boolean",
1050            "broker",
1051            "buckets",
1052            "cached",
1053            "cancel",
1054            "cast",
1055            "catalog",
1056            "charset",
1057            "cluster",
1058            "collation",
1059            "columns",
1060            "comment",
1061            "commit",
1062            "config",
1063            "connection",
1064            "count",
1065            "current",
1066            "data",
1067            "date",
1068            "datetime",
1069            "day",
1070            "deferred",
1071            "distributed",
1072            "dynamic",
1073            "enable",
1074            "end",
1075            "events",
1076            "export",
1077            "external",
1078            "fields",
1079            "first",
1080            "follower",
1081            "format",
1082            "free",
1083            "frontend",
1084            "full",
1085            "functions",
1086            "global",
1087            "grants",
1088            "hash",
1089            "help",
1090            "hour",
1091            "install",
1092            "intermediate",
1093            "json",
1094            "label",
1095            "last",
1096            "less",
1097            "level",
1098            "link",
1099            "local",
1100            "location",
1101            "max",
1102            "merge",
1103            "min",
1104            "minute",
1105            "modify",
1106            "month",
1107            "name",
1108            "names",
1109            "negative",
1110            "nulls",
1111            "observer",
1112            "offset",
1113            "only",
1114            "open",
1115            "overwrite",
1116            "password",
1117            "path",
1118            "plan",
1119            "plugin",
1120            "plugins",
1121            "policy",
1122            "process",
1123            "properties",
1124            "property",
1125            "query",
1126            "quota",
1127            "recover",
1128            "refresh",
1129            "repair",
1130            "replica",
1131            "repository",
1132            "resource",
1133            "restore",
1134            "resume",
1135            "role",
1136            "roles",
1137            "rollback",
1138            "rollup",
1139            "routine",
1140            "sample",
1141            "second",
1142            "semi",
1143            "session",
1144            "signed",
1145            "snapshot",
1146            "start",
1147            "stats",
1148            "status",
1149            "stop",
1150            "stream",
1151            "string",
1152            "sum",
1153            "tables",
1154            "tablet",
1155            "temporary",
1156            "text",
1157            "timestamp",
1158            "transaction",
1159            "trash",
1160            "trim",
1161            "truncate",
1162            "type",
1163            "user",
1164            "value",
1165            "variables",
1166            "verbose",
1167            "version",
1168            "view",
1169            "warnings",
1170            "week",
1171            "work",
1172            "year",
1173        ]);
1174        set
1175    });
1176
1177    /// PostgreSQL-specific reserved keywords
1178    pub static POSTGRES_RESERVED: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
1179        let mut set = SQL_RESERVED.clone();
1180        set.extend([
1181            "analyse",
1182            "analyze",
1183            "asymmetric",
1184            "binary",
1185            "collation",
1186            "concurrently",
1187            "current_catalog",
1188            "current_role",
1189            "current_schema",
1190            "deferrable",
1191            "do",
1192            "freeze",
1193            "ilike",
1194            "initially",
1195            "isnull",
1196            "lateral",
1197            "notnull",
1198            "placing",
1199            "returning",
1200            "similar",
1201            "symmetric",
1202            "variadic",
1203            "verbose",
1204        ]);
1205        // PostgreSQL doesn't require quoting for these keywords
1206        set.remove("default");
1207        set.remove("interval");
1208        set.remove("match");
1209        set.remove("offset");
1210        set.remove("table");
1211        set
1212    });
1213
1214    /// Redshift-specific reserved keywords
1215    /// Based on: https://docs.aws.amazon.com/redshift/latest/dg/r_pg_keywords.html
1216    /// Note: `index` is NOT reserved in Redshift (unlike PostgreSQL)
1217    pub static REDSHIFT_RESERVED: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
1218        [
1219            "aes128",
1220            "aes256",
1221            "all",
1222            "allowoverwrite",
1223            "analyse",
1224            "analyze",
1225            "and",
1226            "any",
1227            "array",
1228            "as",
1229            "asc",
1230            "authorization",
1231            "az64",
1232            "backup",
1233            "between",
1234            "binary",
1235            "blanksasnull",
1236            "both",
1237            "bytedict",
1238            "bzip2",
1239            "case",
1240            "cast",
1241            "check",
1242            "collate",
1243            "column",
1244            "constraint",
1245            "create",
1246            "credentials",
1247            "cross",
1248            "current_date",
1249            "current_time",
1250            "current_timestamp",
1251            "current_user",
1252            "current_user_id",
1253            "default",
1254            "deferrable",
1255            "deflate",
1256            "defrag",
1257            "delta",
1258            "delta32k",
1259            "desc",
1260            "disable",
1261            "distinct",
1262            "do",
1263            "else",
1264            "emptyasnull",
1265            "enable",
1266            "encode",
1267            "encrypt",
1268            "encryption",
1269            "end",
1270            "except",
1271            "explicit",
1272            "false",
1273            "for",
1274            "foreign",
1275            "freeze",
1276            "from",
1277            "full",
1278            "globaldict256",
1279            "globaldict64k",
1280            "grant",
1281            "group",
1282            "gzip",
1283            "having",
1284            "identity",
1285            "ignore",
1286            "ilike",
1287            "in",
1288            "initially",
1289            "inner",
1290            "intersect",
1291            "interval",
1292            "into",
1293            "is",
1294            "isnull",
1295            "join",
1296            "leading",
1297            "left",
1298            "like",
1299            "limit",
1300            "localtime",
1301            "localtimestamp",
1302            "lun",
1303            "luns",
1304            "lzo",
1305            "lzop",
1306            "minus",
1307            "mostly16",
1308            "mostly32",
1309            "mostly8",
1310            "natural",
1311            "new",
1312            "not",
1313            "notnull",
1314            "null",
1315            "nulls",
1316            "off",
1317            "offline",
1318            "offset",
1319            "oid",
1320            "old",
1321            "on",
1322            "only",
1323            "open",
1324            "or",
1325            "order",
1326            "outer",
1327            "overlaps",
1328            "parallel",
1329            "partition",
1330            "percent",
1331            "permissions",
1332            "pivot",
1333            "placing",
1334            "primary",
1335            "raw",
1336            "readratio",
1337            "recover",
1338            "references",
1339            "rejectlog",
1340            "resort",
1341            "respect",
1342            "restore",
1343            "right",
1344            "select",
1345            "session_user",
1346            "similar",
1347            "snapshot",
1348            "some",
1349            "sysdate",
1350            "system",
1351            "table",
1352            "tag",
1353            "tdes",
1354            "text255",
1355            "text32k",
1356            "then",
1357            "timestamp",
1358            "to",
1359            "top",
1360            "trailing",
1361            "true",
1362            "truncatecolumns",
1363            "type",
1364            "union",
1365            "unique",
1366            "unnest",
1367            "unpivot",
1368            "user",
1369            "using",
1370            "verbose",
1371            "wallet",
1372            "when",
1373            "where",
1374            "with",
1375            "without",
1376        ]
1377        .into_iter()
1378        .collect()
1379    });
1380
1381    /// DuckDB-specific reserved keywords
1382    pub static DUCKDB_RESERVED: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
1383        let mut set = POSTGRES_RESERVED.clone();
1384        set.extend([
1385            "anti",
1386            "asof",
1387            "columns",
1388            "describe",
1389            "groups",
1390            "macro",
1391            "pivot",
1392            "pivot_longer",
1393            "pivot_wider",
1394            "qualify",
1395            "replace",
1396            "respect",
1397            "semi",
1398            "show",
1399            "table",
1400            "unpivot",
1401        ]);
1402        set.remove("at");
1403        set.remove("key");
1404        set.remove("range");
1405        set.remove("row");
1406        set.remove("values");
1407        set
1408    });
1409
1410    /// Presto/Trino/Athena-specific reserved keywords
1411    pub static PRESTO_TRINO_RESERVED: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
1412        let mut set = SQL_RESERVED.clone();
1413        set.extend([
1414            "alter",
1415            "and",
1416            "as",
1417            "between",
1418            "by",
1419            "case",
1420            "cast",
1421            "constraint",
1422            "create",
1423            "cross",
1424            "cube",
1425            "current_catalog",
1426            "current_date",
1427            "current_path",
1428            "current_role",
1429            "current_schema",
1430            "current_time",
1431            "current_timestamp",
1432            "current_user",
1433            "deallocate",
1434            "delete",
1435            "describe",
1436            "distinct",
1437            "drop",
1438            "else",
1439            "end",
1440            "escape",
1441            "except",
1442            "execute",
1443            "exists",
1444            "extract",
1445            "false",
1446            "for",
1447            "from",
1448            "full",
1449            "group",
1450            "grouping",
1451            "having",
1452            "in",
1453            "inner",
1454            "insert",
1455            "intersect",
1456            "into",
1457            "is",
1458            "join",
1459            "json_array",
1460            "json_exists",
1461            "json_object",
1462            "json_query",
1463            "json_table",
1464            "json_value",
1465            "left",
1466            "like",
1467            "listagg",
1468            "localtime",
1469            "localtimestamp",
1470            "natural",
1471            "normalize",
1472            "not",
1473            "null",
1474            "on",
1475            "or",
1476            "order",
1477            "outer",
1478            "prepare",
1479            "recursive",
1480            "right",
1481            "rollup",
1482            "select",
1483            "skip",
1484            "table",
1485            "then",
1486            "trim",
1487            "true",
1488            "uescape",
1489            "union",
1490            "unnest",
1491            "using",
1492            "values",
1493            "when",
1494            "where",
1495            "with",
1496        ]);
1497        // Match sqlglot behavior for Presto/Trino: KEY does not require identifier quoting.
1498        set.remove("key");
1499        set
1500    });
1501
1502    /// StarRocks-specific reserved keywords
1503    /// Based on: https://docs.starrocks.io/docs/sql-reference/sql-statements/keywords/#reserved-keywords
1504    pub static STARROCKS_RESERVED: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
1505        [
1506            "add",
1507            "all",
1508            "alter",
1509            "analyze",
1510            "and",
1511            "array",
1512            "as",
1513            "asc",
1514            "between",
1515            "bigint",
1516            "bitmap",
1517            "both",
1518            "by",
1519            "case",
1520            "char",
1521            "character",
1522            "check",
1523            "collate",
1524            "column",
1525            "compaction",
1526            "convert",
1527            "create",
1528            "cross",
1529            "cube",
1530            "current_date",
1531            "current_role",
1532            "current_time",
1533            "current_timestamp",
1534            "current_user",
1535            "database",
1536            "databases",
1537            "decimal",
1538            "decimalv2",
1539            "decimal32",
1540            "decimal64",
1541            "decimal128",
1542            "default",
1543            "deferred",
1544            "delete",
1545            "dense_rank",
1546            "desc",
1547            "describe",
1548            "distinct",
1549            "double",
1550            "drop",
1551            "dual",
1552            "else",
1553            "except",
1554            "exists",
1555            "explain",
1556            "false",
1557            "first_value",
1558            "float",
1559            "for",
1560            "force",
1561            "from",
1562            "full",
1563            "function",
1564            "grant",
1565            "group",
1566            "grouping",
1567            "grouping_id",
1568            "groups",
1569            "having",
1570            "hll",
1571            "host",
1572            "if",
1573            "ignore",
1574            "immediate",
1575            "in",
1576            "index",
1577            "infile",
1578            "inner",
1579            "insert",
1580            "int",
1581            "integer",
1582            "intersect",
1583            "into",
1584            "is",
1585            "join",
1586            "json",
1587            "key",
1588            "keys",
1589            "kill",
1590            "lag",
1591            "largeint",
1592            "last_value",
1593            "lateral",
1594            "lead",
1595            "left",
1596            "like",
1597            "limit",
1598            "load",
1599            "localtime",
1600            "localtimestamp",
1601            "maxvalue",
1602            "minus",
1603            "mod",
1604            "not",
1605            "ntile",
1606            "null",
1607            "on",
1608            "or",
1609            "order",
1610            "outer",
1611            "outfile",
1612            "over",
1613            "partition",
1614            "percentile",
1615            "primary",
1616            "procedure",
1617            "qualify",
1618            "range",
1619            "rank",
1620            "read",
1621            "regexp",
1622            "release",
1623            "rename",
1624            "replace",
1625            "revoke",
1626            "right",
1627            "rlike",
1628            "row",
1629            "row_number",
1630            "rows",
1631            "schema",
1632            "schemas",
1633            "select",
1634            "set",
1635            "set_var",
1636            "show",
1637            "smallint",
1638            "system",
1639            "table",
1640            "terminated",
1641            "text",
1642            "then",
1643            "tinyint",
1644            "to",
1645            "true",
1646            "union",
1647            "unique",
1648            "unsigned",
1649            "update",
1650            "use",
1651            "using",
1652            "values",
1653            "varchar",
1654            "when",
1655            "where",
1656            "with",
1657        ]
1658        .into_iter()
1659        .collect()
1660    });
1661
1662    /// SingleStore-specific reserved keywords
1663    /// Based on: https://docs.singlestore.com/cloud/reference/sql-reference/restricted-keywords/list-of-restricted-keywords/
1664    pub static SINGLESTORE_RESERVED: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
1665        let mut set = MYSQL_RESERVED.clone();
1666        set.extend([
1667            // Additional SingleStore reserved keywords from Python sqlglot
1668            // NOTE: "all" is excluded because ORDER BY ALL needs ALL unquoted
1669            "abs",
1670            "account",
1671            "acos",
1672            "adddate",
1673            "addtime",
1674            "admin",
1675            "aes_decrypt",
1676            "aes_encrypt",
1677            "aggregate",
1678            "aggregates",
1679            "aggregator",
1680            "anti_join",
1681            "any_value",
1682            "approx_count_distinct",
1683            "approx_percentile",
1684            "arrange",
1685            "arrangement",
1686            "asin",
1687            "atan",
1688            "atan2",
1689            "attach",
1690            "autostats",
1691            "avro",
1692            "background",
1693            "backup",
1694            "batch",
1695            "batches",
1696            "boot_strapping",
1697            "ceil",
1698            "ceiling",
1699            "coercibility",
1700            "columnar",
1701            "columnstore",
1702            "compile",
1703            "concurrent",
1704            "connection_id",
1705            "cos",
1706            "cot",
1707            "current_security_groups",
1708            "current_security_roles",
1709            "dayname",
1710            "dayofmonth",
1711            "dayofweek",
1712            "dayofyear",
1713            "degrees",
1714            "dot_product",
1715            "dump",
1716            "durability",
1717            "earliest",
1718            "echo",
1719            "election",
1720            "euclidean_distance",
1721            "exp",
1722            "extractor",
1723            "extractors",
1724            "floor",
1725            "foreground",
1726            "found_rows",
1727            "from_base64",
1728            "from_days",
1729            "from_unixtime",
1730            "fs",
1731            "fulltext",
1732            "gc",
1733            "gcs",
1734            "geography",
1735            "geography_area",
1736            "geography_contains",
1737            "geography_distance",
1738            "geography_intersects",
1739            "geography_latitude",
1740            "geography_length",
1741            "geography_longitude",
1742            "geographypoint",
1743            "geography_point",
1744            "geography_within_distance",
1745            "geometry",
1746            "geometry_area",
1747            "geometry_contains",
1748            "geometry_distance",
1749            "geometry_filter",
1750            "geometry_intersects",
1751            "geometry_length",
1752            "geometrypoint",
1753            "geometry_point",
1754            "geometry_within_distance",
1755            "geometry_x",
1756            "geometry_y",
1757            "greatest",
1758            "groups",
1759            "group_concat",
1760            "gzip",
1761            "hdfs",
1762            "hex",
1763            "highlight",
1764            "ifnull",
1765            "ilike",
1766            "inet_aton",
1767            "inet_ntoa",
1768            "inet6_aton",
1769            "inet6_ntoa",
1770            "initcap",
1771            "instr",
1772            "interpreter_mode",
1773            "isnull",
1774            "json",
1775            "json_agg",
1776            "json_array_contains_double",
1777            "json_array_contains_json",
1778            "json_array_contains_string",
1779            "json_delete_key",
1780            "json_extract_double",
1781            "json_extract_json",
1782            "json_extract_string",
1783            "json_extract_bigint",
1784            "json_get_type",
1785            "json_length",
1786            "json_set_double",
1787            "json_set_json",
1788            "json_set_string",
1789            "kafka",
1790            "lag",
1791            "last_day",
1792            "last_insert_id",
1793            "latest",
1794            "lcase",
1795            "lead",
1796            "leaf",
1797            "least",
1798            "leaves",
1799            "length",
1800            "license",
1801            "links",
1802            "llvm",
1803            "ln",
1804            "load",
1805            "locate",
1806            "log",
1807            "log10",
1808            "log2",
1809            "lpad",
1810            "lz4",
1811            "management",
1812            "match",
1813            "mbc",
1814            "md5",
1815            "median",
1816            "memsql",
1817            "memsql_deserialize",
1818            "memsql_serialize",
1819            "metadata",
1820            "microsecond",
1821            "minute",
1822            "model",
1823            "monthname",
1824            "months_between",
1825            "mpl",
1826            "namespace",
1827            "node",
1828            "noparam",
1829            "now",
1830            "nth_value",
1831            "ntile",
1832            "nullcols",
1833            "nullif",
1834            "object",
1835            "octet_length",
1836            "offsets",
1837            "online",
1838            "optimizer",
1839            "orphan",
1840            "parquet",
1841            "partitions",
1842            "pause",
1843            "percentile_cont",
1844            "percentile_disc",
1845            "periodic",
1846            "persisted",
1847            "pi",
1848            "pipeline",
1849            "pipelines",
1850            "plancache",
1851            "plugins",
1852            "pool",
1853            "pools",
1854            "pow",
1855            "power",
1856            "process",
1857            "processlist",
1858            "profile",
1859            "profiles",
1860            "quarter",
1861            "queries",
1862            "query",
1863            "radians",
1864            "rand",
1865            "record",
1866            "reduce",
1867            "redundancy",
1868            "regexp_match",
1869            "regexp_substr",
1870            "remote",
1871            "replication",
1872            "resource",
1873            "resource_pool",
1874            "restore",
1875            "retry",
1876            "role",
1877            "roles",
1878            "round",
1879            "rpad",
1880            "rtrim",
1881            "running",
1882            "s3",
1883            "scalar",
1884            "sec_to_time",
1885            "second",
1886            "security_lists_intersect",
1887            "semi_join",
1888            "sha",
1889            "sha1",
1890            "sha2",
1891            "shard",
1892            "sharded",
1893            "sharded_id",
1894            "sigmoid",
1895            "sign",
1896            "sin",
1897            "skip",
1898            "sleep",
1899            "snapshot",
1900            "soname",
1901            "sparse",
1902            "spatial_check_index",
1903            "split",
1904            "sqrt",
1905            "standalone",
1906            "std",
1907            "stddev",
1908            "stddev_pop",
1909            "stddev_samp",
1910            "stop",
1911            "str_to_date",
1912            "subdate",
1913            "substr",
1914            "substring_index",
1915            "success",
1916            "synchronize",
1917            "table_checksum",
1918            "tan",
1919            "task",
1920            "timediff",
1921            "time_bucket",
1922            "time_format",
1923            "time_to_sec",
1924            "timestampadd",
1925            "timestampdiff",
1926            "to_base64",
1927            "to_char",
1928            "to_date",
1929            "to_days",
1930            "to_json",
1931            "to_number",
1932            "to_seconds",
1933            "to_timestamp",
1934            "tracelogs",
1935            "transform",
1936            "trim",
1937            "trunc",
1938            "truncate",
1939            "ucase",
1940            "unhex",
1941            "unix_timestamp",
1942            "utc_date",
1943            "utc_time",
1944            "utc_timestamp",
1945            "vacuum",
1946            "variance",
1947            "var_pop",
1948            "var_samp",
1949            "vector_sub",
1950            "voting",
1951            "week",
1952            "weekday",
1953            "weekofyear",
1954            "workload",
1955            "year",
1956        ]);
1957        // Remove "all" because ORDER BY ALL needs ALL unquoted
1958        set.remove("all");
1959        set
1960    });
1961
1962    /// SQLite-specific reserved keywords
1963    /// SQLite has a very minimal set of reserved keywords - most things can be used as identifiers unquoted
1964    /// Reference: https://www.sqlite.org/lang_keywords.html
1965    pub static SQLITE_RESERVED: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
1966        // SQLite only truly reserves these - everything else can be used as identifier unquoted
1967        [
1968            "abort",
1969            "action",
1970            "add",
1971            "after",
1972            "all",
1973            "alter",
1974            "always",
1975            "analyze",
1976            "and",
1977            "as",
1978            "asc",
1979            "attach",
1980            "autoincrement",
1981            "before",
1982            "begin",
1983            "between",
1984            "by",
1985            "cascade",
1986            "case",
1987            "cast",
1988            "check",
1989            "collate",
1990            "column",
1991            "commit",
1992            "conflict",
1993            "constraint",
1994            "create",
1995            "cross",
1996            "current",
1997            "current_date",
1998            "current_time",
1999            "current_timestamp",
2000            "database",
2001            "default",
2002            "deferrable",
2003            "deferred",
2004            "delete",
2005            "desc",
2006            "detach",
2007            "distinct",
2008            "do",
2009            "drop",
2010            "each",
2011            "else",
2012            "end",
2013            "escape",
2014            "except",
2015            "exclude",
2016            "exclusive",
2017            "exists",
2018            "explain",
2019            "fail",
2020            "filter",
2021            "first",
2022            "following",
2023            "for",
2024            "foreign",
2025            "from",
2026            "full",
2027            "generated",
2028            "glob",
2029            "group",
2030            "groups",
2031            "having",
2032            "if",
2033            "ignore",
2034            "immediate",
2035            "in",
2036            "index",
2037            "indexed",
2038            "initially",
2039            "inner",
2040            "insert",
2041            "instead",
2042            "intersect",
2043            "into",
2044            "is",
2045            "isnull",
2046            "join",
2047            "key",
2048            "last",
2049            "left",
2050            "like",
2051            "limit",
2052            "natural",
2053            "no",
2054            "not",
2055            "nothing",
2056            "notnull",
2057            "null",
2058            "nulls",
2059            "of",
2060            "offset",
2061            "on",
2062            "or",
2063            "order",
2064            "others",
2065            "outer",
2066            "partition",
2067            "plan",
2068            "pragma",
2069            "preceding",
2070            "primary",
2071            "query",
2072            "raise",
2073            "range",
2074            "recursive",
2075            "references",
2076            "regexp",
2077            "reindex",
2078            "release",
2079            "rename",
2080            "replace",
2081            "restrict",
2082            "returning",
2083            "right",
2084            "rollback",
2085            "row",
2086            "rows",
2087            "savepoint",
2088            "select",
2089            "set",
2090            "table",
2091            "temp",
2092            "temporary",
2093            "then",
2094            "ties",
2095            "to",
2096            "transaction",
2097            "trigger",
2098            "unbounded",
2099            "union",
2100            "unique",
2101            "update",
2102            "using",
2103            "vacuum",
2104            "values",
2105            "view",
2106            "virtual",
2107            "when",
2108            "where",
2109            "window",
2110            "with",
2111            "without",
2112        ]
2113        .into_iter()
2114        .collect()
2115    });
2116}
2117
2118impl Generator {
2119    /// Create a new generator with the default configuration.
2120    ///
2121    /// Equivalent to `Generator::with_config(GeneratorConfig::default())`.
2122    /// Uses uppercase keywords, double-quote identifier quoting, no pretty-printing,
2123    /// and no dialect-specific transformations.
2124    pub fn new() -> Self {
2125        Self::with_config(GeneratorConfig::default())
2126    }
2127
2128    /// Create a generator with a custom [`GeneratorConfig`].
2129    ///
2130    /// Use this when you need dialect-specific output, pretty-printing, or other
2131    /// non-default settings.
2132    pub fn with_config(config: GeneratorConfig) -> Self {
2133        Self::with_arc_config(Arc::new(config))
2134    }
2135
2136    /// Create a generator from a shared [`Arc<GeneratorConfig>`].
2137    ///
2138    /// This avoids cloning the configuration when multiple generators share the
2139    /// same settings (e.g. during transpilation). The [`Arc`] is cheap to clone.
2140    pub(crate) fn with_arc_config(config: Arc<GeneratorConfig>) -> Self {
2141        Self {
2142            config,
2143            output: String::new(),
2144            unsupported_messages: Vec::new(),
2145            indent_level: 0,
2146            athena_hive_context: false,
2147            sqlite_inline_pk_columns: std::collections::HashSet::new(),
2148            merge_strip_qualifiers: Vec::new(),
2149            clickhouse_nullable_depth: 0,
2150        }
2151    }
2152
2153    /// Add column aliases to a query expression for TSQL SELECT INTO.
2154    /// This ensures that unaliased columns get explicit aliases (e.g., `a` -> `a AS a`).
2155    /// Recursively processes all SELECT expressions in the query tree.
2156    fn add_column_aliases_to_query(expr: Expression) -> Expression {
2157        match expr {
2158            Expression::Select(mut select) => {
2159                // Add aliases to all select expressions that don't already have them
2160                select.expressions = select
2161                    .expressions
2162                    .into_iter()
2163                    .map(|e| Self::add_alias_to_expression(e))
2164                    .collect();
2165
2166                // Recursively process subqueries in FROM clause
2167                if let Some(ref mut from) = select.from {
2168                    from.expressions = from
2169                        .expressions
2170                        .iter()
2171                        .cloned()
2172                        .map(|e| Self::add_column_aliases_to_query(e))
2173                        .collect();
2174                }
2175
2176                Expression::Select(select)
2177            }
2178            Expression::Subquery(mut sq) => {
2179                sq.this = Self::add_column_aliases_to_query(sq.this);
2180                Expression::Subquery(sq)
2181            }
2182            Expression::Paren(mut p) => {
2183                p.this = Self::add_column_aliases_to_query(p.this);
2184                Expression::Paren(p)
2185            }
2186            // For other expressions (Union, Intersect, etc.), pass through
2187            other => other,
2188        }
2189    }
2190
2191    /// Add an alias to a single select expression if it doesn't already have one.
2192    /// Returns the expression with alias (e.g., `a` -> `a AS a`).
2193    fn add_alias_to_expression(expr: Expression) -> Expression {
2194        use crate::expressions::Alias;
2195
2196        match &expr {
2197            // Already aliased - just return it
2198            Expression::Alias(_) => expr,
2199
2200            // Column reference: add alias from column name
2201            Expression::Column(col) => Expression::Alias(Box::new(Alias {
2202                this: expr.clone(),
2203                alias: col.name.clone(),
2204                column_aliases: Vec::new(),
2205                pre_alias_comments: Vec::new(),
2206                trailing_comments: Vec::new(),
2207                inferred_type: None,
2208            })),
2209
2210            // Identifier: add alias from identifier name
2211            Expression::Identifier(ident) => Expression::Alias(Box::new(Alias {
2212                this: expr.clone(),
2213                alias: ident.clone(),
2214                column_aliases: Vec::new(),
2215                pre_alias_comments: Vec::new(),
2216                trailing_comments: Vec::new(),
2217                inferred_type: None,
2218            })),
2219
2220            // Subquery: recursively process and add alias if inner returns a named column
2221            Expression::Subquery(sq) => {
2222                let processed = Self::add_column_aliases_to_query(Expression::Subquery(sq.clone()));
2223                // Subqueries that are already aliased keep their alias
2224                if sq.alias.is_some() {
2225                    processed
2226                } else {
2227                    // If there's no alias, keep it as-is (let TSQL handle it)
2228                    processed
2229                }
2230            }
2231
2232            // Star expressions (*) - don't alias
2233            Expression::Star(_) => expr,
2234
2235            // For other expressions, don't add an alias
2236            // (function calls, literals, etc. would need explicit aliases anyway)
2237            _ => expr,
2238        }
2239    }
2240
2241    /// Try to evaluate a constant arithmetic expression to a number literal.
2242    /// Returns the evaluated result if the expression is a constant arithmetic expression,
2243    /// otherwise returns the original expression.
2244    fn try_evaluate_constant(expr: &Expression) -> Option<i64> {
2245        match expr {
2246            Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(_)) => { let Literal::Number(n) = lit.as_ref() else { unreachable!() }; n.parse::<i64>().ok() },
2247            Expression::Add(op) => {
2248                let left = Self::try_evaluate_constant(&op.left)?;
2249                let right = Self::try_evaluate_constant(&op.right)?;
2250                Some(left + right)
2251            }
2252            Expression::Sub(op) => {
2253                let left = Self::try_evaluate_constant(&op.left)?;
2254                let right = Self::try_evaluate_constant(&op.right)?;
2255                Some(left - right)
2256            }
2257            Expression::Mul(op) => {
2258                let left = Self::try_evaluate_constant(&op.left)?;
2259                let right = Self::try_evaluate_constant(&op.right)?;
2260                Some(left * right)
2261            }
2262            Expression::Div(op) => {
2263                let left = Self::try_evaluate_constant(&op.left)?;
2264                let right = Self::try_evaluate_constant(&op.right)?;
2265                if right != 0 {
2266                    Some(left / right)
2267                } else {
2268                    None
2269                }
2270            }
2271            Expression::Paren(p) => Self::try_evaluate_constant(&p.this),
2272            _ => None,
2273        }
2274    }
2275
2276    /// Check if an identifier is a reserved keyword for the current dialect
2277    fn is_reserved_keyword(&self, name: &str) -> bool {
2278        use crate::dialects::DialectType;
2279        let mut buf = [0u8; 128];
2280        let lower_ref: &str = if name.len() <= 128 {
2281            for (i, b) in name.bytes().enumerate() {
2282                buf[i] = b.to_ascii_lowercase();
2283            }
2284            // SAFETY: input is valid UTF-8 and ASCII lowercase preserves that
2285            std::str::from_utf8(&buf[..name.len()]).unwrap_or(name)
2286        } else {
2287            return false;
2288        };
2289
2290        match self.config.dialect {
2291            Some(DialectType::BigQuery) => reserved_keywords::BIGQUERY_RESERVED.contains(lower_ref),
2292            Some(DialectType::MySQL) | Some(DialectType::TiDB) => {
2293                reserved_keywords::MYSQL_RESERVED.contains(lower_ref)
2294            }
2295            Some(DialectType::Doris) => reserved_keywords::DORIS_RESERVED.contains(lower_ref),
2296            Some(DialectType::SingleStore) => {
2297                reserved_keywords::SINGLESTORE_RESERVED.contains(lower_ref)
2298            }
2299            Some(DialectType::StarRocks) => {
2300                reserved_keywords::STARROCKS_RESERVED.contains(lower_ref)
2301            }
2302            Some(DialectType::PostgreSQL)
2303            | Some(DialectType::CockroachDB)
2304            | Some(DialectType::Materialize)
2305            | Some(DialectType::RisingWave) => {
2306                reserved_keywords::POSTGRES_RESERVED.contains(lower_ref)
2307            }
2308            Some(DialectType::Redshift) => reserved_keywords::REDSHIFT_RESERVED.contains(lower_ref),
2309            // Snowflake: Python sqlglot has RESERVED_KEYWORDS = set() for Snowflake,
2310            // meaning it never quotes identifiers based on reserved word status.
2311            Some(DialectType::Snowflake) => false,
2312            // ClickHouse: don't quote reserved keywords to preserve identity output
2313            Some(DialectType::ClickHouse) => false,
2314            Some(DialectType::DuckDB) => reserved_keywords::DUCKDB_RESERVED.contains(lower_ref),
2315            // Teradata: Python sqlglot has RESERVED_KEYWORDS = set() for Teradata
2316            Some(DialectType::Teradata) => false,
2317            // TSQL, Fabric, Oracle, Spark, Hive, Solr: Python sqlglot has no RESERVED_KEYWORDS for these dialects, so don't quote identifiers
2318            Some(DialectType::TSQL)
2319            | Some(DialectType::Fabric)
2320            | Some(DialectType::Oracle)
2321            | Some(DialectType::Spark)
2322            | Some(DialectType::Databricks)
2323            | Some(DialectType::Hive)
2324            | Some(DialectType::Solr) => false,
2325            Some(DialectType::Presto) | Some(DialectType::Trino) | Some(DialectType::Athena) => {
2326                reserved_keywords::PRESTO_TRINO_RESERVED.contains(lower_ref)
2327            }
2328            Some(DialectType::SQLite) => reserved_keywords::SQLITE_RESERVED.contains(lower_ref),
2329            // For Generic dialect or None, don't add extra quoting to preserve identity
2330            Some(DialectType::Generic) | None => false,
2331            // For other dialects, use standard SQL reserved keywords
2332            _ => reserved_keywords::SQL_RESERVED.contains(lower_ref),
2333        }
2334    }
2335
2336    /// Normalize function name based on dialect settings
2337    fn normalize_func_name<'a>(&self, name: &'a str) -> Cow<'a, str> {
2338        match self.config.normalize_functions {
2339            NormalizeFunctions::Upper => Cow::Owned(name.to_ascii_uppercase()),
2340            NormalizeFunctions::Lower => Cow::Owned(name.to_ascii_lowercase()),
2341            NormalizeFunctions::None => Cow::Borrowed(name),
2342        }
2343    }
2344
2345    /// Generate a SQL string from an AST expression.
2346    ///
2347    /// This is the primary generation method. It clears any previous internal state,
2348    /// walks the expression tree, and returns the resulting SQL text. The output
2349    /// respects the [`GeneratorConfig`] that was supplied at construction time.
2350    ///
2351    /// The generator can be reused across multiple calls; each call to `generate`
2352    /// resets the internal buffer.
2353    pub fn generate(&mut self, expr: &Expression) -> Result<String> {
2354        self.output.clear();
2355        self.unsupported_messages.clear();
2356        self.generate_expression(expr)?;
2357        if self.config.unsupported_level == UnsupportedLevel::Raise
2358            && !self.unsupported_messages.is_empty()
2359        {
2360            return Err(crate::error::Error::generate(
2361                self.format_unsupported_messages(),
2362            ));
2363        }
2364        Ok(std::mem::take(&mut self.output))
2365    }
2366
2367    /// Returns the unsupported diagnostics collected during the most recent generate call.
2368    pub fn unsupported_messages(&self) -> &[String] {
2369        &self.unsupported_messages
2370    }
2371
2372    fn unsupported(&mut self, message: impl Into<String>) -> Result<()> {
2373        let message = message.into();
2374        if self.config.unsupported_level == UnsupportedLevel::Immediate {
2375            return Err(crate::error::Error::generate(message));
2376        }
2377        self.unsupported_messages.push(message);
2378        Ok(())
2379    }
2380
2381    fn write_unsupported_comment(&mut self, message: &str) -> Result<()> {
2382        self.unsupported(message.to_string())?;
2383        self.write("/* ");
2384        self.write(message);
2385        self.write(" */");
2386        Ok(())
2387    }
2388
2389    fn format_unsupported_messages(&self) -> String {
2390        let limit = self.config.max_unsupported.max(1);
2391        if self.unsupported_messages.len() <= limit {
2392            return self.unsupported_messages.join("; ");
2393        }
2394
2395        let mut messages = self
2396            .unsupported_messages
2397            .iter()
2398            .take(limit)
2399            .cloned()
2400            .collect::<Vec<_>>();
2401        messages.push(format!(
2402            "... and {} more",
2403            self.unsupported_messages.len() - limit
2404        ));
2405        messages.join("; ")
2406    }
2407
2408    /// Convenience: generate SQL with the default configuration (no dialect, compact output).
2409    ///
2410    /// This is a static helper that creates a throwaway `Generator` internally.
2411    /// For repeated generation, prefer constructing a `Generator` once and calling
2412    /// [`generate`](Self::generate) on it.
2413    pub fn sql(expr: &Expression) -> Result<String> {
2414        let mut gen = Generator::new();
2415        gen.generate(expr)
2416    }
2417
2418    /// Convenience: generate SQL with pretty-printing enabled (indented, multi-line).
2419    ///
2420    /// Produces human-readable output with newlines and indentation. A trailing
2421    /// semicolon is appended automatically if not already present.
2422    pub fn pretty_sql(expr: &Expression) -> Result<String> {
2423        let config = GeneratorConfig {
2424            pretty: true,
2425            ..Default::default()
2426        };
2427        let mut gen = Generator::with_config(config);
2428        let mut sql = gen.generate(expr)?;
2429        // Add semicolon for pretty output
2430        if !sql.ends_with(';') {
2431            sql.push(';');
2432        }
2433        Ok(sql)
2434    }
2435
2436    fn generate_expression(&mut self, expr: &Expression) -> Result<()> {
2437        match expr {
2438            Expression::Select(select) => self.generate_select(select),
2439            Expression::Union(union) => self.generate_union(union),
2440            Expression::Intersect(intersect) => self.generate_intersect(intersect),
2441            Expression::Except(except) => self.generate_except(except),
2442            Expression::Insert(insert) => self.generate_insert(insert),
2443            Expression::Update(update) => self.generate_update(update),
2444            Expression::Delete(delete) => self.generate_delete(delete),
2445            Expression::Literal(lit) => self.generate_literal(lit),
2446            Expression::Boolean(b) => self.generate_boolean(b),
2447            Expression::Null(_) => {
2448                self.write_keyword("NULL");
2449                Ok(())
2450            }
2451            Expression::Identifier(id) => self.generate_identifier(id),
2452            Expression::Column(col) => self.generate_column(col),
2453            Expression::Pseudocolumn(pc) => self.generate_pseudocolumn(pc),
2454            Expression::Connect(c) => self.generate_connect_expr(c),
2455            Expression::Prior(p) => self.generate_prior(p),
2456            Expression::ConnectByRoot(cbr) => self.generate_connect_by_root(cbr),
2457            Expression::MatchRecognize(mr) => self.generate_match_recognize(mr),
2458            Expression::Table(table) => self.generate_table(table),
2459            Expression::StageReference(sr) => self.generate_stage_reference(sr),
2460            Expression::HistoricalData(hd) => self.generate_historical_data(hd),
2461            Expression::JoinedTable(jt) => self.generate_joined_table(jt),
2462            Expression::Star(star) => self.generate_star(star),
2463            Expression::BracedWildcard(expr) => self.generate_braced_wildcard(expr),
2464            Expression::Alias(alias) => self.generate_alias(alias),
2465            Expression::Cast(cast) => self.generate_cast(cast),
2466            Expression::Collation(coll) => self.generate_collation(coll),
2467            Expression::Case(case) => self.generate_case(case),
2468            Expression::Function(func) => self.generate_function(func),
2469            Expression::FunctionEmits(fe) => self.generate_function_emits(fe),
2470            Expression::AggregateFunction(func) => self.generate_aggregate_function(func),
2471            Expression::WindowFunction(wf) => self.generate_window_function(wf),
2472            Expression::WithinGroup(wg) => self.generate_within_group(wg),
2473            Expression::Interval(interval) => self.generate_interval(interval),
2474
2475            // String functions
2476            Expression::ConcatWs(f) => self.generate_concat_ws(f),
2477            Expression::Substring(f) => self.generate_substring(f),
2478            Expression::Upper(f) => self.generate_unary_func("UPPER", f),
2479            Expression::Lower(f) => self.generate_unary_func("LOWER", f),
2480            Expression::Length(f) => self.generate_unary_func("LENGTH", f),
2481            Expression::Trim(f) => self.generate_trim(f),
2482            Expression::LTrim(f) => self.generate_simple_func("LTRIM", &f.this),
2483            Expression::RTrim(f) => self.generate_simple_func("RTRIM", &f.this),
2484            Expression::Replace(f) => self.generate_replace(f),
2485            Expression::Reverse(f) => self.generate_simple_func("REVERSE", &f.this),
2486            Expression::Left(f) => self.generate_left_right("LEFT", f),
2487            Expression::Right(f) => self.generate_left_right("RIGHT", f),
2488            Expression::Repeat(f) => self.generate_repeat(f),
2489            Expression::Lpad(f) => self.generate_pad("LPAD", f),
2490            Expression::Rpad(f) => self.generate_pad("RPAD", f),
2491            Expression::Split(f) => self.generate_split(f),
2492            Expression::RegexpLike(f) => self.generate_regexp_like(f),
2493            Expression::RegexpReplace(f) => self.generate_regexp_replace(f),
2494            Expression::RegexpExtract(f) => self.generate_regexp_extract(f),
2495            Expression::Overlay(f) => self.generate_overlay(f),
2496
2497            // Math functions
2498            Expression::Abs(f) => self.generate_simple_func("ABS", &f.this),
2499            Expression::Round(f) => self.generate_round(f),
2500            Expression::Floor(f) => self.generate_floor(f),
2501            Expression::Ceil(f) => self.generate_ceil(f),
2502            Expression::Power(f) => self.generate_power(f),
2503            Expression::Sqrt(f) => self.generate_sqrt_cbrt(f, "SQRT", "|/"),
2504            Expression::Cbrt(f) => self.generate_sqrt_cbrt(f, "CBRT", "||/"),
2505            Expression::Ln(f) => self.generate_simple_func("LN", &f.this),
2506            Expression::Log(f) => self.generate_log(f),
2507            Expression::Exp(f) => self.generate_simple_func("EXP", &f.this),
2508            Expression::Sign(f) => self.generate_simple_func("SIGN", &f.this),
2509            Expression::Greatest(f) => self.generate_vararg_func("GREATEST", &f.expressions),
2510            Expression::Least(f) => self.generate_vararg_func("LEAST", &f.expressions),
2511
2512            // Date/time functions
2513            Expression::CurrentDate(_) => {
2514                self.write_keyword("CURRENT_DATE");
2515                Ok(())
2516            }
2517            Expression::CurrentTime(f) => self.generate_current_time(f),
2518            Expression::CurrentTimestamp(f) => self.generate_current_timestamp(f),
2519            Expression::AtTimeZone(f) => self.generate_at_time_zone(f),
2520            Expression::DateAdd(f) => self.generate_date_add(f, "DATE_ADD"),
2521            Expression::DateSub(f) => self.generate_date_add(f, "DATE_SUB"),
2522            Expression::DateDiff(f) => self.generate_datediff(f),
2523            Expression::DateTrunc(f) => self.generate_date_trunc(f),
2524            Expression::Extract(f) => self.generate_extract(f),
2525            Expression::ToDate(f) => self.generate_to_date(f),
2526            Expression::ToTimestamp(f) => self.generate_to_timestamp(f),
2527
2528            // Control flow functions
2529            Expression::Coalesce(f) => {
2530                // Use original function name if preserved (COALESCE, IFNULL)
2531                let func_name = f.original_name.as_deref().unwrap_or("COALESCE");
2532                self.generate_vararg_func(func_name, &f.expressions)
2533            }
2534            Expression::NullIf(f) => self.generate_binary_func("NULLIF", &f.this, &f.expression),
2535            Expression::IfFunc(f) => self.generate_if_func(f),
2536            Expression::IfNull(f) => self.generate_ifnull(f),
2537            Expression::Nvl(f) => self.generate_nvl(f),
2538            Expression::Nvl2(f) => self.generate_nvl2(f),
2539
2540            // Type conversion
2541            Expression::TryCast(cast) => self.generate_try_cast(cast),
2542            Expression::SafeCast(cast) => self.generate_safe_cast(cast),
2543
2544            // Typed aggregate functions
2545            Expression::Count(f) => self.generate_count(f),
2546            Expression::Sum(f) => self.generate_agg_func("SUM", f),
2547            Expression::Avg(f) => self.generate_agg_func("AVG", f),
2548            Expression::Min(f) => self.generate_agg_func("MIN", f),
2549            Expression::Max(f) => self.generate_agg_func("MAX", f),
2550            Expression::GroupConcat(f) => self.generate_group_concat(f),
2551            Expression::StringAgg(f) => self.generate_string_agg(f),
2552            Expression::ListAgg(f) => self.generate_listagg(f),
2553            Expression::ArrayAgg(f) => {
2554                // Allow cross-dialect transforms to override the function name
2555                // (e.g., COLLECT_LIST for Spark)
2556                let override_name = f
2557                    .name
2558                    .as_ref()
2559                    .filter(|n| !n.eq_ignore_ascii_case("ARRAY_AGG"))
2560                    .map(|n| n.to_ascii_uppercase());
2561                match override_name {
2562                    Some(name) => self.generate_agg_func(&name, f),
2563                    None => self.generate_agg_func("ARRAY_AGG", f),
2564                }
2565            }
2566            Expression::ArrayConcatAgg(f) => self.generate_agg_func("ARRAY_CONCAT_AGG", f),
2567            Expression::CountIf(f) => self.generate_agg_func("COUNT_IF", f),
2568            Expression::SumIf(f) => self.generate_sum_if(f),
2569            Expression::Stddev(f) => self.generate_agg_func("STDDEV", f),
2570            Expression::StddevPop(f) => self.generate_agg_func("STDDEV_POP", f),
2571            Expression::StddevSamp(f) => self.generate_stddev_samp(f),
2572            Expression::Variance(f) => self.generate_agg_func("VARIANCE", f),
2573            Expression::VarPop(f) => {
2574                let name = if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
2575                    "VARIANCE_POP"
2576                } else {
2577                    "VAR_POP"
2578                };
2579                self.generate_agg_func(name, f)
2580            }
2581            Expression::VarSamp(f) => self.generate_agg_func("VAR_SAMP", f),
2582            Expression::Skewness(f) => {
2583                let name = match self.config.dialect {
2584                    Some(DialectType::Snowflake) => "SKEW",
2585                    _ => "SKEWNESS",
2586                };
2587                self.generate_agg_func(name, f)
2588            }
2589            Expression::Median(f) => self.generate_agg_func("MEDIAN", f),
2590            Expression::Mode(f) => self.generate_agg_func("MODE", f),
2591            Expression::First(f) => self.generate_agg_func_with_ignore_nulls_bool("FIRST", f),
2592            Expression::Last(f) => self.generate_agg_func_with_ignore_nulls_bool("LAST", f),
2593            Expression::AnyValue(f) => self.generate_agg_func("ANY_VALUE", f),
2594            Expression::ApproxDistinct(f) => {
2595                match self.config.dialect {
2596                    Some(DialectType::Hive)
2597                    | Some(DialectType::Spark)
2598                    | Some(DialectType::Databricks)
2599                    | Some(DialectType::BigQuery) => {
2600                        // These dialects use APPROX_COUNT_DISTINCT (single arg only)
2601                        self.generate_agg_func("APPROX_COUNT_DISTINCT", f)
2602                    }
2603                    Some(DialectType::Redshift) => {
2604                        // Redshift uses APPROXIMATE COUNT(DISTINCT expr)
2605                        self.write_keyword("APPROXIMATE COUNT");
2606                        self.write("(");
2607                        self.write_keyword("DISTINCT");
2608                        self.write(" ");
2609                        self.generate_expression(&f.this)?;
2610                        self.write(")");
2611                        Ok(())
2612                    }
2613                    _ => self.generate_agg_func("APPROX_DISTINCT", f),
2614                }
2615            }
2616            Expression::ApproxCountDistinct(f) => {
2617                self.generate_agg_func("APPROX_COUNT_DISTINCT", f)
2618            }
2619            Expression::ApproxPercentile(f) => self.generate_approx_percentile(f),
2620            Expression::Percentile(f) => self.generate_percentile("PERCENTILE", f),
2621            Expression::LogicalAnd(f) => {
2622                let name = match self.config.dialect {
2623                    Some(DialectType::Snowflake) => "BOOLAND_AGG",
2624                    Some(DialectType::Spark)
2625                    | Some(DialectType::Databricks)
2626                    | Some(DialectType::PostgreSQL)
2627                    | Some(DialectType::DuckDB)
2628                    | Some(DialectType::Redshift) => "BOOL_AND",
2629                    Some(DialectType::Oracle)
2630                    | Some(DialectType::SQLite)
2631                    | Some(DialectType::MySQL) => "MIN",
2632                    _ => "BOOL_AND",
2633                };
2634                self.generate_agg_func(name, f)
2635            }
2636            Expression::LogicalOr(f) => {
2637                let name = match self.config.dialect {
2638                    Some(DialectType::Snowflake) => "BOOLOR_AGG",
2639                    Some(DialectType::Spark)
2640                    | Some(DialectType::Databricks)
2641                    | Some(DialectType::PostgreSQL)
2642                    | Some(DialectType::DuckDB)
2643                    | Some(DialectType::Redshift) => "BOOL_OR",
2644                    Some(DialectType::Oracle)
2645                    | Some(DialectType::SQLite)
2646                    | Some(DialectType::MySQL) => "MAX",
2647                    _ => "BOOL_OR",
2648                };
2649                self.generate_agg_func(name, f)
2650            }
2651
2652            // Typed window functions
2653            Expression::RowNumber(_) => {
2654                if self.config.dialect == Some(DialectType::ClickHouse) {
2655                    self.write("row_number");
2656                } else {
2657                    self.write_keyword("ROW_NUMBER");
2658                }
2659                self.write("()");
2660                Ok(())
2661            }
2662            Expression::Rank(r) => {
2663                self.write_keyword("RANK");
2664                self.write("(");
2665                // Oracle hypothetical rank args: RANK(val1, val2, ...) WITHIN GROUP (ORDER BY ...)
2666                if !r.args.is_empty() {
2667                    for (i, arg) in r.args.iter().enumerate() {
2668                        if i > 0 {
2669                            self.write(", ");
2670                        }
2671                        self.generate_expression(arg)?;
2672                    }
2673                } else if let Some(order_by) = &r.order_by {
2674                    // DuckDB: RANK(ORDER BY col)
2675                    self.write_keyword(" ORDER BY ");
2676                    for (i, ob) in order_by.iter().enumerate() {
2677                        if i > 0 {
2678                            self.write(", ");
2679                        }
2680                        self.generate_ordered(ob)?;
2681                    }
2682                }
2683                self.write(")");
2684                Ok(())
2685            }
2686            Expression::DenseRank(dr) => {
2687                self.write_keyword("DENSE_RANK");
2688                self.write("(");
2689                // Oracle hypothetical rank args: DENSE_RANK(val1, val2, ...) WITHIN GROUP (ORDER BY ...)
2690                for (i, arg) in dr.args.iter().enumerate() {
2691                    if i > 0 {
2692                        self.write(", ");
2693                    }
2694                    self.generate_expression(arg)?;
2695                }
2696                self.write(")");
2697                Ok(())
2698            }
2699            Expression::NTile(f) => self.generate_ntile(f),
2700            Expression::Lead(f) => self.generate_lead_lag("LEAD", f),
2701            Expression::Lag(f) => self.generate_lead_lag("LAG", f),
2702            Expression::FirstValue(f) => self.generate_value_func_with_ignore_nulls_bool("FIRST_VALUE", f),
2703            Expression::LastValue(f) => self.generate_value_func_with_ignore_nulls_bool("LAST_VALUE", f),
2704            Expression::NthValue(f) => self.generate_nth_value(f),
2705            Expression::PercentRank(pr) => {
2706                self.write_keyword("PERCENT_RANK");
2707                self.write("(");
2708                // Oracle hypothetical rank args: PERCENT_RANK(val1, val2, ...) WITHIN GROUP (ORDER BY ...)
2709                if !pr.args.is_empty() {
2710                    for (i, arg) in pr.args.iter().enumerate() {
2711                        if i > 0 {
2712                            self.write(", ");
2713                        }
2714                        self.generate_expression(arg)?;
2715                    }
2716                } else if let Some(order_by) = &pr.order_by {
2717                    // DuckDB: PERCENT_RANK(ORDER BY col)
2718                    self.write_keyword(" ORDER BY ");
2719                    for (i, ob) in order_by.iter().enumerate() {
2720                        if i > 0 {
2721                            self.write(", ");
2722                        }
2723                        self.generate_ordered(ob)?;
2724                    }
2725                }
2726                self.write(")");
2727                Ok(())
2728            }
2729            Expression::CumeDist(cd) => {
2730                self.write_keyword("CUME_DIST");
2731                self.write("(");
2732                // Oracle hypothetical rank args: CUME_DIST(val1, val2, ...) WITHIN GROUP (ORDER BY ...)
2733                if !cd.args.is_empty() {
2734                    for (i, arg) in cd.args.iter().enumerate() {
2735                        if i > 0 {
2736                            self.write(", ");
2737                        }
2738                        self.generate_expression(arg)?;
2739                    }
2740                } else if let Some(order_by) = &cd.order_by {
2741                    // DuckDB: CUME_DIST(ORDER BY col)
2742                    self.write_keyword(" ORDER BY ");
2743                    for (i, ob) in order_by.iter().enumerate() {
2744                        if i > 0 {
2745                            self.write(", ");
2746                        }
2747                        self.generate_ordered(ob)?;
2748                    }
2749                }
2750                self.write(")");
2751                Ok(())
2752            }
2753            Expression::PercentileCont(f) => self.generate_percentile("PERCENTILE_CONT", f),
2754            Expression::PercentileDisc(f) => self.generate_percentile("PERCENTILE_DISC", f),
2755
2756            // Additional string functions
2757            Expression::Contains(f) => {
2758                self.generate_binary_func("CONTAINS", &f.this, &f.expression)
2759            }
2760            Expression::StartsWith(f) => {
2761                let name = match self.config.dialect {
2762                    Some(DialectType::Spark) | Some(DialectType::Databricks) => "STARTSWITH",
2763                    _ => "STARTS_WITH",
2764                };
2765                self.generate_binary_func(name, &f.this, &f.expression)
2766            }
2767            Expression::EndsWith(f) => {
2768                let name = match self.config.dialect {
2769                    Some(DialectType::Snowflake) => "ENDSWITH",
2770                    Some(DialectType::Spark) | Some(DialectType::Databricks) => "ENDSWITH",
2771                    Some(DialectType::ClickHouse) => "endsWith",
2772                    _ => "ENDS_WITH",
2773                };
2774                self.generate_binary_func(name, &f.this, &f.expression)
2775            }
2776            Expression::Position(f) => self.generate_position(f),
2777            Expression::Initcap(f) => match self.config.dialect {
2778                Some(DialectType::Presto)
2779                | Some(DialectType::Trino)
2780                | Some(DialectType::Athena) => {
2781                    self.write_keyword("REGEXP_REPLACE");
2782                    self.write("(");
2783                    self.generate_expression(&f.this)?;
2784                    self.write(", '(\\w)(\\w*)', x -> UPPER(x[1]) || LOWER(x[2]))");
2785                    Ok(())
2786                }
2787                _ => self.generate_simple_func("INITCAP", &f.this),
2788            },
2789            Expression::Ascii(f) => self.generate_simple_func("ASCII", &f.this),
2790            Expression::Chr(f) => self.generate_simple_func("CHR", &f.this),
2791            Expression::CharFunc(f) => self.generate_char_func(f),
2792            Expression::Soundex(f) => self.generate_simple_func("SOUNDEX", &f.this),
2793            Expression::Levenshtein(f) => {
2794                self.generate_binary_func("LEVENSHTEIN", &f.this, &f.expression)
2795            }
2796
2797            // Additional math functions
2798            Expression::ModFunc(f) => self.generate_mod_func(f),
2799            Expression::Random(_) => {
2800                self.write_keyword("RANDOM");
2801                self.write("()");
2802                Ok(())
2803            }
2804            Expression::Rand(f) => self.generate_rand(f),
2805            Expression::TruncFunc(f) => self.generate_truncate_func(f),
2806            Expression::Pi(_) => {
2807                self.write_keyword("PI");
2808                self.write("()");
2809                Ok(())
2810            }
2811            Expression::Radians(f) => self.generate_simple_func("RADIANS", &f.this),
2812            Expression::Degrees(f) => self.generate_simple_func("DEGREES", &f.this),
2813            Expression::Sin(f) => self.generate_simple_func("SIN", &f.this),
2814            Expression::Cos(f) => self.generate_simple_func("COS", &f.this),
2815            Expression::Tan(f) => self.generate_simple_func("TAN", &f.this),
2816            Expression::Asin(f) => self.generate_simple_func("ASIN", &f.this),
2817            Expression::Acos(f) => self.generate_simple_func("ACOS", &f.this),
2818            Expression::Atan(f) => self.generate_simple_func("ATAN", &f.this),
2819            Expression::Atan2(f) => {
2820                let name = f.original_name.as_deref().unwrap_or("ATAN2");
2821                self.generate_binary_func(name, &f.this, &f.expression)
2822            }
2823
2824            // Control flow
2825            Expression::Decode(f) => self.generate_decode(f),
2826
2827            // Additional date/time functions
2828            Expression::DateFormat(f) => self.generate_date_format("DATE_FORMAT", f),
2829            Expression::FormatDate(f) => self.generate_date_format("FORMAT_DATE", f),
2830            Expression::Year(f) => self.generate_simple_func("YEAR", &f.this),
2831            Expression::Month(f) => self.generate_simple_func("MONTH", &f.this),
2832            Expression::Day(f) => self.generate_simple_func("DAY", &f.this),
2833            Expression::Hour(f) => self.generate_simple_func("HOUR", &f.this),
2834            Expression::Minute(f) => self.generate_simple_func("MINUTE", &f.this),
2835            Expression::Second(f) => self.generate_simple_func("SECOND", &f.this),
2836            Expression::DayOfWeek(f) => {
2837                let name = match self.config.dialect {
2838                    Some(DialectType::Presto)
2839                    | Some(DialectType::Trino)
2840                    | Some(DialectType::Athena) => "DAY_OF_WEEK",
2841                    Some(DialectType::DuckDB) => "ISODOW",
2842                    _ => "DAYOFWEEK",
2843                };
2844                self.generate_simple_func(name, &f.this)
2845            }
2846            Expression::DayOfMonth(f) => {
2847                let name = match self.config.dialect {
2848                    Some(DialectType::Presto)
2849                    | Some(DialectType::Trino)
2850                    | Some(DialectType::Athena) => "DAY_OF_MONTH",
2851                    _ => "DAYOFMONTH",
2852                };
2853                self.generate_simple_func(name, &f.this)
2854            }
2855            Expression::DayOfYear(f) => {
2856                let name = match self.config.dialect {
2857                    Some(DialectType::Presto)
2858                    | Some(DialectType::Trino)
2859                    | Some(DialectType::Athena) => "DAY_OF_YEAR",
2860                    _ => "DAYOFYEAR",
2861                };
2862                self.generate_simple_func(name, &f.this)
2863            }
2864            Expression::WeekOfYear(f) => {
2865                // Python sqlglot default is WEEK_OF_YEAR; Hive/DuckDB/Spark/MySQL override to WEEKOFYEAR
2866                let name = match self.config.dialect {
2867                    Some(DialectType::Hive)
2868                    | Some(DialectType::DuckDB)
2869                    | Some(DialectType::Spark)
2870                    | Some(DialectType::Databricks)
2871                    | Some(DialectType::MySQL) => "WEEKOFYEAR",
2872                    _ => "WEEK_OF_YEAR",
2873                };
2874                self.generate_simple_func(name, &f.this)
2875            }
2876            Expression::Quarter(f) => self.generate_simple_func("QUARTER", &f.this),
2877            Expression::AddMonths(f) => {
2878                self.generate_binary_func("ADD_MONTHS", &f.this, &f.expression)
2879            }
2880            Expression::MonthsBetween(f) => {
2881                self.generate_binary_func("MONTHS_BETWEEN", &f.this, &f.expression)
2882            }
2883            Expression::LastDay(f) => self.generate_last_day(f),
2884            Expression::NextDay(f) => self.generate_binary_func("NEXT_DAY", &f.this, &f.expression),
2885            Expression::Epoch(f) => self.generate_simple_func("EPOCH", &f.this),
2886            Expression::EpochMs(f) => self.generate_simple_func("EPOCH_MS", &f.this),
2887            Expression::FromUnixtime(f) => self.generate_from_unixtime(f),
2888            Expression::UnixTimestamp(f) => self.generate_unix_timestamp(f),
2889            Expression::MakeDate(f) => self.generate_make_date(f),
2890            Expression::MakeTimestamp(f) => self.generate_make_timestamp(f),
2891            Expression::TimestampTrunc(f) => self.generate_date_trunc(f),
2892
2893            // Array functions
2894            Expression::ArrayFunc(f) => self.generate_array_constructor(f),
2895            Expression::ArrayLength(f) => self.generate_simple_func("ARRAY_LENGTH", &f.this),
2896            Expression::ArraySize(f) => self.generate_simple_func("ARRAY_SIZE", &f.this),
2897            Expression::Cardinality(f) => self.generate_simple_func("CARDINALITY", &f.this),
2898            Expression::ArrayContains(f) => {
2899                self.generate_binary_func("ARRAY_CONTAINS", &f.this, &f.expression)
2900            }
2901            Expression::ArrayPosition(f) => {
2902                self.generate_binary_func("ARRAY_POSITION", &f.this, &f.expression)
2903            }
2904            Expression::ArrayAppend(f) => {
2905                self.generate_binary_func("ARRAY_APPEND", &f.this, &f.expression)
2906            }
2907            Expression::ArrayPrepend(f) => {
2908                self.generate_binary_func("ARRAY_PREPEND", &f.this, &f.expression)
2909            }
2910            Expression::ArrayConcat(f) => self.generate_vararg_func("ARRAY_CONCAT", &f.expressions),
2911            Expression::ArraySort(f) => self.generate_array_sort(f),
2912            Expression::ArrayReverse(f) => self.generate_simple_func("ARRAY_REVERSE", &f.this),
2913            Expression::ArrayDistinct(f) => self.generate_simple_func("ARRAY_DISTINCT", &f.this),
2914            Expression::ArrayJoin(f) => self.generate_array_join("ARRAY_JOIN", f),
2915            Expression::ArrayToString(f) => self.generate_array_join("ARRAY_TO_STRING", f),
2916            Expression::Unnest(f) => self.generate_unnest(f),
2917            Expression::Explode(f) => self.generate_simple_func("EXPLODE", &f.this),
2918            Expression::ExplodeOuter(f) => self.generate_simple_func("EXPLODE_OUTER", &f.this),
2919            Expression::ArrayFilter(f) => self.generate_array_filter(f),
2920            Expression::ArrayTransform(f) => self.generate_array_transform(f),
2921            Expression::ArrayFlatten(f) => self.generate_simple_func("FLATTEN", &f.this),
2922            Expression::ArrayCompact(f) => {
2923                if matches!(self.config.dialect, Some(DialectType::DuckDB)) {
2924                    // DuckDB: ARRAY_COMPACT(arr) -> LIST_FILTER(arr, _u -> NOT _u IS NULL)
2925                    self.write("LIST_FILTER(");
2926                    self.generate_expression(&f.this)?;
2927                    self.write(", _u -> NOT _u IS NULL)");
2928                    Ok(())
2929                } else {
2930                    self.generate_simple_func("ARRAY_COMPACT", &f.this)
2931                }
2932            }
2933            Expression::ArrayIntersect(f) => {
2934                let func_name = f.original_name.as_deref().unwrap_or("ARRAY_INTERSECT");
2935                self.generate_vararg_func(func_name, &f.expressions)
2936            }
2937            Expression::ArrayUnion(f) => {
2938                self.generate_binary_func("ARRAY_UNION", &f.this, &f.expression)
2939            }
2940            Expression::ArrayExcept(f) => {
2941                self.generate_binary_func("ARRAY_EXCEPT", &f.this, &f.expression)
2942            }
2943            Expression::ArrayRemove(f) => {
2944                self.generate_binary_func("ARRAY_REMOVE", &f.this, &f.expression)
2945            }
2946            Expression::ArrayZip(f) => self.generate_vararg_func("ARRAYS_ZIP", &f.expressions),
2947            Expression::Sequence(f) => self.generate_sequence("SEQUENCE", f),
2948            Expression::Generate(f) => self.generate_sequence("GENERATE_SERIES", f),
2949
2950            // Struct functions
2951            Expression::StructFunc(f) => self.generate_struct_constructor(f),
2952            Expression::StructExtract(f) => self.generate_struct_extract(f),
2953            Expression::NamedStruct(f) => self.generate_named_struct(f),
2954
2955            // Map functions
2956            Expression::MapFunc(f) => self.generate_map_constructor(f),
2957            Expression::MapFromEntries(f) => self.generate_simple_func("MAP_FROM_ENTRIES", &f.this),
2958            Expression::MapFromArrays(f) => {
2959                self.generate_binary_func("MAP_FROM_ARRAYS", &f.this, &f.expression)
2960            }
2961            Expression::MapKeys(f) => self.generate_simple_func("MAP_KEYS", &f.this),
2962            Expression::MapValues(f) => self.generate_simple_func("MAP_VALUES", &f.this),
2963            Expression::MapContainsKey(f) => {
2964                self.generate_binary_func("MAP_CONTAINS_KEY", &f.this, &f.expression)
2965            }
2966            Expression::MapConcat(f) => self.generate_vararg_func("MAP_CONCAT", &f.expressions),
2967            Expression::ElementAt(f) => {
2968                self.generate_binary_func("ELEMENT_AT", &f.this, &f.expression)
2969            }
2970            Expression::TransformKeys(f) => self.generate_transform_func("TRANSFORM_KEYS", f),
2971            Expression::TransformValues(f) => self.generate_transform_func("TRANSFORM_VALUES", f),
2972
2973            // JSON functions
2974            Expression::JsonExtract(f) => self.generate_json_extract("JSON_EXTRACT", f),
2975            Expression::JsonExtractScalar(f) => {
2976                self.generate_json_extract("JSON_EXTRACT_SCALAR", f)
2977            }
2978            Expression::JsonExtractPath(f) => self.generate_json_path("JSON_EXTRACT_PATH", f),
2979            Expression::JsonArray(f) => self.generate_vararg_func("JSON_ARRAY", &f.expressions),
2980            Expression::JsonObject(f) => self.generate_json_object(f),
2981            Expression::JsonQuery(f) => self.generate_json_extract("JSON_QUERY", f),
2982            Expression::JsonValue(f) => self.generate_json_extract("JSON_VALUE", f),
2983            Expression::JsonArrayLength(f) => {
2984                self.generate_simple_func("JSON_ARRAY_LENGTH", &f.this)
2985            }
2986            Expression::JsonKeys(f) => self.generate_simple_func("JSON_KEYS", &f.this),
2987            Expression::JsonType(f) => self.generate_simple_func("JSON_TYPE", &f.this),
2988            Expression::ParseJson(f) => {
2989                let name = match self.config.dialect {
2990                    Some(DialectType::Presto)
2991                    | Some(DialectType::Trino)
2992                    | Some(DialectType::Athena) => "JSON_PARSE",
2993                    Some(DialectType::PostgreSQL) | Some(DialectType::Redshift) => {
2994                        // PostgreSQL: CAST(x AS JSON)
2995                        self.write_keyword("CAST");
2996                        self.write("(");
2997                        self.generate_expression(&f.this)?;
2998                        self.write_keyword(" AS ");
2999                        self.write_keyword("JSON");
3000                        self.write(")");
3001                        return Ok(());
3002                    }
3003                    Some(DialectType::Hive)
3004                    | Some(DialectType::Spark)
3005                    | Some(DialectType::MySQL)
3006                    | Some(DialectType::SingleStore)
3007                    | Some(DialectType::TiDB)
3008                    | Some(DialectType::TSQL) => {
3009                        // Hive/Spark/MySQL/TSQL: just emit the string literal
3010                        self.generate_expression(&f.this)?;
3011                        return Ok(());
3012                    }
3013                    Some(DialectType::DuckDB) => "JSON",
3014                    _ => "PARSE_JSON",
3015                };
3016                self.generate_simple_func(name, &f.this)
3017            }
3018            Expression::ToJson(f) => self.generate_simple_func("TO_JSON", &f.this),
3019            Expression::JsonSet(f) => self.generate_json_modify("JSON_SET", f),
3020            Expression::JsonInsert(f) => self.generate_json_modify("JSON_INSERT", f),
3021            Expression::JsonRemove(f) => self.generate_json_path("JSON_REMOVE", f),
3022            Expression::JsonMergePatch(f) => {
3023                self.generate_binary_func("JSON_MERGE_PATCH", &f.this, &f.expression)
3024            }
3025            Expression::JsonArrayAgg(f) => self.generate_json_array_agg(f),
3026            Expression::JsonObjectAgg(f) => self.generate_json_object_agg(f),
3027
3028            // Type casting/conversion
3029            Expression::Convert(f) => self.generate_convert(f),
3030            Expression::Typeof(f) => self.generate_simple_func("TYPEOF", &f.this),
3031
3032            // Additional expressions
3033            Expression::Lambda(f) => self.generate_lambda(f),
3034            Expression::Parameter(f) => self.generate_parameter(f),
3035            Expression::Placeholder(f) => self.generate_placeholder(f),
3036            Expression::NamedArgument(f) => self.generate_named_argument(f),
3037            Expression::TableArgument(f) => self.generate_table_argument(f),
3038            Expression::SqlComment(f) => self.generate_sql_comment(f),
3039
3040            // Additional predicates
3041            Expression::NullSafeEq(op) => self.generate_null_safe_eq(op),
3042            Expression::NullSafeNeq(op) => self.generate_null_safe_neq(op),
3043            Expression::Glob(op) => self.generate_binary_op(op, "GLOB"),
3044            Expression::SimilarTo(f) => self.generate_similar_to(f),
3045            Expression::Any(f) => self.generate_quantified("ANY", f),
3046            Expression::All(f) => self.generate_quantified("ALL", f),
3047            Expression::Overlaps(f) => self.generate_overlaps(f),
3048
3049            // Bitwise operations
3050            Expression::BitwiseLeftShift(op) => {
3051                if matches!(
3052                    self.config.dialect,
3053                    Some(DialectType::Presto) | Some(DialectType::Trino)
3054                ) {
3055                    self.write_keyword("BITWISE_ARITHMETIC_SHIFT_LEFT");
3056                    self.write("(");
3057                    self.generate_expression(&op.left)?;
3058                    self.write(", ");
3059                    self.generate_expression(&op.right)?;
3060                    self.write(")");
3061                    Ok(())
3062                } else if matches!(
3063                    self.config.dialect,
3064                    Some(DialectType::Spark) | Some(DialectType::Databricks)
3065                ) {
3066                    self.write_keyword("SHIFTLEFT");
3067                    self.write("(");
3068                    self.generate_expression(&op.left)?;
3069                    self.write(", ");
3070                    self.generate_expression(&op.right)?;
3071                    self.write(")");
3072                    Ok(())
3073                } else {
3074                    self.generate_binary_op(op, "<<")
3075                }
3076            }
3077            Expression::BitwiseRightShift(op) => {
3078                if matches!(
3079                    self.config.dialect,
3080                    Some(DialectType::Presto) | Some(DialectType::Trino)
3081                ) {
3082                    self.write_keyword("BITWISE_ARITHMETIC_SHIFT_RIGHT");
3083                    self.write("(");
3084                    self.generate_expression(&op.left)?;
3085                    self.write(", ");
3086                    self.generate_expression(&op.right)?;
3087                    self.write(")");
3088                    Ok(())
3089                } else if matches!(
3090                    self.config.dialect,
3091                    Some(DialectType::Spark) | Some(DialectType::Databricks)
3092                ) {
3093                    self.write_keyword("SHIFTRIGHT");
3094                    self.write("(");
3095                    self.generate_expression(&op.left)?;
3096                    self.write(", ");
3097                    self.generate_expression(&op.right)?;
3098                    self.write(")");
3099                    Ok(())
3100                } else {
3101                    self.generate_binary_op(op, ">>")
3102                }
3103            }
3104            Expression::BitwiseAndAgg(f) => self.generate_agg_func("BIT_AND", f),
3105            Expression::BitwiseOrAgg(f) => self.generate_agg_func("BIT_OR", f),
3106            Expression::BitwiseXorAgg(f) => self.generate_agg_func("BIT_XOR", f),
3107
3108            // Array/struct/map access
3109            Expression::Subscript(s) => self.generate_subscript(s),
3110            Expression::Dot(d) => self.generate_dot_access(d),
3111            Expression::MethodCall(m) => self.generate_method_call(m),
3112            Expression::ArraySlice(s) => self.generate_array_slice(s),
3113
3114            Expression::And(op) => self.generate_connector_op(op, ConnectorOperator::And),
3115            Expression::Or(op) => self.generate_connector_op(op, ConnectorOperator::Or),
3116            Expression::Add(op) => self.generate_binary_op(op, "+"),
3117            Expression::Sub(op) => self.generate_binary_op(op, "-"),
3118            Expression::Mul(op) => self.generate_binary_op(op, "*"),
3119            Expression::Div(op) => self.generate_binary_op(op, "/"),
3120            Expression::IntDiv(f) => {
3121                use crate::dialects::DialectType;
3122                if matches!(self.config.dialect, Some(DialectType::DuckDB)) {
3123                    // DuckDB uses // operator for integer division
3124                    self.generate_expression(&f.this)?;
3125                    self.write(" // ");
3126                    self.generate_expression(&f.expression)?;
3127                    Ok(())
3128                } else if matches!(
3129                    self.config.dialect,
3130                    Some(DialectType::Hive | DialectType::Spark | DialectType::Databricks)
3131                ) {
3132                    // Hive/Spark use DIV as an infix operator
3133                    self.generate_expression(&f.this)?;
3134                    self.write(" ");
3135                    self.write_keyword("DIV");
3136                    self.write(" ");
3137                    self.generate_expression(&f.expression)?;
3138                    Ok(())
3139                } else {
3140                    // Other dialects use DIV function
3141                    self.write_keyword("DIV");
3142                    self.write("(");
3143                    self.generate_expression(&f.this)?;
3144                    self.write(", ");
3145                    self.generate_expression(&f.expression)?;
3146                    self.write(")");
3147                    Ok(())
3148                }
3149            }
3150            Expression::Mod(op) => {
3151                if matches!(self.config.dialect, Some(DialectType::Teradata)) {
3152                    self.generate_binary_op(op, "MOD")
3153                } else {
3154                    self.generate_binary_op(op, "%")
3155                }
3156            }
3157            Expression::Eq(op) => self.generate_binary_op(op, "="),
3158            Expression::Neq(op) => self.generate_binary_op(op, "<>"),
3159            Expression::Lt(op) => self.generate_binary_op(op, "<"),
3160            Expression::Lte(op) => self.generate_binary_op(op, "<="),
3161            Expression::Gt(op) => self.generate_binary_op(op, ">"),
3162            Expression::Gte(op) => self.generate_binary_op(op, ">="),
3163            Expression::Like(op) => self.generate_like_op(op, "LIKE"),
3164            Expression::ILike(op) => self.generate_like_op(op, "ILIKE"),
3165            Expression::Match(op) => self.generate_binary_op(op, "MATCH"),
3166            Expression::Concat(op) => {
3167                // In Solr, || is OR, not string concatenation (DPIPE_IS_STRING_CONCAT = False)
3168                if self.config.dialect == Some(DialectType::Solr) {
3169                    self.generate_binary_op(op, "OR")
3170                } else if self.config.dialect == Some(DialectType::MySQL) {
3171                    self.generate_mysql_concat_from_concat(op)
3172                } else {
3173                    self.generate_binary_op(op, "||")
3174                }
3175            }
3176            Expression::BitwiseAnd(op) => {
3177                // Presto/Trino use BITWISE_AND function
3178                if matches!(
3179                    self.config.dialect,
3180                    Some(DialectType::Presto) | Some(DialectType::Trino)
3181                ) {
3182                    self.write_keyword("BITWISE_AND");
3183                    self.write("(");
3184                    self.generate_expression(&op.left)?;
3185                    self.write(", ");
3186                    self.generate_expression(&op.right)?;
3187                    self.write(")");
3188                    Ok(())
3189                } else {
3190                    self.generate_binary_op(op, "&")
3191                }
3192            }
3193            Expression::BitwiseOr(op) => {
3194                // Presto/Trino use BITWISE_OR function
3195                if matches!(
3196                    self.config.dialect,
3197                    Some(DialectType::Presto) | Some(DialectType::Trino)
3198                ) {
3199                    self.write_keyword("BITWISE_OR");
3200                    self.write("(");
3201                    self.generate_expression(&op.left)?;
3202                    self.write(", ");
3203                    self.generate_expression(&op.right)?;
3204                    self.write(")");
3205                    Ok(())
3206                } else {
3207                    self.generate_binary_op(op, "|")
3208                }
3209            }
3210            Expression::BitwiseXor(op) => {
3211                // Presto/Trino use BITWISE_XOR function, PostgreSQL uses #, others use ^
3212                if matches!(
3213                    self.config.dialect,
3214                    Some(DialectType::Presto) | Some(DialectType::Trino)
3215                ) {
3216                    self.write_keyword("BITWISE_XOR");
3217                    self.write("(");
3218                    self.generate_expression(&op.left)?;
3219                    self.write(", ");
3220                    self.generate_expression(&op.right)?;
3221                    self.write(")");
3222                    Ok(())
3223                } else if matches!(self.config.dialect, Some(DialectType::PostgreSQL)) {
3224                    self.generate_binary_op(op, "#")
3225                } else {
3226                    self.generate_binary_op(op, "^")
3227                }
3228            }
3229            Expression::Adjacent(op) => self.generate_binary_op(op, "-|-"),
3230            Expression::TsMatch(op) => self.generate_binary_op(op, "@@"),
3231            Expression::PropertyEQ(op) => self.generate_binary_op(op, ":="),
3232            Expression::ArrayContainsAll(op) => self.generate_binary_op(op, "@>"),
3233            Expression::ArrayContainedBy(op) => self.generate_binary_op(op, "<@"),
3234            Expression::ArrayOverlaps(op) => self.generate_binary_op(op, "&&"),
3235            Expression::JSONBContainsAllTopKeys(op) => self.generate_binary_op(op, "?&"),
3236            Expression::JSONBContainsAnyTopKeys(op) => self.generate_binary_op(op, "?|"),
3237            Expression::JSONBContains(f) => {
3238                // PostgreSQL JSONB contains key operator: a ? b
3239                self.generate_expression(&f.this)?;
3240                self.write_space();
3241                self.write("?");
3242                self.write_space();
3243                self.generate_expression(&f.expression)
3244            }
3245            Expression::JSONBDeleteAtPath(op) => self.generate_binary_op(op, "#-"),
3246            Expression::ExtendsLeft(op) => self.generate_binary_op(op, "&<"),
3247            Expression::ExtendsRight(op) => self.generate_binary_op(op, "&>"),
3248            Expression::Not(op) => self.generate_unary_op(op, "NOT"),
3249            Expression::Neg(op) => self.generate_unary_op(op, "-"),
3250            Expression::BitwiseNot(op) => {
3251                // Presto/Trino use BITWISE_NOT function
3252                if matches!(
3253                    self.config.dialect,
3254                    Some(DialectType::Presto) | Some(DialectType::Trino)
3255                ) {
3256                    self.write_keyword("BITWISE_NOT");
3257                    self.write("(");
3258                    self.generate_expression(&op.this)?;
3259                    self.write(")");
3260                    Ok(())
3261                } else {
3262                    self.generate_unary_op(op, "~")
3263                }
3264            }
3265            Expression::In(in_expr) => self.generate_in(in_expr),
3266            Expression::Between(between) => self.generate_between(between),
3267            Expression::IsNull(is_null) => self.generate_is_null(is_null),
3268            Expression::IsTrue(is_true) => self.generate_is_true(is_true),
3269            Expression::IsFalse(is_false) => self.generate_is_false(is_false),
3270            Expression::IsJson(is_json) => self.generate_is_json(is_json),
3271            Expression::Is(is_expr) => self.generate_is(is_expr),
3272            Expression::Exists(exists) => self.generate_exists(exists),
3273            Expression::MemberOf(member_of) => self.generate_member_of(member_of),
3274            Expression::Subquery(subquery) => self.generate_subquery(subquery),
3275            Expression::Paren(paren) => {
3276                // JoinedTable already outputs its own parentheses, so don't double-wrap
3277                let skip_parens = matches!(&paren.this, Expression::JoinedTable(_));
3278
3279                if !skip_parens {
3280                    self.write("(");
3281                    if self.config.pretty {
3282                        self.write_newline();
3283                        self.indent_level += 1;
3284                        self.write_indent();
3285                    }
3286                }
3287                self.generate_expression(&paren.this)?;
3288                if !skip_parens {
3289                    if self.config.pretty {
3290                        self.write_newline();
3291                        self.indent_level -= 1;
3292                        self.write_indent();
3293                    }
3294                    self.write(")");
3295                }
3296                // Output trailing comments after closing paren
3297                for comment in &paren.trailing_comments {
3298                    self.write(" ");
3299                    self.write_formatted_comment(comment);
3300                }
3301                Ok(())
3302            }
3303            Expression::Array(arr) => self.generate_array(arr),
3304            Expression::Tuple(tuple) => self.generate_tuple(tuple),
3305            Expression::PipeOperator(pipe) => self.generate_pipe_operator(pipe),
3306            Expression::Ordered(ordered) => self.generate_ordered(ordered),
3307            Expression::DataType(dt) => self.generate_data_type(dt),
3308            Expression::Raw(raw) => {
3309                self.write(&raw.sql);
3310                Ok(())
3311            }
3312            Expression::Command(cmd) => {
3313                self.write(&cmd.this);
3314                Ok(())
3315            }
3316            Expression::Kill(kill) => {
3317                self.write_keyword("KILL");
3318                if let Some(kind) = &kill.kind {
3319                    self.write_space();
3320                    self.write_keyword(kind);
3321                }
3322                self.write_space();
3323                self.generate_expression(&kill.this)?;
3324                Ok(())
3325            }
3326            Expression::Execute(exec) => {
3327                self.write_keyword("EXECUTE");
3328                self.write_space();
3329                self.generate_expression(&exec.this)?;
3330                for (i, param) in exec.parameters.iter().enumerate() {
3331                    if i == 0 {
3332                        self.write_space();
3333                    } else {
3334                        self.write(", ");
3335                    }
3336                    self.write(&param.name);
3337                    // Only write = value for named parameters (not positional)
3338                    if !param.positional {
3339                        self.write(" = ");
3340                        self.generate_expression(&param.value)?;
3341                    }
3342                }
3343                Ok(())
3344            }
3345            Expression::Annotated(annotated) => {
3346                self.generate_expression(&annotated.this)?;
3347                for comment in &annotated.trailing_comments {
3348                    self.write(" ");
3349                    self.write_formatted_comment(comment);
3350                }
3351                Ok(())
3352            }
3353
3354            // DDL statements
3355            Expression::CreateTable(ct) => self.generate_create_table(ct),
3356            Expression::DropTable(dt) => self.generate_drop_table(dt),
3357            Expression::AlterTable(at) => self.generate_alter_table(at),
3358            Expression::CreateIndex(ci) => self.generate_create_index(ci),
3359            Expression::DropIndex(di) => self.generate_drop_index(di),
3360            Expression::CreateView(cv) => self.generate_create_view(cv),
3361            Expression::DropView(dv) => self.generate_drop_view(dv),
3362            Expression::AlterView(av) => self.generate_alter_view(av),
3363            Expression::AlterIndex(ai) => self.generate_alter_index(ai),
3364            Expression::Truncate(tr) => self.generate_truncate(tr),
3365            Expression::Use(u) => self.generate_use(u),
3366            // Phase 4: Additional DDL statements
3367            Expression::CreateSchema(cs) => self.generate_create_schema(cs),
3368            Expression::DropSchema(ds) => self.generate_drop_schema(ds),
3369            Expression::DropNamespace(dn) => self.generate_drop_namespace(dn),
3370            Expression::CreateDatabase(cd) => self.generate_create_database(cd),
3371            Expression::DropDatabase(dd) => self.generate_drop_database(dd),
3372            Expression::CreateFunction(cf) => self.generate_create_function(cf),
3373            Expression::DropFunction(df) => self.generate_drop_function(df),
3374            Expression::CreateProcedure(cp) => self.generate_create_procedure(cp),
3375            Expression::DropProcedure(dp) => self.generate_drop_procedure(dp),
3376            Expression::CreateSequence(cs) => self.generate_create_sequence(cs),
3377            Expression::DropSequence(ds) => self.generate_drop_sequence(ds),
3378            Expression::AlterSequence(als) => self.generate_alter_sequence(als),
3379            Expression::CreateTrigger(ct) => self.generate_create_trigger(ct),
3380            Expression::DropTrigger(dt) => self.generate_drop_trigger(dt),
3381            Expression::CreateType(ct) => self.generate_create_type(ct),
3382            Expression::DropType(dt) => self.generate_drop_type(dt),
3383            Expression::Describe(d) => self.generate_describe(d),
3384            Expression::Show(s) => self.generate_show(s),
3385
3386            // CACHE/UNCACHE/LOAD TABLE (Spark/Hive)
3387            Expression::Cache(c) => self.generate_cache(c),
3388            Expression::Uncache(u) => self.generate_uncache(u),
3389            Expression::LoadData(l) => self.generate_load_data(l),
3390            Expression::Pragma(p) => self.generate_pragma(p),
3391            Expression::Grant(g) => self.generate_grant(g),
3392            Expression::Revoke(r) => self.generate_revoke(r),
3393            Expression::Comment(c) => self.generate_comment(c),
3394            Expression::SetStatement(s) => self.generate_set_statement(s),
3395
3396            // PIVOT/UNPIVOT
3397            Expression::Pivot(pivot) => self.generate_pivot(pivot),
3398            Expression::Unpivot(unpivot) => self.generate_unpivot(unpivot),
3399
3400            // VALUES table constructor
3401            Expression::Values(values) => self.generate_values(values),
3402
3403            // === BATCH-GENERATED MATCH ARMS (481 variants) ===
3404            Expression::AIAgg(e) => self.generate_ai_agg(e),
3405            Expression::AIClassify(e) => self.generate_ai_classify(e),
3406            Expression::AddPartition(e) => self.generate_add_partition(e),
3407            Expression::AlgorithmProperty(e) => self.generate_algorithm_property(e),
3408            Expression::Aliases(e) => self.generate_aliases(e),
3409            Expression::AllowedValuesProperty(e) => self.generate_allowed_values_property(e),
3410            Expression::AlterColumn(e) => self.generate_alter_column(e),
3411            Expression::AlterSession(e) => self.generate_alter_session(e),
3412            Expression::AlterSet(e) => self.generate_alter_set(e),
3413            Expression::AlterSortKey(e) => self.generate_alter_sort_key(e),
3414            Expression::Analyze(e) => self.generate_analyze(e),
3415            Expression::AnalyzeDelete(e) => self.generate_analyze_delete(e),
3416            Expression::AnalyzeHistogram(e) => self.generate_analyze_histogram(e),
3417            Expression::AnalyzeListChainedRows(e) => self.generate_analyze_list_chained_rows(e),
3418            Expression::AnalyzeSample(e) => self.generate_analyze_sample(e),
3419            Expression::AnalyzeStatistics(e) => self.generate_analyze_statistics(e),
3420            Expression::AnalyzeValidate(e) => self.generate_analyze_validate(e),
3421            Expression::AnalyzeWith(e) => self.generate_analyze_with(e),
3422            Expression::Anonymous(e) => self.generate_anonymous(e),
3423            Expression::AnonymousAggFunc(e) => self.generate_anonymous_agg_func(e),
3424            Expression::Apply(e) => self.generate_apply(e),
3425            Expression::ApproxPercentileEstimate(e) => self.generate_approx_percentile_estimate(e),
3426            Expression::ApproxQuantile(e) => self.generate_approx_quantile(e),
3427            Expression::ApproxQuantiles(e) => self.generate_approx_quantiles(e),
3428            Expression::ApproxTopK(e) => self.generate_approx_top_k(e),
3429            Expression::ApproxTopKAccumulate(e) => self.generate_approx_top_k_accumulate(e),
3430            Expression::ApproxTopKCombine(e) => self.generate_approx_top_k_combine(e),
3431            Expression::ApproxTopKEstimate(e) => self.generate_approx_top_k_estimate(e),
3432            Expression::ApproxTopSum(e) => self.generate_approx_top_sum(e),
3433            Expression::ArgMax(e) => self.generate_arg_max(e),
3434            Expression::ArgMin(e) => self.generate_arg_min(e),
3435            Expression::ArrayAll(e) => self.generate_array_all(e),
3436            Expression::ArrayAny(e) => self.generate_array_any(e),
3437            Expression::ArrayConstructCompact(e) => self.generate_array_construct_compact(e),
3438            Expression::ArraySum(e) => self.generate_array_sum(e),
3439            Expression::AtIndex(e) => self.generate_at_index(e),
3440            Expression::Attach(e) => self.generate_attach(e),
3441            Expression::AttachOption(e) => self.generate_attach_option(e),
3442            Expression::AutoIncrementProperty(e) => self.generate_auto_increment_property(e),
3443            Expression::AutoRefreshProperty(e) => self.generate_auto_refresh_property(e),
3444            Expression::BackupProperty(e) => self.generate_backup_property(e),
3445            Expression::Base64DecodeBinary(e) => self.generate_base64_decode_binary(e),
3446            Expression::Base64DecodeString(e) => self.generate_base64_decode_string(e),
3447            Expression::Base64Encode(e) => self.generate_base64_encode(e),
3448            Expression::BlockCompressionProperty(e) => self.generate_block_compression_property(e),
3449            Expression::Booland(e) => self.generate_booland(e),
3450            Expression::Boolor(e) => self.generate_boolor(e),
3451            Expression::BuildProperty(e) => self.generate_build_property(e),
3452            Expression::ByteString(e) => self.generate_byte_string(e),
3453            Expression::CaseSpecificColumnConstraint(e) => {
3454                self.generate_case_specific_column_constraint(e)
3455            }
3456            Expression::CastToStrType(e) => self.generate_cast_to_str_type(e),
3457            Expression::Changes(e) => self.generate_changes(e),
3458            Expression::CharacterSetColumnConstraint(e) => {
3459                self.generate_character_set_column_constraint(e)
3460            }
3461            Expression::CharacterSetProperty(e) => self.generate_character_set_property(e),
3462            Expression::CheckColumnConstraint(e) => self.generate_check_column_constraint(e),
3463            Expression::AssumeColumnConstraint(e) => self.generate_assume_column_constraint(e),
3464            Expression::CheckJson(e) => self.generate_check_json(e),
3465            Expression::CheckXml(e) => self.generate_check_xml(e),
3466            Expression::ChecksumProperty(e) => self.generate_checksum_property(e),
3467            Expression::Clone(e) => self.generate_clone(e),
3468            Expression::ClusterBy(e) => self.generate_cluster_by(e),
3469            Expression::ClusterByColumnsProperty(e) => self.generate_cluster_by_columns_property(e),
3470            Expression::ClusteredByProperty(e) => self.generate_clustered_by_property(e),
3471            Expression::CollateProperty(e) => self.generate_collate_property(e),
3472            Expression::ColumnConstraint(e) => self.generate_column_constraint(e),
3473            Expression::ColumnDef(e) => self.generate_column_def_expr(e),
3474            Expression::ColumnPosition(e) => self.generate_column_position(e),
3475            Expression::ColumnPrefix(e) => self.generate_column_prefix(e),
3476            Expression::Columns(e) => self.generate_columns(e),
3477            Expression::CombinedAggFunc(e) => self.generate_combined_agg_func(e),
3478            Expression::CombinedParameterizedAgg(e) => self.generate_combined_parameterized_agg(e),
3479            Expression::Commit(e) => self.generate_commit(e),
3480            Expression::Comprehension(e) => self.generate_comprehension(e),
3481            Expression::Compress(e) => self.generate_compress(e),
3482            Expression::CompressColumnConstraint(e) => self.generate_compress_column_constraint(e),
3483            Expression::ComputedColumnConstraint(e) => self.generate_computed_column_constraint(e),
3484            Expression::ConditionalInsert(e) => self.generate_conditional_insert(e),
3485            Expression::Constraint(e) => self.generate_constraint(e),
3486            Expression::ConvertTimezone(e) => self.generate_convert_timezone(e),
3487            Expression::ConvertToCharset(e) => self.generate_convert_to_charset(e),
3488            Expression::Copy(e) => self.generate_copy(e),
3489            Expression::CopyParameter(e) => self.generate_copy_parameter(e),
3490            Expression::Corr(e) => self.generate_corr(e),
3491            Expression::CosineDistance(e) => self.generate_cosine_distance(e),
3492            Expression::CovarPop(e) => self.generate_covar_pop(e),
3493            Expression::CovarSamp(e) => self.generate_covar_samp(e),
3494            Expression::Credentials(e) => self.generate_credentials(e),
3495            Expression::CredentialsProperty(e) => self.generate_credentials_property(e),
3496            Expression::Cte(e) => self.generate_cte(e),
3497            Expression::Cube(e) => self.generate_cube(e),
3498            Expression::CurrentDatetime(e) => self.generate_current_datetime(e),
3499            Expression::CurrentSchema(e) => self.generate_current_schema(e),
3500            Expression::CurrentSchemas(e) => self.generate_current_schemas(e),
3501            Expression::CurrentUser(e) => self.generate_current_user(e),
3502            Expression::DPipe(e) => self.generate_d_pipe(e),
3503            Expression::DataBlocksizeProperty(e) => self.generate_data_blocksize_property(e),
3504            Expression::DataDeletionProperty(e) => self.generate_data_deletion_property(e),
3505            Expression::Date(e) => self.generate_date_func(e),
3506            Expression::DateBin(e) => self.generate_date_bin(e),
3507            Expression::DateFormatColumnConstraint(e) => {
3508                self.generate_date_format_column_constraint(e)
3509            }
3510            Expression::DateFromParts(e) => self.generate_date_from_parts(e),
3511            Expression::Datetime(e) => self.generate_datetime(e),
3512            Expression::DatetimeAdd(e) => self.generate_datetime_add(e),
3513            Expression::DatetimeDiff(e) => self.generate_datetime_diff(e),
3514            Expression::DatetimeSub(e) => self.generate_datetime_sub(e),
3515            Expression::DatetimeTrunc(e) => self.generate_datetime_trunc(e),
3516            Expression::Dayname(e) => self.generate_dayname(e),
3517            Expression::Declare(e) => self.generate_declare(e),
3518            Expression::DeclareItem(e) => self.generate_declare_item(e),
3519            Expression::DecodeCase(e) => self.generate_decode_case(e),
3520            Expression::DecompressBinary(e) => self.generate_decompress_binary(e),
3521            Expression::DecompressString(e) => self.generate_decompress_string(e),
3522            Expression::Decrypt(e) => self.generate_decrypt(e),
3523            Expression::DecryptRaw(e) => self.generate_decrypt_raw(e),
3524            Expression::DefinerProperty(e) => self.generate_definer_property(e),
3525            Expression::Detach(e) => self.generate_detach(e),
3526            Expression::DictProperty(e) => self.generate_dict_property(e),
3527            Expression::DictRange(e) => self.generate_dict_range(e),
3528            Expression::Directory(e) => self.generate_directory(e),
3529            Expression::DistKeyProperty(e) => self.generate_dist_key_property(e),
3530            Expression::DistStyleProperty(e) => self.generate_dist_style_property(e),
3531            Expression::DistributeBy(e) => self.generate_distribute_by(e),
3532            Expression::DistributedByProperty(e) => self.generate_distributed_by_property(e),
3533            Expression::DotProduct(e) => self.generate_dot_product(e),
3534            Expression::DropPartition(e) => self.generate_drop_partition(e),
3535            Expression::DuplicateKeyProperty(e) => self.generate_duplicate_key_property(e),
3536            Expression::Elt(e) => self.generate_elt(e),
3537            Expression::Encode(e) => self.generate_encode(e),
3538            Expression::EncodeProperty(e) => self.generate_encode_property(e),
3539            Expression::Encrypt(e) => self.generate_encrypt(e),
3540            Expression::EncryptRaw(e) => self.generate_encrypt_raw(e),
3541            Expression::EngineProperty(e) => self.generate_engine_property(e),
3542            Expression::EnviromentProperty(e) => self.generate_enviroment_property(e),
3543            Expression::EphemeralColumnConstraint(e) => {
3544                self.generate_ephemeral_column_constraint(e)
3545            }
3546            Expression::EqualNull(e) => self.generate_equal_null(e),
3547            Expression::EuclideanDistance(e) => self.generate_euclidean_distance(e),
3548            Expression::ExecuteAsProperty(e) => self.generate_execute_as_property(e),
3549            Expression::Export(e) => self.generate_export(e),
3550            Expression::ExternalProperty(e) => self.generate_external_property(e),
3551            Expression::FallbackProperty(e) => self.generate_fallback_property(e),
3552            Expression::FarmFingerprint(e) => self.generate_farm_fingerprint(e),
3553            Expression::FeaturesAtTime(e) => self.generate_features_at_time(e),
3554            Expression::Fetch(e) => self.generate_fetch(e),
3555            Expression::FileFormatProperty(e) => self.generate_file_format_property(e),
3556            Expression::Filter(e) => self.generate_filter(e),
3557            Expression::Float64(e) => self.generate_float64(e),
3558            Expression::ForIn(e) => self.generate_for_in(e),
3559            Expression::ForeignKey(e) => self.generate_foreign_key(e),
3560            Expression::Format(e) => self.generate_format(e),
3561            Expression::FormatPhrase(e) => self.generate_format_phrase(e),
3562            Expression::FreespaceProperty(e) => self.generate_freespace_property(e),
3563            Expression::From(e) => self.generate_from(e),
3564            Expression::FromBase(e) => self.generate_from_base(e),
3565            Expression::FromTimeZone(e) => self.generate_from_time_zone(e),
3566            Expression::GapFill(e) => self.generate_gap_fill(e),
3567            Expression::GenerateDateArray(e) => self.generate_generate_date_array(e),
3568            Expression::GenerateEmbedding(e) => self.generate_generate_embedding(e),
3569            Expression::GenerateSeries(e) => self.generate_generate_series(e),
3570            Expression::GenerateTimestampArray(e) => self.generate_generate_timestamp_array(e),
3571            Expression::GeneratedAsIdentityColumnConstraint(e) => {
3572                self.generate_generated_as_identity_column_constraint(e)
3573            }
3574            Expression::GeneratedAsRowColumnConstraint(e) => {
3575                self.generate_generated_as_row_column_constraint(e)
3576            }
3577            Expression::Get(e) => self.generate_get(e),
3578            Expression::GetExtract(e) => self.generate_get_extract(e),
3579            Expression::Getbit(e) => self.generate_getbit(e),
3580            Expression::GrantPrincipal(e) => self.generate_grant_principal(e),
3581            Expression::GrantPrivilege(e) => self.generate_grant_privilege(e),
3582            Expression::Group(e) => self.generate_group(e),
3583            Expression::GroupBy(e) => self.generate_group_by(e),
3584            Expression::Grouping(e) => self.generate_grouping(e),
3585            Expression::GroupingId(e) => self.generate_grouping_id(e),
3586            Expression::GroupingSets(e) => self.generate_grouping_sets(e),
3587            Expression::HashAgg(e) => self.generate_hash_agg(e),
3588            Expression::Having(e) => self.generate_having(e),
3589            Expression::HavingMax(e) => self.generate_having_max(e),
3590            Expression::Heredoc(e) => self.generate_heredoc(e),
3591            Expression::HexEncode(e) => self.generate_hex_encode(e),
3592            Expression::Hll(e) => self.generate_hll(e),
3593            Expression::InOutColumnConstraint(e) => self.generate_in_out_column_constraint(e),
3594            Expression::IncludeProperty(e) => self.generate_include_property(e),
3595            Expression::Index(e) => self.generate_index(e),
3596            Expression::IndexColumnConstraint(e) => self.generate_index_column_constraint(e),
3597            Expression::IndexConstraintOption(e) => self.generate_index_constraint_option(e),
3598            Expression::IndexParameters(e) => self.generate_index_parameters(e),
3599            Expression::IndexTableHint(e) => self.generate_index_table_hint(e),
3600            Expression::InheritsProperty(e) => self.generate_inherits_property(e),
3601            Expression::InputModelProperty(e) => self.generate_input_model_property(e),
3602            Expression::InputOutputFormat(e) => self.generate_input_output_format(e),
3603            Expression::Install(e) => self.generate_install(e),
3604            Expression::IntervalOp(e) => self.generate_interval_op(e),
3605            Expression::IntervalSpan(e) => self.generate_interval_span(e),
3606            Expression::IntoClause(e) => self.generate_into_clause(e),
3607            Expression::Introducer(e) => self.generate_introducer(e),
3608            Expression::IsolatedLoadingProperty(e) => self.generate_isolated_loading_property(e),
3609            Expression::JSON(e) => self.generate_json(e),
3610            Expression::JSONArray(e) => self.generate_json_array(e),
3611            Expression::JSONArrayAgg(e) => self.generate_json_array_agg_struct(e),
3612            Expression::JSONArrayAppend(e) => self.generate_json_array_append(e),
3613            Expression::JSONArrayContains(e) => self.generate_json_array_contains(e),
3614            Expression::JSONArrayInsert(e) => self.generate_json_array_insert(e),
3615            Expression::JSONBExists(e) => self.generate_jsonb_exists(e),
3616            Expression::JSONBExtractScalar(e) => self.generate_jsonb_extract_scalar(e),
3617            Expression::JSONBObjectAgg(e) => self.generate_jsonb_object_agg(e),
3618            Expression::JSONObjectAgg(e) => self.generate_json_object_agg_struct(e),
3619            Expression::JSONColumnDef(e) => self.generate_json_column_def(e),
3620            Expression::JSONExists(e) => self.generate_json_exists(e),
3621            Expression::JSONCast(e) => self.generate_json_cast(e),
3622            Expression::JSONExtract(e) => self.generate_json_extract_path(e),
3623            Expression::JSONExtractArray(e) => self.generate_json_extract_array(e),
3624            Expression::JSONExtractQuote(e) => self.generate_json_extract_quote(e),
3625            Expression::JSONExtractScalar(e) => self.generate_json_extract_scalar(e),
3626            Expression::JSONFormat(e) => self.generate_json_format(e),
3627            Expression::JSONKeyValue(e) => self.generate_json_key_value(e),
3628            Expression::JSONKeys(e) => self.generate_json_keys(e),
3629            Expression::JSONKeysAtDepth(e) => self.generate_json_keys_at_depth(e),
3630            Expression::JSONPath(e) => self.generate_json_path_expr(e),
3631            Expression::JSONPathFilter(e) => self.generate_json_path_filter(e),
3632            Expression::JSONPathKey(e) => self.generate_json_path_key(e),
3633            Expression::JSONPathRecursive(e) => self.generate_json_path_recursive(e),
3634            Expression::JSONPathRoot(_) => self.generate_json_path_root(),
3635            Expression::JSONPathScript(e) => self.generate_json_path_script(e),
3636            Expression::JSONPathSelector(e) => self.generate_json_path_selector(e),
3637            Expression::JSONPathSlice(e) => self.generate_json_path_slice(e),
3638            Expression::JSONPathSubscript(e) => self.generate_json_path_subscript(e),
3639            Expression::JSONPathUnion(e) => self.generate_json_path_union(e),
3640            Expression::JSONRemove(e) => self.generate_json_remove(e),
3641            Expression::JSONSchema(e) => self.generate_json_schema(e),
3642            Expression::JSONSet(e) => self.generate_json_set(e),
3643            Expression::JSONStripNulls(e) => self.generate_json_strip_nulls(e),
3644            Expression::JSONTable(e) => self.generate_json_table(e),
3645            Expression::JSONType(e) => self.generate_json_type(e),
3646            Expression::JSONValue(e) => self.generate_json_value(e),
3647            Expression::JSONValueArray(e) => self.generate_json_value_array(e),
3648            Expression::JarowinklerSimilarity(e) => self.generate_jarowinkler_similarity(e),
3649            Expression::JoinHint(e) => self.generate_join_hint(e),
3650            Expression::JournalProperty(e) => self.generate_journal_property(e),
3651            Expression::LanguageProperty(e) => self.generate_language_property(e),
3652            Expression::Lateral(e) => self.generate_lateral(e),
3653            Expression::LikeProperty(e) => self.generate_like_property(e),
3654            Expression::Limit(e) => self.generate_limit(e),
3655            Expression::LimitOptions(e) => self.generate_limit_options(e),
3656            Expression::List(e) => self.generate_list(e),
3657            Expression::ToMap(e) => self.generate_tomap(e),
3658            Expression::Localtime(e) => self.generate_localtime(e),
3659            Expression::Localtimestamp(e) => self.generate_localtimestamp(e),
3660            Expression::LocationProperty(e) => self.generate_location_property(e),
3661            Expression::Lock(e) => self.generate_lock(e),
3662            Expression::LockProperty(e) => self.generate_lock_property(e),
3663            Expression::LockingProperty(e) => self.generate_locking_property(e),
3664            Expression::LockingStatement(e) => self.generate_locking_statement(e),
3665            Expression::LogProperty(e) => self.generate_log_property(e),
3666            Expression::MD5Digest(e) => self.generate_md5_digest(e),
3667            Expression::MLForecast(e) => self.generate_ml_forecast(e),
3668            Expression::MLTranslate(e) => self.generate_ml_translate(e),
3669            Expression::MakeInterval(e) => self.generate_make_interval(e),
3670            Expression::ManhattanDistance(e) => self.generate_manhattan_distance(e),
3671            Expression::Map(e) => self.generate_map(e),
3672            Expression::MapCat(e) => self.generate_map_cat(e),
3673            Expression::MapDelete(e) => self.generate_map_delete(e),
3674            Expression::MapInsert(e) => self.generate_map_insert(e),
3675            Expression::MapPick(e) => self.generate_map_pick(e),
3676            Expression::MaskingPolicyColumnConstraint(e) => {
3677                self.generate_masking_policy_column_constraint(e)
3678            }
3679            Expression::MatchAgainst(e) => self.generate_match_against(e),
3680            Expression::MatchRecognizeMeasure(e) => self.generate_match_recognize_measure(e),
3681            Expression::MaterializedProperty(e) => self.generate_materialized_property(e),
3682            Expression::Merge(e) => self.generate_merge(e),
3683            Expression::MergeBlockRatioProperty(e) => self.generate_merge_block_ratio_property(e),
3684            Expression::MergeTreeTTL(e) => self.generate_merge_tree_ttl(e),
3685            Expression::MergeTreeTTLAction(e) => self.generate_merge_tree_ttl_action(e),
3686            Expression::Minhash(e) => self.generate_minhash(e),
3687            Expression::ModelAttribute(e) => self.generate_model_attribute(e),
3688            Expression::Monthname(e) => self.generate_monthname(e),
3689            Expression::MultitableInserts(e) => self.generate_multitable_inserts(e),
3690            Expression::NextValueFor(e) => self.generate_next_value_for(e),
3691            Expression::Normal(e) => self.generate_normal(e),
3692            Expression::Normalize(e) => self.generate_normalize(e),
3693            Expression::NotNullColumnConstraint(e) => self.generate_not_null_column_constraint(e),
3694            Expression::Nullif(e) => self.generate_nullif(e),
3695            Expression::NumberToStr(e) => self.generate_number_to_str(e),
3696            Expression::ObjectAgg(e) => self.generate_object_agg(e),
3697            Expression::ObjectIdentifier(e) => self.generate_object_identifier(e),
3698            Expression::ObjectInsert(e) => self.generate_object_insert(e),
3699            Expression::Offset(e) => self.generate_offset(e),
3700            Expression::Qualify(e) => self.generate_qualify(e),
3701            Expression::OnCluster(e) => self.generate_on_cluster(e),
3702            Expression::OnCommitProperty(e) => self.generate_on_commit_property(e),
3703            Expression::OnCondition(e) => self.generate_on_condition(e),
3704            Expression::OnConflict(e) => self.generate_on_conflict(e),
3705            Expression::OnProperty(e) => self.generate_on_property(e),
3706            Expression::Opclass(e) => self.generate_opclass(e),
3707            Expression::OpenJSON(e) => self.generate_open_json(e),
3708            Expression::OpenJSONColumnDef(e) => self.generate_open_json_column_def(e),
3709            Expression::Operator(e) => self.generate_operator(e),
3710            Expression::OrderBy(e) => self.generate_order_by(e),
3711            Expression::OutputModelProperty(e) => self.generate_output_model_property(e),
3712            Expression::OverflowTruncateBehavior(e) => self.generate_overflow_truncate_behavior(e),
3713            Expression::ParameterizedAgg(e) => self.generate_parameterized_agg(e),
3714            Expression::ParseDatetime(e) => self.generate_parse_datetime(e),
3715            Expression::ParseIp(e) => self.generate_parse_ip(e),
3716            Expression::ParseJSON(e) => self.generate_parse_json(e),
3717            Expression::ParseTime(e) => self.generate_parse_time(e),
3718            Expression::ParseUrl(e) => self.generate_parse_url(e),
3719            Expression::Partition(e) => self.generate_partition_expr(e),
3720            Expression::PartitionBoundSpec(e) => self.generate_partition_bound_spec(e),
3721            Expression::PartitionByListProperty(e) => self.generate_partition_by_list_property(e),
3722            Expression::PartitionByRangeProperty(e) => self.generate_partition_by_range_property(e),
3723            Expression::PartitionByRangePropertyDynamic(e) => {
3724                self.generate_partition_by_range_property_dynamic(e)
3725            }
3726            Expression::PartitionByTruncate(e) => self.generate_partition_by_truncate(e),
3727            Expression::PartitionList(e) => self.generate_partition_list(e),
3728            Expression::PartitionRange(e) => self.generate_partition_range(e),
3729            Expression::PartitionByProperty(e) => self.generate_partition_by_property(e),
3730            Expression::PartitionedByBucket(e) => self.generate_partitioned_by_bucket(e),
3731            Expression::PartitionedByProperty(e) => self.generate_partitioned_by_property(e),
3732            Expression::PartitionedOfProperty(e) => self.generate_partitioned_of_property(e),
3733            Expression::PeriodForSystemTimeConstraint(e) => {
3734                self.generate_period_for_system_time_constraint(e)
3735            }
3736            Expression::PivotAlias(e) => self.generate_pivot_alias(e),
3737            Expression::PivotAny(e) => self.generate_pivot_any(e),
3738            Expression::Predict(e) => self.generate_predict(e),
3739            Expression::PreviousDay(e) => self.generate_previous_day(e),
3740            Expression::PrimaryKey(e) => self.generate_primary_key(e),
3741            Expression::PrimaryKeyColumnConstraint(e) => {
3742                self.generate_primary_key_column_constraint(e)
3743            }
3744            Expression::PathColumnConstraint(e) => self.generate_path_column_constraint(e),
3745            Expression::ProjectionDef(e) => self.generate_projection_def(e),
3746            Expression::OptionsProperty(e) => self.generate_options_property(e),
3747            Expression::Properties(e) => self.generate_properties(e),
3748            Expression::Property(e) => self.generate_property(e),
3749            Expression::PseudoType(e) => self.generate_pseudo_type(e),
3750            Expression::Put(e) => self.generate_put(e),
3751            Expression::Quantile(e) => self.generate_quantile(e),
3752            Expression::QueryBand(e) => self.generate_query_band(e),
3753            Expression::QueryOption(e) => self.generate_query_option(e),
3754            Expression::QueryTransform(e) => self.generate_query_transform(e),
3755            Expression::Randn(e) => self.generate_randn(e),
3756            Expression::Randstr(e) => self.generate_randstr(e),
3757            Expression::RangeBucket(e) => self.generate_range_bucket(e),
3758            Expression::RangeN(e) => self.generate_range_n(e),
3759            Expression::ReadCSV(e) => self.generate_read_csv(e),
3760            Expression::ReadParquet(e) => self.generate_read_parquet(e),
3761            Expression::RecursiveWithSearch(e) => self.generate_recursive_with_search(e),
3762            Expression::Reduce(e) => self.generate_reduce(e),
3763            Expression::Reference(e) => self.generate_reference(e),
3764            Expression::Refresh(e) => self.generate_refresh(e),
3765            Expression::RefreshTriggerProperty(e) => self.generate_refresh_trigger_property(e),
3766            Expression::RegexpCount(e) => self.generate_regexp_count(e),
3767            Expression::RegexpExtractAll(e) => self.generate_regexp_extract_all(e),
3768            Expression::RegexpFullMatch(e) => self.generate_regexp_full_match(e),
3769            Expression::RegexpILike(e) => self.generate_regexp_i_like(e),
3770            Expression::RegexpInstr(e) => self.generate_regexp_instr(e),
3771            Expression::RegexpSplit(e) => self.generate_regexp_split(e),
3772            Expression::RegrAvgx(e) => self.generate_regr_avgx(e),
3773            Expression::RegrAvgy(e) => self.generate_regr_avgy(e),
3774            Expression::RegrCount(e) => self.generate_regr_count(e),
3775            Expression::RegrIntercept(e) => self.generate_regr_intercept(e),
3776            Expression::RegrR2(e) => self.generate_regr_r2(e),
3777            Expression::RegrSlope(e) => self.generate_regr_slope(e),
3778            Expression::RegrSxx(e) => self.generate_regr_sxx(e),
3779            Expression::RegrSxy(e) => self.generate_regr_sxy(e),
3780            Expression::RegrSyy(e) => self.generate_regr_syy(e),
3781            Expression::RegrValx(e) => self.generate_regr_valx(e),
3782            Expression::RegrValy(e) => self.generate_regr_valy(e),
3783            Expression::RemoteWithConnectionModelProperty(e) => {
3784                self.generate_remote_with_connection_model_property(e)
3785            }
3786            Expression::RenameColumn(e) => self.generate_rename_column(e),
3787            Expression::ReplacePartition(e) => self.generate_replace_partition(e),
3788            Expression::Returning(e) => self.generate_returning(e),
3789            Expression::ReturnsProperty(e) => self.generate_returns_property(e),
3790            Expression::Rollback(e) => self.generate_rollback(e),
3791            Expression::Rollup(e) => self.generate_rollup(e),
3792            Expression::RowFormatDelimitedProperty(e) => {
3793                self.generate_row_format_delimited_property(e)
3794            }
3795            Expression::RowFormatProperty(e) => self.generate_row_format_property(e),
3796            Expression::RowFormatSerdeProperty(e) => self.generate_row_format_serde_property(e),
3797            Expression::SHA2(e) => self.generate_sha2(e),
3798            Expression::SHA2Digest(e) => self.generate_sha2_digest(e),
3799            Expression::SafeAdd(e) => self.generate_safe_add(e),
3800            Expression::SafeDivide(e) => self.generate_safe_divide(e),
3801            Expression::SafeMultiply(e) => self.generate_safe_multiply(e),
3802            Expression::SafeSubtract(e) => self.generate_safe_subtract(e),
3803            Expression::SampleProperty(e) => self.generate_sample_property(e),
3804            Expression::Schema(e) => self.generate_schema(e),
3805            Expression::SchemaCommentProperty(e) => self.generate_schema_comment_property(e),
3806            Expression::ScopeResolution(e) => self.generate_scope_resolution(e),
3807            Expression::Search(e) => self.generate_search(e),
3808            Expression::SearchIp(e) => self.generate_search_ip(e),
3809            Expression::SecurityProperty(e) => self.generate_security_property(e),
3810            Expression::SemanticView(e) => self.generate_semantic_view(e),
3811            Expression::SequenceProperties(e) => self.generate_sequence_properties(e),
3812            Expression::SerdeProperties(e) => self.generate_serde_properties(e),
3813            Expression::SessionParameter(e) => self.generate_session_parameter(e),
3814            Expression::Set(e) => self.generate_set(e),
3815            Expression::SetConfigProperty(e) => self.generate_set_config_property(e),
3816            Expression::SetItem(e) => self.generate_set_item(e),
3817            Expression::SetOperation(e) => self.generate_set_operation(e),
3818            Expression::SetProperty(e) => self.generate_set_property(e),
3819            Expression::SettingsProperty(e) => self.generate_settings_property(e),
3820            Expression::SharingProperty(e) => self.generate_sharing_property(e),
3821            Expression::Slice(e) => self.generate_slice(e),
3822            Expression::SortArray(e) => self.generate_sort_array(e),
3823            Expression::SortBy(e) => self.generate_sort_by(e),
3824            Expression::SortKeyProperty(e) => self.generate_sort_key_property(e),
3825            Expression::SplitPart(e) => self.generate_split_part(e),
3826            Expression::SqlReadWriteProperty(e) => self.generate_sql_read_write_property(e),
3827            Expression::SqlSecurityProperty(e) => self.generate_sql_security_property(e),
3828            Expression::StDistance(e) => self.generate_st_distance(e),
3829            Expression::StPoint(e) => self.generate_st_point(e),
3830            Expression::StabilityProperty(e) => self.generate_stability_property(e),
3831            Expression::StandardHash(e) => self.generate_standard_hash(e),
3832            Expression::StorageHandlerProperty(e) => self.generate_storage_handler_property(e),
3833            Expression::StrPosition(e) => self.generate_str_position(e),
3834            Expression::StrToDate(e) => self.generate_str_to_date(e),
3835            Expression::DateStrToDate(f) => self.generate_simple_func("DATE_STR_TO_DATE", &f.this),
3836            Expression::DateToDateStr(f) => self.generate_simple_func("DATE_TO_DATE_STR", &f.this),
3837            Expression::StrToMap(e) => self.generate_str_to_map(e),
3838            Expression::StrToTime(e) => self.generate_str_to_time(e),
3839            Expression::StrToUnix(e) => self.generate_str_to_unix(e),
3840            Expression::StringToArray(e) => self.generate_string_to_array(e),
3841            Expression::Struct(e) => self.generate_struct(e),
3842            Expression::Stuff(e) => self.generate_stuff(e),
3843            Expression::SubstringIndex(e) => self.generate_substring_index(e),
3844            Expression::Summarize(e) => self.generate_summarize(e),
3845            Expression::Systimestamp(e) => self.generate_systimestamp(e),
3846            Expression::TableAlias(e) => self.generate_table_alias(e),
3847            Expression::TableFromRows(e) => self.generate_table_from_rows(e),
3848            Expression::RowsFrom(e) => self.generate_rows_from(e),
3849            Expression::TableSample(e) => self.generate_table_sample(e),
3850            Expression::Tag(e) => self.generate_tag(e),
3851            Expression::Tags(e) => self.generate_tags(e),
3852            Expression::TemporaryProperty(e) => self.generate_temporary_property(e),
3853            Expression::Time(e) => self.generate_time_func(e),
3854            Expression::TimeAdd(e) => self.generate_time_add(e),
3855            Expression::TimeDiff(e) => self.generate_time_diff(e),
3856            Expression::TimeFromParts(e) => self.generate_time_from_parts(e),
3857            Expression::TimeSlice(e) => self.generate_time_slice(e),
3858            Expression::TimeStrToDate(e) => self.generate_time_str_to_date(e),
3859            Expression::TimeStrToTime(e) => self.generate_time_str_to_time(e),
3860            Expression::TimeSub(e) => self.generate_time_sub(e),
3861            Expression::TimeToStr(e) => self.generate_time_to_str(e),
3862            Expression::TimeToUnix(e) => self.generate_time_to_unix(e),
3863            Expression::TimeTrunc(e) => self.generate_time_trunc(e),
3864            Expression::TimeUnit(e) => self.generate_time_unit(e),
3865            Expression::Timestamp(e) => self.generate_timestamp_func(e),
3866            Expression::TimestampAdd(e) => self.generate_timestamp_add(e),
3867            Expression::TimestampDiff(e) => self.generate_timestamp_diff(e),
3868            Expression::TimestampFromParts(e) => self.generate_timestamp_from_parts(e),
3869            Expression::TimestampSub(e) => self.generate_timestamp_sub(e),
3870            Expression::TimestampTzFromParts(e) => self.generate_timestamp_tz_from_parts(e),
3871            Expression::ToBinary(e) => self.generate_to_binary(e),
3872            Expression::ToBoolean(e) => self.generate_to_boolean(e),
3873            Expression::ToChar(e) => self.generate_to_char(e),
3874            Expression::ToDecfloat(e) => self.generate_to_decfloat(e),
3875            Expression::ToDouble(e) => self.generate_to_double(e),
3876            Expression::ToFile(e) => self.generate_to_file(e),
3877            Expression::ToNumber(e) => self.generate_to_number(e),
3878            Expression::ToTableProperty(e) => self.generate_to_table_property(e),
3879            Expression::Transaction(e) => self.generate_transaction(e),
3880            Expression::Transform(e) => self.generate_transform(e),
3881            Expression::TransformModelProperty(e) => self.generate_transform_model_property(e),
3882            Expression::TransientProperty(e) => self.generate_transient_property(e),
3883            Expression::Translate(e) => self.generate_translate(e),
3884            Expression::TranslateCharacters(e) => self.generate_translate_characters(e),
3885            Expression::TruncateTable(e) => self.generate_truncate_table(e),
3886            Expression::TryBase64DecodeBinary(e) => self.generate_try_base64_decode_binary(e),
3887            Expression::TryBase64DecodeString(e) => self.generate_try_base64_decode_string(e),
3888            Expression::TryToDecfloat(e) => self.generate_try_to_decfloat(e),
3889            Expression::TsOrDsAdd(e) => self.generate_ts_or_ds_add(e),
3890            Expression::TsOrDsDiff(e) => self.generate_ts_or_ds_diff(e),
3891            Expression::TsOrDsToDate(e) => self.generate_ts_or_ds_to_date(e),
3892            Expression::TsOrDsToTime(e) => self.generate_ts_or_ds_to_time(e),
3893            Expression::Unhex(e) => self.generate_unhex(e),
3894            Expression::UnicodeString(e) => self.generate_unicode_string(e),
3895            Expression::Uniform(e) => self.generate_uniform(e),
3896            Expression::UniqueColumnConstraint(e) => self.generate_unique_column_constraint(e),
3897            Expression::UniqueKeyProperty(e) => self.generate_unique_key_property(e),
3898            Expression::RollupProperty(e) => self.generate_rollup_property(e),
3899            Expression::UnixToStr(e) => self.generate_unix_to_str(e),
3900            Expression::UnixToTime(e) => self.generate_unix_to_time(e),
3901            Expression::UnpivotColumns(e) => self.generate_unpivot_columns(e),
3902            Expression::UserDefinedFunction(e) => self.generate_user_defined_function(e),
3903            Expression::UsingTemplateProperty(e) => self.generate_using_template_property(e),
3904            Expression::UtcTime(e) => self.generate_utc_time(e),
3905            Expression::UtcTimestamp(e) => self.generate_utc_timestamp(e),
3906            Expression::Uuid(e) => self.generate_uuid(e),
3907            Expression::Var(v) => {
3908                if matches!(self.config.dialect, Some(DialectType::MySQL))
3909                    && v.this.len() > 2
3910                    && (v.this.starts_with("0x") || v.this.starts_with("0X"))
3911                    && !v.this[2..].chars().all(|c| c.is_ascii_hexdigit())
3912                {
3913                    return self.generate_identifier(&Identifier {
3914                        name: v.this.clone(),
3915                        quoted: true,
3916                        trailing_comments: Vec::new(),
3917                        span: None,
3918                    });
3919                }
3920                self.write(&v.this);
3921                Ok(())
3922            }
3923            Expression::Variadic(e) => {
3924                self.write_keyword("VARIADIC");
3925                self.write_space();
3926                self.generate_expression(&e.this)?;
3927                Ok(())
3928            }
3929            Expression::VarMap(e) => self.generate_var_map(e),
3930            Expression::VectorSearch(e) => self.generate_vector_search(e),
3931            Expression::Version(e) => self.generate_version(e),
3932            Expression::ViewAttributeProperty(e) => self.generate_view_attribute_property(e),
3933            Expression::VolatileProperty(e) => self.generate_volatile_property(e),
3934            Expression::WatermarkColumnConstraint(e) => {
3935                self.generate_watermark_column_constraint(e)
3936            }
3937            Expression::Week(e) => self.generate_week(e),
3938            Expression::When(e) => self.generate_when(e),
3939            Expression::Whens(e) => self.generate_whens(e),
3940            Expression::Where(e) => self.generate_where(e),
3941            Expression::WidthBucket(e) => self.generate_width_bucket(e),
3942            Expression::Window(e) => self.generate_window(e),
3943            Expression::WindowSpec(e) => self.generate_window_spec(e),
3944            Expression::WithDataProperty(e) => self.generate_with_data_property(e),
3945            Expression::WithFill(e) => self.generate_with_fill(e),
3946            Expression::WithJournalTableProperty(e) => self.generate_with_journal_table_property(e),
3947            Expression::WithOperator(e) => self.generate_with_operator(e),
3948            Expression::WithProcedureOptions(e) => self.generate_with_procedure_options(e),
3949            Expression::WithSchemaBindingProperty(e) => {
3950                self.generate_with_schema_binding_property(e)
3951            }
3952            Expression::WithSystemVersioningProperty(e) => {
3953                self.generate_with_system_versioning_property(e)
3954            }
3955            Expression::WithTableHint(e) => self.generate_with_table_hint(e),
3956            Expression::XMLElement(e) => self.generate_xml_element(e),
3957            Expression::XMLGet(e) => self.generate_xml_get(e),
3958            Expression::XMLKeyValueOption(e) => self.generate_xml_key_value_option(e),
3959            Expression::XMLTable(e) => self.generate_xml_table(e),
3960            Expression::Xor(e) => self.generate_xor(e),
3961            Expression::Zipf(e) => self.generate_zipf(e),
3962            _ => self.write_unsupported_comment("unsupported expression"),
3963        }
3964    }
3965
3966    fn generate_select(&mut self, select: &Select) -> Result<()> {
3967        use crate::dialects::DialectType;
3968
3969        // Redshift-style EXCLUDE: for dialects other than Redshift, wrap in a derived table
3970        // e.g., SELECT *, col4 EXCLUDE (col2, col3) FROM t
3971        //   → SELECT * EXCLUDE (col2, col3) FROM (SELECT *, col4 FROM t)
3972        if let Some(exclude) = &select.exclude {
3973            if !exclude.is_empty()
3974                && !matches!(self.config.dialect, Some(DialectType::Redshift))
3975            {
3976                // Build the inner select (same as original but without exclude)
3977                let mut inner_select = select.clone();
3978                inner_select.exclude = None;
3979                let inner_expr = Expression::Select(Box::new(inner_select));
3980
3981                // Build the subquery
3982                let subquery = crate::expressions::Subquery {
3983                    this: inner_expr,
3984                    alias: None,
3985                    column_aliases: Vec::new(),
3986                    order_by: None,
3987                    limit: None,
3988                    offset: None,
3989                    distribute_by: None,
3990                    sort_by: None,
3991                    cluster_by: None,
3992                    lateral: false,
3993                    modifiers_inside: false,
3994                    trailing_comments: Vec::new(),
3995                    inferred_type: None,
3996                };
3997
3998                // Build the outer select: SELECT * EXCLUDE (cols) FROM (inner)
3999                let star = Expression::Star(crate::expressions::Star {
4000                    table: None,
4001                    except: Some(
4002                        exclude.iter().map(|e| {
4003                            match e {
4004                                Expression::Column(col) => col.name.clone(),
4005                                Expression::Identifier(id) => id.clone(),
4006                                _ => crate::expressions::Identifier::new("unknown".to_string()),
4007                            }
4008                        }).collect()
4009                    ),
4010                    replace: None,
4011                    rename: None,
4012                    trailing_comments: Vec::new(),
4013                    span: None,
4014                });
4015
4016                let outer_select = Select {
4017                    expressions: vec![star],
4018                    from: Some(crate::expressions::From {
4019                        expressions: vec![Expression::Subquery(Box::new(subquery))],
4020                    }),
4021                    ..Select::new()
4022                };
4023
4024                return self.generate_select(&outer_select);
4025            }
4026        }
4027
4028        // Output leading comments before SELECT
4029        for comment in &select.leading_comments {
4030            self.write_formatted_comment(comment);
4031            self.write(" ");
4032        }
4033
4034        // WITH clause
4035        if let Some(with) = &select.with {
4036            self.generate_with(with)?;
4037            if self.config.pretty {
4038                self.write_newline();
4039                self.write_indent();
4040            } else {
4041                self.write_space();
4042            }
4043        }
4044
4045        // Output post-SELECT comments (comments that appeared after SELECT keyword)
4046        // These are output BEFORE SELECT, as Python SQLGlot normalizes them this way
4047        for comment in &select.post_select_comments {
4048            self.write_formatted_comment(comment);
4049            self.write(" ");
4050        }
4051
4052        self.write_keyword("SELECT");
4053
4054        // Generate query hint if present /*+ ... */
4055        if let Some(hint) = &select.hint {
4056            self.generate_hint(hint)?;
4057        }
4058
4059        // For SQL Server, convert LIMIT to TOP (structural transformation)
4060        // But only when there's no OFFSET (otherwise use OFFSET/FETCH syntax)
4061        // TOP clause (SQL Server style - before DISTINCT)
4062        let use_top_from_limit = matches!(self.config.dialect, Some(DialectType::TSQL))
4063            && select.top.is_none()
4064            && select.limit.is_some()
4065            && select.offset.is_none(); // Don't use TOP when there's OFFSET
4066
4067        // For TOP-supporting dialects: DISTINCT before TOP
4068        // For non-TOP dialects: TOP is converted to LIMIT later; DISTINCT goes here
4069        let is_top_dialect = matches!(
4070            self.config.dialect,
4071            Some(DialectType::TSQL) | Some(DialectType::Teradata) | Some(DialectType::Fabric)
4072        );
4073        let keep_top_verbatim = !is_top_dialect
4074            && select.limit.is_none()
4075            && select
4076                .top
4077                .as_ref()
4078                .map_or(false, |top| top.percent || top.with_ties);
4079
4080        if select.distinct && (is_top_dialect || select.top.is_some()) {
4081            self.write_space();
4082            self.write_keyword("DISTINCT");
4083        }
4084
4085        if is_top_dialect || keep_top_verbatim {
4086            if let Some(top) = &select.top {
4087                self.write_space();
4088                self.write_keyword("TOP");
4089                if top.parenthesized {
4090                    self.write(" (");
4091                    self.generate_expression(&top.this)?;
4092                    self.write(")");
4093                } else {
4094                    self.write_space();
4095                    self.generate_expression(&top.this)?;
4096                }
4097                if top.percent {
4098                    self.write_space();
4099                    self.write_keyword("PERCENT");
4100                }
4101                if top.with_ties {
4102                    self.write_space();
4103                    self.write_keyword("WITH TIES");
4104                }
4105            } else if use_top_from_limit {
4106                // Convert LIMIT to TOP for SQL Server (only when no OFFSET)
4107                if let Some(limit) = &select.limit {
4108                    self.write_space();
4109                    self.write_keyword("TOP");
4110                    // Use parentheses for complex expressions, but not for simple literals
4111                    let is_simple_literal =
4112                        matches!(&limit.this, Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(_)));
4113                    if is_simple_literal {
4114                        self.write_space();
4115                        self.generate_expression(&limit.this)?;
4116                    } else {
4117                        self.write(" (");
4118                        self.generate_expression(&limit.this)?;
4119                        self.write(")");
4120                    }
4121                }
4122            }
4123        }
4124
4125        if select.distinct && !is_top_dialect && select.top.is_none() {
4126            self.write_space();
4127            self.write_keyword("DISTINCT");
4128        }
4129
4130        // DISTINCT ON clause (PostgreSQL)
4131        if let Some(distinct_on) = &select.distinct_on {
4132            self.write_space();
4133            self.write_keyword("ON");
4134            self.write(" (");
4135            for (i, expr) in distinct_on.iter().enumerate() {
4136                if i > 0 {
4137                    self.write(", ");
4138                }
4139                self.generate_expression(expr)?;
4140            }
4141            self.write(")");
4142        }
4143
4144        // MySQL operation modifiers (HIGH_PRIORITY, STRAIGHT_JOIN, SQL_CALC_FOUND_ROWS, etc.)
4145        for modifier in &select.operation_modifiers {
4146            self.write_space();
4147            self.write_keyword(modifier);
4148        }
4149
4150        // BigQuery SELECT AS STRUCT / SELECT AS VALUE
4151        if let Some(kind) = &select.kind {
4152            self.write_space();
4153            self.write_keyword("AS");
4154            self.write_space();
4155            self.write_keyword(kind);
4156        }
4157
4158        // Expressions (only if there are any)
4159        if !select.expressions.is_empty() {
4160            if self.config.pretty {
4161                self.write_newline();
4162                self.indent_level += 1;
4163            } else {
4164                self.write_space();
4165            }
4166        }
4167
4168        for (i, expr) in select.expressions.iter().enumerate() {
4169            if i > 0 {
4170                self.write(",");
4171                if self.config.pretty {
4172                    self.write_newline();
4173                } else {
4174                    self.write_space();
4175                }
4176            }
4177            if self.config.pretty {
4178                self.write_indent();
4179            }
4180            self.generate_expression(expr)?;
4181        }
4182
4183        if self.config.pretty && !select.expressions.is_empty() {
4184            self.indent_level -= 1;
4185        }
4186
4187        // Redshift-style EXCLUDE clause at the end of the projection list
4188        // For Redshift dialect: append EXCLUDE (col1, col2) after the expressions
4189        // For other dialects (DuckDB, Snowflake): this is handled by wrapping in a derived table
4190        // (done after the full select is generated below)
4191        if let Some(exclude) = &select.exclude {
4192            if !exclude.is_empty()
4193                && matches!(
4194                    self.config.dialect,
4195                    Some(DialectType::Redshift)
4196                )
4197            {
4198                self.write_space();
4199                self.write_keyword("EXCLUDE");
4200                self.write(" (");
4201                for (i, col) in exclude.iter().enumerate() {
4202                    if i > 0 {
4203                        self.write(", ");
4204                    }
4205                    self.generate_expression(col)?;
4206                }
4207                self.write(")");
4208            }
4209        }
4210
4211        // INTO clause (SELECT ... INTO table_name)
4212        // Also handles Oracle PL/SQL: BULK COLLECT INTO v1, v2, ...
4213        if let Some(into) = &select.into {
4214            if self.config.pretty {
4215                self.write_newline();
4216                self.write_indent();
4217            } else {
4218                self.write_space();
4219            }
4220            if into.bulk_collect {
4221                self.write_keyword("BULK COLLECT INTO");
4222            } else {
4223                self.write_keyword("INTO");
4224            }
4225            if into.temporary {
4226                self.write_space();
4227                self.write_keyword("TEMPORARY");
4228            }
4229            if into.unlogged {
4230                self.write_space();
4231                self.write_keyword("UNLOGGED");
4232            }
4233            self.write_space();
4234            // If we have multiple expressions, output them comma-separated
4235            if !into.expressions.is_empty() {
4236                for (i, expr) in into.expressions.iter().enumerate() {
4237                    if i > 0 {
4238                        self.write(", ");
4239                    }
4240                    self.generate_expression(expr)?;
4241                }
4242            } else {
4243                self.generate_expression(&into.this)?;
4244            }
4245        }
4246
4247        // FROM clause
4248        if let Some(from) = &select.from {
4249            if self.config.pretty {
4250                self.write_newline();
4251                self.write_indent();
4252            } else {
4253                self.write_space();
4254            }
4255            self.write_keyword("FROM");
4256            self.write_space();
4257
4258            // BigQuery, Hive, Spark, Databricks, SQLite, and ClickHouse prefer explicit CROSS JOIN over comma syntax for multiple tables
4259            // But keep commas when TABLESAMPLE is present (Spark/Hive handle TABLESAMPLE differently with commas)
4260            // Also keep commas when the source dialect is Generic/None and target is one of these dialects
4261            // (Python sqlglot: the Hive/Spark parser marks comma joins as CROSS, but Generic parser keeps them implicit)
4262            let has_tablesample = from
4263                .expressions
4264                .iter()
4265                .any(|e| matches!(e, Expression::TableSample(_)));
4266            let is_cross_join_dialect = matches!(
4267                self.config.dialect,
4268                Some(DialectType::BigQuery)
4269                    | Some(DialectType::Hive)
4270                    | Some(DialectType::Spark)
4271                    | Some(DialectType::Databricks)
4272                    | Some(DialectType::SQLite)
4273                    | Some(DialectType::ClickHouse)
4274            );
4275            // Skip CROSS JOIN conversion when source is Generic/None and target is a CROSS JOIN dialect
4276            // This matches Python sqlglot where comma-to-CROSS-JOIN is done in the dialect's parser, not generator
4277            let source_is_same_as_target = self.config.source_dialect.is_some()
4278                && self.config.source_dialect == self.config.dialect;
4279            let source_is_cross_join_dialect = matches!(
4280                self.config.source_dialect,
4281                Some(DialectType::BigQuery)
4282                    | Some(DialectType::Hive)
4283                    | Some(DialectType::Spark)
4284                    | Some(DialectType::Databricks)
4285                    | Some(DialectType::SQLite)
4286                    | Some(DialectType::ClickHouse)
4287            );
4288            let use_cross_join = !has_tablesample
4289                && is_cross_join_dialect
4290                && (source_is_same_as_target
4291                    || source_is_cross_join_dialect
4292                    || self.config.source_dialect.is_none());
4293
4294            // Snowflake wraps standalone VALUES in FROM clause with parentheses
4295            let wrap_values_in_parens = matches!(self.config.dialect, Some(DialectType::Snowflake));
4296
4297            for (i, expr) in from.expressions.iter().enumerate() {
4298                if i > 0 {
4299                    if use_cross_join {
4300                        self.write(" CROSS JOIN ");
4301                    } else {
4302                        self.write(", ");
4303                    }
4304                }
4305                if wrap_values_in_parens && matches!(expr, Expression::Values(_)) {
4306                    self.write("(");
4307                    self.generate_expression(expr)?;
4308                    self.write(")");
4309                } else {
4310                    self.generate_expression(expr)?;
4311                }
4312            }
4313        }
4314
4315        // JOINs - handle nested join structure for pretty printing
4316        // Deferred-condition joins "own" the non-deferred joins that follow them
4317        // until the next deferred join or end of list
4318        if self.config.pretty {
4319            self.generate_joins_with_nesting(&select.joins)?;
4320        } else {
4321            for join in &select.joins {
4322                self.generate_join(join)?;
4323            }
4324            // Output deferred ON/USING conditions (right-to-left, which is reverse order)
4325            for join in select.joins.iter().rev() {
4326                if join.deferred_condition {
4327                    self.generate_join_condition(join)?;
4328                }
4329            }
4330        }
4331
4332        // LATERAL VIEW clauses (Hive/Spark)
4333        for (lv_idx, lateral_view) in select.lateral_views.iter().enumerate() {
4334            self.generate_lateral_view(lateral_view, lv_idx)?;
4335        }
4336
4337        // PREWHERE (ClickHouse)
4338        if let Some(prewhere) = &select.prewhere {
4339            self.write_clause_condition("PREWHERE", prewhere)?;
4340        }
4341
4342        // WHERE
4343        if let Some(where_clause) = &select.where_clause {
4344            self.write_clause_condition("WHERE", &where_clause.this)?;
4345        }
4346
4347        // CONNECT BY (Oracle hierarchical queries)
4348        if let Some(connect) = &select.connect {
4349            self.generate_connect(connect)?;
4350        }
4351
4352        // GROUP BY
4353        if let Some(group_by) = &select.group_by {
4354            if self.config.pretty {
4355                // Output leading comments on their own lines before GROUP BY
4356                for comment in &group_by.comments {
4357                    self.write_newline();
4358                    self.write_indent();
4359                    self.write_formatted_comment(comment);
4360                }
4361                self.write_newline();
4362                self.write_indent();
4363            } else {
4364                self.write_space();
4365                // In non-pretty mode, output comments inline
4366                for comment in &group_by.comments {
4367                    self.write_formatted_comment(comment);
4368                    self.write_space();
4369                }
4370            }
4371            self.write_keyword("GROUP BY");
4372            // Handle ALL/DISTINCT modifier: Some(true) = ALL, Some(false) = DISTINCT
4373            match group_by.all {
4374                Some(true) => {
4375                    self.write_space();
4376                    self.write_keyword("ALL");
4377                }
4378                Some(false) => {
4379                    self.write_space();
4380                    self.write_keyword("DISTINCT");
4381                }
4382                None => {}
4383            }
4384            if !group_by.expressions.is_empty() {
4385                // Check for trailing WITH CUBE or WITH ROLLUP (Hive/MySQL syntax)
4386                // These are represented as Cube/Rollup expressions with empty expressions at the end
4387                let mut trailing_cube = false;
4388                let mut trailing_rollup = false;
4389                let mut plain_expressions: Vec<&Expression> = Vec::new();
4390                let mut grouping_sets_expressions: Vec<&Expression> = Vec::new();
4391                let mut cube_expressions: Vec<&Expression> = Vec::new();
4392                let mut rollup_expressions: Vec<&Expression> = Vec::new();
4393
4394                for expr in &group_by.expressions {
4395                    match expr {
4396                        Expression::Cube(c) if c.expressions.is_empty() => {
4397                            trailing_cube = true;
4398                        }
4399                        Expression::Rollup(r) if r.expressions.is_empty() => {
4400                            trailing_rollup = true;
4401                        }
4402                        Expression::Function(f) if f.name == "CUBE" => {
4403                            cube_expressions.push(expr);
4404                        }
4405                        Expression::Function(f) if f.name == "ROLLUP" => {
4406                            rollup_expressions.push(expr);
4407                        }
4408                        Expression::Function(f) if f.name == "GROUPING SETS" => {
4409                            grouping_sets_expressions.push(expr);
4410                        }
4411                        _ => {
4412                            plain_expressions.push(expr);
4413                        }
4414                    }
4415                }
4416
4417                // Reorder: plain expressions first, then GROUPING SETS, CUBE, ROLLUP
4418                let mut regular_expressions: Vec<&Expression> = Vec::new();
4419                regular_expressions.extend(plain_expressions);
4420                regular_expressions.extend(grouping_sets_expressions);
4421                regular_expressions.extend(cube_expressions);
4422                regular_expressions.extend(rollup_expressions);
4423
4424                if self.config.pretty {
4425                    self.write_newline();
4426                    self.indent_level += 1;
4427                    self.write_indent();
4428                } else {
4429                    self.write_space();
4430                }
4431
4432                for (i, expr) in regular_expressions.iter().enumerate() {
4433                    if i > 0 {
4434                        if self.config.pretty {
4435                            self.write(",");
4436                            self.write_newline();
4437                            self.write_indent();
4438                        } else {
4439                            self.write(", ");
4440                        }
4441                    }
4442                    self.generate_expression(expr)?;
4443                }
4444
4445                if self.config.pretty {
4446                    self.indent_level -= 1;
4447                }
4448
4449                // Output trailing WITH CUBE or WITH ROLLUP
4450                if trailing_cube {
4451                    self.write_space();
4452                    self.write_keyword("WITH CUBE");
4453                } else if trailing_rollup {
4454                    self.write_space();
4455                    self.write_keyword("WITH ROLLUP");
4456                }
4457            }
4458
4459            // ClickHouse: WITH TOTALS
4460            if group_by.totals {
4461                self.write_space();
4462                self.write_keyword("WITH TOTALS");
4463            }
4464        }
4465
4466        // HAVING
4467        if let Some(having) = &select.having {
4468            if self.config.pretty {
4469                // Output leading comments on their own lines before HAVING
4470                for comment in &having.comments {
4471                    self.write_newline();
4472                    self.write_indent();
4473                    self.write_formatted_comment(comment);
4474                }
4475            } else {
4476                for comment in &having.comments {
4477                    self.write_space();
4478                    self.write_formatted_comment(comment);
4479                }
4480            }
4481            self.write_clause_condition("HAVING", &having.this)?;
4482        }
4483
4484        // QUALIFY and WINDOW clause ordering depends on input SQL
4485        if select.qualify_after_window {
4486            // WINDOW before QUALIFY (DuckDB style)
4487            if let Some(windows) = &select.windows {
4488                self.write_window_clause(windows)?;
4489            }
4490            if let Some(qualify) = &select.qualify {
4491                self.write_clause_condition("QUALIFY", &qualify.this)?;
4492            }
4493        } else {
4494            // QUALIFY before WINDOW (Snowflake/BigQuery default)
4495            if let Some(qualify) = &select.qualify {
4496                self.write_clause_condition("QUALIFY", &qualify.this)?;
4497            }
4498            if let Some(windows) = &select.windows {
4499                self.write_window_clause(windows)?;
4500            }
4501        }
4502
4503        // DISTRIBUTE BY (Hive/Spark)
4504        if let Some(distribute_by) = &select.distribute_by {
4505            self.write_clause_expressions("DISTRIBUTE BY", &distribute_by.expressions)?;
4506        }
4507
4508        // CLUSTER BY (Hive/Spark)
4509        if let Some(cluster_by) = &select.cluster_by {
4510            self.write_order_clause("CLUSTER BY", &cluster_by.expressions)?;
4511        }
4512
4513        // SORT BY (Hive/Spark - comes before ORDER BY)
4514        if let Some(sort_by) = &select.sort_by {
4515            self.write_order_clause("SORT BY", &sort_by.expressions)?;
4516        }
4517
4518        // ORDER BY (or ORDER SIBLINGS BY for Oracle hierarchical queries)
4519        if let Some(order_by) = &select.order_by {
4520            if self.config.pretty {
4521                // Output leading comments on their own lines before ORDER BY
4522                for comment in &order_by.comments {
4523                    self.write_newline();
4524                    self.write_indent();
4525                    self.write_formatted_comment(comment);
4526                }
4527            } else {
4528                for comment in &order_by.comments {
4529                    self.write_space();
4530                    self.write_formatted_comment(comment);
4531                }
4532            }
4533            let keyword = if order_by.siblings {
4534                "ORDER SIBLINGS BY"
4535            } else {
4536                "ORDER BY"
4537            };
4538            self.write_order_clause(keyword, &order_by.expressions)?;
4539        }
4540
4541        // TSQL: FETCH requires ORDER BY. If there's a FETCH but no ORDER BY, add ORDER BY (SELECT NULL) OFFSET 0 ROWS
4542        if select.order_by.is_none()
4543            && select.fetch.is_some()
4544            && matches!(
4545                self.config.dialect,
4546                Some(DialectType::TSQL) | Some(DialectType::Fabric)
4547            )
4548        {
4549            if self.config.pretty {
4550                self.write_newline();
4551                self.write_indent();
4552            } else {
4553                self.write_space();
4554            }
4555            self.write_keyword("ORDER BY (SELECT NULL) OFFSET 0 ROWS");
4556        }
4557
4558        // LIMIT and OFFSET
4559        // PostgreSQL and others use: LIMIT count OFFSET offset
4560        // SQL Server uses: OFFSET ... FETCH (no LIMIT)
4561        // Presto/Trino uses: OFFSET n LIMIT m (offset before limit)
4562        let is_presto_like = matches!(
4563            self.config.dialect,
4564            Some(DialectType::Presto) | Some(DialectType::Trino)
4565        );
4566
4567        if is_presto_like && select.offset.is_some() {
4568            // Presto/Trino syntax: OFFSET n LIMIT m (offset comes first)
4569            if let Some(offset) = &select.offset {
4570                if self.config.pretty {
4571                    self.write_newline();
4572                    self.write_indent();
4573                } else {
4574                    self.write_space();
4575                }
4576                self.write_keyword("OFFSET");
4577                self.write_space();
4578                self.write_limit_expr(&offset.this)?;
4579                if offset.rows == Some(true) {
4580                    self.write_space();
4581                    self.write_keyword("ROWS");
4582                }
4583            }
4584            if let Some(limit) = &select.limit {
4585                if self.config.pretty {
4586                    self.write_newline();
4587                    self.write_indent();
4588                } else {
4589                    self.write_space();
4590                }
4591                self.write_keyword("LIMIT");
4592                self.write_space();
4593                self.write_limit_expr(&limit.this)?;
4594                if limit.percent {
4595                    self.write_space();
4596                    self.write_keyword("PERCENT");
4597                }
4598                // Emit any comments that were captured from before the LIMIT keyword
4599                for comment in &limit.comments {
4600                    self.write(" ");
4601                    self.write_formatted_comment(comment);
4602                }
4603            }
4604        } else {
4605            // Check if FETCH will be converted to LIMIT (used for ordering)
4606            let fetch_as_limit = select.fetch.as_ref().map_or(false, |fetch| {
4607                !fetch.percent
4608                    && !fetch.with_ties
4609                    && fetch.count.is_some()
4610                    && matches!(
4611                        self.config.dialect,
4612                        Some(DialectType::Spark)
4613                            | Some(DialectType::Hive)
4614                            | Some(DialectType::DuckDB)
4615                            | Some(DialectType::SQLite)
4616                            | Some(DialectType::MySQL)
4617                            | Some(DialectType::BigQuery)
4618                            | Some(DialectType::Databricks)
4619                            | Some(DialectType::StarRocks)
4620                            | Some(DialectType::Doris)
4621                            | Some(DialectType::Athena)
4622                            | Some(DialectType::ClickHouse)
4623                            | Some(DialectType::Redshift)
4624                    )
4625            });
4626
4627            // Standard LIMIT clause (skip for SQL Server - we use TOP or OFFSET/FETCH instead)
4628            if let Some(limit) = &select.limit {
4629                // SQL Server uses TOP (no OFFSET) or OFFSET/FETCH (with OFFSET) instead of LIMIT
4630                if !matches!(self.config.dialect, Some(DialectType::TSQL)) {
4631                    if self.config.pretty {
4632                        self.write_newline();
4633                        self.write_indent();
4634                    } else {
4635                        self.write_space();
4636                    }
4637                    self.write_keyword("LIMIT");
4638                    self.write_space();
4639                    self.write_limit_expr(&limit.this)?;
4640                    if limit.percent {
4641                        self.write_space();
4642                        self.write_keyword("PERCENT");
4643                    }
4644                    // Emit any comments that were captured from before the LIMIT keyword
4645                    for comment in &limit.comments {
4646                        self.write(" ");
4647                        self.write_formatted_comment(comment);
4648                    }
4649                }
4650            }
4651
4652            // Convert TOP to LIMIT for non-TOP dialects
4653            if select.top.is_some() && !is_top_dialect && select.limit.is_none() {
4654                if let Some(top) = &select.top {
4655                    if !top.percent && !top.with_ties {
4656                        if self.config.pretty {
4657                            self.write_newline();
4658                            self.write_indent();
4659                        } else {
4660                            self.write_space();
4661                        }
4662                        self.write_keyword("LIMIT");
4663                        self.write_space();
4664                        self.generate_expression(&top.this)?;
4665                    }
4666                }
4667            }
4668
4669            // If FETCH will be converted to LIMIT and there's also OFFSET,
4670            // emit LIMIT from FETCH BEFORE the OFFSET
4671            if fetch_as_limit && select.offset.is_some() {
4672                if let Some(fetch) = &select.fetch {
4673                    if self.config.pretty {
4674                        self.write_newline();
4675                        self.write_indent();
4676                    } else {
4677                        self.write_space();
4678                    }
4679                    self.write_keyword("LIMIT");
4680                    self.write_space();
4681                    self.generate_expression(fetch.count.as_ref().unwrap())?;
4682                }
4683            }
4684
4685            // OFFSET
4686            // In SQL Server, OFFSET requires ORDER BY and uses different syntax
4687            // OFFSET x ROWS FETCH NEXT y ROWS ONLY
4688            if let Some(offset) = &select.offset {
4689                if self.config.pretty {
4690                    self.write_newline();
4691                    self.write_indent();
4692                } else {
4693                    self.write_space();
4694                }
4695                if matches!(self.config.dialect, Some(DialectType::TSQL)) {
4696                    // SQL Server 2012+ OFFSET ... FETCH syntax
4697                    self.write_keyword("OFFSET");
4698                    self.write_space();
4699                    self.write_limit_expr(&offset.this)?;
4700                    self.write_space();
4701                    self.write_keyword("ROWS");
4702                    // If there was a LIMIT, use FETCH NEXT ... ROWS ONLY
4703                    if let Some(limit) = &select.limit {
4704                        self.write_space();
4705                        self.write_keyword("FETCH NEXT");
4706                        self.write_space();
4707                        self.write_limit_expr(&limit.this)?;
4708                        self.write_space();
4709                        self.write_keyword("ROWS ONLY");
4710                    }
4711                } else {
4712                    self.write_keyword("OFFSET");
4713                    self.write_space();
4714                    self.write_limit_expr(&offset.this)?;
4715                    // Output ROWS keyword if it was in the original SQL
4716                    if offset.rows == Some(true) {
4717                        self.write_space();
4718                        self.write_keyword("ROWS");
4719                    }
4720                }
4721            }
4722        }
4723
4724        // ClickHouse LIMIT BY clause (after LIMIT/OFFSET)
4725        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
4726            if let Some(limit_by) = &select.limit_by {
4727                if !limit_by.is_empty() {
4728                    self.write_space();
4729                    self.write_keyword("BY");
4730                    self.write_space();
4731                    for (i, expr) in limit_by.iter().enumerate() {
4732                        if i > 0 {
4733                            self.write(", ");
4734                        }
4735                        self.generate_expression(expr)?;
4736                    }
4737                }
4738            }
4739        }
4740
4741        // ClickHouse SETTINGS and FORMAT modifiers (after LIMIT/OFFSET)
4742        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
4743            if let Some(settings) = &select.settings {
4744                if self.config.pretty {
4745                    self.write_newline();
4746                    self.write_indent();
4747                } else {
4748                    self.write_space();
4749                }
4750                self.write_keyword("SETTINGS");
4751                self.write_space();
4752                for (i, expr) in settings.iter().enumerate() {
4753                    if i > 0 {
4754                        self.write(", ");
4755                    }
4756                    self.generate_expression(expr)?;
4757                }
4758            }
4759
4760            if let Some(format_expr) = &select.format {
4761                if self.config.pretty {
4762                    self.write_newline();
4763                    self.write_indent();
4764                } else {
4765                    self.write_space();
4766                }
4767                self.write_keyword("FORMAT");
4768                self.write_space();
4769                self.generate_expression(format_expr)?;
4770            }
4771        }
4772
4773        // FETCH FIRST/NEXT
4774        if let Some(fetch) = &select.fetch {
4775            // Check if we already emitted LIMIT from FETCH before OFFSET
4776            let fetch_already_as_limit = select.offset.is_some()
4777                && !fetch.percent
4778                && !fetch.with_ties
4779                && fetch.count.is_some()
4780                && matches!(
4781                    self.config.dialect,
4782                    Some(DialectType::Spark)
4783                        | Some(DialectType::Hive)
4784                        | Some(DialectType::DuckDB)
4785                        | Some(DialectType::SQLite)
4786                        | Some(DialectType::MySQL)
4787                        | Some(DialectType::BigQuery)
4788                        | Some(DialectType::Databricks)
4789                        | Some(DialectType::StarRocks)
4790                        | Some(DialectType::Doris)
4791                        | Some(DialectType::Athena)
4792                        | Some(DialectType::ClickHouse)
4793                        | Some(DialectType::Redshift)
4794                );
4795
4796            if fetch_already_as_limit {
4797                // Already emitted as LIMIT before OFFSET, skip
4798            } else {
4799                if self.config.pretty {
4800                    self.write_newline();
4801                    self.write_indent();
4802                } else {
4803                    self.write_space();
4804                }
4805
4806                // Convert FETCH to LIMIT for dialects that prefer LIMIT syntax
4807                let use_limit = !fetch.percent
4808                    && !fetch.with_ties
4809                    && fetch.count.is_some()
4810                    && matches!(
4811                        self.config.dialect,
4812                        Some(DialectType::Spark)
4813                            | Some(DialectType::Hive)
4814                            | Some(DialectType::DuckDB)
4815                            | Some(DialectType::SQLite)
4816                            | Some(DialectType::MySQL)
4817                            | Some(DialectType::BigQuery)
4818                            | Some(DialectType::Databricks)
4819                            | Some(DialectType::StarRocks)
4820                            | Some(DialectType::Doris)
4821                            | Some(DialectType::Athena)
4822                            | Some(DialectType::ClickHouse)
4823                            | Some(DialectType::Redshift)
4824                    );
4825
4826                if use_limit {
4827                    self.write_keyword("LIMIT");
4828                    self.write_space();
4829                    self.generate_expression(fetch.count.as_ref().unwrap())?;
4830                } else {
4831                    self.write_keyword("FETCH");
4832                    self.write_space();
4833                    self.write_keyword(&fetch.direction);
4834                    if let Some(ref count) = fetch.count {
4835                        self.write_space();
4836                        self.generate_expression(count)?;
4837                    }
4838                    if fetch.percent {
4839                        self.write_space();
4840                        self.write_keyword("PERCENT");
4841                    }
4842                    if fetch.rows {
4843                        self.write_space();
4844                        self.write_keyword("ROWS");
4845                    }
4846                    if fetch.with_ties {
4847                        self.write_space();
4848                        self.write_keyword("WITH TIES");
4849                    } else {
4850                        self.write_space();
4851                        self.write_keyword("ONLY");
4852                    }
4853                }
4854            } // close fetch_already_as_limit else
4855        }
4856
4857        // SAMPLE / TABLESAMPLE
4858        if let Some(sample) = &select.sample {
4859            use crate::dialects::DialectType;
4860            if self.config.pretty {
4861                self.write_newline();
4862            } else {
4863                self.write_space();
4864            }
4865
4866            if sample.is_using_sample {
4867                // DuckDB USING SAMPLE: METHOD (size UNIT) [REPEATABLE (seed)]
4868                self.write_keyword("USING SAMPLE");
4869                self.generate_sample_body(sample)?;
4870            } else {
4871                self.write_keyword("TABLESAMPLE");
4872
4873                // Snowflake defaults to BERNOULLI when no explicit method is given
4874                let snowflake_bernoulli =
4875                    matches!(self.config.dialect, Some(DialectType::Snowflake))
4876                        && !sample.explicit_method;
4877                if snowflake_bernoulli {
4878                    self.write_space();
4879                    self.write_keyword("BERNOULLI");
4880                }
4881
4882                // Handle BUCKET sampling: TABLESAMPLE (BUCKET 1 OUT OF 5 ON x)
4883                if matches!(sample.method, SampleMethod::Bucket) {
4884                    self.write_space();
4885                    self.write("(");
4886                    self.write_keyword("BUCKET");
4887                    self.write_space();
4888                    if let Some(ref num) = sample.bucket_numerator {
4889                        self.generate_expression(num)?;
4890                    }
4891                    self.write_space();
4892                    self.write_keyword("OUT OF");
4893                    self.write_space();
4894                    if let Some(ref denom) = sample.bucket_denominator {
4895                        self.generate_expression(denom)?;
4896                    }
4897                    if let Some(ref field) = sample.bucket_field {
4898                        self.write_space();
4899                        self.write_keyword("ON");
4900                        self.write_space();
4901                        self.generate_expression(field)?;
4902                    }
4903                    self.write(")");
4904                } else if sample.unit_after_size {
4905                    // Syntax: TABLESAMPLE [METHOD] (size ROWS) or TABLESAMPLE [METHOD] (size PERCENT)
4906                    if sample.explicit_method && sample.method_before_size {
4907                        self.write_space();
4908                        match sample.method {
4909                            SampleMethod::Bernoulli => self.write_keyword("BERNOULLI"),
4910                            SampleMethod::System => self.write_keyword("SYSTEM"),
4911                            SampleMethod::Block => self.write_keyword("BLOCK"),
4912                            SampleMethod::Row => self.write_keyword("ROW"),
4913                            SampleMethod::Reservoir => self.write_keyword("RESERVOIR"),
4914                            _ => {}
4915                        }
4916                    }
4917                    self.write(" (");
4918                    self.generate_expression(&sample.size)?;
4919                    self.write_space();
4920                    match sample.method {
4921                        SampleMethod::Percent => self.write_keyword("PERCENT"),
4922                        SampleMethod::Row => self.write_keyword("ROWS"),
4923                        SampleMethod::Reservoir => self.write_keyword("ROWS"),
4924                        _ => {
4925                            self.write_keyword("PERCENT");
4926                        }
4927                    }
4928                    self.write(")");
4929                } else {
4930                    // Syntax: TABLESAMPLE METHOD (size)
4931                    self.write_space();
4932                    match sample.method {
4933                        SampleMethod::Bernoulli => self.write_keyword("BERNOULLI"),
4934                        SampleMethod::System => self.write_keyword("SYSTEM"),
4935                        SampleMethod::Block => self.write_keyword("BLOCK"),
4936                        SampleMethod::Row => self.write_keyword("ROW"),
4937                        SampleMethod::Percent => self.write_keyword("BERNOULLI"),
4938                        SampleMethod::Bucket => {}
4939                        SampleMethod::Reservoir => self.write_keyword("RESERVOIR"),
4940                    }
4941                    self.write(" (");
4942                    self.generate_expression(&sample.size)?;
4943                    if matches!(sample.method, SampleMethod::Percent) {
4944                        self.write_space();
4945                        self.write_keyword("PERCENT");
4946                    }
4947                    self.write(")");
4948                }
4949            }
4950
4951            if let Some(seed) = &sample.seed {
4952                self.write_space();
4953                // Databricks/Spark use REPEATABLE, not SEED
4954                let use_seed = sample.use_seed_keyword
4955                    && !matches!(
4956                        self.config.dialect,
4957                        Some(crate::dialects::DialectType::Databricks)
4958                            | Some(crate::dialects::DialectType::Spark)
4959                    );
4960                if use_seed {
4961                    self.write_keyword("SEED");
4962                } else {
4963                    self.write_keyword("REPEATABLE");
4964                }
4965                self.write(" (");
4966                self.generate_expression(seed)?;
4967                self.write(")");
4968            }
4969        }
4970
4971        // FOR UPDATE/SHARE locks
4972        // Skip locking clauses for dialects that don't support them
4973        if self.config.locking_reads_supported {
4974            for lock in &select.locks {
4975                if self.config.pretty {
4976                    self.write_newline();
4977                    self.write_indent();
4978                } else {
4979                    self.write_space();
4980                }
4981                self.generate_lock(lock)?;
4982            }
4983        }
4984
4985        // FOR XML clause (T-SQL)
4986        if !select.for_xml.is_empty() {
4987            if self.config.pretty {
4988                self.write_newline();
4989                self.write_indent();
4990            } else {
4991                self.write_space();
4992            }
4993            self.write_keyword("FOR XML");
4994            for (i, opt) in select.for_xml.iter().enumerate() {
4995                if self.config.pretty {
4996                    if i > 0 {
4997                        self.write(",");
4998                    }
4999                    self.write_newline();
5000                    self.write_indent();
5001                    self.write("  "); // extra indent for options
5002                } else {
5003                    if i > 0 {
5004                        self.write(",");
5005                    }
5006                    self.write_space();
5007                }
5008                self.generate_for_xml_option(opt)?;
5009            }
5010        }
5011
5012        // TSQL: OPTION clause
5013        if let Some(ref option) = select.option {
5014            if matches!(
5015                self.config.dialect,
5016                Some(crate::dialects::DialectType::TSQL)
5017                    | Some(crate::dialects::DialectType::Fabric)
5018            ) {
5019                self.write_space();
5020                self.write(option);
5021            }
5022        }
5023
5024        Ok(())
5025    }
5026
5027    /// Generate a single FOR XML option
5028    fn generate_for_xml_option(&mut self, opt: &Expression) -> Result<()> {
5029        match opt {
5030            Expression::QueryOption(qo) => {
5031                // Extract the option name from Var
5032                if let Expression::Var(var) = &*qo.this {
5033                    self.write(&var.this);
5034                } else {
5035                    self.generate_expression(&qo.this)?;
5036                }
5037                // If there's an expression (like PATH('element')), output it in parens
5038                if let Some(expr) = &qo.expression {
5039                    self.write("(");
5040                    self.generate_expression(expr)?;
5041                    self.write(")");
5042                }
5043            }
5044            _ => {
5045                self.generate_expression(opt)?;
5046            }
5047        }
5048        Ok(())
5049    }
5050
5051    fn generate_with(&mut self, with: &With) -> Result<()> {
5052        use crate::dialects::DialectType;
5053
5054        // Output leading comments before WITH
5055        for comment in &with.leading_comments {
5056            self.write_formatted_comment(comment);
5057            self.write(" ");
5058        }
5059        self.write_keyword("WITH");
5060        if with.recursive && self.config.cte_recursive_keyword_required {
5061            self.write_space();
5062            self.write_keyword("RECURSIVE");
5063        }
5064        self.write_space();
5065
5066        // BigQuery doesn't support column aliases in CTE definitions
5067        let skip_cte_columns = matches!(self.config.dialect, Some(DialectType::BigQuery));
5068
5069        for (i, cte) in with.ctes.iter().enumerate() {
5070            if i > 0 {
5071                self.write(",");
5072                if self.config.pretty {
5073                    self.write_space();
5074                } else {
5075                    self.write(" ");
5076                }
5077            }
5078            if matches!(self.config.dialect, Some(DialectType::ClickHouse)) && !cte.alias_first {
5079                self.generate_expression(&cte.this)?;
5080                self.write_space();
5081                self.write_keyword("AS");
5082                self.write_space();
5083                self.generate_identifier(&cte.alias)?;
5084                continue;
5085            }
5086            self.generate_identifier(&cte.alias)?;
5087            // Output CTE comments after alias name, before AS
5088            for comment in &cte.comments {
5089                self.write_space();
5090                self.write_formatted_comment(comment);
5091            }
5092            if !cte.columns.is_empty() && !skip_cte_columns {
5093                self.write("(");
5094                for (j, col) in cte.columns.iter().enumerate() {
5095                    if j > 0 {
5096                        self.write(", ");
5097                    }
5098                    self.generate_identifier(col)?;
5099                }
5100                self.write(")");
5101            }
5102            // USING KEY (columns) for DuckDB recursive CTEs
5103            if !cte.key_expressions.is_empty() {
5104                self.write_space();
5105                self.write_keyword("USING KEY");
5106                self.write(" (");
5107                for (i, key) in cte.key_expressions.iter().enumerate() {
5108                    if i > 0 {
5109                        self.write(", ");
5110                    }
5111                    self.generate_identifier(key)?;
5112                }
5113                self.write(")");
5114            }
5115            self.write_space();
5116            self.write_keyword("AS");
5117            // MATERIALIZED / NOT MATERIALIZED
5118            if let Some(materialized) = cte.materialized {
5119                self.write_space();
5120                if materialized {
5121                    self.write_keyword("MATERIALIZED");
5122                } else {
5123                    self.write_keyword("NOT MATERIALIZED");
5124                }
5125            }
5126            self.write(" (");
5127            if self.config.pretty {
5128                self.write_newline();
5129                self.indent_level += 1;
5130                self.write_indent();
5131            }
5132            // For Spark/Databricks, VALUES in a CTE must be wrapped with SELECT * FROM
5133            // e.g., WITH t AS (VALUES ('foo_val') AS t(foo1)) -> WITH t AS (SELECT * FROM VALUES ('foo_val') AS t(foo1))
5134            let wrap_values_in_select = matches!(
5135                self.config.dialect,
5136                Some(DialectType::Spark) | Some(DialectType::Databricks)
5137            ) && matches!(&cte.this, Expression::Values(_));
5138
5139            if wrap_values_in_select {
5140                self.write_keyword("SELECT");
5141                self.write(" * ");
5142                self.write_keyword("FROM");
5143                self.write_space();
5144            }
5145            self.generate_expression(&cte.this)?;
5146            if self.config.pretty {
5147                self.write_newline();
5148                self.indent_level -= 1;
5149                self.write_indent();
5150            }
5151            self.write(")");
5152        }
5153
5154        // Generate SEARCH/CYCLE clause if present
5155        if let Some(search) = &with.search {
5156            self.write_space();
5157            self.generate_expression(search)?;
5158        }
5159
5160        Ok(())
5161    }
5162
5163    /// Generate joins with proper nesting structure for pretty printing.
5164    /// Deferred-condition joins "own" the non-deferred joins that follow them
5165    /// within the same nesting_group.
5166    fn generate_joins_with_nesting(&mut self, joins: &[Join]) -> Result<()> {
5167        let mut i = 0;
5168        while i < joins.len() {
5169            if joins[i].deferred_condition {
5170                let parent_group = joins[i].nesting_group;
5171
5172                // This join owns the following non-deferred joins in the same nesting_group
5173                // First output the join keyword and table (without condition)
5174                self.generate_join_without_condition(&joins[i])?;
5175
5176                // Find the range of child joins: same nesting_group and not deferred
5177                let child_start = i + 1;
5178                let mut child_end = child_start;
5179                while child_end < joins.len()
5180                    && !joins[child_end].deferred_condition
5181                    && joins[child_end].nesting_group == parent_group
5182                {
5183                    child_end += 1;
5184                }
5185
5186                // Output child joins with extra indentation
5187                if child_start < child_end {
5188                    self.indent_level += 1;
5189                    for j in child_start..child_end {
5190                        self.generate_join(&joins[j])?;
5191                    }
5192                    self.indent_level -= 1;
5193                }
5194
5195                // Output the deferred condition at the parent level
5196                self.generate_join_condition(&joins[i])?;
5197
5198                i = child_end;
5199            } else {
5200                // Regular join (no nesting)
5201                self.generate_join(&joins[i])?;
5202                i += 1;
5203            }
5204        }
5205        Ok(())
5206    }
5207
5208    /// Generate a join's keyword and table reference, but not its ON/USING condition.
5209    /// Used for deferred-condition joins where the condition is output after child joins.
5210    fn generate_join_without_condition(&mut self, join: &Join) -> Result<()> {
5211        // Save and temporarily clear the condition to prevent generate_join from outputting it
5212        // We achieve this by creating a modified copy
5213        let mut join_copy = join.clone();
5214        join_copy.on = None;
5215        join_copy.using = Vec::new();
5216        join_copy.deferred_condition = false;
5217        self.generate_join(&join_copy)
5218    }
5219
5220    fn generate_join(&mut self, join: &Join) -> Result<()> {
5221        // Implicit (comma) joins: output as ", table" instead of "CROSS JOIN table"
5222        if join.kind == JoinKind::Implicit {
5223            self.write(",");
5224            if self.config.pretty {
5225                self.write_newline();
5226                self.write_indent();
5227            } else {
5228                self.write_space();
5229            }
5230            self.generate_expression(&join.this)?;
5231            return Ok(());
5232        }
5233
5234        if self.config.pretty {
5235            self.write_newline();
5236            self.write_indent();
5237        } else {
5238            self.write_space();
5239        }
5240
5241        // Helper: format hint suffix (e.g., " LOOP" or "")
5242        // Only include join hints for dialects that support them
5243        let hint_str = if self.config.join_hints {
5244            join.join_hint
5245                .as_ref()
5246                .map(|h| format!(" {}", h))
5247                .unwrap_or_default()
5248        } else {
5249            String::new()
5250        };
5251
5252        let clickhouse_join_keyword =
5253            if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
5254                if let Some(hint) = &join.join_hint {
5255                    let mut global = false;
5256                    let mut strictness: Option<&'static str> = None;
5257                    for part in hint.split_whitespace() {
5258                        if part.eq_ignore_ascii_case("GLOBAL") {
5259                            global = true;
5260                        } else if part.eq_ignore_ascii_case("ANY") {
5261                            strictness = Some("ANY");
5262                        } else if part.eq_ignore_ascii_case("ASOF") {
5263                            strictness = Some("ASOF");
5264                        } else if part.eq_ignore_ascii_case("SEMI") {
5265                            strictness = Some("SEMI");
5266                        } else if part.eq_ignore_ascii_case("ANTI") {
5267                            strictness = Some("ANTI");
5268                        }
5269                    }
5270
5271                    if global || strictness.is_some() {
5272                        let join_type = match join.kind {
5273                            JoinKind::Left => {
5274                                if join.use_outer_keyword {
5275                                    "LEFT OUTER"
5276                                } else if join.use_inner_keyword {
5277                                    "LEFT INNER"
5278                                } else {
5279                                    "LEFT"
5280                                }
5281                            }
5282                            JoinKind::Right => {
5283                                if join.use_outer_keyword {
5284                                    "RIGHT OUTER"
5285                                } else if join.use_inner_keyword {
5286                                    "RIGHT INNER"
5287                                } else {
5288                                    "RIGHT"
5289                                }
5290                            }
5291                            JoinKind::Full => {
5292                                if join.use_outer_keyword {
5293                                    "FULL OUTER"
5294                                } else {
5295                                    "FULL"
5296                                }
5297                            }
5298                            JoinKind::Inner => {
5299                                if join.use_inner_keyword {
5300                                    "INNER"
5301                                } else {
5302                                    ""
5303                                }
5304                            }
5305                            _ => "",
5306                        };
5307
5308                        let mut parts = Vec::new();
5309                        if global {
5310                            parts.push("GLOBAL");
5311                        }
5312                        if !join_type.is_empty() {
5313                            parts.push(join_type);
5314                        }
5315                        if let Some(strict) = strictness {
5316                            parts.push(strict);
5317                        }
5318                        parts.push("JOIN");
5319                        Some(parts.join(" "))
5320                    } else {
5321                        None
5322                    }
5323                } else {
5324                    None
5325                }
5326            } else {
5327                None
5328            };
5329
5330        // Output any comments associated with this join
5331        // In pretty mode, comments go on their own line before the join keyword
5332        // In non-pretty mode, comments go inline before the join keyword
5333        if !join.comments.is_empty() {
5334            if self.config.pretty {
5335                // In pretty mode, go back before the newline+indent we just wrote
5336                // and output comments on their own lines
5337                // We need to output comments BEFORE the join keyword on separate lines
5338                // Trim the trailing newline+indent we already wrote
5339                let trimmed = self.output.trim_end().len();
5340                self.output.truncate(trimmed);
5341                for comment in &join.comments {
5342                    self.write_newline();
5343                    self.write_indent();
5344                    self.write_formatted_comment(comment);
5345                }
5346                self.write_newline();
5347                self.write_indent();
5348            } else {
5349                for comment in &join.comments {
5350                    self.write_formatted_comment(comment);
5351                    self.write_space();
5352                }
5353            }
5354        }
5355
5356        let directed_str = if join.directed { " DIRECTED" } else { "" };
5357
5358        if let Some(keyword) = clickhouse_join_keyword {
5359            self.write_keyword(&keyword);
5360        } else {
5361            match join.kind {
5362                JoinKind::Inner => {
5363                    if join.use_inner_keyword {
5364                        if hint_str.is_empty() && directed_str.is_empty() {
5365                            self.write_keyword("INNER JOIN");
5366                        } else {
5367                            self.write_keyword("INNER");
5368                            if !hint_str.is_empty() { self.write_keyword(&hint_str); }
5369                            if !directed_str.is_empty() { self.write_keyword(directed_str); }
5370                            self.write_keyword(" JOIN");
5371                        }
5372                    } else {
5373                        if !hint_str.is_empty() {
5374                            self.write_keyword(hint_str.trim());
5375                            self.write_keyword(" ");
5376                        }
5377                        if !directed_str.is_empty() {
5378                            self.write_keyword("DIRECTED ");
5379                        }
5380                        self.write_keyword("JOIN");
5381                    }
5382                }
5383                JoinKind::Left => {
5384                    if join.use_outer_keyword {
5385                        if hint_str.is_empty() && directed_str.is_empty() {
5386                            self.write_keyword("LEFT OUTER JOIN");
5387                        } else {
5388                            self.write_keyword("LEFT OUTER");
5389                            if !hint_str.is_empty() { self.write_keyword(&hint_str); }
5390                            if !directed_str.is_empty() { self.write_keyword(directed_str); }
5391                            self.write_keyword(" JOIN");
5392                        }
5393                    } else if join.use_inner_keyword {
5394                        if hint_str.is_empty() && directed_str.is_empty() {
5395                            self.write_keyword("LEFT INNER JOIN");
5396                        } else {
5397                            self.write_keyword("LEFT INNER");
5398                            if !hint_str.is_empty() { self.write_keyword(&hint_str); }
5399                            if !directed_str.is_empty() { self.write_keyword(directed_str); }
5400                            self.write_keyword(" JOIN");
5401                        }
5402                    } else {
5403                        if hint_str.is_empty() && directed_str.is_empty() {
5404                            self.write_keyword("LEFT JOIN");
5405                        } else {
5406                            self.write_keyword("LEFT");
5407                            if !hint_str.is_empty() { self.write_keyword(&hint_str); }
5408                            if !directed_str.is_empty() { self.write_keyword(directed_str); }
5409                            self.write_keyword(" JOIN");
5410                        }
5411                    }
5412                }
5413                JoinKind::Right => {
5414                    if join.use_outer_keyword {
5415                        if hint_str.is_empty() && directed_str.is_empty() {
5416                            self.write_keyword("RIGHT OUTER JOIN");
5417                        } else {
5418                            self.write_keyword("RIGHT OUTER");
5419                            if !hint_str.is_empty() { self.write_keyword(&hint_str); }
5420                            if !directed_str.is_empty() { self.write_keyword(directed_str); }
5421                            self.write_keyword(" JOIN");
5422                        }
5423                    } else if join.use_inner_keyword {
5424                        if hint_str.is_empty() && directed_str.is_empty() {
5425                            self.write_keyword("RIGHT INNER JOIN");
5426                        } else {
5427                            self.write_keyword("RIGHT INNER");
5428                            if !hint_str.is_empty() { self.write_keyword(&hint_str); }
5429                            if !directed_str.is_empty() { self.write_keyword(directed_str); }
5430                            self.write_keyword(" JOIN");
5431                        }
5432                    } else {
5433                        if hint_str.is_empty() && directed_str.is_empty() {
5434                            self.write_keyword("RIGHT JOIN");
5435                        } else {
5436                            self.write_keyword("RIGHT");
5437                            if !hint_str.is_empty() { self.write_keyword(&hint_str); }
5438                            if !directed_str.is_empty() { self.write_keyword(directed_str); }
5439                            self.write_keyword(" JOIN");
5440                        }
5441                    }
5442                }
5443                JoinKind::Full => {
5444                    if join.use_outer_keyword {
5445                        if hint_str.is_empty() && directed_str.is_empty() {
5446                            self.write_keyword("FULL OUTER JOIN");
5447                        } else {
5448                            self.write_keyword("FULL OUTER");
5449                            if !hint_str.is_empty() { self.write_keyword(&hint_str); }
5450                            if !directed_str.is_empty() { self.write_keyword(directed_str); }
5451                            self.write_keyword(" JOIN");
5452                        }
5453                    } else {
5454                        if hint_str.is_empty() && directed_str.is_empty() {
5455                            self.write_keyword("FULL JOIN");
5456                        } else {
5457                            self.write_keyword("FULL");
5458                            if !hint_str.is_empty() { self.write_keyword(&hint_str); }
5459                            if !directed_str.is_empty() { self.write_keyword(directed_str); }
5460                            self.write_keyword(" JOIN");
5461                        }
5462                    }
5463                }
5464                JoinKind::Outer => {
5465                    if directed_str.is_empty() {
5466                        self.write_keyword("OUTER JOIN");
5467                    } else {
5468                        self.write_keyword("OUTER");
5469                        self.write_keyword(directed_str);
5470                        self.write_keyword(" JOIN");
5471                    }
5472                }
5473                JoinKind::Cross => {
5474                    if directed_str.is_empty() {
5475                        self.write_keyword("CROSS JOIN");
5476                    } else {
5477                        self.write_keyword("CROSS");
5478                        self.write_keyword(directed_str);
5479                        self.write_keyword(" JOIN");
5480                    }
5481                }
5482                JoinKind::Natural => {
5483                    if join.use_inner_keyword {
5484                        if directed_str.is_empty() {
5485                            self.write_keyword("NATURAL INNER JOIN");
5486                        } else {
5487                            self.write_keyword("NATURAL INNER");
5488                            self.write_keyword(directed_str);
5489                            self.write_keyword(" JOIN");
5490                        }
5491                    } else {
5492                        if directed_str.is_empty() {
5493                            self.write_keyword("NATURAL JOIN");
5494                        } else {
5495                            self.write_keyword("NATURAL");
5496                            self.write_keyword(directed_str);
5497                            self.write_keyword(" JOIN");
5498                        }
5499                    }
5500                }
5501                JoinKind::NaturalLeft => {
5502                    if join.use_outer_keyword {
5503                        if directed_str.is_empty() {
5504                            self.write_keyword("NATURAL LEFT OUTER JOIN");
5505                        } else {
5506                            self.write_keyword("NATURAL LEFT OUTER");
5507                            self.write_keyword(directed_str);
5508                            self.write_keyword(" JOIN");
5509                        }
5510                    } else {
5511                        if directed_str.is_empty() {
5512                            self.write_keyword("NATURAL LEFT JOIN");
5513                        } else {
5514                            self.write_keyword("NATURAL LEFT");
5515                            self.write_keyword(directed_str);
5516                            self.write_keyword(" JOIN");
5517                        }
5518                    }
5519                }
5520                JoinKind::NaturalRight => {
5521                    if join.use_outer_keyword {
5522                        if directed_str.is_empty() {
5523                            self.write_keyword("NATURAL RIGHT OUTER JOIN");
5524                        } else {
5525                            self.write_keyword("NATURAL RIGHT OUTER");
5526                            self.write_keyword(directed_str);
5527                            self.write_keyword(" JOIN");
5528                        }
5529                    } else {
5530                        if directed_str.is_empty() {
5531                            self.write_keyword("NATURAL RIGHT JOIN");
5532                        } else {
5533                            self.write_keyword("NATURAL RIGHT");
5534                            self.write_keyword(directed_str);
5535                            self.write_keyword(" JOIN");
5536                        }
5537                    }
5538                }
5539                JoinKind::NaturalFull => {
5540                    if join.use_outer_keyword {
5541                        if directed_str.is_empty() {
5542                            self.write_keyword("NATURAL FULL OUTER JOIN");
5543                        } else {
5544                            self.write_keyword("NATURAL FULL OUTER");
5545                            self.write_keyword(directed_str);
5546                            self.write_keyword(" JOIN");
5547                        }
5548                    } else {
5549                        if directed_str.is_empty() {
5550                            self.write_keyword("NATURAL FULL JOIN");
5551                        } else {
5552                            self.write_keyword("NATURAL FULL");
5553                            self.write_keyword(directed_str);
5554                            self.write_keyword(" JOIN");
5555                        }
5556                    }
5557                }
5558                JoinKind::Semi => self.write_keyword("SEMI JOIN"),
5559                JoinKind::Anti => self.write_keyword("ANTI JOIN"),
5560                JoinKind::LeftSemi => self.write_keyword("LEFT SEMI JOIN"),
5561                JoinKind::LeftAnti => self.write_keyword("LEFT ANTI JOIN"),
5562                JoinKind::RightSemi => self.write_keyword("RIGHT SEMI JOIN"),
5563                JoinKind::RightAnti => self.write_keyword("RIGHT ANTI JOIN"),
5564                JoinKind::CrossApply => {
5565                    // CROSS APPLY -> INNER JOIN LATERAL for non-TSQL dialects
5566                    if matches!(self.config.dialect, Some(DialectType::TSQL) | None) {
5567                        self.write_keyword("CROSS APPLY");
5568                    } else {
5569                        self.write_keyword("INNER JOIN LATERAL");
5570                    }
5571                }
5572                JoinKind::OuterApply => {
5573                    // OUTER APPLY -> LEFT JOIN LATERAL for non-TSQL dialects
5574                    if matches!(self.config.dialect, Some(DialectType::TSQL) | None) {
5575                        self.write_keyword("OUTER APPLY");
5576                    } else {
5577                        self.write_keyword("LEFT JOIN LATERAL");
5578                    }
5579                }
5580                JoinKind::AsOf => self.write_keyword("ASOF JOIN"),
5581                JoinKind::AsOfLeft => {
5582                    if join.use_outer_keyword {
5583                        self.write_keyword("ASOF LEFT OUTER JOIN");
5584                    } else {
5585                        self.write_keyword("ASOF LEFT JOIN");
5586                    }
5587                }
5588                JoinKind::AsOfRight => {
5589                    if join.use_outer_keyword {
5590                        self.write_keyword("ASOF RIGHT OUTER JOIN");
5591                    } else {
5592                        self.write_keyword("ASOF RIGHT JOIN");
5593                    }
5594                }
5595                JoinKind::Lateral => self.write_keyword("LATERAL JOIN"),
5596                JoinKind::LeftLateral => {
5597                    if join.use_outer_keyword {
5598                        self.write_keyword("LEFT OUTER LATERAL JOIN");
5599                    } else {
5600                        self.write_keyword("LEFT LATERAL JOIN");
5601                    }
5602                }
5603                JoinKind::Straight => self.write_keyword("STRAIGHT_JOIN"),
5604                JoinKind::Implicit => {
5605                    // BigQuery, Hive, Spark, and Databricks prefer explicit CROSS JOIN over comma syntax
5606                    // But only when source is the same dialect (identity) or source is another CROSS JOIN dialect
5607                    // When source is Generic, keep commas (Python sqlglot: parser marks joins, not generator)
5608                    use crate::dialects::DialectType;
5609                    let is_cj_dialect = matches!(
5610                        self.config.dialect,
5611                        Some(DialectType::BigQuery)
5612                            | Some(DialectType::Hive)
5613                            | Some(DialectType::Spark)
5614                            | Some(DialectType::Databricks)
5615                    );
5616                    let source_is_same = self.config.source_dialect.is_some()
5617                        && self.config.source_dialect == self.config.dialect;
5618                    let source_is_cj = matches!(
5619                        self.config.source_dialect,
5620                        Some(DialectType::BigQuery)
5621                            | Some(DialectType::Hive)
5622                            | Some(DialectType::Spark)
5623                            | Some(DialectType::Databricks)
5624                    );
5625                    if is_cj_dialect
5626                        && (source_is_same || source_is_cj || self.config.source_dialect.is_none())
5627                    {
5628                        self.write_keyword("CROSS JOIN");
5629                    } else {
5630                        // Implicit join uses comma: FROM a, b
5631                        // We already wrote a space before the match, so replace with comma
5632                        // by removing trailing space and writing ", "
5633                        self.output.truncate(self.output.trim_end().len());
5634                        self.write(",");
5635                    }
5636                }
5637                JoinKind::Array => self.write_keyword("ARRAY JOIN"),
5638                JoinKind::LeftArray => self.write_keyword("LEFT ARRAY JOIN"),
5639                JoinKind::Paste => self.write_keyword("PASTE JOIN"),
5640                JoinKind::Positional => self.write_keyword("POSITIONAL JOIN"),
5641            }
5642        }
5643
5644        // ARRAY JOIN items need comma-separated output (Tuple holds multiple items)
5645        if matches!(join.kind, JoinKind::Array | JoinKind::LeftArray) {
5646            self.write_space();
5647            match &join.this {
5648                Expression::Tuple(t) => {
5649                    for (i, item) in t.expressions.iter().enumerate() {
5650                        if i > 0 {
5651                            self.write(", ");
5652                        }
5653                        self.generate_expression(item)?;
5654                    }
5655                }
5656                other => {
5657                    self.generate_expression(other)?;
5658                }
5659            }
5660        } else {
5661            self.write_space();
5662            self.generate_expression(&join.this)?;
5663        }
5664
5665        // Only output MATCH_CONDITION/ON/USING inline if the condition wasn't deferred
5666        if !join.deferred_condition {
5667            // Output MATCH_CONDITION first (Snowflake ASOF JOIN)
5668            if let Some(match_cond) = &join.match_condition {
5669                self.write_space();
5670                self.write_keyword("MATCH_CONDITION");
5671                self.write(" (");
5672                self.generate_expression(match_cond)?;
5673                self.write(")");
5674            }
5675
5676            if let Some(on) = &join.on {
5677                if self.config.pretty {
5678                    self.write_newline();
5679                    self.indent_level += 1;
5680                    self.write_indent();
5681                    self.write_keyword("ON");
5682                    self.write_space();
5683                    self.generate_join_on_condition(on)?;
5684                    self.indent_level -= 1;
5685                } else {
5686                    self.write_space();
5687                    self.write_keyword("ON");
5688                    self.write_space();
5689                    self.generate_expression(on)?;
5690                }
5691            }
5692
5693            if !join.using.is_empty() {
5694                if self.config.pretty {
5695                    self.write_newline();
5696                    self.indent_level += 1;
5697                    self.write_indent();
5698                    self.write_keyword("USING");
5699                    self.write(" (");
5700                    for (i, col) in join.using.iter().enumerate() {
5701                        if i > 0 {
5702                            self.write(", ");
5703                        }
5704                        self.generate_identifier(col)?;
5705                    }
5706                    self.write(")");
5707                    self.indent_level -= 1;
5708                } else {
5709                    self.write_space();
5710                    self.write_keyword("USING");
5711                    self.write(" (");
5712                    for (i, col) in join.using.iter().enumerate() {
5713                        if i > 0 {
5714                            self.write(", ");
5715                        }
5716                        self.generate_identifier(col)?;
5717                    }
5718                    self.write(")");
5719                }
5720            }
5721        }
5722
5723        // Generate PIVOT/UNPIVOT expressions that follow this join
5724        for pivot in &join.pivots {
5725            self.write_space();
5726            self.generate_expression(pivot)?;
5727        }
5728
5729        Ok(())
5730    }
5731
5732    /// Generate just the ON/USING/MATCH_CONDITION for a join (used for deferred conditions)
5733    fn generate_join_condition(&mut self, join: &Join) -> Result<()> {
5734        // Generate MATCH_CONDITION first (Snowflake ASOF JOIN)
5735        if let Some(match_cond) = &join.match_condition {
5736            self.write_space();
5737            self.write_keyword("MATCH_CONDITION");
5738            self.write(" (");
5739            self.generate_expression(match_cond)?;
5740            self.write(")");
5741        }
5742
5743        if let Some(on) = &join.on {
5744            if self.config.pretty {
5745                self.write_newline();
5746                self.indent_level += 1;
5747                self.write_indent();
5748                self.write_keyword("ON");
5749                self.write_space();
5750                // In pretty mode, split AND conditions onto separate lines
5751                self.generate_join_on_condition(on)?;
5752                self.indent_level -= 1;
5753            } else {
5754                self.write_space();
5755                self.write_keyword("ON");
5756                self.write_space();
5757                self.generate_expression(on)?;
5758            }
5759        }
5760
5761        if !join.using.is_empty() {
5762            if self.config.pretty {
5763                self.write_newline();
5764                self.indent_level += 1;
5765                self.write_indent();
5766                self.write_keyword("USING");
5767                self.write(" (");
5768                for (i, col) in join.using.iter().enumerate() {
5769                    if i > 0 {
5770                        self.write(", ");
5771                    }
5772                    self.generate_identifier(col)?;
5773                }
5774                self.write(")");
5775                self.indent_level -= 1;
5776            } else {
5777                self.write_space();
5778                self.write_keyword("USING");
5779                self.write(" (");
5780                for (i, col) in join.using.iter().enumerate() {
5781                    if i > 0 {
5782                        self.write(", ");
5783                    }
5784                    self.generate_identifier(col)?;
5785                }
5786                self.write(")");
5787            }
5788        }
5789
5790        // Generate PIVOT/UNPIVOT expressions that follow this join (for deferred conditions)
5791        for pivot in &join.pivots {
5792            self.write_space();
5793            self.generate_expression(pivot)?;
5794        }
5795
5796        Ok(())
5797    }
5798
5799    /// Generate JOIN ON condition with AND clauses on separate lines in pretty mode
5800    fn generate_join_on_condition(&mut self, expr: &Expression) -> Result<()> {
5801        if let Expression::And(and_op) = expr {
5802            if let Some(conditions) = self.flatten_connector_terms(and_op, ConnectorOperator::And) {
5803                self.generate_expression(conditions[0])?;
5804                for condition in conditions.iter().skip(1) {
5805                    self.write_newline();
5806                    self.write_indent();
5807                    self.write_keyword("AND");
5808                    self.write_space();
5809                    self.generate_expression(condition)?;
5810                }
5811                return Ok(());
5812            }
5813        }
5814
5815        self.generate_expression(expr)
5816    }
5817
5818    fn generate_joined_table(&mut self, jt: &JoinedTable) -> Result<()> {
5819        // Parenthesized join: (tbl1 CROSS JOIN tbl2)
5820        self.write("(");
5821        self.generate_expression(&jt.left)?;
5822
5823        // Generate all joins
5824        for join in &jt.joins {
5825            self.generate_join(join)?;
5826        }
5827
5828        // Generate LATERAL VIEW clauses (Hive/Spark)
5829        for (lv_idx, lv) in jt.lateral_views.iter().enumerate() {
5830            self.generate_lateral_view(lv, lv_idx)?;
5831        }
5832
5833        self.write(")");
5834
5835        // Alias
5836        if let Some(alias) = &jt.alias {
5837            self.write_space();
5838            self.write_keyword("AS");
5839            self.write_space();
5840            self.generate_identifier(alias)?;
5841        }
5842
5843        Ok(())
5844    }
5845
5846    fn generate_lateral_view(&mut self, lv: &LateralView, lv_index: usize) -> Result<()> {
5847        use crate::dialects::DialectType;
5848
5849        if self.config.pretty {
5850            self.write_newline();
5851            self.write_indent();
5852        } else {
5853            self.write_space();
5854        }
5855
5856        // For Hive/Spark/Databricks (or no dialect specified), output native LATERAL VIEW syntax
5857        // For PostgreSQL and other specific dialects, convert to CROSS JOIN (LATERAL or UNNEST)
5858        let use_lateral_join = matches!(
5859            self.config.dialect,
5860            Some(DialectType::PostgreSQL)
5861                | Some(DialectType::DuckDB)
5862                | Some(DialectType::Snowflake)
5863                | Some(DialectType::TSQL)
5864                | Some(DialectType::Presto)
5865                | Some(DialectType::Trino)
5866                | Some(DialectType::Athena)
5867        );
5868
5869        // Check if target dialect should use UNNEST instead of EXPLODE
5870        let use_unnest = matches!(
5871            self.config.dialect,
5872            Some(DialectType::DuckDB)
5873                | Some(DialectType::Presto)
5874                | Some(DialectType::Trino)
5875                | Some(DialectType::Athena)
5876        );
5877
5878        // Check if we need POSEXPLODE -> UNNEST WITH ORDINALITY
5879        let (is_posexplode, is_inline, func_args) = match &lv.this {
5880            Expression::Explode(uf) => {
5881                // Expression::Explode is the dedicated EXPLODE expression type
5882                (false, false, vec![uf.this.clone()])
5883            }
5884            Expression::Unnest(uf) => {
5885                let mut args = vec![uf.this.clone()];
5886                args.extend(uf.expressions.clone());
5887                (false, false, args)
5888            }
5889            Expression::Function(func) => {
5890                if func.name.eq_ignore_ascii_case("POSEXPLODE") || func.name.eq_ignore_ascii_case("POSEXPLODE_OUTER") {
5891                    (true, false, func.args.clone())
5892                } else if func.name.eq_ignore_ascii_case("INLINE") {
5893                    (false, true, func.args.clone())
5894                } else if func.name.eq_ignore_ascii_case("EXPLODE") || func.name.eq_ignore_ascii_case("EXPLODE_OUTER") {
5895                    (false, false, func.args.clone())
5896                } else {
5897                    (false, false, vec![])
5898                }
5899            }
5900            _ => (false, false, vec![]),
5901        };
5902
5903        if use_lateral_join {
5904            // Convert to CROSS JOIN for PostgreSQL-like dialects
5905            if lv.outer {
5906                self.write_keyword("LEFT JOIN LATERAL");
5907            } else {
5908                self.write_keyword("CROSS JOIN");
5909            }
5910            self.write_space();
5911
5912            if use_unnest && !func_args.is_empty() {
5913                // Convert EXPLODE(y) -> UNNEST(y), POSEXPLODE(y) -> UNNEST(y)
5914                // For DuckDB, also convert ARRAY(y) -> [y]
5915                let unnest_args = if matches!(self.config.dialect, Some(DialectType::DuckDB)) {
5916                    // DuckDB: ARRAY(y) -> [y]
5917                    func_args
5918                        .iter()
5919                        .map(|a| {
5920                            if let Expression::Function(ref f) = a {
5921                                if f.name.eq_ignore_ascii_case("ARRAY") && f.args.len() == 1 {
5922                                    return Expression::ArrayFunc(Box::new(
5923                                        crate::expressions::ArrayConstructor {
5924                                            expressions: f.args.clone(),
5925                                            bracket_notation: true,
5926                                            use_list_keyword: false,
5927                                        },
5928                                    ));
5929                                }
5930                            }
5931                            a.clone()
5932                        })
5933                        .collect::<Vec<_>>()
5934                } else if matches!(
5935                    self.config.dialect,
5936                    Some(DialectType::Presto)
5937                        | Some(DialectType::Trino)
5938                        | Some(DialectType::Athena)
5939                ) {
5940                    // Presto: ARRAY(y) -> ARRAY[y]
5941                    func_args
5942                        .iter()
5943                        .map(|a| {
5944                            if let Expression::Function(ref f) = a {
5945                                if f.name.eq_ignore_ascii_case("ARRAY") && f.args.len() >= 1 {
5946                                    return Expression::ArrayFunc(Box::new(
5947                                        crate::expressions::ArrayConstructor {
5948                                            expressions: f.args.clone(),
5949                                            bracket_notation: true,
5950                                            use_list_keyword: false,
5951                                        },
5952                                    ));
5953                                }
5954                            }
5955                            a.clone()
5956                        })
5957                        .collect::<Vec<_>>()
5958                } else {
5959                    func_args
5960                };
5961
5962                // POSEXPLODE -> LATERAL (SELECT pos - 1 AS pos, col FROM UNNEST(y) WITH ORDINALITY AS t(col, pos))
5963                if is_posexplode {
5964                    self.write_keyword("LATERAL");
5965                    self.write(" (");
5966                    self.write_keyword("SELECT");
5967                    self.write_space();
5968
5969                    // Build the outer SELECT list: pos - 1 AS pos, then data columns
5970                    // column_aliases[0] is the position column, rest are data columns
5971                    let pos_alias = if !lv.column_aliases.is_empty() {
5972                        lv.column_aliases[0].clone()
5973                    } else {
5974                        Identifier::new("pos")
5975                    };
5976                    let data_aliases: Vec<Identifier> = if lv.column_aliases.len() > 1 {
5977                        lv.column_aliases[1..].to_vec()
5978                    } else {
5979                        vec![Identifier::new("col")]
5980                    };
5981
5982                    // pos - 1 AS pos
5983                    self.generate_identifier(&pos_alias)?;
5984                    self.write(" - 1");
5985                    self.write_space();
5986                    self.write_keyword("AS");
5987                    self.write_space();
5988                    self.generate_identifier(&pos_alias)?;
5989
5990                    // , col [, key, value ...]
5991                    for data_col in &data_aliases {
5992                        self.write(", ");
5993                        self.generate_identifier(data_col)?;
5994                    }
5995
5996                    self.write_space();
5997                    self.write_keyword("FROM");
5998                    self.write_space();
5999                    self.write_keyword("UNNEST");
6000                    self.write("(");
6001                    for (i, arg) in unnest_args.iter().enumerate() {
6002                        if i > 0 {
6003                            self.write(", ");
6004                        }
6005                        self.generate_expression(arg)?;
6006                    }
6007                    self.write(")");
6008                    self.write_space();
6009                    self.write_keyword("WITH ORDINALITY");
6010                    self.write_space();
6011                    self.write_keyword("AS");
6012                    self.write_space();
6013
6014                    // Inner alias: t(data_cols..., pos) - data columns first, pos last
6015                    let table_alias_ident = lv
6016                        .table_alias
6017                        .clone()
6018                        .unwrap_or_else(|| Identifier::new("t"));
6019                    self.generate_identifier(&table_alias_ident)?;
6020                    self.write("(");
6021                    for (i, data_col) in data_aliases.iter().enumerate() {
6022                        if i > 0 {
6023                            self.write(", ");
6024                        }
6025                        self.generate_identifier(data_col)?;
6026                    }
6027                    self.write(", ");
6028                    self.generate_identifier(&pos_alias)?;
6029                    self.write("))");
6030                } else if is_inline && matches!(self.config.dialect, Some(DialectType::DuckDB)) {
6031                    // INLINE -> LATERAL (SELECT UNNEST(arg, max_depth => 2)) AS alias
6032                    self.write_keyword("LATERAL");
6033                    self.write(" (");
6034                    self.write_keyword("SELECT");
6035                    self.write_space();
6036                    self.write_keyword("UNNEST");
6037                    self.write("(");
6038                    for (i, arg) in unnest_args.iter().enumerate() {
6039                        if i > 0 {
6040                            self.write(", ");
6041                        }
6042                        self.generate_expression(arg)?;
6043                    }
6044                    self.write(", ");
6045                    self.write_keyword("max_depth");
6046                    self.write(" => 2))");
6047
6048                    // Add table and column aliases
6049                    if let Some(alias) = &lv.table_alias {
6050                        self.write_space();
6051                        self.write_keyword("AS");
6052                        self.write_space();
6053                        self.generate_identifier(alias)?;
6054                        if !lv.column_aliases.is_empty() {
6055                            self.write("(");
6056                            for (i, col) in lv.column_aliases.iter().enumerate() {
6057                                if i > 0 {
6058                                    self.write(", ");
6059                                }
6060                                self.generate_identifier(col)?;
6061                            }
6062                            self.write(")");
6063                        }
6064                    } else if !lv.column_aliases.is_empty() {
6065                        // Auto-generate alias like _u_N
6066                        self.write_space();
6067                        self.write_keyword("AS");
6068                        self.write_space();
6069                        self.write(&format!("_u_{}", lv_index));
6070                        self.write("(");
6071                        for (i, col) in lv.column_aliases.iter().enumerate() {
6072                            if i > 0 {
6073                                self.write(", ");
6074                            }
6075                            self.generate_identifier(col)?;
6076                        }
6077                        self.write(")");
6078                    }
6079                } else {
6080                    self.write_keyword("UNNEST");
6081                    self.write("(");
6082                    for (i, arg) in unnest_args.iter().enumerate() {
6083                        if i > 0 {
6084                            self.write(", ");
6085                        }
6086                        self.generate_expression(arg)?;
6087                    }
6088                    self.write(")");
6089
6090                    // Add table and column aliases for non-POSEXPLODE
6091                    if let Some(alias) = &lv.table_alias {
6092                        self.write_space();
6093                        self.write_keyword("AS");
6094                        self.write_space();
6095                        self.generate_identifier(alias)?;
6096                        if !lv.column_aliases.is_empty() {
6097                            self.write("(");
6098                            for (i, col) in lv.column_aliases.iter().enumerate() {
6099                                if i > 0 {
6100                                    self.write(", ");
6101                                }
6102                                self.generate_identifier(col)?;
6103                            }
6104                            self.write(")");
6105                        }
6106                    } else if !lv.column_aliases.is_empty() {
6107                        self.write_space();
6108                        self.write_keyword("AS");
6109                        self.write(" t(");
6110                        for (i, col) in lv.column_aliases.iter().enumerate() {
6111                            if i > 0 {
6112                                self.write(", ");
6113                            }
6114                            self.generate_identifier(col)?;
6115                        }
6116                        self.write(")");
6117                    }
6118                }
6119            } else {
6120                // Not EXPLODE/POSEXPLODE or not using UNNEST, use LATERAL
6121                if !lv.outer {
6122                    self.write_keyword("LATERAL");
6123                    self.write_space();
6124                }
6125                self.generate_expression(&lv.this)?;
6126
6127                // Add table and column aliases
6128                if let Some(alias) = &lv.table_alias {
6129                    self.write_space();
6130                    self.write_keyword("AS");
6131                    self.write_space();
6132                    self.generate_identifier(alias)?;
6133                    if !lv.column_aliases.is_empty() {
6134                        self.write("(");
6135                        for (i, col) in lv.column_aliases.iter().enumerate() {
6136                            if i > 0 {
6137                                self.write(", ");
6138                            }
6139                            self.generate_identifier(col)?;
6140                        }
6141                        self.write(")");
6142                    }
6143                } else if !lv.column_aliases.is_empty() {
6144                    self.write_space();
6145                    self.write_keyword("AS");
6146                    self.write(" t(");
6147                    for (i, col) in lv.column_aliases.iter().enumerate() {
6148                        if i > 0 {
6149                            self.write(", ");
6150                        }
6151                        self.generate_identifier(col)?;
6152                    }
6153                    self.write(")");
6154                }
6155            }
6156
6157            // For LEFT JOIN LATERAL, need ON TRUE
6158            if lv.outer {
6159                self.write_space();
6160                self.write_keyword("ON TRUE");
6161            }
6162        } else {
6163            // Output native LATERAL VIEW syntax (Hive/Spark/Databricks or default)
6164            self.write_keyword("LATERAL VIEW");
6165            if lv.outer {
6166                self.write_space();
6167                self.write_keyword("OUTER");
6168            }
6169            if self.config.pretty {
6170                self.write_newline();
6171                self.write_indent();
6172            } else {
6173                self.write_space();
6174            }
6175            self.generate_expression(&lv.this)?;
6176
6177            // Table alias
6178            if let Some(alias) = &lv.table_alias {
6179                self.write_space();
6180                self.generate_identifier(alias)?;
6181            }
6182
6183            // Column aliases
6184            if !lv.column_aliases.is_empty() {
6185                self.write_space();
6186                self.write_keyword("AS");
6187                self.write_space();
6188                for (i, col) in lv.column_aliases.iter().enumerate() {
6189                    if i > 0 {
6190                        self.write(", ");
6191                    }
6192                    self.generate_identifier(col)?;
6193                }
6194            }
6195        }
6196
6197        Ok(())
6198    }
6199
6200    fn generate_union(&mut self, union: &Union) -> Result<()> {
6201        // WITH clause
6202        if let Some(with) = &union.with {
6203            self.generate_with(with)?;
6204            self.write_space();
6205        }
6206        self.generate_expression(&union.left)?;
6207        if self.config.pretty {
6208            self.write_newline();
6209            self.write_indent();
6210        } else {
6211            self.write_space();
6212        }
6213
6214        // BigQuery set operation modifiers: [side] [kind] UNION
6215        if let Some(side) = &union.side {
6216            self.write_keyword(side);
6217            self.write_space();
6218        }
6219        if let Some(kind) = &union.kind {
6220            self.write_keyword(kind);
6221            self.write_space();
6222        }
6223
6224        self.write_keyword("UNION");
6225        if union.all {
6226            self.write_space();
6227            self.write_keyword("ALL");
6228        } else if union.distinct {
6229            self.write_space();
6230            self.write_keyword("DISTINCT");
6231        }
6232
6233        // BigQuery: CORRESPONDING/STRICT CORRESPONDING -> BY NAME, BY (cols) -> ON (cols)
6234        // DuckDB: BY NAME
6235        if union.corresponding || union.by_name {
6236            self.write_space();
6237            self.write_keyword("BY NAME");
6238        }
6239        if !union.on_columns.is_empty() {
6240            self.write_space();
6241            self.write_keyword("ON");
6242            self.write(" (");
6243            for (i, col) in union.on_columns.iter().enumerate() {
6244                if i > 0 {
6245                    self.write(", ");
6246                }
6247                self.generate_expression(col)?;
6248            }
6249            self.write(")");
6250        }
6251
6252        if self.config.pretty {
6253            self.write_newline();
6254            self.write_indent();
6255        } else {
6256            self.write_space();
6257        }
6258        self.generate_expression(&union.right)?;
6259        // ORDER BY, LIMIT, OFFSET for the set operation
6260        if let Some(order_by) = &union.order_by {
6261            if self.config.pretty {
6262                self.write_newline();
6263            } else {
6264                self.write_space();
6265            }
6266            self.write_keyword("ORDER BY");
6267            self.write_space();
6268            for (i, ordered) in order_by.expressions.iter().enumerate() {
6269                if i > 0 {
6270                    self.write(", ");
6271                }
6272                self.generate_ordered(ordered)?;
6273            }
6274        }
6275        if let Some(limit) = &union.limit {
6276            if self.config.pretty {
6277                self.write_newline();
6278            } else {
6279                self.write_space();
6280            }
6281            self.write_keyword("LIMIT");
6282            self.write_space();
6283            self.generate_expression(limit)?;
6284        }
6285        if let Some(offset) = &union.offset {
6286            if self.config.pretty {
6287                self.write_newline();
6288            } else {
6289                self.write_space();
6290            }
6291            self.write_keyword("OFFSET");
6292            self.write_space();
6293            self.generate_expression(offset)?;
6294        }
6295        // DISTRIBUTE BY (Hive/Spark)
6296        if let Some(distribute_by) = &union.distribute_by {
6297            self.write_space();
6298            self.write_keyword("DISTRIBUTE BY");
6299            self.write_space();
6300            for (i, expr) in distribute_by.expressions.iter().enumerate() {
6301                if i > 0 {
6302                    self.write(", ");
6303                }
6304                self.generate_expression(expr)?;
6305            }
6306        }
6307        // SORT BY (Hive/Spark)
6308        if let Some(sort_by) = &union.sort_by {
6309            self.write_space();
6310            self.write_keyword("SORT BY");
6311            self.write_space();
6312            for (i, ord) in sort_by.expressions.iter().enumerate() {
6313                if i > 0 {
6314                    self.write(", ");
6315                }
6316                self.generate_ordered(ord)?;
6317            }
6318        }
6319        // CLUSTER BY (Hive/Spark)
6320        if let Some(cluster_by) = &union.cluster_by {
6321            self.write_space();
6322            self.write_keyword("CLUSTER BY");
6323            self.write_space();
6324            for (i, ord) in cluster_by.expressions.iter().enumerate() {
6325                if i > 0 {
6326                    self.write(", ");
6327                }
6328                self.generate_ordered(ord)?;
6329            }
6330        }
6331        Ok(())
6332    }
6333
6334    fn generate_intersect(&mut self, intersect: &Intersect) -> Result<()> {
6335        // WITH clause
6336        if let Some(with) = &intersect.with {
6337            self.generate_with(with)?;
6338            self.write_space();
6339        }
6340        self.generate_expression(&intersect.left)?;
6341        if self.config.pretty {
6342            self.write_newline();
6343            self.write_indent();
6344        } else {
6345            self.write_space();
6346        }
6347
6348        // BigQuery set operation modifiers: [side] [kind] INTERSECT
6349        if let Some(side) = &intersect.side {
6350            self.write_keyword(side);
6351            self.write_space();
6352        }
6353        if let Some(kind) = &intersect.kind {
6354            self.write_keyword(kind);
6355            self.write_space();
6356        }
6357
6358        self.write_keyword("INTERSECT");
6359        if intersect.all {
6360            self.write_space();
6361            self.write_keyword("ALL");
6362        } else if intersect.distinct {
6363            self.write_space();
6364            self.write_keyword("DISTINCT");
6365        }
6366
6367        // BigQuery: CORRESPONDING/STRICT CORRESPONDING -> BY NAME, BY (cols) -> ON (cols)
6368        // DuckDB: BY NAME
6369        if intersect.corresponding || intersect.by_name {
6370            self.write_space();
6371            self.write_keyword("BY NAME");
6372        }
6373        if !intersect.on_columns.is_empty() {
6374            self.write_space();
6375            self.write_keyword("ON");
6376            self.write(" (");
6377            for (i, col) in intersect.on_columns.iter().enumerate() {
6378                if i > 0 {
6379                    self.write(", ");
6380                }
6381                self.generate_expression(col)?;
6382            }
6383            self.write(")");
6384        }
6385
6386        if self.config.pretty {
6387            self.write_newline();
6388            self.write_indent();
6389        } else {
6390            self.write_space();
6391        }
6392        self.generate_expression(&intersect.right)?;
6393        // ORDER BY, LIMIT, OFFSET for the set operation
6394        if let Some(order_by) = &intersect.order_by {
6395            if self.config.pretty {
6396                self.write_newline();
6397            } else {
6398                self.write_space();
6399            }
6400            self.write_keyword("ORDER BY");
6401            self.write_space();
6402            for (i, ordered) in order_by.expressions.iter().enumerate() {
6403                if i > 0 {
6404                    self.write(", ");
6405                }
6406                self.generate_ordered(ordered)?;
6407            }
6408        }
6409        if let Some(limit) = &intersect.limit {
6410            if self.config.pretty {
6411                self.write_newline();
6412            } else {
6413                self.write_space();
6414            }
6415            self.write_keyword("LIMIT");
6416            self.write_space();
6417            self.generate_expression(limit)?;
6418        }
6419        if let Some(offset) = &intersect.offset {
6420            if self.config.pretty {
6421                self.write_newline();
6422            } else {
6423                self.write_space();
6424            }
6425            self.write_keyword("OFFSET");
6426            self.write_space();
6427            self.generate_expression(offset)?;
6428        }
6429        // DISTRIBUTE BY (Hive/Spark)
6430        if let Some(distribute_by) = &intersect.distribute_by {
6431            self.write_space();
6432            self.write_keyword("DISTRIBUTE BY");
6433            self.write_space();
6434            for (i, expr) in distribute_by.expressions.iter().enumerate() {
6435                if i > 0 {
6436                    self.write(", ");
6437                }
6438                self.generate_expression(expr)?;
6439            }
6440        }
6441        // SORT BY (Hive/Spark)
6442        if let Some(sort_by) = &intersect.sort_by {
6443            self.write_space();
6444            self.write_keyword("SORT BY");
6445            self.write_space();
6446            for (i, ord) in sort_by.expressions.iter().enumerate() {
6447                if i > 0 {
6448                    self.write(", ");
6449                }
6450                self.generate_ordered(ord)?;
6451            }
6452        }
6453        // CLUSTER BY (Hive/Spark)
6454        if let Some(cluster_by) = &intersect.cluster_by {
6455            self.write_space();
6456            self.write_keyword("CLUSTER BY");
6457            self.write_space();
6458            for (i, ord) in cluster_by.expressions.iter().enumerate() {
6459                if i > 0 {
6460                    self.write(", ");
6461                }
6462                self.generate_ordered(ord)?;
6463            }
6464        }
6465        Ok(())
6466    }
6467
6468    fn generate_except(&mut self, except: &Except) -> Result<()> {
6469        use crate::dialects::DialectType;
6470
6471        // WITH clause
6472        if let Some(with) = &except.with {
6473            self.generate_with(with)?;
6474            self.write_space();
6475        }
6476
6477        self.generate_expression(&except.left)?;
6478        if self.config.pretty {
6479            self.write_newline();
6480            self.write_indent();
6481        } else {
6482            self.write_space();
6483        }
6484
6485        // BigQuery set operation modifiers: [side] [kind] EXCEPT
6486        if let Some(side) = &except.side {
6487            self.write_keyword(side);
6488            self.write_space();
6489        }
6490        if let Some(kind) = &except.kind {
6491            self.write_keyword(kind);
6492            self.write_space();
6493        }
6494
6495        // Oracle uses MINUS instead of EXCEPT (but not for EXCEPT ALL)
6496        match self.config.dialect {
6497            Some(DialectType::Oracle) if !except.all => {
6498                self.write_keyword("MINUS");
6499            }
6500            Some(DialectType::ClickHouse) => {
6501                // ClickHouse: drop ALL from EXCEPT ALL
6502                self.write_keyword("EXCEPT");
6503                if except.distinct {
6504                    self.write_space();
6505                    self.write_keyword("DISTINCT");
6506                }
6507            }
6508            Some(DialectType::BigQuery) => {
6509                // BigQuery: bare EXCEPT defaults to EXCEPT DISTINCT
6510                self.write_keyword("EXCEPT");
6511                if except.all {
6512                    self.write_space();
6513                    self.write_keyword("ALL");
6514                } else {
6515                    self.write_space();
6516                    self.write_keyword("DISTINCT");
6517                }
6518            }
6519            _ => {
6520                self.write_keyword("EXCEPT");
6521                if except.all {
6522                    self.write_space();
6523                    self.write_keyword("ALL");
6524                } else if except.distinct {
6525                    self.write_space();
6526                    self.write_keyword("DISTINCT");
6527                }
6528            }
6529        }
6530
6531        // BigQuery: CORRESPONDING/STRICT CORRESPONDING -> BY NAME, BY (cols) -> ON (cols)
6532        // DuckDB: BY NAME
6533        if except.corresponding || except.by_name {
6534            self.write_space();
6535            self.write_keyword("BY NAME");
6536        }
6537        if !except.on_columns.is_empty() {
6538            self.write_space();
6539            self.write_keyword("ON");
6540            self.write(" (");
6541            for (i, col) in except.on_columns.iter().enumerate() {
6542                if i > 0 {
6543                    self.write(", ");
6544                }
6545                self.generate_expression(col)?;
6546            }
6547            self.write(")");
6548        }
6549
6550        if self.config.pretty {
6551            self.write_newline();
6552            self.write_indent();
6553        } else {
6554            self.write_space();
6555        }
6556        self.generate_expression(&except.right)?;
6557        // ORDER BY, LIMIT, OFFSET for the set operation
6558        if let Some(order_by) = &except.order_by {
6559            if self.config.pretty {
6560                self.write_newline();
6561            } else {
6562                self.write_space();
6563            }
6564            self.write_keyword("ORDER BY");
6565            self.write_space();
6566            for (i, ordered) in order_by.expressions.iter().enumerate() {
6567                if i > 0 {
6568                    self.write(", ");
6569                }
6570                self.generate_ordered(ordered)?;
6571            }
6572        }
6573        if let Some(limit) = &except.limit {
6574            if self.config.pretty {
6575                self.write_newline();
6576            } else {
6577                self.write_space();
6578            }
6579            self.write_keyword("LIMIT");
6580            self.write_space();
6581            self.generate_expression(limit)?;
6582        }
6583        if let Some(offset) = &except.offset {
6584            if self.config.pretty {
6585                self.write_newline();
6586            } else {
6587                self.write_space();
6588            }
6589            self.write_keyword("OFFSET");
6590            self.write_space();
6591            self.generate_expression(offset)?;
6592        }
6593        // DISTRIBUTE BY (Hive/Spark)
6594        if let Some(distribute_by) = &except.distribute_by {
6595            self.write_space();
6596            self.write_keyword("DISTRIBUTE BY");
6597            self.write_space();
6598            for (i, expr) in distribute_by.expressions.iter().enumerate() {
6599                if i > 0 {
6600                    self.write(", ");
6601                }
6602                self.generate_expression(expr)?;
6603            }
6604        }
6605        // SORT BY (Hive/Spark)
6606        if let Some(sort_by) = &except.sort_by {
6607            self.write_space();
6608            self.write_keyword("SORT BY");
6609            self.write_space();
6610            for (i, ord) in sort_by.expressions.iter().enumerate() {
6611                if i > 0 {
6612                    self.write(", ");
6613                }
6614                self.generate_ordered(ord)?;
6615            }
6616        }
6617        // CLUSTER BY (Hive/Spark)
6618        if let Some(cluster_by) = &except.cluster_by {
6619            self.write_space();
6620            self.write_keyword("CLUSTER BY");
6621            self.write_space();
6622            for (i, ord) in cluster_by.expressions.iter().enumerate() {
6623                if i > 0 {
6624                    self.write(", ");
6625                }
6626                self.generate_ordered(ord)?;
6627            }
6628        }
6629        Ok(())
6630    }
6631
6632    fn generate_insert(&mut self, insert: &Insert) -> Result<()> {
6633        // For TSQL/Fabric/Spark/Hive/Databricks, CTEs must be prepended before INSERT
6634        let prepend_query_cte = if insert.with.is_none() {
6635            use crate::dialects::DialectType;
6636            let should_prepend = matches!(
6637                self.config.dialect,
6638                Some(DialectType::TSQL)
6639                    | Some(DialectType::Fabric)
6640                    | Some(DialectType::Spark)
6641                    | Some(DialectType::Databricks)
6642                    | Some(DialectType::Hive)
6643            );
6644            if should_prepend {
6645                if let Some(Expression::Select(select)) = &insert.query {
6646                    select.with.clone()
6647                } else {
6648                    None
6649                }
6650            } else {
6651                None
6652            }
6653        } else {
6654            None
6655        };
6656
6657        // Output WITH clause if on INSERT (e.g., WITH ... INSERT INTO ...)
6658        if let Some(with) = &insert.with {
6659            self.generate_with(with)?;
6660            self.write_space();
6661        } else if let Some(with) = &prepend_query_cte {
6662            self.generate_with(with)?;
6663            self.write_space();
6664        }
6665
6666        // Output leading comments before INSERT
6667        for comment in &insert.leading_comments {
6668            self.write_formatted_comment(comment);
6669            self.write(" ");
6670        }
6671
6672        // Handle directory insert (INSERT OVERWRITE DIRECTORY)
6673        if let Some(dir) = &insert.directory {
6674            self.write_keyword("INSERT OVERWRITE");
6675            if dir.local {
6676                self.write_space();
6677                self.write_keyword("LOCAL");
6678            }
6679            self.write_space();
6680            self.write_keyword("DIRECTORY");
6681            self.write_space();
6682            self.write("'");
6683            self.write(&dir.path);
6684            self.write("'");
6685
6686            // ROW FORMAT clause
6687            if let Some(row_format) = &dir.row_format {
6688                self.write_space();
6689                self.write_keyword("ROW FORMAT");
6690                if row_format.delimited {
6691                    self.write_space();
6692                    self.write_keyword("DELIMITED");
6693                }
6694                if let Some(val) = &row_format.fields_terminated_by {
6695                    self.write_space();
6696                    self.write_keyword("FIELDS TERMINATED BY");
6697                    self.write_space();
6698                    self.write("'");
6699                    self.write(val);
6700                    self.write("'");
6701                }
6702                if let Some(val) = &row_format.collection_items_terminated_by {
6703                    self.write_space();
6704                    self.write_keyword("COLLECTION ITEMS TERMINATED BY");
6705                    self.write_space();
6706                    self.write("'");
6707                    self.write(val);
6708                    self.write("'");
6709                }
6710                if let Some(val) = &row_format.map_keys_terminated_by {
6711                    self.write_space();
6712                    self.write_keyword("MAP KEYS TERMINATED BY");
6713                    self.write_space();
6714                    self.write("'");
6715                    self.write(val);
6716                    self.write("'");
6717                }
6718                if let Some(val) = &row_format.lines_terminated_by {
6719                    self.write_space();
6720                    self.write_keyword("LINES TERMINATED BY");
6721                    self.write_space();
6722                    self.write("'");
6723                    self.write(val);
6724                    self.write("'");
6725                }
6726                if let Some(val) = &row_format.null_defined_as {
6727                    self.write_space();
6728                    self.write_keyword("NULL DEFINED AS");
6729                    self.write_space();
6730                    self.write("'");
6731                    self.write(val);
6732                    self.write("'");
6733                }
6734            }
6735
6736            // STORED AS clause
6737            if let Some(format) = &dir.stored_as {
6738                self.write_space();
6739                self.write_keyword("STORED AS");
6740                self.write_space();
6741                self.write_keyword(format);
6742            }
6743
6744            // Query (SELECT statement)
6745            if let Some(query) = &insert.query {
6746                self.write_space();
6747                self.generate_expression(query)?;
6748            }
6749
6750            return Ok(());
6751        }
6752
6753        if insert.is_replace {
6754            // MySQL/SQLite REPLACE INTO statement
6755            self.write_keyword("REPLACE INTO");
6756        } else if insert.overwrite {
6757            // Use dialect-specific INSERT OVERWRITE format
6758            self.write_keyword("INSERT");
6759            // Output hint if present (Oracle: INSERT /*+ APPEND */ INTO)
6760            if let Some(ref hint) = insert.hint {
6761                self.generate_hint(hint)?;
6762            }
6763            self.write(&self.config.insert_overwrite.to_ascii_uppercase());
6764        } else if let Some(ref action) = insert.conflict_action {
6765            // SQLite conflict action: INSERT OR ABORT|FAIL|IGNORE|REPLACE|ROLLBACK INTO
6766            self.write_keyword("INSERT OR");
6767            self.write_space();
6768            self.write_keyword(action);
6769            self.write_space();
6770            self.write_keyword("INTO");
6771        } else if insert.ignore {
6772            // MySQL INSERT IGNORE syntax
6773            self.write_keyword("INSERT IGNORE INTO");
6774        } else {
6775            self.write_keyword("INSERT");
6776            // Output hint if present (Oracle: INSERT /*+ APPEND */ INTO)
6777            if let Some(ref hint) = insert.hint {
6778                self.generate_hint(hint)?;
6779            }
6780            self.write_space();
6781            self.write_keyword("INTO");
6782        }
6783        // ClickHouse: INSERT INTO FUNCTION func_name(args...)
6784        if let Some(ref func) = insert.function_target {
6785            self.write_space();
6786            self.write_keyword("FUNCTION");
6787            self.write_space();
6788            self.generate_expression(func)?;
6789        } else {
6790            self.write_space();
6791            self.generate_table(&insert.table)?;
6792        }
6793
6794        // Table alias (PostgreSQL: INSERT INTO table AS t(...), Oracle: INSERT INTO table t ...)
6795        if let Some(ref alias) = insert.alias {
6796            self.write_space();
6797            if insert.alias_explicit_as {
6798                self.write_keyword("AS");
6799                self.write_space();
6800            }
6801            self.generate_identifier(alias)?;
6802        }
6803
6804        // IF EXISTS clause (Hive)
6805        if insert.if_exists {
6806            self.write_space();
6807            self.write_keyword("IF EXISTS");
6808        }
6809
6810        // REPLACE WHERE clause (Databricks)
6811        if let Some(ref replace_where) = insert.replace_where {
6812            if self.config.pretty {
6813                self.write_newline();
6814                self.write_indent();
6815            } else {
6816                self.write_space();
6817            }
6818            self.write_keyword("REPLACE WHERE");
6819            self.write_space();
6820            self.generate_expression(replace_where)?;
6821        }
6822
6823        // Generate PARTITION clause if present
6824        if !insert.partition.is_empty() {
6825            self.write_space();
6826            self.write_keyword("PARTITION");
6827            self.write("(");
6828            for (i, (col, val)) in insert.partition.iter().enumerate() {
6829                if i > 0 {
6830                    self.write(", ");
6831                }
6832                self.generate_identifier(col)?;
6833                if let Some(v) = val {
6834                    self.write(" = ");
6835                    self.generate_expression(v)?;
6836                }
6837            }
6838            self.write(")");
6839        }
6840
6841        // ClickHouse: PARTITION BY expr
6842        if let Some(ref partition_by) = insert.partition_by {
6843            self.write_space();
6844            self.write_keyword("PARTITION BY");
6845            self.write_space();
6846            self.generate_expression(partition_by)?;
6847        }
6848
6849        // ClickHouse: SETTINGS key = val, ...
6850        if !insert.settings.is_empty() {
6851            self.write_space();
6852            self.write_keyword("SETTINGS");
6853            self.write_space();
6854            for (i, setting) in insert.settings.iter().enumerate() {
6855                if i > 0 {
6856                    self.write(", ");
6857                }
6858                self.generate_expression(setting)?;
6859            }
6860        }
6861
6862        if !insert.columns.is_empty() {
6863            if insert.alias.is_some() && insert.alias_explicit_as {
6864                // No space when explicit AS alias is present: INSERT INTO table AS t(a, b, c)
6865                self.write("(");
6866            } else {
6867                // Space for implicit alias or no alias: INSERT INTO dest d (i, value)
6868                self.write(" (");
6869            }
6870            for (i, col) in insert.columns.iter().enumerate() {
6871                if i > 0 {
6872                    self.write(", ");
6873                }
6874                self.generate_identifier(col)?;
6875            }
6876            self.write(")");
6877        }
6878
6879        // OUTPUT clause (TSQL)
6880        if let Some(ref output) = insert.output {
6881            self.generate_output_clause(output)?;
6882        }
6883
6884        // BY NAME modifier (DuckDB)
6885        if insert.by_name {
6886            self.write_space();
6887            self.write_keyword("BY NAME");
6888        }
6889
6890        if insert.default_values {
6891            self.write_space();
6892            self.write_keyword("DEFAULT VALUES");
6893        } else if let Some(query) = &insert.query {
6894            if self.config.pretty {
6895                self.write_newline();
6896            } else {
6897                self.write_space();
6898            }
6899            // If we prepended CTEs from nested SELECT (TSQL), strip the WITH from SELECT
6900            if prepend_query_cte.is_some() {
6901                if let Expression::Select(select) = query {
6902                    let mut select_no_with = select.clone();
6903                    select_no_with.with = None;
6904                    self.generate_select(&select_no_with)?;
6905                } else {
6906                    self.generate_expression(query)?;
6907                }
6908            } else {
6909                self.generate_expression(query)?;
6910            }
6911        } else if !insert.values.is_empty() {
6912            if self.config.pretty {
6913                // Pretty printing: VALUES on new line, each tuple indented
6914                self.write_newline();
6915                self.write_keyword("VALUES");
6916                self.write_newline();
6917                self.indent_level += 1;
6918                for (i, row) in insert.values.iter().enumerate() {
6919                    if i > 0 {
6920                        self.write(",");
6921                        self.write_newline();
6922                    }
6923                    self.write_indent();
6924                    self.write("(");
6925                    for (j, val) in row.iter().enumerate() {
6926                        if j > 0 {
6927                            self.write(", ");
6928                        }
6929                        self.generate_expression(val)?;
6930                    }
6931                    self.write(")");
6932                }
6933                self.indent_level -= 1;
6934            } else {
6935                // Non-pretty: single line
6936                self.write_space();
6937                self.write_keyword("VALUES");
6938                for (i, row) in insert.values.iter().enumerate() {
6939                    if i > 0 {
6940                        self.write(",");
6941                    }
6942                    self.write(" (");
6943                    for (j, val) in row.iter().enumerate() {
6944                        if j > 0 {
6945                            self.write(", ");
6946                        }
6947                        self.generate_expression(val)?;
6948                    }
6949                    self.write(")");
6950                }
6951            }
6952        }
6953
6954        // Source table (Hive/Spark): INSERT OVERWRITE TABLE target TABLE source
6955        if let Some(ref source) = insert.source {
6956            self.write_space();
6957            self.write_keyword("TABLE");
6958            self.write_space();
6959            self.generate_expression(source)?;
6960        }
6961
6962        // Source alias (MySQL: VALUES (...) AS new_data)
6963        if let Some(alias) = &insert.source_alias {
6964            self.write_space();
6965            self.write_keyword("AS");
6966            self.write_space();
6967            self.generate_identifier(alias)?;
6968        }
6969
6970        // ON CONFLICT clause (Materialize doesn't support ON CONFLICT)
6971        if let Some(on_conflict) = &insert.on_conflict {
6972            if !matches!(self.config.dialect, Some(DialectType::Materialize)) {
6973                self.write_space();
6974                self.generate_expression(on_conflict)?;
6975            }
6976        }
6977
6978        // RETURNING clause
6979        if !insert.returning.is_empty() {
6980            self.write_space();
6981            self.write_keyword("RETURNING");
6982            self.write_space();
6983            for (i, expr) in insert.returning.iter().enumerate() {
6984                if i > 0 {
6985                    self.write(", ");
6986                }
6987                self.generate_expression(expr)?;
6988            }
6989        }
6990
6991        Ok(())
6992    }
6993
6994    fn generate_update(&mut self, update: &Update) -> Result<()> {
6995        // Output leading comments before UPDATE
6996        for comment in &update.leading_comments {
6997            self.write_formatted_comment(comment);
6998            self.write(" ");
6999        }
7000
7001        // WITH clause (CTEs)
7002        if let Some(ref with) = update.with {
7003            self.generate_with(with)?;
7004            self.write_space();
7005        }
7006
7007        self.write_keyword("UPDATE");
7008        self.write_space();
7009        self.generate_table(&update.table)?;
7010
7011        let mysql_like_update_from = matches!(
7012            self.config.dialect,
7013            Some(DialectType::MySQL) | Some(DialectType::SingleStore)
7014        ) && update.from_clause.is_some();
7015
7016        let mut set_pairs = update.set.clone();
7017
7018        // MySQL-style UPDATE doesn't support FROM after SET. Convert FROM tables to JOIN ... ON TRUE.
7019        let mut pre_set_joins = update.table_joins.clone();
7020        if mysql_like_update_from {
7021            let target_name = update
7022                .table
7023                .alias
7024                .as_ref()
7025                .map(|a| a.name.clone())
7026                .unwrap_or_else(|| update.table.name.name.clone());
7027
7028            for (col, _) in &mut set_pairs {
7029                if !col.name.contains('.') {
7030                    col.name = format!("{}.{}", target_name, col.name);
7031                }
7032            }
7033
7034            if let Some(from_clause) = &update.from_clause {
7035                for table_expr in &from_clause.expressions {
7036                    pre_set_joins.push(crate::expressions::Join {
7037                        this: table_expr.clone(),
7038                        on: Some(Expression::Boolean(crate::expressions::BooleanLiteral {
7039                            value: true,
7040                        })),
7041                        using: Vec::new(),
7042                        kind: crate::expressions::JoinKind::Inner,
7043                        use_inner_keyword: false,
7044                        use_outer_keyword: false,
7045                        deferred_condition: false,
7046                        join_hint: None,
7047                        match_condition: None,
7048                        pivots: Vec::new(),
7049                        comments: Vec::new(),
7050                        nesting_group: 0,
7051                        directed: false,
7052                    });
7053                }
7054            }
7055            for join in &update.from_joins {
7056                let mut join = join.clone();
7057                if join.on.is_none() && join.using.is_empty() {
7058                    join.on = Some(Expression::Boolean(crate::expressions::BooleanLiteral {
7059                        value: true,
7060                    }));
7061                }
7062                pre_set_joins.push(join);
7063            }
7064        }
7065
7066        // Extra tables for multi-table UPDATE (MySQL syntax)
7067        for extra_table in &update.extra_tables {
7068            self.write(", ");
7069            self.generate_table(extra_table)?;
7070        }
7071
7072        // JOINs attached to the table list (MySQL multi-table syntax)
7073        for join in &pre_set_joins {
7074            // generate_join already adds a leading space
7075            self.generate_join(join)?;
7076        }
7077
7078        // Teradata: FROM clause comes before SET
7079        let teradata_from_before_set = matches!(self.config.dialect, Some(DialectType::Teradata));
7080        if teradata_from_before_set && !mysql_like_update_from {
7081            if let Some(ref from_clause) = update.from_clause {
7082                self.write_space();
7083                self.write_keyword("FROM");
7084                self.write_space();
7085                for (i, table_expr) in from_clause.expressions.iter().enumerate() {
7086                    if i > 0 {
7087                        self.write(", ");
7088                    }
7089                    self.generate_expression(table_expr)?;
7090                }
7091            }
7092            for join in &update.from_joins {
7093                self.generate_join(join)?;
7094            }
7095        }
7096
7097        self.write_space();
7098        self.write_keyword("SET");
7099        self.write_space();
7100
7101        for (i, (col, val)) in set_pairs.iter().enumerate() {
7102            if i > 0 {
7103                self.write(", ");
7104            }
7105            self.generate_identifier(col)?;
7106            self.write(" = ");
7107            self.generate_expression(val)?;
7108        }
7109
7110        // OUTPUT clause (TSQL)
7111        if let Some(ref output) = update.output {
7112            self.generate_output_clause(output)?;
7113        }
7114
7115        // FROM clause (after SET for non-Teradata, non-MySQL dialects)
7116        if !mysql_like_update_from && !teradata_from_before_set {
7117            if let Some(ref from_clause) = update.from_clause {
7118                self.write_space();
7119                self.write_keyword("FROM");
7120                self.write_space();
7121                // Generate each table in the FROM clause
7122                for (i, table_expr) in from_clause.expressions.iter().enumerate() {
7123                    if i > 0 {
7124                        self.write(", ");
7125                    }
7126                    self.generate_expression(table_expr)?;
7127                }
7128            }
7129        }
7130
7131        if !mysql_like_update_from && !teradata_from_before_set {
7132            // JOINs after FROM clause (PostgreSQL, Snowflake, SQL Server syntax)
7133            for join in &update.from_joins {
7134                self.generate_join(join)?;
7135            }
7136        }
7137
7138        if let Some(where_clause) = &update.where_clause {
7139            self.write_space();
7140            self.write_keyword("WHERE");
7141            self.write_space();
7142            self.generate_expression(&where_clause.this)?;
7143        }
7144
7145        // RETURNING clause
7146        if !update.returning.is_empty() {
7147            self.write_space();
7148            self.write_keyword("RETURNING");
7149            self.write_space();
7150            for (i, expr) in update.returning.iter().enumerate() {
7151                if i > 0 {
7152                    self.write(", ");
7153                }
7154                self.generate_expression(expr)?;
7155            }
7156        }
7157
7158        // ORDER BY clause (MySQL)
7159        if let Some(ref order_by) = update.order_by {
7160            self.write_space();
7161            self.generate_order_by(order_by)?;
7162        }
7163
7164        // LIMIT clause (MySQL)
7165        if let Some(ref limit) = update.limit {
7166            self.write_space();
7167            self.write_keyword("LIMIT");
7168            self.write_space();
7169            self.generate_expression(limit)?;
7170        }
7171
7172        Ok(())
7173    }
7174
7175    fn generate_delete(&mut self, delete: &Delete) -> Result<()> {
7176        // Output WITH clause if present
7177        if let Some(with) = &delete.with {
7178            self.generate_with(with)?;
7179            self.write_space();
7180        }
7181
7182        // Output leading comments before DELETE
7183        for comment in &delete.leading_comments {
7184            self.write_formatted_comment(comment);
7185            self.write(" ");
7186        }
7187
7188        // MySQL multi-table DELETE or TSQL DELETE with OUTPUT before FROM
7189        if !delete.tables.is_empty() && !delete.tables_from_using {
7190            // DELETE t1[, t2] [OUTPUT ...] FROM ... syntax (tables before FROM)
7191            self.write_keyword("DELETE");
7192            self.write_space();
7193            for (i, tbl) in delete.tables.iter().enumerate() {
7194                if i > 0 {
7195                    self.write(", ");
7196                }
7197                self.generate_table(tbl)?;
7198            }
7199            // TSQL: OUTPUT clause between target table and FROM
7200            if let Some(ref output) = delete.output {
7201                self.generate_output_clause(output)?;
7202            }
7203            self.write_space();
7204            self.write_keyword("FROM");
7205            self.write_space();
7206            self.generate_table(&delete.table)?;
7207        } else if !delete.tables.is_empty() && delete.tables_from_using {
7208            // DELETE FROM t1, t2 USING ... syntax (tables after FROM)
7209            self.write_keyword("DELETE FROM");
7210            self.write_space();
7211            for (i, tbl) in delete.tables.iter().enumerate() {
7212                if i > 0 {
7213                    self.write(", ");
7214                }
7215                self.generate_table(tbl)?;
7216            }
7217        } else if delete.no_from && matches!(self.config.dialect, Some(DialectType::BigQuery)) {
7218            // BigQuery-style DELETE without FROM keyword
7219            self.write_keyword("DELETE");
7220            self.write_space();
7221            self.generate_table(&delete.table)?;
7222        } else {
7223            self.write_keyword("DELETE FROM");
7224            self.write_space();
7225            self.generate_table(&delete.table)?;
7226        }
7227
7228        // ClickHouse: ON CLUSTER clause
7229        if let Some(ref on_cluster) = delete.on_cluster {
7230            self.write_space();
7231            self.generate_on_cluster(on_cluster)?;
7232        }
7233
7234        // FORCE INDEX hint (MySQL)
7235        if let Some(ref idx) = delete.force_index {
7236            self.write_space();
7237            self.write_keyword("FORCE INDEX");
7238            self.write(" (");
7239            self.write(idx);
7240            self.write(")");
7241        }
7242
7243        // Optional alias
7244        if let Some(ref alias) = delete.alias {
7245            self.write_space();
7246            if delete.alias_explicit_as
7247                || matches!(self.config.dialect, Some(DialectType::BigQuery))
7248            {
7249                self.write_keyword("AS");
7250                self.write_space();
7251            }
7252            self.generate_identifier(alias)?;
7253        }
7254
7255        // JOINs (MySQL multi-table) - when NOT tables_from_using, JOINs come before USING
7256        if !delete.tables_from_using {
7257            for join in &delete.joins {
7258                self.generate_join(join)?;
7259            }
7260        }
7261
7262        // USING clause (PostgreSQL/DuckDB/MySQL)
7263        if !delete.using.is_empty() {
7264            self.write_space();
7265            self.write_keyword("USING");
7266            for (i, table) in delete.using.iter().enumerate() {
7267                if i > 0 {
7268                    self.write(",");
7269                }
7270                self.write_space();
7271                // Check if the table has subquery hints (DuckDB USING with subquery)
7272                if !table.hints.is_empty() && table.name.is_empty() {
7273                    // Subquery in USING: (VALUES ...) AS alias(cols)
7274                    self.generate_expression(&table.hints[0])?;
7275                    if let Some(ref alias) = table.alias {
7276                        self.write_space();
7277                        if table.alias_explicit_as {
7278                            self.write_keyword("AS");
7279                            self.write_space();
7280                        }
7281                        self.generate_identifier(alias)?;
7282                        if !table.column_aliases.is_empty() {
7283                            self.write("(");
7284                            for (j, col_alias) in table.column_aliases.iter().enumerate() {
7285                                if j > 0 {
7286                                    self.write(", ");
7287                                }
7288                                self.generate_identifier(col_alias)?;
7289                            }
7290                            self.write(")");
7291                        }
7292                    }
7293                } else {
7294                    self.generate_table(table)?;
7295                }
7296            }
7297        }
7298
7299        // JOINs (MySQL multi-table) - when tables_from_using, JOINs come after USING
7300        if delete.tables_from_using {
7301            for join in &delete.joins {
7302                self.generate_join(join)?;
7303            }
7304        }
7305
7306        // OUTPUT clause (TSQL) - only if not already emitted in the early position
7307        let output_already_emitted =
7308            !delete.tables.is_empty() && !delete.tables_from_using && delete.output.is_some();
7309        if !output_already_emitted {
7310            if let Some(ref output) = delete.output {
7311                self.generate_output_clause(output)?;
7312            }
7313        }
7314
7315        if let Some(where_clause) = &delete.where_clause {
7316            self.write_space();
7317            self.write_keyword("WHERE");
7318            self.write_space();
7319            self.generate_expression(&where_clause.this)?;
7320        }
7321
7322        // ORDER BY clause (MySQL)
7323        if let Some(ref order_by) = delete.order_by {
7324            self.write_space();
7325            self.generate_order_by(order_by)?;
7326        }
7327
7328        // LIMIT clause (MySQL)
7329        if let Some(ref limit) = delete.limit {
7330            self.write_space();
7331            self.write_keyword("LIMIT");
7332            self.write_space();
7333            self.generate_expression(limit)?;
7334        }
7335
7336        // RETURNING clause (PostgreSQL)
7337        if !delete.returning.is_empty() {
7338            self.write_space();
7339            self.write_keyword("RETURNING");
7340            self.write_space();
7341            for (i, expr) in delete.returning.iter().enumerate() {
7342                if i > 0 {
7343                    self.write(", ");
7344                }
7345                self.generate_expression(expr)?;
7346            }
7347        }
7348
7349        Ok(())
7350    }
7351
7352    // ==================== DDL Generation ====================
7353
7354    fn generate_create_table(&mut self, ct: &CreateTable) -> Result<()> {
7355        // Athena: Determine if this is Hive-style DDL or Trino-style DML
7356        // CREATE TABLE AS SELECT uses Trino (double quotes)
7357        // CREATE TABLE (without AS SELECT) and CREATE EXTERNAL TABLE use Hive (backticks)
7358        let saved_athena_hive_context = self.athena_hive_context;
7359        let is_clickhouse = matches!(self.config.dialect, Some(DialectType::ClickHouse));
7360        if matches!(
7361            self.config.dialect,
7362            Some(crate::dialects::DialectType::Athena)
7363        ) {
7364            // Use Hive context if:
7365            // 1. It's an EXTERNAL table, OR
7366            // 2. There's no AS SELECT clause
7367            let is_external = ct
7368                .table_modifier
7369                .as_ref()
7370                .map(|m| m.eq_ignore_ascii_case("EXTERNAL"))
7371                .unwrap_or(false);
7372            let has_as_select = ct.as_select.is_some();
7373            self.athena_hive_context = is_external || !has_as_select;
7374        }
7375
7376        // TSQL: Convert CREATE TABLE AS SELECT to SELECT * INTO table FROM (subquery) AS temp
7377        if matches!(
7378            self.config.dialect,
7379            Some(crate::dialects::DialectType::TSQL)
7380        ) {
7381            if let Some(ref query) = ct.as_select {
7382                // Output WITH CTE clause if present
7383                if let Some(with_cte) = &ct.with_cte {
7384                    self.generate_with(with_cte)?;
7385                    self.write_space();
7386                }
7387
7388                // Generate: SELECT * INTO [table] FROM (subquery) AS temp
7389                self.write_keyword("SELECT");
7390                self.write(" * ");
7391                self.write_keyword("INTO");
7392                self.write_space();
7393
7394                // If temporary, prefix with # for TSQL temp table
7395                if ct.temporary {
7396                    self.write("#");
7397                }
7398                self.generate_table(&ct.name)?;
7399
7400                self.write_space();
7401                self.write_keyword("FROM");
7402                self.write(" (");
7403                // For TSQL, add aliases to select columns to preserve column names
7404                let aliased_query = Self::add_column_aliases_to_query(query.clone());
7405                self.generate_expression(&aliased_query)?;
7406                self.write(") ");
7407                self.write_keyword("AS");
7408                self.write(" temp");
7409                return Ok(());
7410            }
7411        }
7412
7413        // Output WITH CTE clause if present
7414        if let Some(with_cte) = &ct.with_cte {
7415            self.generate_with(with_cte)?;
7416            self.write_space();
7417        }
7418
7419        // Output leading comments before CREATE
7420        for comment in &ct.leading_comments {
7421            self.write_formatted_comment(comment);
7422            self.write(" ");
7423        }
7424        self.write_keyword("CREATE");
7425
7426        if ct.or_replace {
7427            self.write_space();
7428            self.write_keyword("OR REPLACE");
7429        }
7430
7431        if ct.temporary {
7432            self.write_space();
7433            // Oracle uses GLOBAL TEMPORARY TABLE syntax
7434            if matches!(self.config.dialect, Some(DialectType::Oracle)) {
7435                self.write_keyword("GLOBAL TEMPORARY");
7436            } else {
7437                self.write_keyword("TEMPORARY");
7438            }
7439        }
7440
7441        // Table modifier: DYNAMIC, ICEBERG, EXTERNAL, HYBRID, TRANSIENT
7442        let is_dictionary = ct
7443            .table_modifier
7444            .as_ref()
7445            .map(|m| m.eq_ignore_ascii_case("DICTIONARY"))
7446            .unwrap_or(false);
7447        if let Some(ref modifier) = ct.table_modifier {
7448            // TRANSIENT is Snowflake-specific - skip for other dialects
7449            let skip_transient = modifier.eq_ignore_ascii_case("TRANSIENT")
7450                && !matches!(self.config.dialect, Some(DialectType::Snowflake) | None);
7451            // Teradata-specific modifiers: VOLATILE, SET, MULTISET, SET TABLE combinations
7452            let is_teradata_modifier = modifier.eq_ignore_ascii_case("VOLATILE")
7453                || modifier.eq_ignore_ascii_case("SET")
7454                || modifier.eq_ignore_ascii_case("MULTISET")
7455                || modifier.to_ascii_uppercase().contains("VOLATILE")
7456                || modifier.to_ascii_uppercase().starts_with("SET ")
7457                || modifier.to_ascii_uppercase().starts_with("MULTISET ");
7458            let skip_teradata =
7459                is_teradata_modifier && !matches!(self.config.dialect, Some(DialectType::Teradata));
7460            if !skip_transient && !skip_teradata {
7461                self.write_space();
7462                self.write_keyword(modifier);
7463            }
7464        }
7465
7466        if !is_dictionary {
7467            self.write_space();
7468            self.write_keyword("TABLE");
7469        }
7470
7471        if ct.if_not_exists {
7472            self.write_space();
7473            self.write_keyword("IF NOT EXISTS");
7474        }
7475
7476        self.write_space();
7477        self.generate_table(&ct.name)?;
7478
7479        // ClickHouse: ON CLUSTER clause
7480        if let Some(ref on_cluster) = ct.on_cluster {
7481            self.write_space();
7482            self.generate_on_cluster(on_cluster)?;
7483        }
7484
7485        // Teradata: options after table name before column list (comma-separated)
7486        if matches!(
7487            self.config.dialect,
7488            Some(crate::dialects::DialectType::Teradata)
7489        ) && !ct.teradata_post_name_options.is_empty()
7490        {
7491            for opt in &ct.teradata_post_name_options {
7492                self.write(", ");
7493                self.write(opt);
7494            }
7495        }
7496
7497        // Snowflake: COPY GRANTS clause
7498        if ct.copy_grants {
7499            self.write_space();
7500            self.write_keyword("COPY GRANTS");
7501        }
7502
7503        // Snowflake: USING TEMPLATE clause (before columns or AS SELECT)
7504        if let Some(ref using_template) = ct.using_template {
7505            self.write_space();
7506            self.write_keyword("USING TEMPLATE");
7507            self.write_space();
7508            self.generate_expression(using_template)?;
7509            return Ok(());
7510        }
7511
7512        // Handle [SHALLOW | DEEP] CLONE/COPY source_table [AT(...) | BEFORE(...)]
7513        if let Some(ref clone_source) = ct.clone_source {
7514            self.write_space();
7515            if ct.is_copy && self.config.supports_table_copy {
7516                // BigQuery uses COPY
7517                self.write_keyword("COPY");
7518            } else if ct.shallow_clone {
7519                self.write_keyword("SHALLOW CLONE");
7520            } else {
7521                self.write_keyword("CLONE");
7522            }
7523            self.write_space();
7524            self.generate_table(clone_source)?;
7525            // Generate AT/BEFORE time travel clause (stored as Raw expression)
7526            if let Some(ref at_clause) = ct.clone_at_clause {
7527                self.write_space();
7528                self.generate_expression(at_clause)?;
7529            }
7530            return Ok(());
7531        }
7532
7533        // Handle PARTITION OF property
7534        // Output order: PARTITION OF <table> (<columns/constraints>) FOR VALUES ...
7535        // Columns/constraints must appear BETWEEN the table name and the partition bound spec
7536        if let Some(ref partition_of) = ct.partition_of {
7537            self.write_space();
7538
7539            // Extract the PartitionedOfProperty parts to generate them separately
7540            if let Expression::PartitionedOfProperty(ref pop) = partition_of {
7541                // Output: PARTITION OF <table>
7542                self.write_keyword("PARTITION OF");
7543                self.write_space();
7544                self.generate_expression(&pop.this)?;
7545
7546                // Output columns/constraints if present (e.g., (unitsales DEFAULT 0) or (CONSTRAINT ...))
7547                if !ct.columns.is_empty() || !ct.constraints.is_empty() {
7548                    self.write(" (");
7549                    let mut first = true;
7550                    for col in &ct.columns {
7551                        if !first {
7552                            self.write(", ");
7553                        }
7554                        first = false;
7555                        self.generate_column_def(col)?;
7556                    }
7557                    for constraint in &ct.constraints {
7558                        if !first {
7559                            self.write(", ");
7560                        }
7561                        first = false;
7562                        self.generate_table_constraint(constraint)?;
7563                    }
7564                    self.write(")");
7565                }
7566
7567                // Output partition bound spec: FOR VALUES ... or DEFAULT
7568                if let Expression::PartitionBoundSpec(_) = pop.expression.as_ref() {
7569                    self.write_space();
7570                    self.write_keyword("FOR VALUES");
7571                    self.write_space();
7572                    self.generate_expression(&pop.expression)?;
7573                } else {
7574                    self.write_space();
7575                    self.write_keyword("DEFAULT");
7576                }
7577            } else {
7578                // Fallback: generate the whole expression if it's not a PartitionedOfProperty
7579                self.generate_expression(partition_of)?;
7580
7581                // Output columns/constraints if present
7582                if !ct.columns.is_empty() || !ct.constraints.is_empty() {
7583                    self.write(" (");
7584                    let mut first = true;
7585                    for col in &ct.columns {
7586                        if !first {
7587                            self.write(", ");
7588                        }
7589                        first = false;
7590                        self.generate_column_def(col)?;
7591                    }
7592                    for constraint in &ct.constraints {
7593                        if !first {
7594                            self.write(", ");
7595                        }
7596                        first = false;
7597                        self.generate_table_constraint(constraint)?;
7598                    }
7599                    self.write(")");
7600                }
7601            }
7602
7603            // Output table properties (e.g., PARTITION BY RANGE(population))
7604            for prop in &ct.properties {
7605                self.write_space();
7606                self.generate_expression(prop)?;
7607            }
7608
7609            return Ok(());
7610        }
7611
7612        // SQLite: Inline single-column PRIMARY KEY constraints into column definition
7613        // This matches Python sqlglot's behavior for SQLite dialect
7614        self.sqlite_inline_pk_columns.clear();
7615        if matches!(
7616            self.config.dialect,
7617            Some(crate::dialects::DialectType::SQLite)
7618        ) {
7619            for constraint in &ct.constraints {
7620                if let TableConstraint::PrimaryKey { columns, name, .. } = constraint {
7621                    // Only inline if: single column, no constraint name, and column exists in table
7622                    if columns.len() == 1 && name.is_none() {
7623                        let pk_col_name = columns[0].name.to_ascii_lowercase();
7624                        // Check if this column exists in the table
7625                        if ct
7626                            .columns
7627                            .iter()
7628                            .any(|c| c.name.name.to_ascii_lowercase() == pk_col_name)
7629                        {
7630                            self.sqlite_inline_pk_columns.insert(pk_col_name);
7631                        }
7632                    }
7633                }
7634            }
7635        }
7636
7637        // Output columns if present (even for CTAS with columns)
7638        if !ct.columns.is_empty() {
7639            if self.config.pretty {
7640                // Pretty print: each column on new line
7641                self.write(" (");
7642                self.write_newline();
7643                self.indent_level += 1;
7644                for (i, col) in ct.columns.iter().enumerate() {
7645                    if i > 0 {
7646                        self.write(",");
7647                        self.write_newline();
7648                    }
7649                    self.write_indent();
7650                    self.generate_column_def(col)?;
7651                }
7652                // Table constraints (skip inlined PRIMARY KEY for SQLite)
7653                for constraint in &ct.constraints {
7654                    // Skip single-column PRIMARY KEY that was inlined for SQLite
7655                    if let TableConstraint::PrimaryKey { columns, name, .. } = constraint {
7656                        if columns.len() == 1
7657                            && name.is_none()
7658                            && self
7659                                .sqlite_inline_pk_columns
7660                                .contains(&columns[0].name.to_ascii_lowercase())
7661                        {
7662                            continue;
7663                        }
7664                    }
7665                    self.write(",");
7666                    self.write_newline();
7667                    self.write_indent();
7668                    self.generate_table_constraint(constraint)?;
7669                }
7670                self.indent_level -= 1;
7671                self.write_newline();
7672                self.write(")");
7673            } else {
7674                self.write(" (");
7675                for (i, col) in ct.columns.iter().enumerate() {
7676                    if i > 0 {
7677                        self.write(", ");
7678                    }
7679                    self.generate_column_def(col)?;
7680                }
7681                // Table constraints (skip inlined PRIMARY KEY for SQLite)
7682                let mut first_constraint = true;
7683                for constraint in &ct.constraints {
7684                    // Skip single-column PRIMARY KEY that was inlined for SQLite
7685                    if let TableConstraint::PrimaryKey { columns, name, .. } = constraint {
7686                        if columns.len() == 1
7687                            && name.is_none()
7688                            && self
7689                                .sqlite_inline_pk_columns
7690                                .contains(&columns[0].name.to_ascii_lowercase())
7691                        {
7692                            continue;
7693                        }
7694                    }
7695                    if first_constraint {
7696                        self.write(", ");
7697                        first_constraint = false;
7698                    } else {
7699                        self.write(", ");
7700                    }
7701                    self.generate_table_constraint(constraint)?;
7702                }
7703                self.write(")");
7704            }
7705        } else if !ct.constraints.is_empty() {
7706            // No columns but constraints exist (e.g., CREATE TABLE A LIKE B or CREATE TABLE A TAG (...))
7707            let has_like_only = ct
7708                .constraints
7709                .iter()
7710                .all(|c| matches!(c, TableConstraint::Like { .. }));
7711            let has_tags_only = ct
7712                .constraints
7713                .iter()
7714                .all(|c| matches!(c, TableConstraint::Tags(_)));
7715            // PostgreSQL: CREATE TABLE A (LIKE B INCLUDING ALL) (with parens)
7716            // Most dialects: CREATE TABLE A LIKE B (no parens)
7717            // Snowflake: CREATE TABLE A TAG (...) (no outer parens, but TAG has its own)
7718            let is_pg_like = matches!(
7719                self.config.dialect,
7720                Some(crate::dialects::DialectType::PostgreSQL)
7721                    | Some(crate::dialects::DialectType::CockroachDB)
7722                    | Some(crate::dialects::DialectType::Materialize)
7723                    | Some(crate::dialects::DialectType::RisingWave)
7724                    | Some(crate::dialects::DialectType::Redshift)
7725                    | Some(crate::dialects::DialectType::Presto)
7726                    | Some(crate::dialects::DialectType::Trino)
7727                    | Some(crate::dialects::DialectType::Athena)
7728            );
7729            let use_parens = if has_like_only {
7730                is_pg_like
7731            } else {
7732                !has_tags_only
7733            };
7734            if self.config.pretty && use_parens {
7735                self.write(" (");
7736                self.write_newline();
7737                self.indent_level += 1;
7738                for (i, constraint) in ct.constraints.iter().enumerate() {
7739                    if i > 0 {
7740                        self.write(",");
7741                        self.write_newline();
7742                    }
7743                    self.write_indent();
7744                    self.generate_table_constraint(constraint)?;
7745                }
7746                self.indent_level -= 1;
7747                self.write_newline();
7748                self.write(")");
7749            } else {
7750                if use_parens {
7751                    self.write(" (");
7752                } else {
7753                    self.write_space();
7754                }
7755                for (i, constraint) in ct.constraints.iter().enumerate() {
7756                    if i > 0 {
7757                        self.write(", ");
7758                    }
7759                    self.generate_table_constraint(constraint)?;
7760                }
7761                if use_parens {
7762                    self.write(")");
7763                }
7764            }
7765        }
7766
7767        // TSQL ON filegroup or ON filegroup (partition_column) clause
7768        if let Some(ref on_prop) = ct.on_property {
7769            self.write(" ");
7770            self.write_keyword("ON");
7771            self.write(" ");
7772            self.generate_expression(&on_prop.this)?;
7773        }
7774
7775        // Output SchemaCommentProperty BEFORE WITH properties (Presto/Hive/Spark style)
7776        // For ClickHouse, SchemaCommentProperty goes after AS SELECT, handled later
7777        if !is_clickhouse {
7778            for prop in &ct.properties {
7779                if let Expression::SchemaCommentProperty(_) = prop {
7780                    if self.config.pretty {
7781                        self.write_newline();
7782                    } else {
7783                        self.write_space();
7784                    }
7785                    self.generate_expression(prop)?;
7786                }
7787            }
7788        }
7789
7790        // WITH properties (output after columns if columns exist, otherwise before AS)
7791        if !ct.with_properties.is_empty() {
7792            // Snowflake ICEBERG/DYNAMIC TABLE: output properties inline (space-separated, no WITH wrapper)
7793            let is_snowflake_special_table = matches!(
7794                self.config.dialect,
7795                Some(crate::dialects::DialectType::Snowflake)
7796            ) && (ct.table_modifier.as_deref() == Some("ICEBERG")
7797                || ct.table_modifier.as_deref() == Some("DYNAMIC"));
7798            if is_snowflake_special_table {
7799                for (key, value) in &ct.with_properties {
7800                    self.write_space();
7801                    self.write(key);
7802                    self.write("=");
7803                    self.write(value);
7804                }
7805            } else if self.config.pretty {
7806                self.write_newline();
7807                self.write_keyword("WITH");
7808                self.write(" (");
7809                self.write_newline();
7810                self.indent_level += 1;
7811                for (i, (key, value)) in ct.with_properties.iter().enumerate() {
7812                    if i > 0 {
7813                        self.write(",");
7814                        self.write_newline();
7815                    }
7816                    self.write_indent();
7817                    self.write(key);
7818                    self.write("=");
7819                    self.write(value);
7820                }
7821                self.indent_level -= 1;
7822                self.write_newline();
7823                self.write(")");
7824            } else {
7825                self.write_space();
7826                self.write_keyword("WITH");
7827                self.write(" (");
7828                for (i, (key, value)) in ct.with_properties.iter().enumerate() {
7829                    if i > 0 {
7830                        self.write(", ");
7831                    }
7832                    self.write(key);
7833                    self.write("=");
7834                    self.write(value);
7835                }
7836                self.write(")");
7837            }
7838        }
7839
7840        let (pre_as_properties, post_as_properties): (Vec<&Expression>, Vec<&Expression>) =
7841            if is_clickhouse && ct.as_select.is_some() {
7842                let mut pre = Vec::new();
7843                let mut post = Vec::new();
7844                for prop in &ct.properties {
7845                    if matches!(prop, Expression::SchemaCommentProperty(_)) {
7846                        post.push(prop);
7847                    } else {
7848                        pre.push(prop);
7849                    }
7850                }
7851                (pre, post)
7852            } else {
7853                (ct.properties.iter().collect(), Vec::new())
7854            };
7855
7856        // Table properties like DEFAULT COLLATE (BigQuery), OPTIONS (...), TBLPROPERTIES (...), or PROPERTIES (...)
7857        for prop in pre_as_properties {
7858            // SchemaCommentProperty was already output before WITH properties (except for ClickHouse)
7859            if !is_clickhouse && matches!(prop, Expression::SchemaCommentProperty(_)) {
7860                continue;
7861            }
7862            if self.config.pretty {
7863                self.write_newline();
7864            } else {
7865                self.write_space();
7866            }
7867            // BigQuery: Properties containing OPTIONS should be wrapped with OPTIONS (...)
7868            // Hive: Properties should be wrapped with TBLPROPERTIES (...)
7869            // Doris/StarRocks: Properties should be wrapped with PROPERTIES (...)
7870            if let Expression::Properties(props) = prop {
7871                let is_hive_dialect = matches!(
7872                    self.config.dialect,
7873                    Some(crate::dialects::DialectType::Hive)
7874                        | Some(crate::dialects::DialectType::Spark)
7875                        | Some(crate::dialects::DialectType::Databricks)
7876                        | Some(crate::dialects::DialectType::Athena)
7877                );
7878                let is_doris_starrocks = matches!(
7879                    self.config.dialect,
7880                    Some(crate::dialects::DialectType::Doris)
7881                        | Some(crate::dialects::DialectType::StarRocks)
7882                );
7883                if is_hive_dialect {
7884                    self.generate_tblproperties_clause(&props.expressions)?;
7885                } else if is_doris_starrocks {
7886                    self.generate_properties_clause(&props.expressions)?;
7887                } else {
7888                    self.generate_options_clause(&props.expressions)?;
7889                }
7890            } else {
7891                self.generate_expression(prop)?;
7892            }
7893        }
7894
7895        // Post-table properties like TSQL WITH(SYSTEM_VERSIONING=ON(...)) or Doris PROPERTIES
7896        for prop in &ct.post_table_properties {
7897            if let Expression::WithSystemVersioningProperty(ref svp) = prop {
7898                self.write(" WITH(");
7899                self.generate_system_versioning_content(svp)?;
7900                self.write(")");
7901            } else if let Expression::Properties(props) = prop {
7902                // Doris/StarRocks: PROPERTIES ('key'='value', ...) in post_table_properties
7903                let is_doris_starrocks = matches!(
7904                    self.config.dialect,
7905                    Some(crate::dialects::DialectType::Doris)
7906                        | Some(crate::dialects::DialectType::StarRocks)
7907                );
7908                self.write_space();
7909                if is_doris_starrocks {
7910                    self.generate_properties_clause(&props.expressions)?;
7911                } else {
7912                    self.generate_options_clause(&props.expressions)?;
7913                }
7914            } else {
7915                self.write_space();
7916                self.generate_expression(prop)?;
7917            }
7918        }
7919
7920        // StarRocks ROLLUP property: ROLLUP (r1(col1, col2), r2(col1))
7921        // Only output for StarRocks target
7922        if let Some(ref rollup) = ct.rollup {
7923            if matches!(self.config.dialect, Some(DialectType::StarRocks)) {
7924                self.write_space();
7925                self.generate_rollup_property(rollup)?;
7926            }
7927        }
7928
7929        // MySQL table options (ENGINE=val, AUTO_INCREMENT=val, etc.)
7930        // Only output for MySQL-compatible dialects; strip for others during transpilation
7931        // COMMENT is also used by Hive/Spark so we selectively preserve it
7932        let is_mysql_compatible = matches!(
7933            self.config.dialect,
7934            Some(DialectType::MySQL)
7935                | Some(DialectType::SingleStore)
7936                | Some(DialectType::Doris)
7937                | Some(DialectType::StarRocks)
7938                | None
7939        );
7940        let is_hive_compatible = matches!(
7941            self.config.dialect,
7942            Some(DialectType::Hive)
7943                | Some(DialectType::Spark)
7944                | Some(DialectType::Databricks)
7945                | Some(DialectType::Athena)
7946        );
7947        let mysql_pretty_options =
7948            self.config.pretty && matches!(self.config.dialect, Some(DialectType::MySQL));
7949        for (key, value) in &ct.mysql_table_options {
7950            // Skip non-MySQL-specific options for non-MySQL targets
7951            let should_output = if is_mysql_compatible {
7952                true
7953            } else if is_hive_compatible && key == "COMMENT" {
7954                true // COMMENT is valid in Hive/Spark table definitions
7955            } else {
7956                false
7957            };
7958            if should_output {
7959                if mysql_pretty_options {
7960                    self.write_newline();
7961                    self.write_indent();
7962                } else {
7963                    self.write_space();
7964                }
7965                self.write_keyword(key);
7966                // StarRocks/Doris: COMMENT 'value' (no =), others: COMMENT='value'
7967                if key == "COMMENT" && !self.config.schema_comment_with_eq {
7968                    self.write_space();
7969                } else {
7970                    self.write("=");
7971                }
7972                self.write(value);
7973            }
7974        }
7975
7976        // Spark/Databricks: USING PARQUET for temporary tables that don't already have a storage format
7977        if ct.temporary
7978            && matches!(
7979                self.config.dialect,
7980                Some(DialectType::Spark) | Some(DialectType::Databricks)
7981            )
7982            && ct.as_select.is_none()
7983        {
7984            self.write_space();
7985            self.write_keyword("USING PARQUET");
7986        }
7987
7988        // PostgreSQL INHERITS clause
7989        if !ct.inherits.is_empty() {
7990            self.write_space();
7991            self.write_keyword("INHERITS");
7992            self.write(" (");
7993            for (i, parent) in ct.inherits.iter().enumerate() {
7994                if i > 0 {
7995                    self.write(", ");
7996                }
7997                self.generate_table(parent)?;
7998            }
7999            self.write(")");
8000        }
8001
8002        // CREATE TABLE AS SELECT
8003        if let Some(ref query) = ct.as_select {
8004            self.write_space();
8005            self.write_keyword("AS");
8006            self.write_space();
8007            if ct.as_select_parenthesized {
8008                self.write("(");
8009            }
8010            self.generate_expression(query)?;
8011            if ct.as_select_parenthesized {
8012                self.write(")");
8013            }
8014
8015            // Teradata: WITH DATA / WITH NO DATA
8016            if let Some(with_data) = ct.with_data {
8017                self.write_space();
8018                self.write_keyword("WITH");
8019                if !with_data {
8020                    self.write_space();
8021                    self.write_keyword("NO");
8022                }
8023                self.write_space();
8024                self.write_keyword("DATA");
8025            }
8026
8027            // Teradata: AND STATISTICS / AND NO STATISTICS
8028            if let Some(with_statistics) = ct.with_statistics {
8029                self.write_space();
8030                self.write_keyword("AND");
8031                if !with_statistics {
8032                    self.write_space();
8033                    self.write_keyword("NO");
8034                }
8035                self.write_space();
8036                self.write_keyword("STATISTICS");
8037            }
8038
8039            // Teradata: Index specifications
8040            for index in &ct.teradata_indexes {
8041                self.write_space();
8042                match index.kind {
8043                    TeradataIndexKind::NoPrimary => {
8044                        self.write_keyword("NO PRIMARY INDEX");
8045                    }
8046                    TeradataIndexKind::Primary => {
8047                        self.write_keyword("PRIMARY INDEX");
8048                    }
8049                    TeradataIndexKind::PrimaryAmp => {
8050                        self.write_keyword("PRIMARY AMP INDEX");
8051                    }
8052                    TeradataIndexKind::Unique => {
8053                        self.write_keyword("UNIQUE INDEX");
8054                    }
8055                    TeradataIndexKind::UniquePrimary => {
8056                        self.write_keyword("UNIQUE PRIMARY INDEX");
8057                    }
8058                    TeradataIndexKind::Secondary => {
8059                        self.write_keyword("INDEX");
8060                    }
8061                }
8062                // Output index name if present
8063                if let Some(ref name) = index.name {
8064                    self.write_space();
8065                    self.write(name);
8066                }
8067                // Output columns if present
8068                if !index.columns.is_empty() {
8069                    self.write(" (");
8070                    for (i, col) in index.columns.iter().enumerate() {
8071                        if i > 0 {
8072                            self.write(", ");
8073                        }
8074                        self.write(col);
8075                    }
8076                    self.write(")");
8077                }
8078            }
8079
8080            // Teradata: ON COMMIT behavior for volatile tables
8081            if let Some(ref on_commit) = ct.on_commit {
8082                self.write_space();
8083                self.write_keyword("ON COMMIT");
8084                self.write_space();
8085                match on_commit {
8086                    OnCommit::PreserveRows => self.write_keyword("PRESERVE ROWS"),
8087                    OnCommit::DeleteRows => self.write_keyword("DELETE ROWS"),
8088                }
8089            }
8090
8091            if !post_as_properties.is_empty() {
8092                for prop in post_as_properties {
8093                    self.write_space();
8094                    self.generate_expression(prop)?;
8095                }
8096            }
8097
8098            // Restore Athena Hive context before early return
8099            self.athena_hive_context = saved_athena_hive_context;
8100            return Ok(());
8101        }
8102
8103        // ON COMMIT behavior (for non-CTAS tables)
8104        if let Some(ref on_commit) = ct.on_commit {
8105            self.write_space();
8106            self.write_keyword("ON COMMIT");
8107            self.write_space();
8108            match on_commit {
8109                OnCommit::PreserveRows => self.write_keyword("PRESERVE ROWS"),
8110                OnCommit::DeleteRows => self.write_keyword("DELETE ROWS"),
8111            }
8112        }
8113
8114        // Restore Athena Hive context
8115        self.athena_hive_context = saved_athena_hive_context;
8116
8117        Ok(())
8118    }
8119
8120    /// Generate column definition as an expression (for ROWS FROM alias columns, XMLTABLE/JSON_TABLE)
8121    /// Outputs: "col_name" TYPE [PATH 'xpath'] (not the full CREATE TABLE column definition)
8122    fn generate_column_def_expr(&mut self, col: &ColumnDef) -> Result<()> {
8123        // Output column name
8124        self.generate_identifier(&col.name)?;
8125        // Output data type if known
8126        if !matches!(col.data_type, DataType::Unknown) {
8127            self.write_space();
8128            self.generate_data_type(&col.data_type)?;
8129        }
8130        // Output PATH constraint if present (for XMLTABLE/JSON_TABLE columns)
8131        for constraint in &col.constraints {
8132            if let ColumnConstraint::Path(path_expr) = constraint {
8133                self.write_space();
8134                self.write_keyword("PATH");
8135                self.write_space();
8136                self.generate_expression(path_expr)?;
8137            }
8138        }
8139        Ok(())
8140    }
8141
8142    fn generate_column_def(&mut self, col: &ColumnDef) -> Result<()> {
8143        // Check if this is a TSQL computed column (no data type)
8144        let has_computed_no_type = matches!(&col.data_type, DataType::Custom { name } if name.is_empty())
8145            && col
8146                .constraints
8147                .iter()
8148                .any(|c| matches!(c, ColumnConstraint::ComputedColumn(_)));
8149        // Some dialects (notably TSQL/Fabric) do not include an explicit type for computed columns.
8150        let omit_computed_type = !self.config.computed_column_with_type
8151            && col
8152                .constraints
8153                .iter()
8154                .any(|c| matches!(c, ColumnConstraint::ComputedColumn(_)));
8155
8156        // Check if this is a partition column spec (no data type, type is Unknown)
8157        // This is used in PostgreSQL PARTITION OF syntax where columns only have constraints
8158        let is_partition_column_spec = matches!(col.data_type, DataType::Unknown);
8159
8160        // Check if this is a DYNAMIC TABLE column (no data type, empty Custom name, no constraints)
8161        // Also check the no_type flag for SQLite columns without types
8162        let has_no_type = col.no_type
8163            || (matches!(&col.data_type, DataType::Custom { name } if name.is_empty())
8164                && col.constraints.is_empty());
8165
8166        self.generate_identifier(&col.name)?;
8167
8168        // Check for SERIAL/BIGSERIAL/SMALLSERIAL expansion for Materialize and PostgreSQL
8169        let serial_expansion = if matches!(
8170            self.config.dialect,
8171            Some(DialectType::Materialize) | Some(DialectType::PostgreSQL)
8172        ) {
8173            if let DataType::Custom { ref name } = col.data_type {
8174                if name.eq_ignore_ascii_case("SERIAL") {
8175                    Some("INT")
8176                } else if name.eq_ignore_ascii_case("BIGSERIAL") {
8177                    Some("BIGINT")
8178                } else if name.eq_ignore_ascii_case("SMALLSERIAL") {
8179                    Some("SMALLINT")
8180                } else {
8181                    None
8182                }
8183            } else {
8184                None
8185            }
8186        } else {
8187            None
8188        };
8189
8190        if !has_computed_no_type && !omit_computed_type && !is_partition_column_spec && !has_no_type
8191        {
8192            self.write_space();
8193            // ClickHouse CREATE TABLE column types: suppress automatic Nullable wrapping
8194            // since ClickHouse uses explicit Nullable() in its type system.
8195            let saved_nullable_depth = self.clickhouse_nullable_depth;
8196            if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
8197                self.clickhouse_nullable_depth = -1;
8198            }
8199            if let Some(int_type) = serial_expansion {
8200                // SERIAL -> INT (+ constraints added below)
8201                self.write_keyword(int_type);
8202            } else if col.unsigned && matches!(self.config.dialect, Some(DialectType::DuckDB)) {
8203                // For DuckDB: convert unsigned integer types to their unsigned equivalents
8204                let unsigned_type = match &col.data_type {
8205                    DataType::Int { .. } => Some("UINTEGER"),
8206                    DataType::BigInt { .. } => Some("UBIGINT"),
8207                    DataType::SmallInt { .. } => Some("USMALLINT"),
8208                    DataType::TinyInt { .. } => Some("UTINYINT"),
8209                    _ => None,
8210                };
8211                if let Some(utype) = unsigned_type {
8212                    self.write_keyword(utype);
8213                } else {
8214                    self.generate_data_type(&col.data_type)?;
8215                }
8216            } else {
8217                self.generate_data_type(&col.data_type)?;
8218            }
8219            self.clickhouse_nullable_depth = saved_nullable_depth;
8220        }
8221
8222        // MySQL type modifiers (must come right after data type)
8223        // Skip UNSIGNED for DuckDB (already mapped to unsigned type above)
8224        if col.unsigned && !matches!(self.config.dialect, Some(DialectType::DuckDB)) {
8225            self.write_space();
8226            self.write_keyword("UNSIGNED");
8227        }
8228        if col.zerofill {
8229            self.write_space();
8230            self.write_keyword("ZEROFILL");
8231        }
8232
8233        // Teradata column attributes (must come right after data type, in specific order)
8234        // ORDER: CHARACTER SET, UPPERCASE, CASESPECIFIC, FORMAT, TITLE, INLINE LENGTH, COMPRESS
8235
8236        if let Some(ref charset) = col.character_set {
8237            self.write_space();
8238            self.write_keyword("CHARACTER SET");
8239            self.write_space();
8240            self.write(charset);
8241        }
8242
8243        if col.uppercase {
8244            self.write_space();
8245            self.write_keyword("UPPERCASE");
8246        }
8247
8248        if let Some(casespecific) = col.casespecific {
8249            self.write_space();
8250            if casespecific {
8251                self.write_keyword("CASESPECIFIC");
8252            } else {
8253                self.write_keyword("NOT CASESPECIFIC");
8254            }
8255        }
8256
8257        if let Some(ref format) = col.format {
8258            self.write_space();
8259            self.write_keyword("FORMAT");
8260            self.write(" '");
8261            self.write(format);
8262            self.write("'");
8263        }
8264
8265        if let Some(ref title) = col.title {
8266            self.write_space();
8267            self.write_keyword("TITLE");
8268            self.write(" '");
8269            self.write(title);
8270            self.write("'");
8271        }
8272
8273        if let Some(length) = col.inline_length {
8274            self.write_space();
8275            self.write_keyword("INLINE LENGTH");
8276            self.write(" ");
8277            self.write(&length.to_string());
8278        }
8279
8280        if let Some(ref compress) = col.compress {
8281            self.write_space();
8282            self.write_keyword("COMPRESS");
8283            if !compress.is_empty() {
8284                // Single string literal: output without parentheses (Teradata syntax)
8285                if compress.len() == 1 {
8286                    if let Expression::Literal(lit) = &compress[0] {
8287                        if let Literal::String(_) = lit.as_ref() {
8288                        self.write_space();
8289                        self.generate_expression(&compress[0])?;
8290                    }
8291                    } else {
8292                        self.write(" (");
8293                        self.generate_expression(&compress[0])?;
8294                        self.write(")");
8295                    }
8296                } else {
8297                    self.write(" (");
8298                    for (i, val) in compress.iter().enumerate() {
8299                        if i > 0 {
8300                            self.write(", ");
8301                        }
8302                        self.generate_expression(val)?;
8303                    }
8304                    self.write(")");
8305                }
8306            }
8307        }
8308
8309        // Column constraints - output in original order if constraint_order is populated
8310        // Otherwise fall back to legacy fixed order for backward compatibility
8311        if !col.constraint_order.is_empty() {
8312            // Use constraint_order for original ordering
8313            // Track indices for constraints stored in the constraints Vec
8314            let mut references_idx = 0;
8315            let mut check_idx = 0;
8316            let mut generated_idx = 0;
8317            let mut collate_idx = 0;
8318            let mut comment_idx = 0;
8319            // The preprocessing in dialects/mod.rs now handles the correct ordering of
8320            // NOT NULL relative to IDENTITY for PostgreSQL, so no deferral needed here.
8321            let defer_not_null_after_identity = false;
8322            let mut pending_not_null_after_identity = false;
8323
8324            for constraint_type in &col.constraint_order {
8325                match constraint_type {
8326                    ConstraintType::PrimaryKey => {
8327                        // Materialize doesn't support PRIMARY KEY column constraints
8328                        if col.primary_key
8329                            && !matches!(self.config.dialect, Some(DialectType::Materialize))
8330                        {
8331                            if let Some(ref cname) = col.primary_key_constraint_name {
8332                                self.write_space();
8333                                self.write_keyword("CONSTRAINT");
8334                                self.write_space();
8335                                self.write(cname);
8336                            }
8337                            self.write_space();
8338                            self.write_keyword("PRIMARY KEY");
8339                            if let Some(ref order) = col.primary_key_order {
8340                                self.write_space();
8341                                match order {
8342                                    SortOrder::Asc => self.write_keyword("ASC"),
8343                                    SortOrder::Desc => self.write_keyword("DESC"),
8344                                }
8345                            }
8346                        }
8347                    }
8348                    ConstraintType::Unique => {
8349                        if col.unique {
8350                            if let Some(ref cname) = col.unique_constraint_name {
8351                                self.write_space();
8352                                self.write_keyword("CONSTRAINT");
8353                                self.write_space();
8354                                self.write(cname);
8355                            }
8356                            self.write_space();
8357                            self.write_keyword("UNIQUE");
8358                            // PostgreSQL 15+: NULLS NOT DISTINCT
8359                            if col.unique_nulls_not_distinct {
8360                                self.write(" NULLS NOT DISTINCT");
8361                            }
8362                        }
8363                    }
8364                    ConstraintType::NotNull => {
8365                        if col.nullable == Some(false) {
8366                            if defer_not_null_after_identity {
8367                                pending_not_null_after_identity = true;
8368                                continue;
8369                            }
8370                            if let Some(ref cname) = col.not_null_constraint_name {
8371                                self.write_space();
8372                                self.write_keyword("CONSTRAINT");
8373                                self.write_space();
8374                                self.write(cname);
8375                            }
8376                            self.write_space();
8377                            self.write_keyword("NOT NULL");
8378                        }
8379                    }
8380                    ConstraintType::Null => {
8381                        if col.nullable == Some(true) {
8382                            self.write_space();
8383                            self.write_keyword("NULL");
8384                        }
8385                    }
8386                    ConstraintType::Default => {
8387                        if let Some(ref default) = col.default {
8388                            self.write_space();
8389                            self.write_keyword("DEFAULT");
8390                            self.write_space();
8391                            self.generate_expression(default)?;
8392                        }
8393                    }
8394                    ConstraintType::AutoIncrement => {
8395                        if col.auto_increment {
8396                            // DuckDB doesn't support AUTO_INCREMENT - skip entirely
8397                            if matches!(
8398                                self.config.dialect,
8399                                Some(crate::dialects::DialectType::DuckDB)
8400                            ) {
8401                                // Skip - DuckDB uses sequences or rowid instead
8402                            } else if matches!(
8403                                self.config.dialect,
8404                                Some(crate::dialects::DialectType::Materialize)
8405                            ) {
8406                                // Materialize strips AUTO_INCREMENT but adds NOT NULL
8407                                if !matches!(col.nullable, Some(false)) {
8408                                    self.write_space();
8409                                    self.write_keyword("NOT NULL");
8410                                }
8411                            } else if matches!(
8412                                self.config.dialect,
8413                                Some(crate::dialects::DialectType::PostgreSQL)
8414                            ) {
8415                                // PostgreSQL: AUTO_INCREMENT -> GENERATED BY DEFAULT AS IDENTITY
8416                                self.write_space();
8417                                self.generate_auto_increment_keyword(col)?;
8418                            } else {
8419                                self.write_space();
8420                                self.generate_auto_increment_keyword(col)?;
8421                                if pending_not_null_after_identity {
8422                                    self.write_space();
8423                                    self.write_keyword("NOT NULL");
8424                                    pending_not_null_after_identity = false;
8425                                }
8426                            }
8427                        } // close else for DuckDB skip
8428                    }
8429                    ConstraintType::References => {
8430                        // Find next References constraint
8431                        while references_idx < col.constraints.len() {
8432                            if let ColumnConstraint::References(fk_ref) =
8433                                &col.constraints[references_idx]
8434                            {
8435                                // CONSTRAINT name if present
8436                                if let Some(ref name) = fk_ref.constraint_name {
8437                                    self.write_space();
8438                                    self.write_keyword("CONSTRAINT");
8439                                    self.write_space();
8440                                    self.write(name);
8441                                }
8442                                self.write_space();
8443                                if fk_ref.has_foreign_key_keywords {
8444                                    self.write_keyword("FOREIGN KEY");
8445                                    self.write_space();
8446                                }
8447                                self.write_keyword("REFERENCES");
8448                                self.write_space();
8449                                self.generate_table(&fk_ref.table)?;
8450                                if !fk_ref.columns.is_empty() {
8451                                    self.write(" (");
8452                                    for (i, c) in fk_ref.columns.iter().enumerate() {
8453                                        if i > 0 {
8454                                            self.write(", ");
8455                                        }
8456                                        self.generate_identifier(c)?;
8457                                    }
8458                                    self.write(")");
8459                                }
8460                                self.generate_referential_actions(fk_ref)?;
8461                                references_idx += 1;
8462                                break;
8463                            }
8464                            references_idx += 1;
8465                        }
8466                    }
8467                    ConstraintType::Check => {
8468                        // Find next Check constraint
8469                        while check_idx < col.constraints.len() {
8470                            if let ColumnConstraint::Check(expr) = &col.constraints[check_idx] {
8471                                // Output CONSTRAINT name if present (only for first CHECK)
8472                                if check_idx == 0 {
8473                                    if let Some(ref cname) = col.check_constraint_name {
8474                                        self.write_space();
8475                                        self.write_keyword("CONSTRAINT");
8476                                        self.write_space();
8477                                        self.write(cname);
8478                                    }
8479                                }
8480                                self.write_space();
8481                                self.write_keyword("CHECK");
8482                                self.write(" (");
8483                                self.generate_expression(expr)?;
8484                                self.write(")");
8485                                check_idx += 1;
8486                                break;
8487                            }
8488                            check_idx += 1;
8489                        }
8490                    }
8491                    ConstraintType::GeneratedAsIdentity => {
8492                        // Find next GeneratedAsIdentity constraint
8493                        while generated_idx < col.constraints.len() {
8494                            if let ColumnConstraint::GeneratedAsIdentity(gen) =
8495                                &col.constraints[generated_idx]
8496                            {
8497                                self.write_space();
8498                                // Redshift uses IDENTITY(start, increment) syntax
8499                                if matches!(
8500                                    self.config.dialect,
8501                                    Some(crate::dialects::DialectType::Redshift)
8502                                ) {
8503                                    self.write_keyword("IDENTITY");
8504                                    self.write("(");
8505                                    if let Some(ref start) = gen.start {
8506                                        self.generate_expression(start)?;
8507                                    } else {
8508                                        self.write("0");
8509                                    }
8510                                    self.write(", ");
8511                                    if let Some(ref incr) = gen.increment {
8512                                        self.generate_expression(incr)?;
8513                                    } else {
8514                                        self.write("1");
8515                                    }
8516                                    self.write(")");
8517                                } else {
8518                                    self.write_keyword("GENERATED");
8519                                    if gen.always {
8520                                        self.write_space();
8521                                        self.write_keyword("ALWAYS");
8522                                    } else {
8523                                        self.write_space();
8524                                        self.write_keyword("BY DEFAULT");
8525                                        if gen.on_null {
8526                                            self.write_space();
8527                                            self.write_keyword("ON NULL");
8528                                        }
8529                                    }
8530                                    self.write_space();
8531                                    self.write_keyword("AS IDENTITY");
8532
8533                                    let has_options = gen.start.is_some()
8534                                        || gen.increment.is_some()
8535                                        || gen.minvalue.is_some()
8536                                        || gen.maxvalue.is_some()
8537                                        || gen.cycle.is_some();
8538                                    if has_options {
8539                                        self.write(" (");
8540                                        let mut first = true;
8541                                        if let Some(ref start) = gen.start {
8542                                            if !first {
8543                                                self.write(" ");
8544                                            }
8545                                            first = false;
8546                                            self.write_keyword("START WITH");
8547                                            self.write_space();
8548                                            self.generate_expression(start)?;
8549                                        }
8550                                        if let Some(ref incr) = gen.increment {
8551                                            if !first {
8552                                                self.write(" ");
8553                                            }
8554                                            first = false;
8555                                            self.write_keyword("INCREMENT BY");
8556                                            self.write_space();
8557                                            self.generate_expression(incr)?;
8558                                        }
8559                                        if let Some(ref minv) = gen.minvalue {
8560                                            if !first {
8561                                                self.write(" ");
8562                                            }
8563                                            first = false;
8564                                            self.write_keyword("MINVALUE");
8565                                            self.write_space();
8566                                            self.generate_expression(minv)?;
8567                                        }
8568                                        if let Some(ref maxv) = gen.maxvalue {
8569                                            if !first {
8570                                                self.write(" ");
8571                                            }
8572                                            first = false;
8573                                            self.write_keyword("MAXVALUE");
8574                                            self.write_space();
8575                                            self.generate_expression(maxv)?;
8576                                        }
8577                                        if let Some(cycle) = gen.cycle {
8578                                            if !first {
8579                                                self.write(" ");
8580                                            }
8581                                            if cycle {
8582                                                self.write_keyword("CYCLE");
8583                                            } else {
8584                                                self.write_keyword("NO CYCLE");
8585                                            }
8586                                        }
8587                                        self.write(")");
8588                                    }
8589                                }
8590                                generated_idx += 1;
8591                                break;
8592                            }
8593                            generated_idx += 1;
8594                        }
8595                    }
8596                    ConstraintType::Collate => {
8597                        // Find next Collate constraint
8598                        while collate_idx < col.constraints.len() {
8599                            if let ColumnConstraint::Collate(collation) =
8600                                &col.constraints[collate_idx]
8601                            {
8602                                self.write_space();
8603                                self.write_keyword("COLLATE");
8604                                self.write_space();
8605                                self.generate_identifier(collation)?;
8606                                collate_idx += 1;
8607                                break;
8608                            }
8609                            collate_idx += 1;
8610                        }
8611                    }
8612                    ConstraintType::Comment => {
8613                        // Find next Comment constraint
8614                        while comment_idx < col.constraints.len() {
8615                            if let ColumnConstraint::Comment(comment) =
8616                                &col.constraints[comment_idx]
8617                            {
8618                                self.write_space();
8619                                self.write_keyword("COMMENT");
8620                                self.write_space();
8621                                self.generate_string_literal(comment)?;
8622                                comment_idx += 1;
8623                                break;
8624                            }
8625                            comment_idx += 1;
8626                        }
8627                    }
8628                    ConstraintType::Tags => {
8629                        // Find next Tags constraint (Snowflake)
8630                        for constraint in &col.constraints {
8631                            if let ColumnConstraint::Tags(tags) = constraint {
8632                                self.write_space();
8633                                self.write_keyword("TAG");
8634                                self.write(" (");
8635                                for (i, expr) in tags.expressions.iter().enumerate() {
8636                                    if i > 0 {
8637                                        self.write(", ");
8638                                    }
8639                                    self.generate_expression(expr)?;
8640                                }
8641                                self.write(")");
8642                                break;
8643                            }
8644                        }
8645                    }
8646                    ConstraintType::ComputedColumn => {
8647                        // Find next ComputedColumn constraint
8648                        for constraint in &col.constraints {
8649                            if let ColumnConstraint::ComputedColumn(cc) = constraint {
8650                                self.write_space();
8651                                self.generate_computed_column_inline(cc)?;
8652                                break;
8653                            }
8654                        }
8655                    }
8656                    ConstraintType::GeneratedAsRow => {
8657                        // Find next GeneratedAsRow constraint
8658                        for constraint in &col.constraints {
8659                            if let ColumnConstraint::GeneratedAsRow(gar) = constraint {
8660                                self.write_space();
8661                                self.generate_generated_as_row_inline(gar)?;
8662                                break;
8663                            }
8664                        }
8665                    }
8666                    ConstraintType::OnUpdate => {
8667                        if let Some(ref expr) = col.on_update {
8668                            self.write_space();
8669                            self.write_keyword("ON UPDATE");
8670                            self.write_space();
8671                            self.generate_expression(expr)?;
8672                        }
8673                    }
8674                    ConstraintType::Encode => {
8675                        if let Some(ref encoding) = col.encoding {
8676                            self.write_space();
8677                            self.write_keyword("ENCODE");
8678                            self.write_space();
8679                            self.write(encoding);
8680                        }
8681                    }
8682                    ConstraintType::Path => {
8683                        // Find next Path constraint
8684                        for constraint in &col.constraints {
8685                            if let ColumnConstraint::Path(path_expr) = constraint {
8686                                self.write_space();
8687                                self.write_keyword("PATH");
8688                                self.write_space();
8689                                self.generate_expression(path_expr)?;
8690                                break;
8691                            }
8692                        }
8693                    }
8694                }
8695            }
8696            if pending_not_null_after_identity {
8697                self.write_space();
8698                self.write_keyword("NOT NULL");
8699            }
8700        } else {
8701            // Legacy fixed order for backward compatibility
8702            if col.primary_key {
8703                self.write_space();
8704                self.write_keyword("PRIMARY KEY");
8705                if let Some(ref order) = col.primary_key_order {
8706                    self.write_space();
8707                    match order {
8708                        SortOrder::Asc => self.write_keyword("ASC"),
8709                        SortOrder::Desc => self.write_keyword("DESC"),
8710                    }
8711                }
8712            }
8713
8714            if col.unique {
8715                self.write_space();
8716                self.write_keyword("UNIQUE");
8717                // PostgreSQL 15+: NULLS NOT DISTINCT
8718                if col.unique_nulls_not_distinct {
8719                    self.write(" NULLS NOT DISTINCT");
8720                }
8721            }
8722
8723            match col.nullable {
8724                Some(false) => {
8725                    self.write_space();
8726                    self.write_keyword("NOT NULL");
8727                }
8728                Some(true) => {
8729                    self.write_space();
8730                    self.write_keyword("NULL");
8731                }
8732                None => {}
8733            }
8734
8735            if let Some(ref default) = col.default {
8736                self.write_space();
8737                self.write_keyword("DEFAULT");
8738                self.write_space();
8739                self.generate_expression(default)?;
8740            }
8741
8742            if col.auto_increment {
8743                self.write_space();
8744                self.generate_auto_increment_keyword(col)?;
8745            }
8746
8747            // Column-level constraints from Vec
8748            for constraint in &col.constraints {
8749                match constraint {
8750                    ColumnConstraint::References(fk_ref) => {
8751                        self.write_space();
8752                        if fk_ref.has_foreign_key_keywords {
8753                            self.write_keyword("FOREIGN KEY");
8754                            self.write_space();
8755                        }
8756                        self.write_keyword("REFERENCES");
8757                        self.write_space();
8758                        self.generate_table(&fk_ref.table)?;
8759                        if !fk_ref.columns.is_empty() {
8760                            self.write(" (");
8761                            for (i, c) in fk_ref.columns.iter().enumerate() {
8762                                if i > 0 {
8763                                    self.write(", ");
8764                                }
8765                                self.generate_identifier(c)?;
8766                            }
8767                            self.write(")");
8768                        }
8769                        self.generate_referential_actions(fk_ref)?;
8770                    }
8771                    ColumnConstraint::Check(expr) => {
8772                        self.write_space();
8773                        self.write_keyword("CHECK");
8774                        self.write(" (");
8775                        self.generate_expression(expr)?;
8776                        self.write(")");
8777                    }
8778                    ColumnConstraint::GeneratedAsIdentity(gen) => {
8779                        self.write_space();
8780                        // Redshift uses IDENTITY(start, increment) syntax
8781                        if matches!(
8782                            self.config.dialect,
8783                            Some(crate::dialects::DialectType::Redshift)
8784                        ) {
8785                            self.write_keyword("IDENTITY");
8786                            self.write("(");
8787                            if let Some(ref start) = gen.start {
8788                                self.generate_expression(start)?;
8789                            } else {
8790                                self.write("0");
8791                            }
8792                            self.write(", ");
8793                            if let Some(ref incr) = gen.increment {
8794                                self.generate_expression(incr)?;
8795                            } else {
8796                                self.write("1");
8797                            }
8798                            self.write(")");
8799                        } else {
8800                            self.write_keyword("GENERATED");
8801                            if gen.always {
8802                                self.write_space();
8803                                self.write_keyword("ALWAYS");
8804                            } else {
8805                                self.write_space();
8806                                self.write_keyword("BY DEFAULT");
8807                                if gen.on_null {
8808                                    self.write_space();
8809                                    self.write_keyword("ON NULL");
8810                                }
8811                            }
8812                            self.write_space();
8813                            self.write_keyword("AS IDENTITY");
8814
8815                            let has_options = gen.start.is_some()
8816                                || gen.increment.is_some()
8817                                || gen.minvalue.is_some()
8818                                || gen.maxvalue.is_some()
8819                                || gen.cycle.is_some();
8820                            if has_options {
8821                                self.write(" (");
8822                                let mut first = true;
8823                                if let Some(ref start) = gen.start {
8824                                    if !first {
8825                                        self.write(" ");
8826                                    }
8827                                    first = false;
8828                                    self.write_keyword("START WITH");
8829                                    self.write_space();
8830                                    self.generate_expression(start)?;
8831                                }
8832                                if let Some(ref incr) = gen.increment {
8833                                    if !first {
8834                                        self.write(" ");
8835                                    }
8836                                    first = false;
8837                                    self.write_keyword("INCREMENT BY");
8838                                    self.write_space();
8839                                    self.generate_expression(incr)?;
8840                                }
8841                                if let Some(ref minv) = gen.minvalue {
8842                                    if !first {
8843                                        self.write(" ");
8844                                    }
8845                                    first = false;
8846                                    self.write_keyword("MINVALUE");
8847                                    self.write_space();
8848                                    self.generate_expression(minv)?;
8849                                }
8850                                if let Some(ref maxv) = gen.maxvalue {
8851                                    if !first {
8852                                        self.write(" ");
8853                                    }
8854                                    first = false;
8855                                    self.write_keyword("MAXVALUE");
8856                                    self.write_space();
8857                                    self.generate_expression(maxv)?;
8858                                }
8859                                if let Some(cycle) = gen.cycle {
8860                                    if !first {
8861                                        self.write(" ");
8862                                    }
8863                                    if cycle {
8864                                        self.write_keyword("CYCLE");
8865                                    } else {
8866                                        self.write_keyword("NO CYCLE");
8867                                    }
8868                                }
8869                                self.write(")");
8870                            }
8871                        }
8872                    }
8873                    ColumnConstraint::Collate(collation) => {
8874                        self.write_space();
8875                        self.write_keyword("COLLATE");
8876                        self.write_space();
8877                        self.generate_identifier(collation)?;
8878                    }
8879                    ColumnConstraint::Comment(comment) => {
8880                        self.write_space();
8881                        self.write_keyword("COMMENT");
8882                        self.write_space();
8883                        self.generate_string_literal(comment)?;
8884                    }
8885                    ColumnConstraint::Path(path_expr) => {
8886                        self.write_space();
8887                        self.write_keyword("PATH");
8888                        self.write_space();
8889                        self.generate_expression(path_expr)?;
8890                    }
8891                    _ => {} // Other constraints handled above
8892                }
8893            }
8894
8895            // Redshift: ENCODE encoding_type (legacy path)
8896            if let Some(ref encoding) = col.encoding {
8897                self.write_space();
8898                self.write_keyword("ENCODE");
8899                self.write_space();
8900                self.write(encoding);
8901            }
8902        }
8903
8904        // ClickHouse: CODEC(...)
8905        if let Some(ref codec) = col.codec {
8906            self.write_space();
8907            self.write_keyword("CODEC");
8908            self.write("(");
8909            self.write(codec);
8910            self.write(")");
8911        }
8912
8913        // ClickHouse: EPHEMERAL [expr]
8914        if let Some(ref ephemeral) = col.ephemeral {
8915            self.write_space();
8916            self.write_keyword("EPHEMERAL");
8917            if let Some(ref expr) = ephemeral {
8918                self.write_space();
8919                self.generate_expression(expr)?;
8920            }
8921        }
8922
8923        // ClickHouse: MATERIALIZED expr
8924        if let Some(ref mat_expr) = col.materialized_expr {
8925            self.write_space();
8926            self.write_keyword("MATERIALIZED");
8927            self.write_space();
8928            self.generate_expression(mat_expr)?;
8929        }
8930
8931        // ClickHouse: ALIAS expr
8932        if let Some(ref alias_expr) = col.alias_expr {
8933            self.write_space();
8934            self.write_keyword("ALIAS");
8935            self.write_space();
8936            self.generate_expression(alias_expr)?;
8937        }
8938
8939        // ClickHouse: TTL expr
8940        if let Some(ref ttl_expr) = col.ttl_expr {
8941            self.write_space();
8942            self.write_keyword("TTL");
8943            self.write_space();
8944            self.generate_expression(ttl_expr)?;
8945        }
8946
8947        // TSQL: NOT FOR REPLICATION
8948        if col.not_for_replication
8949            && matches!(
8950                self.config.dialect,
8951                Some(crate::dialects::DialectType::TSQL)
8952                    | Some(crate::dialects::DialectType::Fabric)
8953            )
8954        {
8955            self.write_space();
8956            self.write_keyword("NOT FOR REPLICATION");
8957        }
8958
8959        // BigQuery: OPTIONS (key=value, ...) on column - comes after all constraints
8960        if !col.options.is_empty() {
8961            self.write_space();
8962            self.generate_options_clause(&col.options)?;
8963        }
8964
8965        // SQLite: Inline PRIMARY KEY from table constraint
8966        // This comes at the end, after all existing column constraints
8967        if !col.primary_key
8968            && self
8969                .sqlite_inline_pk_columns
8970                .contains(&col.name.name.to_ascii_lowercase())
8971        {
8972            self.write_space();
8973            self.write_keyword("PRIMARY KEY");
8974        }
8975
8976        // SERIAL expansion: add GENERATED BY DEFAULT AS IDENTITY NOT NULL for PostgreSQL,
8977        // just NOT NULL for Materialize (which strips GENERATED AS IDENTITY)
8978        if serial_expansion.is_some() {
8979            if matches!(self.config.dialect, Some(DialectType::PostgreSQL)) {
8980                self.write_space();
8981                self.write_keyword("GENERATED BY DEFAULT AS IDENTITY NOT NULL");
8982            } else if matches!(self.config.dialect, Some(DialectType::Materialize)) {
8983                self.write_space();
8984                self.write_keyword("NOT NULL");
8985            }
8986        }
8987
8988        Ok(())
8989    }
8990
8991    fn generate_table_constraint(&mut self, constraint: &TableConstraint) -> Result<()> {
8992        match constraint {
8993            TableConstraint::PrimaryKey {
8994                name,
8995                columns,
8996                include_columns,
8997                modifiers,
8998                has_constraint_keyword,
8999            } => {
9000                if let Some(ref n) = name {
9001                    if *has_constraint_keyword {
9002                        self.write_keyword("CONSTRAINT");
9003                        self.write_space();
9004                        self.generate_identifier(n)?;
9005                        self.write_space();
9006                    }
9007                }
9008                self.write_keyword("PRIMARY KEY");
9009                // TSQL CLUSTERED/NONCLUSTERED modifier (before columns)
9010                if let Some(ref clustered) = modifiers.clustered {
9011                    self.write_space();
9012                    self.write_keyword(clustered);
9013                }
9014                // MySQL format: PRIMARY KEY name (cols) when no CONSTRAINT keyword
9015                if let Some(ref n) = name {
9016                    if !*has_constraint_keyword {
9017                        self.write_space();
9018                        self.generate_identifier(n)?;
9019                    }
9020                }
9021                self.write(" (");
9022                for (i, col) in columns.iter().enumerate() {
9023                    if i > 0 {
9024                        self.write(", ");
9025                    }
9026                    self.generate_identifier(col)?;
9027                }
9028                self.write(")");
9029                if !include_columns.is_empty() {
9030                    self.write_space();
9031                    self.write_keyword("INCLUDE");
9032                    self.write(" (");
9033                    for (i, col) in include_columns.iter().enumerate() {
9034                        if i > 0 {
9035                            self.write(", ");
9036                        }
9037                        self.generate_identifier(col)?;
9038                    }
9039                    self.write(")");
9040                }
9041                self.generate_constraint_modifiers(modifiers);
9042            }
9043            TableConstraint::Unique {
9044                name,
9045                columns,
9046                columns_parenthesized,
9047                modifiers,
9048                has_constraint_keyword,
9049                nulls_not_distinct,
9050            } => {
9051                if let Some(ref n) = name {
9052                    if *has_constraint_keyword {
9053                        self.write_keyword("CONSTRAINT");
9054                        self.write_space();
9055                        self.generate_identifier(n)?;
9056                        self.write_space();
9057                    }
9058                }
9059                self.write_keyword("UNIQUE");
9060                // TSQL CLUSTERED/NONCLUSTERED modifier (before columns)
9061                if let Some(ref clustered) = modifiers.clustered {
9062                    self.write_space();
9063                    self.write_keyword(clustered);
9064                }
9065                // PostgreSQL 15+: NULLS NOT DISTINCT
9066                if *nulls_not_distinct {
9067                    self.write(" NULLS NOT DISTINCT");
9068                }
9069                // MySQL format: UNIQUE name (cols) when no CONSTRAINT keyword
9070                if let Some(ref n) = name {
9071                    if !*has_constraint_keyword {
9072                        self.write_space();
9073                        self.generate_identifier(n)?;
9074                    }
9075                }
9076                if *columns_parenthesized {
9077                    self.write(" (");
9078                    for (i, col) in columns.iter().enumerate() {
9079                        if i > 0 {
9080                            self.write(", ");
9081                        }
9082                        self.generate_identifier(col)?;
9083                    }
9084                    self.write(")");
9085                } else {
9086                    // UNIQUE without parentheses (e.g., UNIQUE idx_name)
9087                    for col in columns.iter() {
9088                        self.write_space();
9089                        self.generate_identifier(col)?;
9090                    }
9091                }
9092                self.generate_constraint_modifiers(modifiers);
9093            }
9094            TableConstraint::ForeignKey {
9095                name,
9096                columns,
9097                references,
9098                on_delete,
9099                on_update,
9100                modifiers,
9101            } => {
9102                if let Some(ref n) = name {
9103                    self.write_keyword("CONSTRAINT");
9104                    self.write_space();
9105                    self.generate_identifier(n)?;
9106                    self.write_space();
9107                }
9108                self.write_keyword("FOREIGN KEY");
9109                self.write(" (");
9110                for (i, col) in columns.iter().enumerate() {
9111                    if i > 0 {
9112                        self.write(", ");
9113                    }
9114                    self.generate_identifier(col)?;
9115                }
9116                self.write(")");
9117                if let Some(ref refs) = references {
9118                    self.write(" ");
9119                    self.write_keyword("REFERENCES");
9120                    self.write_space();
9121                    self.generate_table(&refs.table)?;
9122                    if !refs.columns.is_empty() {
9123                        if self.config.pretty {
9124                            self.write(" (");
9125                            self.write_newline();
9126                            self.indent_level += 1;
9127                            for (i, col) in refs.columns.iter().enumerate() {
9128                                if i > 0 {
9129                                    self.write(",");
9130                                    self.write_newline();
9131                                }
9132                                self.write_indent();
9133                                self.generate_identifier(col)?;
9134                            }
9135                            self.indent_level -= 1;
9136                            self.write_newline();
9137                            self.write_indent();
9138                            self.write(")");
9139                        } else {
9140                            self.write(" (");
9141                            for (i, col) in refs.columns.iter().enumerate() {
9142                                if i > 0 {
9143                                    self.write(", ");
9144                                }
9145                                self.generate_identifier(col)?;
9146                            }
9147                            self.write(")");
9148                        }
9149                    }
9150                    self.generate_referential_actions(refs)?;
9151                } else {
9152                    // No REFERENCES - output ON DELETE/ON UPDATE directly
9153                    if let Some(ref action) = on_delete {
9154                        self.write_space();
9155                        self.write_keyword("ON DELETE");
9156                        self.write_space();
9157                        self.generate_referential_action(action);
9158                    }
9159                    if let Some(ref action) = on_update {
9160                        self.write_space();
9161                        self.write_keyword("ON UPDATE");
9162                        self.write_space();
9163                        self.generate_referential_action(action);
9164                    }
9165                }
9166                self.generate_constraint_modifiers(modifiers);
9167            }
9168            TableConstraint::Check {
9169                name,
9170                expression,
9171                modifiers,
9172            } => {
9173                if let Some(ref n) = name {
9174                    self.write_keyword("CONSTRAINT");
9175                    self.write_space();
9176                    self.generate_identifier(n)?;
9177                    self.write_space();
9178                }
9179                self.write_keyword("CHECK");
9180                self.write(" (");
9181                self.generate_expression(expression)?;
9182                self.write(")");
9183                self.generate_constraint_modifiers(modifiers);
9184            }
9185            TableConstraint::Assume {
9186                name,
9187                expression,
9188            } => {
9189                if let Some(ref n) = name {
9190                    self.write_keyword("CONSTRAINT");
9191                    self.write_space();
9192                    self.generate_identifier(n)?;
9193                    self.write_space();
9194                }
9195                self.write_keyword("ASSUME");
9196                self.write(" (");
9197                self.generate_expression(expression)?;
9198                self.write(")");
9199            }
9200            TableConstraint::Index {
9201                name,
9202                columns,
9203                kind,
9204                modifiers,
9205                use_key_keyword,
9206                expression,
9207                index_type,
9208                granularity,
9209            } => {
9210                // ClickHouse-style INDEX: INDEX name expr TYPE type_func GRANULARITY n
9211                if expression.is_some() {
9212                    self.write_keyword("INDEX");
9213                    if let Some(ref n) = name {
9214                        self.write_space();
9215                        self.generate_identifier(n)?;
9216                    }
9217                    if let Some(ref expr) = expression {
9218                        self.write_space();
9219                        self.generate_expression(expr)?;
9220                    }
9221                    if let Some(ref idx_type) = index_type {
9222                        self.write_space();
9223                        self.write_keyword("TYPE");
9224                        self.write_space();
9225                        self.generate_expression(idx_type)?;
9226                    }
9227                    if let Some(ref gran) = granularity {
9228                        self.write_space();
9229                        self.write_keyword("GRANULARITY");
9230                        self.write_space();
9231                        self.generate_expression(gran)?;
9232                    }
9233                } else {
9234                    // Standard INDEX syntax
9235                    // Determine the index keyword to use
9236                    // MySQL normalizes KEY to INDEX
9237                    use crate::dialects::DialectType;
9238                    let index_keyword = if *use_key_keyword
9239                        && !matches!(self.config.dialect, Some(DialectType::MySQL))
9240                    {
9241                        "KEY"
9242                    } else {
9243                        "INDEX"
9244                    };
9245
9246                    // Output kind (UNIQUE, FULLTEXT, SPATIAL) if present
9247                    if let Some(ref k) = kind {
9248                        self.write_keyword(k);
9249                        // For UNIQUE, don't add INDEX/KEY keyword
9250                        if k != "UNIQUE" {
9251                            self.write_space();
9252                            self.write_keyword(index_keyword);
9253                        }
9254                    } else {
9255                        self.write_keyword(index_keyword);
9256                    }
9257
9258                    // Output USING before name if using_before_columns is true and there's no name
9259                    if modifiers.using_before_columns && name.is_none() {
9260                        if let Some(ref using) = modifiers.using {
9261                            self.write_space();
9262                            self.write_keyword("USING");
9263                            self.write_space();
9264                            self.write_keyword(using);
9265                        }
9266                    }
9267
9268                    // Output index name if present
9269                    if let Some(ref n) = name {
9270                        self.write_space();
9271                        self.generate_identifier(n)?;
9272                    }
9273
9274                    // Output USING after name but before columns if using_before_columns and there's a name
9275                    if modifiers.using_before_columns && name.is_some() {
9276                        if let Some(ref using) = modifiers.using {
9277                            self.write_space();
9278                            self.write_keyword("USING");
9279                            self.write_space();
9280                            self.write_keyword(using);
9281                        }
9282                    }
9283
9284                    // Output columns
9285                    self.write(" (");
9286                    for (i, col) in columns.iter().enumerate() {
9287                        if i > 0 {
9288                            self.write(", ");
9289                        }
9290                        self.generate_identifier(col)?;
9291                    }
9292                    self.write(")");
9293
9294                    // Output USING after columns if not using_before_columns
9295                    if !modifiers.using_before_columns {
9296                        if let Some(ref using) = modifiers.using {
9297                            self.write_space();
9298                            self.write_keyword("USING");
9299                            self.write_space();
9300                            self.write_keyword(using);
9301                        }
9302                    }
9303
9304                    // Output other constraint modifiers (but skip USING since we already handled it)
9305                    self.generate_constraint_modifiers_without_using(modifiers);
9306                }
9307            }
9308            TableConstraint::Projection { name, expression } => {
9309                // ClickHouse: PROJECTION name (SELECT ...)
9310                self.write_keyword("PROJECTION");
9311                self.write_space();
9312                self.generate_identifier(name)?;
9313                self.write(" (");
9314                self.generate_expression(expression)?;
9315                self.write(")");
9316            }
9317            TableConstraint::Like { source, options } => {
9318                self.write_keyword("LIKE");
9319                self.write_space();
9320                self.generate_table(source)?;
9321                for (action, prop) in options {
9322                    self.write_space();
9323                    match action {
9324                        LikeOptionAction::Including => self.write_keyword("INCLUDING"),
9325                        LikeOptionAction::Excluding => self.write_keyword("EXCLUDING"),
9326                    }
9327                    self.write_space();
9328                    self.write_keyword(prop);
9329                }
9330            }
9331            TableConstraint::PeriodForSystemTime { start_col, end_col } => {
9332                self.write_keyword("PERIOD FOR SYSTEM_TIME");
9333                self.write(" (");
9334                self.generate_identifier(start_col)?;
9335                self.write(", ");
9336                self.generate_identifier(end_col)?;
9337                self.write(")");
9338            }
9339            TableConstraint::Exclude {
9340                name,
9341                using,
9342                elements,
9343                include_columns,
9344                where_clause,
9345                with_params,
9346                using_index_tablespace,
9347                modifiers: _,
9348            } => {
9349                if let Some(ref n) = name {
9350                    self.write_keyword("CONSTRAINT");
9351                    self.write_space();
9352                    self.generate_identifier(n)?;
9353                    self.write_space();
9354                }
9355                self.write_keyword("EXCLUDE");
9356                if let Some(ref method) = using {
9357                    self.write_space();
9358                    self.write_keyword("USING");
9359                    self.write_space();
9360                    self.write(method);
9361                    self.write("(");
9362                } else {
9363                    self.write(" (");
9364                }
9365                for (i, elem) in elements.iter().enumerate() {
9366                    if i > 0 {
9367                        self.write(", ");
9368                    }
9369                    self.write(&elem.expression);
9370                    self.write_space();
9371                    self.write_keyword("WITH");
9372                    self.write_space();
9373                    self.write(&elem.operator);
9374                }
9375                self.write(")");
9376                if !include_columns.is_empty() {
9377                    self.write_space();
9378                    self.write_keyword("INCLUDE");
9379                    self.write(" (");
9380                    for (i, col) in include_columns.iter().enumerate() {
9381                        if i > 0 {
9382                            self.write(", ");
9383                        }
9384                        self.generate_identifier(col)?;
9385                    }
9386                    self.write(")");
9387                }
9388                if !with_params.is_empty() {
9389                    self.write_space();
9390                    self.write_keyword("WITH");
9391                    self.write(" (");
9392                    for (i, (key, val)) in with_params.iter().enumerate() {
9393                        if i > 0 {
9394                            self.write(", ");
9395                        }
9396                        self.write(key);
9397                        self.write("=");
9398                        self.write(val);
9399                    }
9400                    self.write(")");
9401                }
9402                if let Some(ref tablespace) = using_index_tablespace {
9403                    self.write_space();
9404                    self.write_keyword("USING INDEX TABLESPACE");
9405                    self.write_space();
9406                    self.write(tablespace);
9407                }
9408                if let Some(ref where_expr) = where_clause {
9409                    self.write_space();
9410                    self.write_keyword("WHERE");
9411                    self.write(" (");
9412                    self.generate_expression(where_expr)?;
9413                    self.write(")");
9414                }
9415            }
9416            TableConstraint::Tags(tags) => {
9417                self.write_keyword("TAG");
9418                self.write(" (");
9419                for (i, expr) in tags.expressions.iter().enumerate() {
9420                    if i > 0 {
9421                        self.write(", ");
9422                    }
9423                    self.generate_expression(expr)?;
9424                }
9425                self.write(")");
9426            }
9427            TableConstraint::InitiallyDeferred { deferred } => {
9428                self.write_keyword("INITIALLY");
9429                self.write_space();
9430                if *deferred {
9431                    self.write_keyword("DEFERRED");
9432                } else {
9433                    self.write_keyword("IMMEDIATE");
9434                }
9435            }
9436        }
9437        Ok(())
9438    }
9439
9440    fn generate_constraint_modifiers(&mut self, modifiers: &ConstraintModifiers) {
9441        // Output USING BTREE/HASH (MySQL) - comes first
9442        if let Some(using) = &modifiers.using {
9443            self.write_space();
9444            self.write_keyword("USING");
9445            self.write_space();
9446            self.write_keyword(using);
9447        }
9448        // Output ENFORCED/NOT ENFORCED
9449        if let Some(enforced) = modifiers.enforced {
9450            self.write_space();
9451            if enforced {
9452                self.write_keyword("ENFORCED");
9453            } else {
9454                self.write_keyword("NOT ENFORCED");
9455            }
9456        }
9457        // Output DEFERRABLE/NOT DEFERRABLE
9458        if let Some(deferrable) = modifiers.deferrable {
9459            self.write_space();
9460            if deferrable {
9461                self.write_keyword("DEFERRABLE");
9462            } else {
9463                self.write_keyword("NOT DEFERRABLE");
9464            }
9465        }
9466        // Output INITIALLY DEFERRED/INITIALLY IMMEDIATE
9467        if let Some(initially_deferred) = modifiers.initially_deferred {
9468            self.write_space();
9469            if initially_deferred {
9470                self.write_keyword("INITIALLY DEFERRED");
9471            } else {
9472                self.write_keyword("INITIALLY IMMEDIATE");
9473            }
9474        }
9475        // Output NORELY
9476        if modifiers.norely {
9477            self.write_space();
9478            self.write_keyword("NORELY");
9479        }
9480        // Output RELY
9481        if modifiers.rely {
9482            self.write_space();
9483            self.write_keyword("RELY");
9484        }
9485        // Output NOT VALID (PostgreSQL)
9486        if modifiers.not_valid {
9487            self.write_space();
9488            self.write_keyword("NOT VALID");
9489        }
9490        // Output ON CONFLICT (SQLite)
9491        if let Some(on_conflict) = &modifiers.on_conflict {
9492            self.write_space();
9493            self.write_keyword("ON CONFLICT");
9494            self.write_space();
9495            self.write_keyword(on_conflict);
9496        }
9497        // Output TSQL WITH options (PAD_INDEX=ON, STATISTICS_NORECOMPUTE=OFF, ...)
9498        if !modifiers.with_options.is_empty() {
9499            self.write_space();
9500            self.write_keyword("WITH");
9501            self.write(" (");
9502            for (i, (key, value)) in modifiers.with_options.iter().enumerate() {
9503                if i > 0 {
9504                    self.write(", ");
9505                }
9506                self.write(key);
9507                self.write("=");
9508                self.write(value);
9509            }
9510            self.write(")");
9511        }
9512        // Output TSQL ON filegroup
9513        if let Some(ref fg) = modifiers.on_filegroup {
9514            self.write_space();
9515            self.write_keyword("ON");
9516            self.write_space();
9517            let _ = self.generate_identifier(fg);
9518        }
9519    }
9520
9521    /// Generate constraint modifiers without USING (for Index constraints where USING is handled separately)
9522    fn generate_constraint_modifiers_without_using(&mut self, modifiers: &ConstraintModifiers) {
9523        // Output ENFORCED/NOT ENFORCED
9524        if let Some(enforced) = modifiers.enforced {
9525            self.write_space();
9526            if enforced {
9527                self.write_keyword("ENFORCED");
9528            } else {
9529                self.write_keyword("NOT ENFORCED");
9530            }
9531        }
9532        // Output DEFERRABLE/NOT DEFERRABLE
9533        if let Some(deferrable) = modifiers.deferrable {
9534            self.write_space();
9535            if deferrable {
9536                self.write_keyword("DEFERRABLE");
9537            } else {
9538                self.write_keyword("NOT DEFERRABLE");
9539            }
9540        }
9541        // Output INITIALLY DEFERRED/INITIALLY IMMEDIATE
9542        if let Some(initially_deferred) = modifiers.initially_deferred {
9543            self.write_space();
9544            if initially_deferred {
9545                self.write_keyword("INITIALLY DEFERRED");
9546            } else {
9547                self.write_keyword("INITIALLY IMMEDIATE");
9548            }
9549        }
9550        // Output NORELY
9551        if modifiers.norely {
9552            self.write_space();
9553            self.write_keyword("NORELY");
9554        }
9555        // Output RELY
9556        if modifiers.rely {
9557            self.write_space();
9558            self.write_keyword("RELY");
9559        }
9560        // Output NOT VALID (PostgreSQL)
9561        if modifiers.not_valid {
9562            self.write_space();
9563            self.write_keyword("NOT VALID");
9564        }
9565        // Output ON CONFLICT (SQLite)
9566        if let Some(on_conflict) = &modifiers.on_conflict {
9567            self.write_space();
9568            self.write_keyword("ON CONFLICT");
9569            self.write_space();
9570            self.write_keyword(on_conflict);
9571        }
9572        // Output MySQL index-specific modifiers
9573        self.generate_index_specific_modifiers(modifiers);
9574    }
9575
9576    /// Generate MySQL index-specific modifiers (COMMENT, VISIBLE, ENGINE_ATTRIBUTE, WITH PARSER)
9577    fn generate_index_specific_modifiers(&mut self, modifiers: &ConstraintModifiers) {
9578        if let Some(ref comment) = modifiers.comment {
9579            self.write_space();
9580            self.write_keyword("COMMENT");
9581            self.write(" '");
9582            self.write(comment);
9583            self.write("'");
9584        }
9585        if let Some(visible) = modifiers.visible {
9586            self.write_space();
9587            if visible {
9588                self.write_keyword("VISIBLE");
9589            } else {
9590                self.write_keyword("INVISIBLE");
9591            }
9592        }
9593        if let Some(ref attr) = modifiers.engine_attribute {
9594            self.write_space();
9595            self.write_keyword("ENGINE_ATTRIBUTE");
9596            self.write(" = '");
9597            self.write(attr);
9598            self.write("'");
9599        }
9600        if let Some(ref parser) = modifiers.with_parser {
9601            self.write_space();
9602            self.write_keyword("WITH PARSER");
9603            self.write_space();
9604            self.write(parser);
9605        }
9606    }
9607
9608    fn generate_referential_actions(&mut self, fk_ref: &ForeignKeyRef) -> Result<()> {
9609        // MATCH clause before ON DELETE/ON UPDATE (default position, e.g. PostgreSQL)
9610        if !fk_ref.match_after_actions {
9611            if let Some(ref match_type) = fk_ref.match_type {
9612                self.write_space();
9613                self.write_keyword("MATCH");
9614                self.write_space();
9615                match match_type {
9616                    MatchType::Full => self.write_keyword("FULL"),
9617                    MatchType::Partial => self.write_keyword("PARTIAL"),
9618                    MatchType::Simple => self.write_keyword("SIMPLE"),
9619                }
9620            }
9621        }
9622
9623        // Output ON UPDATE and ON DELETE in the original order
9624        if fk_ref.on_update_first {
9625            if let Some(ref action) = fk_ref.on_update {
9626                self.write_space();
9627                self.write_keyword("ON UPDATE");
9628                self.write_space();
9629                self.generate_referential_action(action);
9630            }
9631            if let Some(ref action) = fk_ref.on_delete {
9632                self.write_space();
9633                self.write_keyword("ON DELETE");
9634                self.write_space();
9635                self.generate_referential_action(action);
9636            }
9637        } else {
9638            if let Some(ref action) = fk_ref.on_delete {
9639                self.write_space();
9640                self.write_keyword("ON DELETE");
9641                self.write_space();
9642                self.generate_referential_action(action);
9643            }
9644            if let Some(ref action) = fk_ref.on_update {
9645                self.write_space();
9646                self.write_keyword("ON UPDATE");
9647                self.write_space();
9648                self.generate_referential_action(action);
9649            }
9650        }
9651
9652        // MATCH clause after ON DELETE/ON UPDATE (when original SQL had it after)
9653        if fk_ref.match_after_actions {
9654            if let Some(ref match_type) = fk_ref.match_type {
9655                self.write_space();
9656                self.write_keyword("MATCH");
9657                self.write_space();
9658                match match_type {
9659                    MatchType::Full => self.write_keyword("FULL"),
9660                    MatchType::Partial => self.write_keyword("PARTIAL"),
9661                    MatchType::Simple => self.write_keyword("SIMPLE"),
9662                }
9663            }
9664        }
9665
9666        // DEFERRABLE / NOT DEFERRABLE
9667        if let Some(deferrable) = fk_ref.deferrable {
9668            self.write_space();
9669            if deferrable {
9670                self.write_keyword("DEFERRABLE");
9671            } else {
9672                self.write_keyword("NOT DEFERRABLE");
9673            }
9674        }
9675
9676        Ok(())
9677    }
9678
9679    fn generate_referential_action(&mut self, action: &ReferentialAction) {
9680        match action {
9681            ReferentialAction::Cascade => self.write_keyword("CASCADE"),
9682            ReferentialAction::SetNull => self.write_keyword("SET NULL"),
9683            ReferentialAction::SetDefault => self.write_keyword("SET DEFAULT"),
9684            ReferentialAction::Restrict => self.write_keyword("RESTRICT"),
9685            ReferentialAction::NoAction => self.write_keyword("NO ACTION"),
9686        }
9687    }
9688
9689    fn generate_drop_table(&mut self, dt: &DropTable) -> Result<()> {
9690        // TSQL: IF NOT OBJECT_ID(...) IS NULL BEGIN DROP TABLE ...; END
9691        if let Some(ref object_id_args) = dt.object_id_args {
9692            if matches!(
9693                self.config.dialect,
9694                Some(crate::dialects::DialectType::TSQL) | Some(crate::dialects::DialectType::Fabric)
9695            ) {
9696                self.write_keyword("IF NOT OBJECT_ID");
9697                self.write("(");
9698                self.write(object_id_args);
9699                self.write(")");
9700                self.write_space();
9701                self.write_keyword("IS NULL BEGIN DROP TABLE");
9702                self.write_space();
9703                for (i, table) in dt.names.iter().enumerate() {
9704                    if i > 0 {
9705                        self.write(", ");
9706                    }
9707                    self.generate_table(table)?;
9708                }
9709                self.write("; ");
9710                self.write_keyword("END");
9711                return Ok(());
9712            }
9713        }
9714
9715        // Athena: DROP TABLE uses Hive engine (backticks)
9716        let saved_athena_hive_context = self.athena_hive_context;
9717        if matches!(
9718            self.config.dialect,
9719            Some(crate::dialects::DialectType::Athena)
9720        ) {
9721            self.athena_hive_context = true;
9722        }
9723
9724        // Output leading comments (e.g., "-- comment\nDROP TABLE ...")
9725        for comment in &dt.leading_comments {
9726            self.write_formatted_comment(comment);
9727            self.write_space();
9728        }
9729        self.write_keyword("DROP TABLE");
9730
9731        if dt.if_exists {
9732            self.write_space();
9733            self.write_keyword("IF EXISTS");
9734        }
9735
9736        self.write_space();
9737        for (i, table) in dt.names.iter().enumerate() {
9738            if i > 0 {
9739                self.write(", ");
9740            }
9741            self.generate_table(table)?;
9742        }
9743
9744        if dt.cascade_constraints {
9745            self.write_space();
9746            self.write_keyword("CASCADE CONSTRAINTS");
9747        } else if dt.cascade {
9748            self.write_space();
9749            self.write_keyword("CASCADE");
9750        }
9751
9752        if dt.purge {
9753            self.write_space();
9754            self.write_keyword("PURGE");
9755        }
9756
9757        if dt.sync {
9758            self.write_space();
9759            self.write_keyword("SYNC");
9760        }
9761
9762        // Restore Athena Hive context
9763        self.athena_hive_context = saved_athena_hive_context;
9764
9765        Ok(())
9766    }
9767
9768    fn generate_alter_table(&mut self, at: &AlterTable) -> Result<()> {
9769        // Athena: ALTER TABLE uses Hive engine (backticks)
9770        let saved_athena_hive_context = self.athena_hive_context;
9771        if matches!(
9772            self.config.dialect,
9773            Some(crate::dialects::DialectType::Athena)
9774        ) {
9775            self.athena_hive_context = true;
9776        }
9777
9778        self.write_keyword("ALTER TABLE");
9779        if at.if_exists {
9780            self.write_space();
9781            self.write_keyword("IF EXISTS");
9782        }
9783        self.write_space();
9784        self.generate_table(&at.name)?;
9785
9786        // ClickHouse: ON CLUSTER clause
9787        if let Some(ref on_cluster) = at.on_cluster {
9788            self.write_space();
9789            self.generate_on_cluster(on_cluster)?;
9790        }
9791
9792        // Hive: PARTITION(key=value, ...) clause
9793        if let Some(ref partition) = at.partition {
9794            self.write_space();
9795            self.write_keyword("PARTITION");
9796            self.write("(");
9797            for (i, (key, value)) in partition.iter().enumerate() {
9798                if i > 0 {
9799                    self.write(", ");
9800                }
9801                self.generate_identifier(key)?;
9802                self.write(" = ");
9803                self.generate_expression(value)?;
9804            }
9805            self.write(")");
9806        }
9807
9808        // TSQL: WITH CHECK / WITH NOCHECK modifier
9809        if let Some(ref with_check) = at.with_check {
9810            self.write_space();
9811            self.write_keyword(with_check);
9812        }
9813
9814        if self.config.pretty {
9815            // In pretty mode, format actions with newlines and indentation
9816            self.write_newline();
9817            self.indent_level += 1;
9818            for (i, action) in at.actions.iter().enumerate() {
9819                // Check if this is a continuation of previous ADD COLUMN or ADD CONSTRAINT
9820                let is_continuation = i > 0
9821                    && matches!(
9822                        (&at.actions[i - 1], action),
9823                        (
9824                            AlterTableAction::AddColumn { .. },
9825                            AlterTableAction::AddColumn { .. }
9826                        ) | (
9827                            AlterTableAction::AddConstraint(_),
9828                            AlterTableAction::AddConstraint(_)
9829                        )
9830                    );
9831                if i > 0 {
9832                    self.write(",");
9833                    self.write_newline();
9834                }
9835                self.write_indent();
9836                self.generate_alter_action_with_continuation(action, is_continuation)?;
9837            }
9838            self.indent_level -= 1;
9839        } else {
9840            for (i, action) in at.actions.iter().enumerate() {
9841                // Check if this is a continuation of previous ADD COLUMN or ADD CONSTRAINT
9842                let is_continuation = i > 0
9843                    && matches!(
9844                        (&at.actions[i - 1], action),
9845                        (
9846                            AlterTableAction::AddColumn { .. },
9847                            AlterTableAction::AddColumn { .. }
9848                        ) | (
9849                            AlterTableAction::AddConstraint(_),
9850                            AlterTableAction::AddConstraint(_)
9851                        )
9852                    );
9853                if i > 0 {
9854                    self.write(",");
9855                }
9856                self.write_space();
9857                self.generate_alter_action_with_continuation(action, is_continuation)?;
9858            }
9859        }
9860
9861        // MySQL ALTER TABLE trailing options
9862        if let Some(ref algorithm) = at.algorithm {
9863            self.write(", ");
9864            self.write_keyword("ALGORITHM");
9865            self.write("=");
9866            self.write_keyword(algorithm);
9867        }
9868        if let Some(ref lock) = at.lock {
9869            self.write(", ");
9870            self.write_keyword("LOCK");
9871            self.write("=");
9872            self.write_keyword(lock);
9873        }
9874
9875        // Restore Athena Hive context
9876        self.athena_hive_context = saved_athena_hive_context;
9877
9878        Ok(())
9879    }
9880
9881    fn generate_alter_action_with_continuation(
9882        &mut self,
9883        action: &AlterTableAction,
9884        is_continuation: bool,
9885    ) -> Result<()> {
9886        match action {
9887            AlterTableAction::AddColumn {
9888                column,
9889                if_not_exists,
9890                position,
9891            } => {
9892                use crate::dialects::DialectType;
9893                // For Snowflake: consecutive ADD COLUMN actions are combined with commas
9894                // e.g., "ADD col1, col2" instead of "ADD col1, ADD col2"
9895                // For other dialects, repeat ADD COLUMN for each
9896                let is_snowflake = matches!(self.config.dialect, Some(DialectType::Snowflake));
9897                let is_tsql_like = matches!(
9898                    self.config.dialect,
9899                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
9900                );
9901                // Athena uses "ADD COLUMNS (col_def)" instead of "ADD COLUMN col_def"
9902                let is_athena = matches!(self.config.dialect, Some(DialectType::Athena));
9903
9904                if is_continuation && (is_snowflake || is_tsql_like) {
9905                    // Don't write ADD keyword for continuation in Snowflake/TSQL
9906                } else if is_snowflake {
9907                    self.write_keyword("ADD");
9908                    self.write_space();
9909                } else if is_athena {
9910                    // Athena uses ADD COLUMNS (col_def) syntax
9911                    self.write_keyword("ADD COLUMNS");
9912                    self.write(" (");
9913                } else if self.config.alter_table_include_column_keyword {
9914                    self.write_keyword("ADD COLUMN");
9915                    self.write_space();
9916                } else {
9917                    // Dialects like Oracle and TSQL don't use COLUMN keyword
9918                    self.write_keyword("ADD");
9919                    self.write_space();
9920                }
9921
9922                if *if_not_exists {
9923                    self.write_keyword("IF NOT EXISTS");
9924                    self.write_space();
9925                }
9926                self.generate_column_def(column)?;
9927
9928                // Close parenthesis for Athena
9929                if is_athena {
9930                    self.write(")");
9931                }
9932
9933                // Column position (FIRST or AFTER)
9934                if let Some(pos) = position {
9935                    self.write_space();
9936                    match pos {
9937                        ColumnPosition::First => self.write_keyword("FIRST"),
9938                        ColumnPosition::After(col_name) => {
9939                            self.write_keyword("AFTER");
9940                            self.write_space();
9941                            self.generate_identifier(col_name)?;
9942                        }
9943                    }
9944                }
9945            }
9946            AlterTableAction::DropColumn {
9947                name,
9948                if_exists,
9949                cascade,
9950            } => {
9951                self.write_keyword("DROP COLUMN");
9952                if *if_exists {
9953                    self.write_space();
9954                    self.write_keyword("IF EXISTS");
9955                }
9956                self.write_space();
9957                self.generate_identifier(name)?;
9958                if *cascade {
9959                    self.write_space();
9960                    self.write_keyword("CASCADE");
9961                }
9962            }
9963            AlterTableAction::DropColumns { names } => {
9964                self.write_keyword("DROP COLUMNS");
9965                self.write(" (");
9966                for (i, name) in names.iter().enumerate() {
9967                    if i > 0 {
9968                        self.write(", ");
9969                    }
9970                    self.generate_identifier(name)?;
9971                }
9972                self.write(")");
9973            }
9974            AlterTableAction::RenameColumn {
9975                old_name,
9976                new_name,
9977                if_exists,
9978            } => {
9979                self.write_keyword("RENAME COLUMN");
9980                if *if_exists {
9981                    self.write_space();
9982                    self.write_keyword("IF EXISTS");
9983                }
9984                self.write_space();
9985                self.generate_identifier(old_name)?;
9986                self.write_space();
9987                self.write_keyword("TO");
9988                self.write_space();
9989                self.generate_identifier(new_name)?;
9990            }
9991            AlterTableAction::AlterColumn {
9992                name,
9993                action,
9994                use_modify_keyword,
9995            } => {
9996                use crate::dialects::DialectType;
9997                // MySQL uses MODIFY COLUMN for type changes (SetDataType)
9998                // but ALTER COLUMN for SET DEFAULT, DROP DEFAULT, etc.
9999                let use_modify = *use_modify_keyword
10000                    || (matches!(self.config.dialect, Some(DialectType::MySQL))
10001                        && matches!(action, AlterColumnAction::SetDataType { .. }));
10002                if use_modify {
10003                    self.write_keyword("MODIFY COLUMN");
10004                    self.write_space();
10005                    self.generate_identifier(name)?;
10006                    // For MODIFY COLUMN, output the type directly
10007                    if let AlterColumnAction::SetDataType {
10008                        data_type,
10009                        using: _,
10010                        collate,
10011                    } = action
10012                    {
10013                        self.write_space();
10014                        self.generate_data_type(data_type)?;
10015                        // Output COLLATE clause if present
10016                        if let Some(collate_name) = collate {
10017                            self.write_space();
10018                            self.write_keyword("COLLATE");
10019                            self.write_space();
10020                            // Output as single-quoted string
10021                            self.write(&format!("'{}'", collate_name));
10022                        }
10023                    } else {
10024                        self.write_space();
10025                        self.generate_alter_column_action(action)?;
10026                    }
10027                } else if matches!(self.config.dialect, Some(DialectType::Hive))
10028                    && matches!(action, AlterColumnAction::SetDataType { .. })
10029                {
10030                    // Hive uses CHANGE COLUMN col_name col_name NEW_TYPE
10031                    self.write_keyword("CHANGE COLUMN");
10032                    self.write_space();
10033                    self.generate_identifier(name)?;
10034                    self.write_space();
10035                    self.generate_identifier(name)?;
10036                    if let AlterColumnAction::SetDataType { data_type, .. } = action {
10037                        self.write_space();
10038                        self.generate_data_type(data_type)?;
10039                    }
10040                } else {
10041                    self.write_keyword("ALTER COLUMN");
10042                    self.write_space();
10043                    self.generate_identifier(name)?;
10044                    self.write_space();
10045                    self.generate_alter_column_action(action)?;
10046                }
10047            }
10048            AlterTableAction::RenameTable(new_name) => {
10049                // MySQL-like dialects (MySQL, Doris, StarRocks) use RENAME without TO
10050                let mysql_like = matches!(
10051                    self.config.dialect,
10052                    Some(DialectType::MySQL)
10053                        | Some(DialectType::Doris)
10054                        | Some(DialectType::StarRocks)
10055                        | Some(DialectType::SingleStore)
10056                );
10057                if mysql_like {
10058                    self.write_keyword("RENAME");
10059                } else {
10060                    self.write_keyword("RENAME TO");
10061                }
10062                self.write_space();
10063                // Doris, DuckDB, BigQuery, PostgreSQL strip schema/catalog from target table
10064                let rename_table_with_db = !matches!(
10065                    self.config.dialect,
10066                    Some(DialectType::Doris)
10067                        | Some(DialectType::DuckDB)
10068                        | Some(DialectType::BigQuery)
10069                        | Some(DialectType::PostgreSQL)
10070                );
10071                if !rename_table_with_db {
10072                    let mut stripped = new_name.clone();
10073                    stripped.schema = None;
10074                    stripped.catalog = None;
10075                    self.generate_table(&stripped)?;
10076                } else {
10077                    self.generate_table(new_name)?;
10078                }
10079            }
10080            AlterTableAction::AddConstraint(constraint) => {
10081                // For consecutive ADD CONSTRAINT actions (is_continuation=true), skip ADD keyword
10082                // to produce: ADD CONSTRAINT c1 ..., CONSTRAINT c2 ...
10083                if !is_continuation {
10084                    self.write_keyword("ADD");
10085                    self.write_space();
10086                }
10087                self.generate_table_constraint(constraint)?;
10088            }
10089            AlterTableAction::DropConstraint { name, if_exists } => {
10090                self.write_keyword("DROP CONSTRAINT");
10091                if *if_exists {
10092                    self.write_space();
10093                    self.write_keyword("IF EXISTS");
10094                }
10095                self.write_space();
10096                self.generate_identifier(name)?;
10097            }
10098            AlterTableAction::DropForeignKey { name } => {
10099                self.write_keyword("DROP FOREIGN KEY");
10100                self.write_space();
10101                self.generate_identifier(name)?;
10102            }
10103            AlterTableAction::DropPartition {
10104                partitions,
10105                if_exists,
10106            } => {
10107                self.write_keyword("DROP");
10108                if *if_exists {
10109                    self.write_space();
10110                    self.write_keyword("IF EXISTS");
10111                }
10112                for (i, partition) in partitions.iter().enumerate() {
10113                    if i > 0 {
10114                        self.write(",");
10115                    }
10116                    self.write_space();
10117                    self.write_keyword("PARTITION");
10118                    // Check for special ClickHouse partition formats
10119                    if partition.len() == 1 && partition[0].0.name == "__expr__" {
10120                        // ClickHouse: PARTITION <expression>
10121                        self.write_space();
10122                        self.generate_expression(&partition[0].1)?;
10123                    } else if partition.len() == 1 && partition[0].0.name == "ALL" {
10124                        // ClickHouse: PARTITION ALL
10125                        self.write_space();
10126                        self.write_keyword("ALL");
10127                    } else if partition.len() == 1 && partition[0].0.name == "ID" {
10128                        // ClickHouse: PARTITION ID 'string'
10129                        self.write_space();
10130                        self.write_keyword("ID");
10131                        self.write_space();
10132                        self.generate_expression(&partition[0].1)?;
10133                    } else {
10134                        // Standard SQL: PARTITION(key=value, ...)
10135                        self.write("(");
10136                        for (j, (key, value)) in partition.iter().enumerate() {
10137                            if j > 0 {
10138                                self.write(", ");
10139                            }
10140                            self.generate_identifier(key)?;
10141                            self.write(" = ");
10142                            self.generate_expression(value)?;
10143                        }
10144                        self.write(")");
10145                    }
10146                }
10147            }
10148            AlterTableAction::Delete { where_clause } => {
10149                self.write_keyword("DELETE");
10150                self.write_space();
10151                self.write_keyword("WHERE");
10152                self.write_space();
10153                self.generate_expression(where_clause)?;
10154            }
10155            AlterTableAction::SwapWith(target) => {
10156                self.write_keyword("SWAP WITH");
10157                self.write_space();
10158                self.generate_table(target)?;
10159            }
10160            AlterTableAction::SetProperty { properties } => {
10161                use crate::dialects::DialectType;
10162                self.write_keyword("SET");
10163                // Trino/Presto use SET PROPERTIES syntax with spaces around =
10164                let is_trino_presto = matches!(
10165                    self.config.dialect,
10166                    Some(DialectType::Trino) | Some(DialectType::Presto)
10167                );
10168                if is_trino_presto {
10169                    self.write_space();
10170                    self.write_keyword("PROPERTIES");
10171                }
10172                let eq = if is_trino_presto { " = " } else { "=" };
10173                for (i, (key, value)) in properties.iter().enumerate() {
10174                    if i > 0 {
10175                        self.write(",");
10176                    }
10177                    self.write_space();
10178                    // Handle quoted property names for Trino
10179                    if key.contains(' ') {
10180                        self.generate_string_literal(key)?;
10181                    } else {
10182                        self.write(key);
10183                    }
10184                    self.write(eq);
10185                    self.generate_expression(value)?;
10186                }
10187            }
10188            AlterTableAction::UnsetProperty { properties } => {
10189                self.write_keyword("UNSET");
10190                for (i, name) in properties.iter().enumerate() {
10191                    if i > 0 {
10192                        self.write(",");
10193                    }
10194                    self.write_space();
10195                    self.write(name);
10196                }
10197            }
10198            AlterTableAction::ClusterBy { expressions } => {
10199                self.write_keyword("CLUSTER BY");
10200                self.write(" (");
10201                for (i, expr) in expressions.iter().enumerate() {
10202                    if i > 0 {
10203                        self.write(", ");
10204                    }
10205                    self.generate_expression(expr)?;
10206                }
10207                self.write(")");
10208            }
10209            AlterTableAction::SetTag { expressions } => {
10210                self.write_keyword("SET TAG");
10211                for (i, (key, value)) in expressions.iter().enumerate() {
10212                    if i > 0 {
10213                        self.write(",");
10214                    }
10215                    self.write_space();
10216                    self.write(key);
10217                    self.write(" = ");
10218                    self.generate_expression(value)?;
10219                }
10220            }
10221            AlterTableAction::UnsetTag { names } => {
10222                self.write_keyword("UNSET TAG");
10223                for (i, name) in names.iter().enumerate() {
10224                    if i > 0 {
10225                        self.write(",");
10226                    }
10227                    self.write_space();
10228                    self.write(name);
10229                }
10230            }
10231            AlterTableAction::SetOptions { expressions } => {
10232                self.write_keyword("SET");
10233                self.write(" (");
10234                for (i, expr) in expressions.iter().enumerate() {
10235                    if i > 0 {
10236                        self.write(", ");
10237                    }
10238                    self.generate_expression(expr)?;
10239                }
10240                self.write(")");
10241            }
10242            AlterTableAction::AlterIndex { name, visible } => {
10243                self.write_keyword("ALTER INDEX");
10244                self.write_space();
10245                self.generate_identifier(name)?;
10246                self.write_space();
10247                if *visible {
10248                    self.write_keyword("VISIBLE");
10249                } else {
10250                    self.write_keyword("INVISIBLE");
10251                }
10252            }
10253            AlterTableAction::SetAttribute { attribute } => {
10254                self.write_keyword("SET");
10255                self.write_space();
10256                self.write_keyword(attribute);
10257            }
10258            AlterTableAction::SetStageFileFormat { options } => {
10259                self.write_keyword("SET");
10260                self.write_space();
10261                self.write_keyword("STAGE_FILE_FORMAT");
10262                self.write(" = (");
10263                if let Some(opts) = options {
10264                    self.generate_space_separated_properties(opts)?;
10265                }
10266                self.write(")");
10267            }
10268            AlterTableAction::SetStageCopyOptions { options } => {
10269                self.write_keyword("SET");
10270                self.write_space();
10271                self.write_keyword("STAGE_COPY_OPTIONS");
10272                self.write(" = (");
10273                if let Some(opts) = options {
10274                    self.generate_space_separated_properties(opts)?;
10275                }
10276                self.write(")");
10277            }
10278            AlterTableAction::AddColumns { columns, cascade } => {
10279                // Oracle uses ADD (...) without COLUMNS keyword
10280                // Hive/Spark uses ADD COLUMNS (...)
10281                let is_oracle = matches!(self.config.dialect, Some(DialectType::Oracle));
10282                if is_oracle {
10283                    self.write_keyword("ADD");
10284                } else {
10285                    self.write_keyword("ADD COLUMNS");
10286                }
10287                self.write(" (");
10288                for (i, col) in columns.iter().enumerate() {
10289                    if i > 0 {
10290                        self.write(", ");
10291                    }
10292                    self.generate_column_def(col)?;
10293                }
10294                self.write(")");
10295                if *cascade {
10296                    self.write_space();
10297                    self.write_keyword("CASCADE");
10298                }
10299            }
10300            AlterTableAction::ChangeColumn {
10301                old_name,
10302                new_name,
10303                data_type,
10304                comment,
10305                cascade,
10306            } => {
10307                use crate::dialects::DialectType;
10308                let is_spark = matches!(
10309                    self.config.dialect,
10310                    Some(DialectType::Spark) | Some(DialectType::Databricks)
10311                );
10312                let is_rename = old_name.name != new_name.name;
10313
10314                if is_spark {
10315                    if is_rename {
10316                        // Spark: RENAME COLUMN old TO new
10317                        self.write_keyword("RENAME COLUMN");
10318                        self.write_space();
10319                        self.generate_identifier(old_name)?;
10320                        self.write_space();
10321                        self.write_keyword("TO");
10322                        self.write_space();
10323                        self.generate_identifier(new_name)?;
10324                    } else if comment.is_some() {
10325                        // Spark: ALTER COLUMN old COMMENT 'comment'
10326                        self.write_keyword("ALTER COLUMN");
10327                        self.write_space();
10328                        self.generate_identifier(old_name)?;
10329                        self.write_space();
10330                        self.write_keyword("COMMENT");
10331                        self.write_space();
10332                        self.write("'");
10333                        self.write(comment.as_ref().unwrap());
10334                        self.write("'");
10335                    } else if data_type.is_some() {
10336                        // Spark: ALTER COLUMN old TYPE data_type
10337                        self.write_keyword("ALTER COLUMN");
10338                        self.write_space();
10339                        self.generate_identifier(old_name)?;
10340                        self.write_space();
10341                        self.write_keyword("TYPE");
10342                        self.write_space();
10343                        self.generate_data_type(data_type.as_ref().unwrap())?;
10344                    } else {
10345                        // Fallback to CHANGE COLUMN
10346                        self.write_keyword("CHANGE COLUMN");
10347                        self.write_space();
10348                        self.generate_identifier(old_name)?;
10349                        self.write_space();
10350                        self.generate_identifier(new_name)?;
10351                    }
10352                } else {
10353                    // Hive/MySQL/default: CHANGE [COLUMN] old new [type] [COMMENT '...'] [CASCADE]
10354                    if data_type.is_some() {
10355                        self.write_keyword("CHANGE COLUMN");
10356                    } else {
10357                        self.write_keyword("CHANGE");
10358                    }
10359                    self.write_space();
10360                    self.generate_identifier(old_name)?;
10361                    self.write_space();
10362                    self.generate_identifier(new_name)?;
10363                    if let Some(ref dt) = data_type {
10364                        self.write_space();
10365                        self.generate_data_type(dt)?;
10366                    }
10367                    if let Some(ref c) = comment {
10368                        self.write_space();
10369                        self.write_keyword("COMMENT");
10370                        self.write_space();
10371                        self.write("'");
10372                        self.write(c);
10373                        self.write("'");
10374                    }
10375                    if *cascade {
10376                        self.write_space();
10377                        self.write_keyword("CASCADE");
10378                    }
10379                }
10380            }
10381            AlterTableAction::AddPartition {
10382                partition,
10383                if_not_exists,
10384                location,
10385            } => {
10386                self.write_keyword("ADD");
10387                self.write_space();
10388                if *if_not_exists {
10389                    self.write_keyword("IF NOT EXISTS");
10390                    self.write_space();
10391                }
10392                self.generate_expression(partition)?;
10393                if let Some(ref loc) = location {
10394                    self.write_space();
10395                    self.write_keyword("LOCATION");
10396                    self.write_space();
10397                    self.generate_expression(loc)?;
10398                }
10399            }
10400            AlterTableAction::AlterSortKey {
10401                this,
10402                expressions,
10403                compound,
10404            } => {
10405                // Redshift: ALTER [COMPOUND] SORTKEY AUTO|NONE|(col1, col2)
10406                self.write_keyword("ALTER");
10407                if *compound {
10408                    self.write_space();
10409                    self.write_keyword("COMPOUND");
10410                }
10411                self.write_space();
10412                self.write_keyword("SORTKEY");
10413                self.write_space();
10414                if let Some(style) = this {
10415                    self.write_keyword(style);
10416                } else if !expressions.is_empty() {
10417                    self.write("(");
10418                    for (i, expr) in expressions.iter().enumerate() {
10419                        if i > 0 {
10420                            self.write(", ");
10421                        }
10422                        self.generate_expression(expr)?;
10423                    }
10424                    self.write(")");
10425                }
10426            }
10427            AlterTableAction::AlterDistStyle { style, distkey } => {
10428                // Redshift: ALTER DISTSTYLE ALL|EVEN|AUTO|KEY [DISTKEY col]
10429                self.write_keyword("ALTER");
10430                self.write_space();
10431                self.write_keyword("DISTSTYLE");
10432                self.write_space();
10433                self.write_keyword(style);
10434                if let Some(col) = distkey {
10435                    self.write_space();
10436                    self.write_keyword("DISTKEY");
10437                    self.write_space();
10438                    self.generate_identifier(col)?;
10439                }
10440            }
10441            AlterTableAction::SetTableProperties { properties } => {
10442                // Redshift: SET TABLE PROPERTIES ('a' = '5', 'b' = 'c')
10443                self.write_keyword("SET TABLE PROPERTIES");
10444                self.write(" (");
10445                for (i, (key, value)) in properties.iter().enumerate() {
10446                    if i > 0 {
10447                        self.write(", ");
10448                    }
10449                    self.generate_expression(key)?;
10450                    self.write(" = ");
10451                    self.generate_expression(value)?;
10452                }
10453                self.write(")");
10454            }
10455            AlterTableAction::SetLocation { location } => {
10456                // Redshift: SET LOCATION 's3://bucket/folder/'
10457                self.write_keyword("SET LOCATION");
10458                self.write_space();
10459                self.write("'");
10460                self.write(location);
10461                self.write("'");
10462            }
10463            AlterTableAction::SetFileFormat { format } => {
10464                // Redshift: SET FILE FORMAT AVRO
10465                self.write_keyword("SET FILE FORMAT");
10466                self.write_space();
10467                self.write_keyword(format);
10468            }
10469            AlterTableAction::ReplacePartition { partition, source } => {
10470                // ClickHouse: REPLACE PARTITION expr FROM source
10471                self.write_keyword("REPLACE PARTITION");
10472                self.write_space();
10473                self.generate_expression(partition)?;
10474                if let Some(src) = source {
10475                    self.write_space();
10476                    self.write_keyword("FROM");
10477                    self.write_space();
10478                    self.generate_expression(src)?;
10479                }
10480            }
10481            AlterTableAction::Raw { sql } => {
10482                self.write(sql);
10483            }
10484        }
10485        Ok(())
10486    }
10487
10488    fn generate_alter_column_action(&mut self, action: &AlterColumnAction) -> Result<()> {
10489        match action {
10490            AlterColumnAction::SetDataType {
10491                data_type,
10492                using,
10493                collate,
10494            } => {
10495                use crate::dialects::DialectType;
10496                // Dialect-specific type change syntax:
10497                // - TSQL/Fabric/Hive: no prefix (ALTER COLUMN col datatype)
10498                // - Redshift/Spark: TYPE (ALTER COLUMN col TYPE datatype)
10499                // - Default: SET DATA TYPE (ALTER COLUMN col SET DATA TYPE datatype)
10500                let is_no_prefix = matches!(
10501                    self.config.dialect,
10502                    Some(DialectType::TSQL) | Some(DialectType::Fabric) | Some(DialectType::Hive)
10503                );
10504                let is_type_only = matches!(
10505                    self.config.dialect,
10506                    Some(DialectType::Redshift)
10507                        | Some(DialectType::Spark)
10508                        | Some(DialectType::Databricks)
10509                );
10510                if is_type_only {
10511                    self.write_keyword("TYPE");
10512                    self.write_space();
10513                } else if !is_no_prefix {
10514                    self.write_keyword("SET DATA TYPE");
10515                    self.write_space();
10516                }
10517                self.generate_data_type(data_type)?;
10518                if let Some(ref collation) = collate {
10519                    self.write_space();
10520                    self.write_keyword("COLLATE");
10521                    self.write_space();
10522                    self.write(collation);
10523                }
10524                if let Some(ref using_expr) = using {
10525                    self.write_space();
10526                    self.write_keyword("USING");
10527                    self.write_space();
10528                    self.generate_expression(using_expr)?;
10529                }
10530            }
10531            AlterColumnAction::SetDefault(expr) => {
10532                self.write_keyword("SET DEFAULT");
10533                self.write_space();
10534                self.generate_expression(expr)?;
10535            }
10536            AlterColumnAction::DropDefault => {
10537                self.write_keyword("DROP DEFAULT");
10538            }
10539            AlterColumnAction::SetNotNull => {
10540                self.write_keyword("SET NOT NULL");
10541            }
10542            AlterColumnAction::DropNotNull => {
10543                self.write_keyword("DROP NOT NULL");
10544            }
10545            AlterColumnAction::Comment(comment) => {
10546                self.write_keyword("COMMENT");
10547                self.write_space();
10548                self.generate_string_literal(comment)?;
10549            }
10550            AlterColumnAction::SetVisible => {
10551                self.write_keyword("SET VISIBLE");
10552            }
10553            AlterColumnAction::SetInvisible => {
10554                self.write_keyword("SET INVISIBLE");
10555            }
10556        }
10557        Ok(())
10558    }
10559
10560    fn generate_create_index(&mut self, ci: &CreateIndex) -> Result<()> {
10561        self.write_keyword("CREATE");
10562
10563        if ci.unique {
10564            self.write_space();
10565            self.write_keyword("UNIQUE");
10566        }
10567
10568        // TSQL CLUSTERED/NONCLUSTERED modifier
10569        if let Some(ref clustered) = ci.clustered {
10570            self.write_space();
10571            self.write_keyword(clustered);
10572        }
10573
10574        self.write_space();
10575        self.write_keyword("INDEX");
10576
10577        // PostgreSQL CONCURRENTLY modifier
10578        if ci.concurrently {
10579            self.write_space();
10580            self.write_keyword("CONCURRENTLY");
10581        }
10582
10583        if ci.if_not_exists {
10584            self.write_space();
10585            self.write_keyword("IF NOT EXISTS");
10586        }
10587
10588        // Index name is optional in PostgreSQL when IF NOT EXISTS is specified
10589        if !ci.name.name.is_empty() {
10590            self.write_space();
10591            self.generate_identifier(&ci.name)?;
10592        }
10593        self.write_space();
10594        self.write_keyword("ON");
10595        // Hive uses ON TABLE
10596        if matches!(self.config.dialect, Some(DialectType::Hive)) {
10597            self.write_space();
10598            self.write_keyword("TABLE");
10599        }
10600        self.write_space();
10601        self.generate_table(&ci.table)?;
10602
10603        // Column list (optional for COLUMNSTORE indexes)
10604        // Standard SQL convention: ON t(a) without space before paren
10605        if !ci.columns.is_empty() || ci.using.is_some() {
10606            let space_before_paren = false;
10607
10608            if let Some(ref using) = ci.using {
10609                self.write_space();
10610                self.write_keyword("USING");
10611                self.write_space();
10612                self.write(using);
10613                if space_before_paren {
10614                    self.write(" (");
10615                } else {
10616                    self.write("(");
10617                }
10618            } else {
10619                if space_before_paren {
10620                    self.write(" (");
10621                } else {
10622                    self.write("(");
10623                }
10624            }
10625            for (i, col) in ci.columns.iter().enumerate() {
10626                if i > 0 {
10627                    self.write(", ");
10628                }
10629                self.generate_identifier(&col.column)?;
10630                if let Some(ref opclass) = col.opclass {
10631                    self.write_space();
10632                    self.write(opclass);
10633                }
10634                if col.desc {
10635                    self.write_space();
10636                    self.write_keyword("DESC");
10637                } else if col.asc {
10638                    self.write_space();
10639                    self.write_keyword("ASC");
10640                }
10641                if let Some(nulls_first) = col.nulls_first {
10642                    self.write_space();
10643                    self.write_keyword("NULLS");
10644                    self.write_space();
10645                    self.write_keyword(if nulls_first { "FIRST" } else { "LAST" });
10646                }
10647            }
10648            self.write(")");
10649        }
10650
10651        // PostgreSQL INCLUDE (col1, col2) clause
10652        if !ci.include_columns.is_empty() {
10653            self.write_space();
10654            self.write_keyword("INCLUDE");
10655            self.write(" (");
10656            for (i, col) in ci.include_columns.iter().enumerate() {
10657                if i > 0 {
10658                    self.write(", ");
10659                }
10660                self.generate_identifier(col)?;
10661            }
10662            self.write(")");
10663        }
10664
10665        // TSQL: WITH (option=value, ...) clause
10666        if !ci.with_options.is_empty() {
10667            self.write_space();
10668            self.write_keyword("WITH");
10669            self.write(" (");
10670            for (i, (key, value)) in ci.with_options.iter().enumerate() {
10671                if i > 0 {
10672                    self.write(", ");
10673                }
10674                self.write(key);
10675                self.write("=");
10676                self.write(value);
10677            }
10678            self.write(")");
10679        }
10680
10681        // PostgreSQL WHERE clause for partial indexes
10682        if let Some(ref where_clause) = ci.where_clause {
10683            self.write_space();
10684            self.write_keyword("WHERE");
10685            self.write_space();
10686            self.generate_expression(where_clause)?;
10687        }
10688
10689        // TSQL: ON filegroup or partition scheme clause
10690        if let Some(ref on_fg) = ci.on_filegroup {
10691            self.write_space();
10692            self.write_keyword("ON");
10693            self.write_space();
10694            self.write(on_fg);
10695        }
10696
10697        Ok(())
10698    }
10699
10700    fn generate_drop_index(&mut self, di: &DropIndex) -> Result<()> {
10701        self.write_keyword("DROP INDEX");
10702
10703        if di.concurrently {
10704            self.write_space();
10705            self.write_keyword("CONCURRENTLY");
10706        }
10707
10708        if di.if_exists {
10709            self.write_space();
10710            self.write_keyword("IF EXISTS");
10711        }
10712
10713        self.write_space();
10714        self.generate_identifier(&di.name)?;
10715
10716        if let Some(ref table) = di.table {
10717            self.write_space();
10718            self.write_keyword("ON");
10719            self.write_space();
10720            self.generate_table(table)?;
10721        }
10722
10723        Ok(())
10724    }
10725
10726    fn generate_create_view(&mut self, cv: &CreateView) -> Result<()> {
10727        self.write_keyword("CREATE");
10728
10729        // MySQL: ALGORITHM=...
10730        if let Some(ref algorithm) = cv.algorithm {
10731            self.write_space();
10732            self.write_keyword("ALGORITHM");
10733            self.write("=");
10734            self.write_keyword(algorithm);
10735        }
10736
10737        // MySQL: DEFINER=...
10738        if let Some(ref definer) = cv.definer {
10739            self.write_space();
10740            self.write_keyword("DEFINER");
10741            self.write("=");
10742            self.write(definer);
10743        }
10744
10745        // MySQL: SQL SECURITY DEFINER/INVOKER (before VIEW keyword, unless it appeared after view name)
10746        if cv.security_sql_style && !cv.security_after_name {
10747            if let Some(ref security) = cv.security {
10748                self.write_space();
10749                self.write_keyword("SQL SECURITY");
10750                self.write_space();
10751                match security {
10752                    FunctionSecurity::Definer => self.write_keyword("DEFINER"),
10753                    FunctionSecurity::Invoker => self.write_keyword("INVOKER"),
10754                    FunctionSecurity::None => self.write_keyword("NONE"),
10755                }
10756            }
10757        }
10758
10759        if cv.or_replace {
10760            self.write_space();
10761            self.write_keyword("OR REPLACE");
10762        }
10763
10764        if cv.temporary {
10765            self.write_space();
10766            self.write_keyword("TEMPORARY");
10767        }
10768
10769        if cv.materialized {
10770            self.write_space();
10771            self.write_keyword("MATERIALIZED");
10772        }
10773
10774        // Snowflake: SECURE VIEW
10775        if cv.secure {
10776            self.write_space();
10777            self.write_keyword("SECURE");
10778        }
10779
10780        self.write_space();
10781        self.write_keyword("VIEW");
10782
10783        if cv.if_not_exists {
10784            self.write_space();
10785            self.write_keyword("IF NOT EXISTS");
10786        }
10787
10788        self.write_space();
10789        self.generate_table(&cv.name)?;
10790
10791        // ClickHouse: ON CLUSTER clause
10792        if let Some(ref on_cluster) = cv.on_cluster {
10793            self.write_space();
10794            self.generate_on_cluster(on_cluster)?;
10795        }
10796
10797        // ClickHouse: TO destination_table
10798        if let Some(ref to_table) = cv.to_table {
10799            self.write_space();
10800            self.write_keyword("TO");
10801            self.write_space();
10802            self.generate_table(to_table)?;
10803        }
10804
10805        // For regular VIEW: columns come before COPY GRANTS
10806        // For MATERIALIZED VIEW: COPY GRANTS comes before columns
10807        if !cv.materialized {
10808            // Regular VIEW: columns first
10809            if !cv.columns.is_empty() {
10810                self.write(" (");
10811                for (i, col) in cv.columns.iter().enumerate() {
10812                    if i > 0 {
10813                        self.write(", ");
10814                    }
10815                    self.generate_identifier(&col.name)?;
10816                    // BigQuery: OPTIONS (key=value, ...) on view column
10817                    if !col.options.is_empty() {
10818                        self.write_space();
10819                        self.generate_options_clause(&col.options)?;
10820                    }
10821                    if let Some(ref comment) = col.comment {
10822                        self.write_space();
10823                        self.write_keyword("COMMENT");
10824                        self.write_space();
10825                        self.generate_string_literal(comment)?;
10826                    }
10827                }
10828                self.write(")");
10829            }
10830
10831            // Presto/Trino/StarRocks: SECURITY DEFINER/INVOKER/NONE (after columns)
10832            // Also handles SQL SECURITY after view name (security_after_name)
10833            if !cv.security_sql_style || cv.security_after_name {
10834                if let Some(ref security) = cv.security {
10835                    self.write_space();
10836                    if cv.security_sql_style {
10837                        self.write_keyword("SQL SECURITY");
10838                    } else {
10839                        self.write_keyword("SECURITY");
10840                    }
10841                    self.write_space();
10842                    match security {
10843                        FunctionSecurity::Definer => self.write_keyword("DEFINER"),
10844                        FunctionSecurity::Invoker => self.write_keyword("INVOKER"),
10845                        FunctionSecurity::None => self.write_keyword("NONE"),
10846                    }
10847                }
10848            }
10849
10850            // Snowflake: COPY GRANTS
10851            if cv.copy_grants {
10852                self.write_space();
10853                self.write_keyword("COPY GRANTS");
10854            }
10855        } else {
10856            // MATERIALIZED VIEW: COPY GRANTS first
10857            if cv.copy_grants {
10858                self.write_space();
10859                self.write_keyword("COPY GRANTS");
10860            }
10861
10862            // Doris: If we have a schema (typed columns), generate that instead
10863            if let Some(ref schema) = cv.schema {
10864                self.write(" (");
10865                for (i, expr) in schema.expressions.iter().enumerate() {
10866                    if i > 0 {
10867                        self.write(", ");
10868                    }
10869                    self.generate_expression(expr)?;
10870                }
10871                self.write(")");
10872            } else if !cv.columns.is_empty() {
10873                // Then columns (simple column names without types)
10874                self.write(" (");
10875                for (i, col) in cv.columns.iter().enumerate() {
10876                    if i > 0 {
10877                        self.write(", ");
10878                    }
10879                    self.generate_identifier(&col.name)?;
10880                    // BigQuery: OPTIONS (key=value, ...) on view column
10881                    if !col.options.is_empty() {
10882                        self.write_space();
10883                        self.generate_options_clause(&col.options)?;
10884                    }
10885                    if let Some(ref comment) = col.comment {
10886                        self.write_space();
10887                        self.write_keyword("COMMENT");
10888                        self.write_space();
10889                        self.generate_string_literal(comment)?;
10890                    }
10891                }
10892                self.write(")");
10893            }
10894
10895            // Doris: KEY (columns) for materialized views
10896            if let Some(ref unique_key) = cv.unique_key {
10897                self.write_space();
10898                self.write_keyword("KEY");
10899                self.write(" (");
10900                for (i, expr) in unique_key.expressions.iter().enumerate() {
10901                    if i > 0 {
10902                        self.write(", ");
10903                    }
10904                    self.generate_expression(expr)?;
10905                }
10906                self.write(")");
10907            }
10908        }
10909
10910        // Snowflake: COMMENT = 'text'
10911        if let Some(ref comment) = cv.comment {
10912            self.write_space();
10913            self.write_keyword("COMMENT");
10914            self.write("=");
10915            self.generate_string_literal(comment)?;
10916        }
10917
10918        // Snowflake: TAG (name='value', ...)
10919        if !cv.tags.is_empty() {
10920            self.write_space();
10921            self.write_keyword("TAG");
10922            self.write(" (");
10923            for (i, (name, value)) in cv.tags.iter().enumerate() {
10924                if i > 0 {
10925                    self.write(", ");
10926                }
10927                self.write(name);
10928                self.write("='");
10929                self.write(value);
10930                self.write("'");
10931            }
10932            self.write(")");
10933        }
10934
10935        // BigQuery: OPTIONS (key=value, ...)
10936        if !cv.options.is_empty() {
10937            self.write_space();
10938            self.generate_options_clause(&cv.options)?;
10939        }
10940
10941        // Doris: BUILD IMMEDIATE/DEFERRED for materialized views
10942        if let Some(ref build) = cv.build {
10943            self.write_space();
10944            self.write_keyword("BUILD");
10945            self.write_space();
10946            self.write_keyword(build);
10947        }
10948
10949        // Doris: REFRESH clause for materialized views
10950        if let Some(ref refresh) = cv.refresh {
10951            self.write_space();
10952            self.generate_refresh_trigger_property(refresh)?;
10953        }
10954
10955        // Redshift: AUTO REFRESH YES|NO for materialized views
10956        if let Some(auto_refresh) = cv.auto_refresh {
10957            self.write_space();
10958            self.write_keyword("AUTO REFRESH");
10959            self.write_space();
10960            if auto_refresh {
10961                self.write_keyword("YES");
10962            } else {
10963                self.write_keyword("NO");
10964            }
10965        }
10966
10967        // ClickHouse: Table properties (ENGINE, ORDER BY, SAMPLE, SETTINGS, TTL, etc.)
10968        for prop in &cv.table_properties {
10969            self.write_space();
10970            self.generate_expression(prop)?;
10971        }
10972
10973        // Only output AS clause if there's a real query (not just NULL placeholder)
10974        if !matches!(&cv.query, Expression::Null(_)) {
10975            self.write_space();
10976            self.write_keyword("AS");
10977            self.write_space();
10978
10979            // Teradata: LOCKING clause (between AS and query)
10980            if let Some(ref mode) = cv.locking_mode {
10981                self.write_keyword("LOCKING");
10982                self.write_space();
10983                self.write_keyword(mode);
10984                if let Some(ref access) = cv.locking_access {
10985                    self.write_space();
10986                    self.write_keyword("FOR");
10987                    self.write_space();
10988                    self.write_keyword(access);
10989                }
10990                self.write_space();
10991            }
10992
10993            if cv.query_parenthesized {
10994                self.write("(");
10995            }
10996            self.generate_expression(&cv.query)?;
10997            if cv.query_parenthesized {
10998                self.write(")");
10999            }
11000        }
11001
11002        // Redshift: WITH NO SCHEMA BINDING (after query)
11003        if cv.no_schema_binding {
11004            self.write_space();
11005            self.write_keyword("WITH NO SCHEMA BINDING");
11006        }
11007
11008        Ok(())
11009    }
11010
11011    fn generate_drop_view(&mut self, dv: &DropView) -> Result<()> {
11012        self.write_keyword("DROP");
11013
11014        if dv.materialized {
11015            self.write_space();
11016            self.write_keyword("MATERIALIZED");
11017        }
11018
11019        self.write_space();
11020        self.write_keyword("VIEW");
11021
11022        if dv.if_exists {
11023            self.write_space();
11024            self.write_keyword("IF EXISTS");
11025        }
11026
11027        self.write_space();
11028        self.generate_table(&dv.name)?;
11029
11030        Ok(())
11031    }
11032
11033    fn generate_truncate(&mut self, tr: &Truncate) -> Result<()> {
11034        match tr.target {
11035            TruncateTarget::Database => self.write_keyword("TRUNCATE DATABASE"),
11036            TruncateTarget::Table => self.write_keyword("TRUNCATE TABLE"),
11037        }
11038        if tr.if_exists {
11039            self.write_space();
11040            self.write_keyword("IF EXISTS");
11041        }
11042        self.write_space();
11043        self.generate_table(&tr.table)?;
11044
11045        // ClickHouse: ON CLUSTER clause
11046        if let Some(ref on_cluster) = tr.on_cluster {
11047            self.write_space();
11048            self.generate_on_cluster(on_cluster)?;
11049        }
11050
11051        // Check if first table has a * (multi-table with star)
11052        if !tr.extra_tables.is_empty() {
11053            // Check if the first entry matches the main table (star case)
11054            let skip_first = if let Some(first) = tr.extra_tables.first() {
11055                first.table.name == tr.table.name && first.star
11056            } else {
11057                false
11058            };
11059
11060            // PostgreSQL normalizes away the * suffix (it's the default behavior)
11061            let strip_star = matches!(
11062                self.config.dialect,
11063                Some(crate::dialects::DialectType::PostgreSQL)
11064                    | Some(crate::dialects::DialectType::Redshift)
11065            );
11066            if skip_first && !strip_star {
11067                self.write("*");
11068            }
11069
11070            // Generate additional tables
11071            for (i, entry) in tr.extra_tables.iter().enumerate() {
11072                if i == 0 && skip_first {
11073                    continue; // Already handled the star for first table
11074                }
11075                self.write(", ");
11076                self.generate_table(&entry.table)?;
11077                if entry.star && !strip_star {
11078                    self.write("*");
11079                }
11080            }
11081        }
11082
11083        // RESTART/CONTINUE IDENTITY
11084        if let Some(identity) = &tr.identity {
11085            self.write_space();
11086            match identity {
11087                TruncateIdentity::Restart => self.write_keyword("RESTART IDENTITY"),
11088                TruncateIdentity::Continue => self.write_keyword("CONTINUE IDENTITY"),
11089            }
11090        }
11091
11092        if tr.cascade {
11093            self.write_space();
11094            self.write_keyword("CASCADE");
11095        }
11096
11097        if tr.restrict {
11098            self.write_space();
11099            self.write_keyword("RESTRICT");
11100        }
11101
11102        // Output Hive PARTITION clause
11103        if let Some(ref partition) = tr.partition {
11104            self.write_space();
11105            self.generate_expression(partition)?;
11106        }
11107
11108        Ok(())
11109    }
11110
11111    fn generate_use(&mut self, u: &Use) -> Result<()> {
11112        // Teradata uses "DATABASE <name>" instead of "USE <name>"
11113        if matches!(self.config.dialect, Some(DialectType::Teradata)) {
11114            self.write_keyword("DATABASE");
11115            self.write_space();
11116            self.generate_identifier(&u.this)?;
11117            return Ok(());
11118        }
11119
11120        self.write_keyword("USE");
11121
11122        if let Some(kind) = &u.kind {
11123            self.write_space();
11124            match kind {
11125                UseKind::Database => self.write_keyword("DATABASE"),
11126                UseKind::Schema => self.write_keyword("SCHEMA"),
11127                UseKind::Role => self.write_keyword("ROLE"),
11128                UseKind::Warehouse => self.write_keyword("WAREHOUSE"),
11129                UseKind::Catalog => self.write_keyword("CATALOG"),
11130                UseKind::SecondaryRoles => self.write_keyword("SECONDARY ROLES"),
11131            }
11132        }
11133
11134        self.write_space();
11135        // For SECONDARY ROLES, write the value as-is (ALL, NONE, or role names)
11136        // without quoting, since these are keywords not identifiers
11137        if matches!(&u.kind, Some(UseKind::SecondaryRoles)) {
11138            self.write(&u.this.name);
11139        } else {
11140            self.generate_identifier(&u.this)?;
11141        }
11142        Ok(())
11143    }
11144
11145    fn generate_cache(&mut self, c: &Cache) -> Result<()> {
11146        self.write_keyword("CACHE");
11147        if c.lazy {
11148            self.write_space();
11149            self.write_keyword("LAZY");
11150        }
11151        self.write_space();
11152        self.write_keyword("TABLE");
11153        self.write_space();
11154        self.generate_identifier(&c.table)?;
11155
11156        // OPTIONS clause
11157        if !c.options.is_empty() {
11158            self.write_space();
11159            self.write_keyword("OPTIONS");
11160            self.write("(");
11161            for (i, (key, value)) in c.options.iter().enumerate() {
11162                if i > 0 {
11163                    self.write(", ");
11164                }
11165                self.generate_expression(key)?;
11166                self.write(" = ");
11167                self.generate_expression(value)?;
11168            }
11169            self.write(")");
11170        }
11171
11172        // AS query
11173        if let Some(query) = &c.query {
11174            self.write_space();
11175            self.write_keyword("AS");
11176            self.write_space();
11177            self.generate_expression(query)?;
11178        }
11179
11180        Ok(())
11181    }
11182
11183    fn generate_uncache(&mut self, u: &Uncache) -> Result<()> {
11184        self.write_keyword("UNCACHE TABLE");
11185        if u.if_exists {
11186            self.write_space();
11187            self.write_keyword("IF EXISTS");
11188        }
11189        self.write_space();
11190        self.generate_identifier(&u.table)?;
11191        Ok(())
11192    }
11193
11194    fn generate_load_data(&mut self, l: &LoadData) -> Result<()> {
11195        self.write_keyword("LOAD DATA");
11196        if l.local {
11197            self.write_space();
11198            self.write_keyword("LOCAL");
11199        }
11200        self.write_space();
11201        self.write_keyword("INPATH");
11202        self.write_space();
11203        self.write("'");
11204        self.write(&l.inpath);
11205        self.write("'");
11206
11207        if l.overwrite {
11208            self.write_space();
11209            self.write_keyword("OVERWRITE");
11210        }
11211
11212        self.write_space();
11213        self.write_keyword("INTO TABLE");
11214        self.write_space();
11215        self.generate_expression(&l.table)?;
11216
11217        // PARTITION clause
11218        if !l.partition.is_empty() {
11219            self.write_space();
11220            self.write_keyword("PARTITION");
11221            self.write("(");
11222            for (i, (col, val)) in l.partition.iter().enumerate() {
11223                if i > 0 {
11224                    self.write(", ");
11225                }
11226                self.generate_identifier(col)?;
11227                self.write(" = ");
11228                self.generate_expression(val)?;
11229            }
11230            self.write(")");
11231        }
11232
11233        // INPUTFORMAT clause
11234        if let Some(fmt) = &l.input_format {
11235            self.write_space();
11236            self.write_keyword("INPUTFORMAT");
11237            self.write_space();
11238            self.write("'");
11239            self.write(fmt);
11240            self.write("'");
11241        }
11242
11243        // SERDE clause
11244        if let Some(serde) = &l.serde {
11245            self.write_space();
11246            self.write_keyword("SERDE");
11247            self.write_space();
11248            self.write("'");
11249            self.write(serde);
11250            self.write("'");
11251        }
11252
11253        Ok(())
11254    }
11255
11256    fn generate_pragma(&mut self, p: &Pragma) -> Result<()> {
11257        self.write_keyword("PRAGMA");
11258        self.write_space();
11259
11260        // Schema prefix if present
11261        if let Some(schema) = &p.schema {
11262            self.generate_identifier(schema)?;
11263            self.write(".");
11264        }
11265
11266        // Pragma name
11267        self.generate_identifier(&p.name)?;
11268
11269        // Value assignment or function call
11270        if let Some(value) = &p.value {
11271            self.write(" = ");
11272            self.generate_expression(value)?;
11273        } else if !p.args.is_empty() {
11274            self.write("(");
11275            for (i, arg) in p.args.iter().enumerate() {
11276                if i > 0 {
11277                    self.write(", ");
11278                }
11279                self.generate_expression(arg)?;
11280            }
11281            self.write(")");
11282        }
11283
11284        Ok(())
11285    }
11286
11287    fn generate_grant(&mut self, g: &Grant) -> Result<()> {
11288        self.write_keyword("GRANT");
11289        self.write_space();
11290
11291        // Privileges (with optional column lists)
11292        for (i, privilege) in g.privileges.iter().enumerate() {
11293            if i > 0 {
11294                self.write(", ");
11295            }
11296            self.write_keyword(&privilege.name);
11297            // Output column list if present: SELECT(col1, col2)
11298            if !privilege.columns.is_empty() {
11299                self.write("(");
11300                for (j, col) in privilege.columns.iter().enumerate() {
11301                    if j > 0 {
11302                        self.write(", ");
11303                    }
11304                    self.write(col);
11305                }
11306                self.write(")");
11307            }
11308        }
11309
11310        self.write_space();
11311        self.write_keyword("ON");
11312        self.write_space();
11313
11314        // Object kind (TABLE, SCHEMA, etc.)
11315        if let Some(kind) = &g.kind {
11316            self.write_keyword(kind);
11317            self.write_space();
11318        }
11319
11320        // Securable - normalize function/procedure names to uppercase for PostgreSQL family
11321        {
11322            use crate::dialects::DialectType;
11323            let should_upper = matches!(
11324                self.config.dialect,
11325                Some(DialectType::PostgreSQL)
11326                    | Some(DialectType::CockroachDB)
11327                    | Some(DialectType::Materialize)
11328                    | Some(DialectType::RisingWave)
11329            ) && (g.kind.as_deref() == Some("FUNCTION")
11330                || g.kind.as_deref() == Some("PROCEDURE"));
11331            if should_upper {
11332                use crate::expressions::Identifier;
11333                let upper_id = Identifier {
11334                    name: g.securable.name.to_ascii_uppercase(),
11335                    quoted: g.securable.quoted,
11336                    ..g.securable.clone()
11337                };
11338                self.generate_identifier(&upper_id)?;
11339            } else {
11340                self.generate_identifier(&g.securable)?;
11341            }
11342        }
11343
11344        // Function parameter types (if present)
11345        if !g.function_params.is_empty() {
11346            self.write("(");
11347            for (i, param) in g.function_params.iter().enumerate() {
11348                if i > 0 {
11349                    self.write(", ");
11350                }
11351                self.write(param);
11352            }
11353            self.write(")");
11354        }
11355
11356        self.write_space();
11357        self.write_keyword("TO");
11358        self.write_space();
11359
11360        // Principals
11361        for (i, principal) in g.principals.iter().enumerate() {
11362            if i > 0 {
11363                self.write(", ");
11364            }
11365            if principal.is_role {
11366                self.write_keyword("ROLE");
11367                self.write_space();
11368            } else if principal.is_group {
11369                self.write_keyword("GROUP");
11370                self.write_space();
11371            }
11372            self.generate_identifier(&principal.name)?;
11373        }
11374
11375        // WITH GRANT OPTION
11376        if g.grant_option {
11377            self.write_space();
11378            self.write_keyword("WITH GRANT OPTION");
11379        }
11380
11381        // TSQL: AS principal
11382        if let Some(ref principal) = g.as_principal {
11383            self.write_space();
11384            self.write_keyword("AS");
11385            self.write_space();
11386            self.generate_identifier(principal)?;
11387        }
11388
11389        Ok(())
11390    }
11391
11392    fn generate_revoke(&mut self, r: &Revoke) -> Result<()> {
11393        self.write_keyword("REVOKE");
11394        self.write_space();
11395
11396        // GRANT OPTION FOR
11397        if r.grant_option {
11398            self.write_keyword("GRANT OPTION FOR");
11399            self.write_space();
11400        }
11401
11402        // Privileges (with optional column lists)
11403        for (i, privilege) in r.privileges.iter().enumerate() {
11404            if i > 0 {
11405                self.write(", ");
11406            }
11407            self.write_keyword(&privilege.name);
11408            // Output column list if present: SELECT(col1, col2)
11409            if !privilege.columns.is_empty() {
11410                self.write("(");
11411                for (j, col) in privilege.columns.iter().enumerate() {
11412                    if j > 0 {
11413                        self.write(", ");
11414                    }
11415                    self.write(col);
11416                }
11417                self.write(")");
11418            }
11419        }
11420
11421        self.write_space();
11422        self.write_keyword("ON");
11423        self.write_space();
11424
11425        // Object kind
11426        if let Some(kind) = &r.kind {
11427            self.write_keyword(kind);
11428            self.write_space();
11429        }
11430
11431        // Securable - normalize function/procedure names to uppercase for PostgreSQL family
11432        {
11433            use crate::dialects::DialectType;
11434            let should_upper = matches!(
11435                self.config.dialect,
11436                Some(DialectType::PostgreSQL)
11437                    | Some(DialectType::CockroachDB)
11438                    | Some(DialectType::Materialize)
11439                    | Some(DialectType::RisingWave)
11440            ) && (r.kind.as_deref() == Some("FUNCTION")
11441                || r.kind.as_deref() == Some("PROCEDURE"));
11442            if should_upper {
11443                use crate::expressions::Identifier;
11444                let upper_id = Identifier {
11445                    name: r.securable.name.to_ascii_uppercase(),
11446                    quoted: r.securable.quoted,
11447                    ..r.securable.clone()
11448                };
11449                self.generate_identifier(&upper_id)?;
11450            } else {
11451                self.generate_identifier(&r.securable)?;
11452            }
11453        }
11454
11455        // Function parameter types (if present)
11456        if !r.function_params.is_empty() {
11457            self.write("(");
11458            for (i, param) in r.function_params.iter().enumerate() {
11459                if i > 0 {
11460                    self.write(", ");
11461                }
11462                self.write(param);
11463            }
11464            self.write(")");
11465        }
11466
11467        self.write_space();
11468        self.write_keyword("FROM");
11469        self.write_space();
11470
11471        // Principals
11472        for (i, principal) in r.principals.iter().enumerate() {
11473            if i > 0 {
11474                self.write(", ");
11475            }
11476            if principal.is_role {
11477                self.write_keyword("ROLE");
11478                self.write_space();
11479            } else if principal.is_group {
11480                self.write_keyword("GROUP");
11481                self.write_space();
11482            }
11483            self.generate_identifier(&principal.name)?;
11484        }
11485
11486        // CASCADE or RESTRICT
11487        if r.cascade {
11488            self.write_space();
11489            self.write_keyword("CASCADE");
11490        } else if r.restrict {
11491            self.write_space();
11492            self.write_keyword("RESTRICT");
11493        }
11494
11495        Ok(())
11496    }
11497
11498    fn generate_comment(&mut self, c: &Comment) -> Result<()> {
11499        self.write_keyword("COMMENT");
11500
11501        // IF EXISTS
11502        if c.exists {
11503            self.write_space();
11504            self.write_keyword("IF EXISTS");
11505        }
11506
11507        self.write_space();
11508        self.write_keyword("ON");
11509
11510        // MATERIALIZED
11511        if c.materialized {
11512            self.write_space();
11513            self.write_keyword("MATERIALIZED");
11514        }
11515
11516        self.write_space();
11517        self.write_keyword(&c.kind);
11518        self.write_space();
11519
11520        // Object name
11521        self.generate_expression(&c.this)?;
11522
11523        self.write_space();
11524        self.write_keyword("IS");
11525        self.write_space();
11526
11527        // Comment expression
11528        self.generate_expression(&c.expression)?;
11529
11530        Ok(())
11531    }
11532
11533    fn generate_set_statement(&mut self, s: &SetStatement) -> Result<()> {
11534        self.write_keyword("SET");
11535
11536        for (i, item) in s.items.iter().enumerate() {
11537            if i > 0 {
11538                self.write(",");
11539            }
11540            self.write_space();
11541
11542            // Kind modifier (GLOBAL, LOCAL, SESSION, PERSIST, PERSIST_ONLY, VARIABLE)
11543            let has_variable_kind = item.kind.as_deref() == Some("VARIABLE");
11544            if let Some(ref kind) = item.kind {
11545                // For VARIABLE kind, only output the keyword for dialects that require it
11546                // (Spark, Databricks, DuckDB) - matching Python sqlglot's
11547                // SET_ASSIGNMENT_REQUIRES_VARIABLE_KEYWORD flag
11548                if has_variable_kind {
11549                    if matches!(
11550                        self.config.dialect,
11551                        Some(
11552                            DialectType::Spark
11553                                | DialectType::Databricks
11554                                | DialectType::DuckDB
11555                        )
11556                    ) {
11557                        self.write_keyword("VARIABLE");
11558                        self.write_space();
11559                    }
11560                } else {
11561                    self.write_keyword(kind);
11562                    self.write_space();
11563                }
11564            }
11565
11566            // Check for special SET forms by name
11567            let name_str = match &item.name {
11568                Expression::Identifier(id) => Some(id.name.as_str()),
11569                _ => None,
11570            };
11571
11572            let is_transaction = name_str == Some("TRANSACTION");
11573            let is_character_set = name_str == Some("CHARACTER SET");
11574            let is_names = name_str == Some("NAMES");
11575            let is_collate = name_str == Some("COLLATE");
11576            let is_value_only =
11577                matches!(&item.value, Expression::Identifier(id) if id.name.is_empty());
11578
11579            if is_transaction {
11580                // Output: SET [GLOBAL|SESSION] TRANSACTION <characteristics>
11581                self.write_keyword("TRANSACTION");
11582                if let Expression::Identifier(id) = &item.value {
11583                    if !id.name.is_empty() {
11584                        self.write_space();
11585                        self.write(&id.name);
11586                    }
11587                }
11588            } else if is_character_set {
11589                // Output: SET CHARACTER SET <charset>
11590                self.write_keyword("CHARACTER SET");
11591                self.write_space();
11592                self.generate_set_value(&item.value)?;
11593            } else if is_names {
11594                // Output: SET NAMES <charset>
11595                self.write_keyword("NAMES");
11596                self.write_space();
11597                self.generate_set_value(&item.value)?;
11598            } else if is_collate {
11599                // Output: COLLATE <collation> (part of SET NAMES ... COLLATE ...)
11600                self.write_keyword("COLLATE");
11601                self.write_space();
11602                self.generate_set_value(&item.value)?;
11603            } else if has_variable_kind {
11604                // Output: SET [VARIABLE] <name> = <value>
11605                // VARIABLE keyword already written above if dialect requires it
11606                if let Some(ns) = name_str {
11607                    self.write(ns);
11608                } else {
11609                    self.generate_expression(&item.name)?;
11610                }
11611                self.write(" = ");
11612                self.generate_set_value(&item.value)?;
11613            } else if is_value_only {
11614                // SET <name> ON/OFF without = (TSQL: SET XACT_ABORT ON)
11615                self.generate_expression(&item.name)?;
11616            } else if item.no_equals && matches!(self.config.dialect, Some(DialectType::TSQL)) {
11617                // SET key value without = (TSQL style)
11618                self.generate_expression(&item.name)?;
11619                self.write_space();
11620                self.generate_set_value(&item.value)?;
11621            } else {
11622                // Standard: variable = value
11623                // SET item names should not be quoted (they are config parameter names, not column refs)
11624                match &item.name {
11625                    Expression::Identifier(id) => {
11626                        self.write(&id.name);
11627                    }
11628                    _ => {
11629                        self.generate_expression(&item.name)?;
11630                    }
11631                }
11632                self.write(" = ");
11633                self.generate_set_value(&item.value)?;
11634            }
11635        }
11636
11637        Ok(())
11638    }
11639
11640    /// Generate a SET statement value, writing keyword values (DEFAULT, ON, OFF)
11641    /// directly to avoid reserved keyword quoting.
11642    fn generate_set_value(&mut self, value: &Expression) -> Result<()> {
11643        if let Expression::Identifier(id) = value {
11644            match id.name.as_str() {
11645                "DEFAULT" | "ON" | "OFF" => {
11646                    self.write_keyword(&id.name);
11647                    return Ok(());
11648                }
11649                _ => {}
11650            }
11651        }
11652        self.generate_expression(value)
11653    }
11654
11655    // ==================== Phase 4: Additional DDL Generation ====================
11656
11657    fn generate_alter_view(&mut self, av: &AlterView) -> Result<()> {
11658        self.write_keyword("ALTER");
11659        // MySQL modifiers before VIEW
11660        if let Some(ref algorithm) = av.algorithm {
11661            self.write_space();
11662            self.write_keyword("ALGORITHM");
11663            self.write(" = ");
11664            self.write_keyword(algorithm);
11665        }
11666        if let Some(ref definer) = av.definer {
11667            self.write_space();
11668            self.write_keyword("DEFINER");
11669            self.write(" = ");
11670            self.write(definer);
11671        }
11672        if let Some(ref sql_security) = av.sql_security {
11673            self.write_space();
11674            self.write_keyword("SQL SECURITY");
11675            self.write(" = ");
11676            self.write_keyword(sql_security);
11677        }
11678        self.write_space();
11679        self.write_keyword("VIEW");
11680        self.write_space();
11681        self.generate_table(&av.name)?;
11682
11683        // Hive: Column aliases with optional COMMENT
11684        if !av.columns.is_empty() {
11685            self.write(" (");
11686            for (i, col) in av.columns.iter().enumerate() {
11687                if i > 0 {
11688                    self.write(", ");
11689                }
11690                self.generate_identifier(&col.name)?;
11691                if let Some(ref comment) = col.comment {
11692                    self.write_space();
11693                    self.write_keyword("COMMENT");
11694                    self.write(" ");
11695                    self.generate_string_literal(comment)?;
11696                }
11697            }
11698            self.write(")");
11699        }
11700
11701        // TSQL: WITH option before actions
11702        if let Some(ref opt) = av.with_option {
11703            self.write_space();
11704            self.write_keyword("WITH");
11705            self.write_space();
11706            self.write_keyword(opt);
11707        }
11708
11709        for action in &av.actions {
11710            self.write_space();
11711            match action {
11712                AlterViewAction::Rename(new_name) => {
11713                    self.write_keyword("RENAME TO");
11714                    self.write_space();
11715                    self.generate_table(new_name)?;
11716                }
11717                AlterViewAction::OwnerTo(owner) => {
11718                    self.write_keyword("OWNER TO");
11719                    self.write_space();
11720                    self.generate_identifier(owner)?;
11721                }
11722                AlterViewAction::SetSchema(schema) => {
11723                    self.write_keyword("SET SCHEMA");
11724                    self.write_space();
11725                    self.generate_identifier(schema)?;
11726                }
11727                AlterViewAction::SetAuthorization(auth) => {
11728                    self.write_keyword("SET AUTHORIZATION");
11729                    self.write_space();
11730                    self.write(auth);
11731                }
11732                AlterViewAction::AlterColumn { name, action } => {
11733                    self.write_keyword("ALTER COLUMN");
11734                    self.write_space();
11735                    self.generate_identifier(name)?;
11736                    self.write_space();
11737                    self.generate_alter_column_action(action)?;
11738                }
11739                AlterViewAction::AsSelect(query) => {
11740                    self.write_keyword("AS");
11741                    self.write_space();
11742                    self.generate_expression(query)?;
11743                }
11744                AlterViewAction::SetTblproperties(props) => {
11745                    self.write_keyword("SET TBLPROPERTIES");
11746                    self.write(" (");
11747                    for (i, (key, value)) in props.iter().enumerate() {
11748                        if i > 0 {
11749                            self.write(", ");
11750                        }
11751                        self.generate_string_literal(key)?;
11752                        self.write("=");
11753                        self.generate_string_literal(value)?;
11754                    }
11755                    self.write(")");
11756                }
11757                AlterViewAction::UnsetTblproperties(keys) => {
11758                    self.write_keyword("UNSET TBLPROPERTIES");
11759                    self.write(" (");
11760                    for (i, key) in keys.iter().enumerate() {
11761                        if i > 0 {
11762                            self.write(", ");
11763                        }
11764                        self.generate_string_literal(key)?;
11765                    }
11766                    self.write(")");
11767                }
11768            }
11769        }
11770
11771        Ok(())
11772    }
11773
11774    fn generate_alter_index(&mut self, ai: &AlterIndex) -> Result<()> {
11775        self.write_keyword("ALTER INDEX");
11776        self.write_space();
11777        self.generate_identifier(&ai.name)?;
11778
11779        if let Some(table) = &ai.table {
11780            self.write_space();
11781            self.write_keyword("ON");
11782            self.write_space();
11783            self.generate_table(table)?;
11784        }
11785
11786        for action in &ai.actions {
11787            self.write_space();
11788            match action {
11789                AlterIndexAction::Rename(new_name) => {
11790                    self.write_keyword("RENAME TO");
11791                    self.write_space();
11792                    self.generate_identifier(new_name)?;
11793                }
11794                AlterIndexAction::SetTablespace(tablespace) => {
11795                    self.write_keyword("SET TABLESPACE");
11796                    self.write_space();
11797                    self.generate_identifier(tablespace)?;
11798                }
11799                AlterIndexAction::Visible(visible) => {
11800                    if *visible {
11801                        self.write_keyword("VISIBLE");
11802                    } else {
11803                        self.write_keyword("INVISIBLE");
11804                    }
11805                }
11806            }
11807        }
11808
11809        Ok(())
11810    }
11811
11812    fn generate_create_schema(&mut self, cs: &CreateSchema) -> Result<()> {
11813        // Output leading comments
11814        for comment in &cs.leading_comments {
11815            self.write_formatted_comment(comment);
11816            self.write_space();
11817        }
11818
11819        // Athena: CREATE SCHEMA uses Hive engine (backticks)
11820        let saved_athena_hive_context = self.athena_hive_context;
11821        if matches!(
11822            self.config.dialect,
11823            Some(crate::dialects::DialectType::Athena)
11824        ) {
11825            self.athena_hive_context = true;
11826        }
11827
11828        self.write_keyword("CREATE SCHEMA");
11829
11830        if cs.if_not_exists {
11831            self.write_space();
11832            self.write_keyword("IF NOT EXISTS");
11833        }
11834
11835        self.write_space();
11836        self.generate_identifier(&cs.name)?;
11837
11838        if let Some(ref clone_src) = cs.clone_from {
11839            self.write_keyword(" CLONE ");
11840            self.generate_identifier(clone_src)?;
11841        }
11842
11843        if let Some(ref at_clause) = cs.at_clause {
11844            self.write_space();
11845            self.generate_expression(at_clause)?;
11846        }
11847
11848        if let Some(auth) = &cs.authorization {
11849            self.write_space();
11850            self.write_keyword("AUTHORIZATION");
11851            self.write_space();
11852            self.generate_identifier(auth)?;
11853        }
11854
11855        // Generate schema properties (e.g., DEFAULT COLLATE or WITH (props))
11856        // Separate WITH properties from other properties
11857        let with_properties: Vec<_> = cs
11858            .properties
11859            .iter()
11860            .filter(|p| matches!(p, Expression::Property(_)))
11861            .collect();
11862        let other_properties: Vec<_> = cs
11863            .properties
11864            .iter()
11865            .filter(|p| !matches!(p, Expression::Property(_)))
11866            .collect();
11867
11868        // Generate WITH (props) if we have Property expressions
11869        if !with_properties.is_empty() {
11870            self.write_space();
11871            self.write_keyword("WITH");
11872            self.write(" (");
11873            for (i, prop) in with_properties.iter().enumerate() {
11874                if i > 0 {
11875                    self.write(", ");
11876                }
11877                self.generate_expression(prop)?;
11878            }
11879            self.write(")");
11880        }
11881
11882        // Generate other properties (like DEFAULT COLLATE)
11883        for prop in other_properties {
11884            self.write_space();
11885            self.generate_expression(prop)?;
11886        }
11887
11888        // Restore Athena Hive context
11889        self.athena_hive_context = saved_athena_hive_context;
11890
11891        Ok(())
11892    }
11893
11894    fn generate_drop_schema(&mut self, ds: &DropSchema) -> Result<()> {
11895        self.write_keyword("DROP SCHEMA");
11896
11897        if ds.if_exists {
11898            self.write_space();
11899            self.write_keyword("IF EXISTS");
11900        }
11901
11902        self.write_space();
11903        self.generate_identifier(&ds.name)?;
11904
11905        if ds.cascade {
11906            self.write_space();
11907            self.write_keyword("CASCADE");
11908        }
11909
11910        Ok(())
11911    }
11912
11913    fn generate_drop_namespace(&mut self, dn: &DropNamespace) -> Result<()> {
11914        self.write_keyword("DROP NAMESPACE");
11915
11916        if dn.if_exists {
11917            self.write_space();
11918            self.write_keyword("IF EXISTS");
11919        }
11920
11921        self.write_space();
11922        self.generate_identifier(&dn.name)?;
11923
11924        if dn.cascade {
11925            self.write_space();
11926            self.write_keyword("CASCADE");
11927        }
11928
11929        Ok(())
11930    }
11931
11932    fn generate_create_database(&mut self, cd: &CreateDatabase) -> Result<()> {
11933        self.write_keyword("CREATE DATABASE");
11934
11935        if cd.if_not_exists {
11936            self.write_space();
11937            self.write_keyword("IF NOT EXISTS");
11938        }
11939
11940        self.write_space();
11941        self.generate_identifier(&cd.name)?;
11942
11943        if let Some(ref clone_src) = cd.clone_from {
11944            self.write_keyword(" CLONE ");
11945            self.generate_identifier(clone_src)?;
11946        }
11947
11948        // AT/BEFORE clause for time travel (Snowflake)
11949        if let Some(ref at_clause) = cd.at_clause {
11950            self.write_space();
11951            self.generate_expression(at_clause)?;
11952        }
11953
11954        for option in &cd.options {
11955            self.write_space();
11956            match option {
11957                DatabaseOption::CharacterSet(charset) => {
11958                    self.write_keyword("CHARACTER SET");
11959                    self.write(" = ");
11960                    self.write(&format!("'{}'", charset));
11961                }
11962                DatabaseOption::Collate(collate) => {
11963                    self.write_keyword("COLLATE");
11964                    self.write(" = ");
11965                    self.write(&format!("'{}'", collate));
11966                }
11967                DatabaseOption::Owner(owner) => {
11968                    self.write_keyword("OWNER");
11969                    self.write(" = ");
11970                    self.generate_identifier(owner)?;
11971                }
11972                DatabaseOption::Template(template) => {
11973                    self.write_keyword("TEMPLATE");
11974                    self.write(" = ");
11975                    self.generate_identifier(template)?;
11976                }
11977                DatabaseOption::Encoding(encoding) => {
11978                    self.write_keyword("ENCODING");
11979                    self.write(" = ");
11980                    self.write(&format!("'{}'", encoding));
11981                }
11982                DatabaseOption::Location(location) => {
11983                    self.write_keyword("LOCATION");
11984                    self.write(" = ");
11985                    self.write(&format!("'{}'", location));
11986                }
11987            }
11988        }
11989
11990        Ok(())
11991    }
11992
11993    fn generate_drop_database(&mut self, dd: &DropDatabase) -> Result<()> {
11994        self.write_keyword("DROP DATABASE");
11995
11996        if dd.if_exists {
11997            self.write_space();
11998            self.write_keyword("IF EXISTS");
11999        }
12000
12001        self.write_space();
12002        self.generate_identifier(&dd.name)?;
12003
12004        if dd.sync {
12005            self.write_space();
12006            self.write_keyword("SYNC");
12007        }
12008
12009        Ok(())
12010    }
12011
12012    fn generate_create_function(&mut self, cf: &CreateFunction) -> Result<()> {
12013        self.write_keyword("CREATE");
12014
12015        if cf.or_replace {
12016            self.write_space();
12017            self.write_keyword("OR REPLACE");
12018        }
12019
12020        if cf.temporary {
12021            self.write_space();
12022            self.write_keyword("TEMPORARY");
12023        }
12024
12025        self.write_space();
12026        if cf.is_table_function {
12027            self.write_keyword("TABLE FUNCTION");
12028        } else {
12029            self.write_keyword("FUNCTION");
12030        }
12031
12032        if cf.if_not_exists {
12033            self.write_space();
12034            self.write_keyword("IF NOT EXISTS");
12035        }
12036
12037        self.write_space();
12038        self.generate_table(&cf.name)?;
12039        if cf.has_parens {
12040            let func_multiline = self.config.pretty
12041                && matches!(
12042                    self.config.dialect,
12043                    Some(crate::dialects::DialectType::TSQL)
12044                        | Some(crate::dialects::DialectType::Fabric)
12045                )
12046                && !cf.parameters.is_empty();
12047            if func_multiline {
12048                self.write("(\n");
12049                self.indent_level += 2;
12050                self.write_indent();
12051                self.generate_function_parameters(&cf.parameters)?;
12052                self.write("\n");
12053                self.indent_level -= 2;
12054                self.write(")");
12055            } else {
12056                self.write("(");
12057                self.generate_function_parameters(&cf.parameters)?;
12058                self.write(")");
12059            }
12060        }
12061
12062        // Output RETURNS clause (always comes first after parameters)
12063        // BigQuery and TSQL use multiline formatting for CREATE FUNCTION structure
12064        let use_multiline = self.config.pretty
12065            && matches!(
12066                self.config.dialect,
12067                Some(crate::dialects::DialectType::BigQuery)
12068                    | Some(crate::dialects::DialectType::TSQL)
12069                    | Some(crate::dialects::DialectType::Fabric)
12070            );
12071
12072        if cf.language_first {
12073            // LANGUAGE first, then SQL data access, then RETURNS
12074            if let Some(lang) = &cf.language {
12075                if use_multiline {
12076                    self.write_newline();
12077                } else {
12078                    self.write_space();
12079                }
12080                self.write_keyword("LANGUAGE");
12081                self.write_space();
12082                self.write(lang);
12083            }
12084
12085            // SQL data access comes after LANGUAGE in this case
12086            if let Some(sql_data) = &cf.sql_data_access {
12087                self.write_space();
12088                match sql_data {
12089                    SqlDataAccess::NoSql => self.write_keyword("NO SQL"),
12090                    SqlDataAccess::ContainsSql => self.write_keyword("CONTAINS SQL"),
12091                    SqlDataAccess::ReadsSqlData => self.write_keyword("READS SQL DATA"),
12092                    SqlDataAccess::ModifiesSqlData => self.write_keyword("MODIFIES SQL DATA"),
12093                }
12094            }
12095
12096            if let Some(ref rtb) = cf.returns_table_body {
12097                if use_multiline {
12098                    self.write_newline();
12099                } else {
12100                    self.write_space();
12101                }
12102                self.write_keyword("RETURNS");
12103                self.write_space();
12104                self.write(rtb);
12105            } else if let Some(return_type) = &cf.return_type {
12106                if use_multiline {
12107                    self.write_newline();
12108                } else {
12109                    self.write_space();
12110                }
12111                self.write_keyword("RETURNS");
12112                self.write_space();
12113                self.generate_data_type(return_type)?;
12114            }
12115        } else {
12116            // RETURNS first (default)
12117            // DuckDB macros: skip RETURNS output (empty marker in returns_table_body means TABLE return)
12118            let is_duckdb = matches!(
12119                self.config.dialect,
12120                Some(crate::dialects::DialectType::DuckDB)
12121            );
12122            if let Some(ref rtb) = cf.returns_table_body {
12123                if !(is_duckdb && rtb.is_empty()) {
12124                    if use_multiline {
12125                        self.write_newline();
12126                    } else {
12127                        self.write_space();
12128                    }
12129                    self.write_keyword("RETURNS");
12130                    self.write_space();
12131                    self.write(rtb);
12132                }
12133            } else if let Some(return_type) = &cf.return_type {
12134                if use_multiline {
12135                    self.write_newline();
12136                } else {
12137                    self.write_space();
12138                }
12139                self.write_keyword("RETURNS");
12140                self.write_space();
12141                self.generate_data_type(return_type)?;
12142            }
12143        }
12144
12145        // If we have property_order, use it to output properties in original order
12146        if !cf.property_order.is_empty() {
12147            // For BigQuery, OPTIONS must come before AS - reorder if needed
12148            let is_bigquery = matches!(
12149                self.config.dialect,
12150                Some(crate::dialects::DialectType::BigQuery)
12151            );
12152            let property_order = if is_bigquery {
12153                // Move Options before As if both are present
12154                let mut reordered = Vec::new();
12155                let mut has_as = false;
12156                let mut has_options = false;
12157                for prop in &cf.property_order {
12158                    match prop {
12159                        FunctionPropertyKind::As => has_as = true,
12160                        FunctionPropertyKind::Options => has_options = true,
12161                        _ => {}
12162                    }
12163                }
12164                if has_as && has_options {
12165                    // Output all props except As and Options, then Options, then As
12166                    for prop in &cf.property_order {
12167                        if *prop != FunctionPropertyKind::As
12168                            && *prop != FunctionPropertyKind::Options
12169                        {
12170                            reordered.push(*prop);
12171                        }
12172                    }
12173                    reordered.push(FunctionPropertyKind::Options);
12174                    reordered.push(FunctionPropertyKind::As);
12175                    reordered
12176                } else {
12177                    cf.property_order.clone()
12178                }
12179            } else {
12180                cf.property_order.clone()
12181            };
12182
12183            for prop in &property_order {
12184                match prop {
12185                    FunctionPropertyKind::Set => {
12186                        self.generate_function_set_options(cf)?;
12187                    }
12188                    FunctionPropertyKind::As => {
12189                        self.generate_function_body(cf)?;
12190                    }
12191                    FunctionPropertyKind::Language => {
12192                        if !cf.language_first {
12193                            // Only output here if not already output above
12194                            if let Some(lang) = &cf.language {
12195                                // Only BigQuery uses multiline formatting
12196                                let use_multiline = self.config.pretty
12197                                    && matches!(
12198                                        self.config.dialect,
12199                                        Some(crate::dialects::DialectType::BigQuery)
12200                                    );
12201                                if use_multiline {
12202                                    self.write_newline();
12203                                } else {
12204                                    self.write_space();
12205                                }
12206                                self.write_keyword("LANGUAGE");
12207                                self.write_space();
12208                                self.write(lang);
12209                            }
12210                        }
12211                    }
12212                    FunctionPropertyKind::Determinism => {
12213                        self.generate_function_determinism(cf)?;
12214                    }
12215                    FunctionPropertyKind::NullInput => {
12216                        self.generate_function_null_input(cf)?;
12217                    }
12218                    FunctionPropertyKind::Security => {
12219                        self.generate_function_security(cf)?;
12220                    }
12221                    FunctionPropertyKind::SqlDataAccess => {
12222                        if !cf.language_first {
12223                            // Only output here if not already output above
12224                            self.generate_function_sql_data_access(cf)?;
12225                        }
12226                    }
12227                    FunctionPropertyKind::Options => {
12228                        if !cf.options.is_empty() {
12229                            self.write_space();
12230                            self.generate_options_clause(&cf.options)?;
12231                        }
12232                    }
12233                    FunctionPropertyKind::Environment => {
12234                        if !cf.environment.is_empty() {
12235                            self.write_space();
12236                            self.generate_environment_clause(&cf.environment)?;
12237                        }
12238                    }
12239                    FunctionPropertyKind::Handler => {
12240                        if let Some(ref h) = cf.handler {
12241                            self.write_space();
12242                            self.write_keyword("HANDLER");
12243                            self.write_space();
12244                            self.write("'");
12245                            self.write(h);
12246                            self.write("'");
12247                        }
12248                    }
12249                    FunctionPropertyKind::ParameterStyle => {
12250                        if let Some(ref ps) = cf.parameter_style {
12251                            self.write_space();
12252                            self.write_keyword("PARAMETER STYLE");
12253                            self.write_space();
12254                            self.write_keyword(ps);
12255                        }
12256                    }
12257                }
12258            }
12259
12260            // Output OPTIONS if not tracked in property_order (legacy)
12261            if !cf.options.is_empty() && !cf.property_order.contains(&FunctionPropertyKind::Options)
12262            {
12263                self.write_space();
12264                self.generate_options_clause(&cf.options)?;
12265            }
12266
12267            // Output ENVIRONMENT if not tracked in property_order (legacy)
12268            if !cf.environment.is_empty()
12269                && !cf
12270                    .property_order
12271                    .contains(&FunctionPropertyKind::Environment)
12272            {
12273                self.write_space();
12274                self.generate_environment_clause(&cf.environment)?;
12275            }
12276        } else {
12277            // Legacy behavior when property_order is empty
12278            // BigQuery: DETERMINISTIC/NOT DETERMINISTIC comes before LANGUAGE
12279            if matches!(
12280                self.config.dialect,
12281                Some(crate::dialects::DialectType::BigQuery)
12282            ) {
12283                self.generate_function_determinism(cf)?;
12284            }
12285
12286            // Only BigQuery uses multiline formatting for CREATE FUNCTION structure
12287            let use_multiline = self.config.pretty
12288                && matches!(
12289                    self.config.dialect,
12290                    Some(crate::dialects::DialectType::BigQuery)
12291                );
12292
12293            if !cf.language_first {
12294                if let Some(lang) = &cf.language {
12295                    if use_multiline {
12296                        self.write_newline();
12297                    } else {
12298                        self.write_space();
12299                    }
12300                    self.write_keyword("LANGUAGE");
12301                    self.write_space();
12302                    self.write(lang);
12303                }
12304
12305                // SQL data access characteristic comes after LANGUAGE
12306                self.generate_function_sql_data_access(cf)?;
12307            }
12308
12309            // For non-BigQuery dialects, output DETERMINISTIC/IMMUTABLE/VOLATILE here
12310            if !matches!(
12311                self.config.dialect,
12312                Some(crate::dialects::DialectType::BigQuery)
12313            ) {
12314                self.generate_function_determinism(cf)?;
12315            }
12316
12317            self.generate_function_null_input(cf)?;
12318            self.generate_function_security(cf)?;
12319            self.generate_function_set_options(cf)?;
12320
12321            // BigQuery: OPTIONS (key=value, ...) - comes before AS
12322            if !cf.options.is_empty() {
12323                self.write_space();
12324                self.generate_options_clause(&cf.options)?;
12325            }
12326
12327            // Databricks: ENVIRONMENT (dependencies = '...', ...) - comes before AS
12328            if !cf.environment.is_empty() {
12329                self.write_space();
12330                self.generate_environment_clause(&cf.environment)?;
12331            }
12332
12333            self.generate_function_body(cf)?;
12334        }
12335
12336        Ok(())
12337    }
12338
12339    /// Generate SET options for CREATE FUNCTION
12340    fn generate_function_set_options(&mut self, cf: &CreateFunction) -> Result<()> {
12341        for opt in &cf.set_options {
12342            self.write_space();
12343            self.write_keyword("SET");
12344            self.write_space();
12345            self.write(&opt.name);
12346            match &opt.value {
12347                FunctionSetValue::Value { value, use_to } => {
12348                    if *use_to {
12349                        self.write(" TO ");
12350                    } else {
12351                        self.write(" = ");
12352                    }
12353                    self.write(value);
12354                }
12355                FunctionSetValue::FromCurrent => {
12356                    self.write_space();
12357                    self.write_keyword("FROM CURRENT");
12358                }
12359            }
12360        }
12361        Ok(())
12362    }
12363
12364    /// Generate function body (AS clause)
12365    fn generate_function_body(&mut self, cf: &CreateFunction) -> Result<()> {
12366        if let Some(body) = &cf.body {
12367            // AS stays on same line as previous content (e.g., LANGUAGE js AS)
12368            self.write_space();
12369            // Only BigQuery uses multiline formatting for CREATE FUNCTION body
12370            let use_multiline = self.config.pretty
12371                && matches!(
12372                    self.config.dialect,
12373                    Some(crate::dialects::DialectType::BigQuery)
12374                );
12375            match body {
12376                FunctionBody::Block(block) => {
12377                    self.write_keyword("AS");
12378                    if matches!(
12379                        self.config.dialect,
12380                        Some(crate::dialects::DialectType::TSQL)
12381                    ) {
12382                        self.write(" BEGIN ");
12383                        self.write(block);
12384                        self.write(" END");
12385                    } else if matches!(
12386                        self.config.dialect,
12387                        Some(crate::dialects::DialectType::PostgreSQL)
12388                    ) {
12389                        self.write(" $$");
12390                        self.write(block);
12391                        self.write("$$");
12392                    } else {
12393                        // Escape content for single-quoted output
12394                        let escaped = self.escape_block_for_single_quote(block);
12395                        // In BigQuery pretty mode, body content goes on new line
12396                        if use_multiline {
12397                            self.write_newline();
12398                        } else {
12399                            self.write(" ");
12400                        }
12401                        self.write("'");
12402                        self.write(&escaped);
12403                        self.write("'");
12404                    }
12405                }
12406                FunctionBody::StringLiteral(s) => {
12407                    self.write_keyword("AS");
12408                    // In BigQuery pretty mode, body content goes on new line
12409                    if use_multiline {
12410                        self.write_newline();
12411                    } else {
12412                        self.write(" ");
12413                    }
12414                    self.write("'");
12415                    self.write(s);
12416                    self.write("'");
12417                }
12418                FunctionBody::Expression(expr) => {
12419                    self.write_keyword("AS");
12420                    self.write_space();
12421                    self.generate_expression(expr)?;
12422                }
12423                FunctionBody::External(name) => {
12424                    self.write_keyword("EXTERNAL NAME");
12425                    self.write(" '");
12426                    self.write(name);
12427                    self.write("'");
12428                }
12429                FunctionBody::Return(expr) => {
12430                    if matches!(
12431                        self.config.dialect,
12432                        Some(crate::dialects::DialectType::DuckDB)
12433                    ) {
12434                        // DuckDB macro syntax: AS [TABLE] expression (no RETURN keyword)
12435                        self.write_keyword("AS");
12436                        self.write_space();
12437                        // Empty returns_table_body signals TABLE return
12438                        if cf.returns_table_body.is_some() {
12439                            self.write_keyword("TABLE");
12440                            self.write_space();
12441                        }
12442                        self.generate_expression(expr)?;
12443                    } else {
12444                        if self.config.create_function_return_as {
12445                            self.write_keyword("AS");
12446                            // TSQL pretty: newline between AS and RETURN
12447                            if self.config.pretty
12448                                && matches!(
12449                                    self.config.dialect,
12450                                    Some(crate::dialects::DialectType::TSQL)
12451                                        | Some(crate::dialects::DialectType::Fabric)
12452                                )
12453                            {
12454                                self.write_newline();
12455                            } else {
12456                                self.write_space();
12457                            }
12458                        }
12459                        self.write_keyword("RETURN");
12460                        self.write_space();
12461                        self.generate_expression(expr)?;
12462                    }
12463                }
12464                FunctionBody::Statements(stmts) => {
12465                    self.write_keyword("AS");
12466                    self.write(" BEGIN ");
12467                    for (i, stmt) in stmts.iter().enumerate() {
12468                        if i > 0 {
12469                            self.write(" ");
12470                        }
12471                        self.generate_expression(stmt)?;
12472                        self.write(";");
12473                    }
12474                    self.write(" END");
12475                }
12476                FunctionBody::DollarQuoted { content, tag } => {
12477                    self.write_keyword("AS");
12478                    self.write(" ");
12479                    // Dialects that support dollar-quoted strings: PostgreSQL, Databricks, Redshift, DuckDB
12480                    let supports_dollar_quoting = matches!(
12481                        self.config.dialect,
12482                        Some(crate::dialects::DialectType::PostgreSQL)
12483                            | Some(crate::dialects::DialectType::Databricks)
12484                            | Some(crate::dialects::DialectType::Redshift)
12485                            | Some(crate::dialects::DialectType::DuckDB)
12486                    );
12487                    if supports_dollar_quoting {
12488                        // Output in dollar-quoted format
12489                        self.write("$");
12490                        if let Some(t) = tag {
12491                            self.write(t);
12492                        }
12493                        self.write("$");
12494                        self.write(content);
12495                        self.write("$");
12496                        if let Some(t) = tag {
12497                            self.write(t);
12498                        }
12499                        self.write("$");
12500                    } else {
12501                        // Convert to single-quoted string for other dialects
12502                        let escaped = self.escape_block_for_single_quote(content);
12503                        self.write("'");
12504                        self.write(&escaped);
12505                        self.write("'");
12506                    }
12507                }
12508            }
12509        }
12510        Ok(())
12511    }
12512
12513    /// Generate determinism clause (IMMUTABLE/VOLATILE/DETERMINISTIC)
12514    fn generate_function_determinism(&mut self, cf: &CreateFunction) -> Result<()> {
12515        if let Some(det) = cf.deterministic {
12516            self.write_space();
12517            if matches!(
12518                self.config.dialect,
12519                Some(crate::dialects::DialectType::BigQuery)
12520            ) {
12521                // BigQuery uses DETERMINISTIC/NOT DETERMINISTIC
12522                if det {
12523                    self.write_keyword("DETERMINISTIC");
12524                } else {
12525                    self.write_keyword("NOT DETERMINISTIC");
12526                }
12527            } else {
12528                // PostgreSQL and others use IMMUTABLE/VOLATILE
12529                if det {
12530                    self.write_keyword("IMMUTABLE");
12531                } else {
12532                    self.write_keyword("VOLATILE");
12533                }
12534            }
12535        }
12536        Ok(())
12537    }
12538
12539    /// Generate null input handling clause
12540    fn generate_function_null_input(&mut self, cf: &CreateFunction) -> Result<()> {
12541        if let Some(returns_null) = cf.returns_null_on_null_input {
12542            self.write_space();
12543            if returns_null {
12544                if cf.strict {
12545                    self.write_keyword("STRICT");
12546                } else {
12547                    self.write_keyword("RETURNS NULL ON NULL INPUT");
12548                }
12549            } else {
12550                self.write_keyword("CALLED ON NULL INPUT");
12551            }
12552        }
12553        Ok(())
12554    }
12555
12556    /// Generate security clause
12557    fn generate_function_security(&mut self, cf: &CreateFunction) -> Result<()> {
12558        if let Some(security) = &cf.security {
12559            self.write_space();
12560            // MySQL uses SQL SECURITY prefix
12561            if matches!(
12562                self.config.dialect,
12563                Some(crate::dialects::DialectType::MySQL)
12564            ) {
12565                self.write_keyword("SQL SECURITY");
12566            } else {
12567                self.write_keyword("SECURITY");
12568            }
12569            self.write_space();
12570            match security {
12571                FunctionSecurity::Definer => self.write_keyword("DEFINER"),
12572                FunctionSecurity::Invoker => self.write_keyword("INVOKER"),
12573                FunctionSecurity::None => self.write_keyword("NONE"),
12574            }
12575        }
12576        Ok(())
12577    }
12578
12579    /// Generate SQL data access clause
12580    fn generate_function_sql_data_access(&mut self, cf: &CreateFunction) -> Result<()> {
12581        if let Some(sql_data) = &cf.sql_data_access {
12582            self.write_space();
12583            match sql_data {
12584                SqlDataAccess::NoSql => self.write_keyword("NO SQL"),
12585                SqlDataAccess::ContainsSql => self.write_keyword("CONTAINS SQL"),
12586                SqlDataAccess::ReadsSqlData => self.write_keyword("READS SQL DATA"),
12587                SqlDataAccess::ModifiesSqlData => self.write_keyword("MODIFIES SQL DATA"),
12588            }
12589        }
12590        Ok(())
12591    }
12592
12593    fn generate_function_parameters(&mut self, params: &[FunctionParameter]) -> Result<()> {
12594        for (i, param) in params.iter().enumerate() {
12595            if i > 0 {
12596                self.write(", ");
12597            }
12598
12599            if let Some(mode) = &param.mode {
12600                if let Some(text) = &param.mode_text {
12601                    self.write(text);
12602                } else {
12603                    match mode {
12604                        ParameterMode::In => self.write_keyword("IN"),
12605                        ParameterMode::Out => self.write_keyword("OUT"),
12606                        ParameterMode::InOut => self.write_keyword("INOUT"),
12607                        ParameterMode::Variadic => self.write_keyword("VARIADIC"),
12608                    }
12609                }
12610                self.write_space();
12611            }
12612
12613            if let Some(name) = &param.name {
12614                self.generate_identifier(name)?;
12615                // Skip space and type for empty Custom types (e.g., DuckDB macros)
12616                let skip_type =
12617                    matches!(&param.data_type, DataType::Custom { name } if name.is_empty());
12618                if !skip_type {
12619                    self.write_space();
12620                    self.generate_data_type(&param.data_type)?;
12621                }
12622            } else {
12623                self.generate_data_type(&param.data_type)?;
12624            }
12625
12626            if let Some(default) = &param.default {
12627                if self.config.parameter_default_equals {
12628                    self.write(" = ");
12629                } else {
12630                    self.write(" DEFAULT ");
12631                }
12632                self.generate_expression(default)?;
12633            }
12634        }
12635
12636        Ok(())
12637    }
12638
12639    fn generate_drop_function(&mut self, df: &DropFunction) -> Result<()> {
12640        self.write_keyword("DROP FUNCTION");
12641
12642        if df.if_exists {
12643            self.write_space();
12644            self.write_keyword("IF EXISTS");
12645        }
12646
12647        self.write_space();
12648        self.generate_table(&df.name)?;
12649
12650        if let Some(params) = &df.parameters {
12651            self.write(" (");
12652            for (i, dt) in params.iter().enumerate() {
12653                if i > 0 {
12654                    self.write(", ");
12655                }
12656                self.generate_data_type(dt)?;
12657            }
12658            self.write(")");
12659        }
12660
12661        if df.cascade {
12662            self.write_space();
12663            self.write_keyword("CASCADE");
12664        }
12665
12666        Ok(())
12667    }
12668
12669    fn generate_create_procedure(&mut self, cp: &CreateProcedure) -> Result<()> {
12670        self.write_keyword("CREATE");
12671
12672        if cp.or_replace {
12673            self.write_space();
12674            self.write_keyword("OR REPLACE");
12675        }
12676
12677        self.write_space();
12678        if cp.use_proc_keyword {
12679            self.write_keyword("PROC");
12680        } else {
12681            self.write_keyword("PROCEDURE");
12682        }
12683
12684        if cp.if_not_exists {
12685            self.write_space();
12686            self.write_keyword("IF NOT EXISTS");
12687        }
12688
12689        self.write_space();
12690        self.generate_table(&cp.name)?;
12691        if cp.has_parens {
12692            self.write("(");
12693            self.generate_function_parameters(&cp.parameters)?;
12694            self.write(")");
12695        } else if !cp.parameters.is_empty() {
12696            // TSQL: unparenthesized parameters
12697            self.write_space();
12698            self.generate_function_parameters(&cp.parameters)?;
12699        }
12700
12701        // RETURNS clause (Snowflake)
12702        if let Some(return_type) = &cp.return_type {
12703            self.write_space();
12704            self.write_keyword("RETURNS");
12705            self.write_space();
12706            self.generate_data_type(return_type)?;
12707        }
12708
12709        // EXECUTE AS clause (Snowflake)
12710        if let Some(execute_as) = &cp.execute_as {
12711            self.write_space();
12712            self.write_keyword("EXECUTE AS");
12713            self.write_space();
12714            self.write_keyword(execute_as);
12715        }
12716
12717        if let Some(lang) = &cp.language {
12718            self.write_space();
12719            self.write_keyword("LANGUAGE");
12720            self.write_space();
12721            self.write(lang);
12722        }
12723
12724        if let Some(security) = &cp.security {
12725            self.write_space();
12726            self.write_keyword("SECURITY");
12727            self.write_space();
12728            match security {
12729                FunctionSecurity::Definer => self.write_keyword("DEFINER"),
12730                FunctionSecurity::Invoker => self.write_keyword("INVOKER"),
12731                FunctionSecurity::None => self.write_keyword("NONE"),
12732            }
12733        }
12734
12735        // TSQL WITH options (ENCRYPTION, RECOMPILE, etc.)
12736        if !cp.with_options.is_empty() {
12737            self.write_space();
12738            self.write_keyword("WITH");
12739            self.write_space();
12740            for (i, opt) in cp.with_options.iter().enumerate() {
12741                if i > 0 {
12742                    self.write(", ");
12743                }
12744                self.write(opt);
12745            }
12746        }
12747
12748        if let Some(body) = &cp.body {
12749            self.write_space();
12750            match body {
12751                FunctionBody::Block(block) => {
12752                    self.write_keyword("AS");
12753                    if matches!(
12754                        self.config.dialect,
12755                        Some(crate::dialects::DialectType::TSQL)
12756                    ) {
12757                        self.write(" BEGIN ");
12758                        self.write(block);
12759                        self.write(" END");
12760                    } else if matches!(
12761                        self.config.dialect,
12762                        Some(crate::dialects::DialectType::PostgreSQL)
12763                    ) {
12764                        self.write(" $$");
12765                        self.write(block);
12766                        self.write("$$");
12767                    } else {
12768                        // Escape content for single-quoted output
12769                        let escaped = self.escape_block_for_single_quote(block);
12770                        self.write(" '");
12771                        self.write(&escaped);
12772                        self.write("'");
12773                    }
12774                }
12775                FunctionBody::StringLiteral(s) => {
12776                    self.write_keyword("AS");
12777                    self.write(" '");
12778                    self.write(s);
12779                    self.write("'");
12780                }
12781                FunctionBody::Expression(expr) => {
12782                    self.write_keyword("AS");
12783                    self.write_space();
12784                    self.generate_expression(expr)?;
12785                }
12786                FunctionBody::External(name) => {
12787                    self.write_keyword("EXTERNAL NAME");
12788                    self.write(" '");
12789                    self.write(name);
12790                    self.write("'");
12791                }
12792                FunctionBody::Return(expr) => {
12793                    self.write_keyword("RETURN");
12794                    self.write_space();
12795                    self.generate_expression(expr)?;
12796                }
12797                FunctionBody::Statements(stmts) => {
12798                    self.write_keyword("AS");
12799                    self.write(" BEGIN ");
12800                    for (i, stmt) in stmts.iter().enumerate() {
12801                        if i > 0 {
12802                            self.write(" ");
12803                        }
12804                        self.generate_expression(stmt)?;
12805                        self.write(";");
12806                    }
12807                    self.write(" END");
12808                }
12809                FunctionBody::DollarQuoted { content, tag } => {
12810                    self.write_keyword("AS");
12811                    self.write(" ");
12812                    // Dialects that support dollar-quoted strings: PostgreSQL, Databricks, Redshift, DuckDB
12813                    let supports_dollar_quoting = matches!(
12814                        self.config.dialect,
12815                        Some(crate::dialects::DialectType::PostgreSQL)
12816                            | Some(crate::dialects::DialectType::Databricks)
12817                            | Some(crate::dialects::DialectType::Redshift)
12818                            | Some(crate::dialects::DialectType::DuckDB)
12819                    );
12820                    if supports_dollar_quoting {
12821                        // Output in dollar-quoted format
12822                        self.write("$");
12823                        if let Some(t) = tag {
12824                            self.write(t);
12825                        }
12826                        self.write("$");
12827                        self.write(content);
12828                        self.write("$");
12829                        if let Some(t) = tag {
12830                            self.write(t);
12831                        }
12832                        self.write("$");
12833                    } else {
12834                        // Convert to single-quoted string for other dialects
12835                        let escaped = self.escape_block_for_single_quote(content);
12836                        self.write("'");
12837                        self.write(&escaped);
12838                        self.write("'");
12839                    }
12840                }
12841            }
12842        }
12843
12844        Ok(())
12845    }
12846
12847    fn generate_drop_procedure(&mut self, dp: &DropProcedure) -> Result<()> {
12848        self.write_keyword("DROP PROCEDURE");
12849
12850        if dp.if_exists {
12851            self.write_space();
12852            self.write_keyword("IF EXISTS");
12853        }
12854
12855        self.write_space();
12856        self.generate_table(&dp.name)?;
12857
12858        if let Some(params) = &dp.parameters {
12859            self.write(" (");
12860            for (i, dt) in params.iter().enumerate() {
12861                if i > 0 {
12862                    self.write(", ");
12863                }
12864                self.generate_data_type(dt)?;
12865            }
12866            self.write(")");
12867        }
12868
12869        if dp.cascade {
12870            self.write_space();
12871            self.write_keyword("CASCADE");
12872        }
12873
12874        Ok(())
12875    }
12876
12877    fn generate_create_sequence(&mut self, cs: &CreateSequence) -> Result<()> {
12878        self.write_keyword("CREATE");
12879
12880        if cs.or_replace {
12881            self.write_space();
12882            self.write_keyword("OR REPLACE");
12883        }
12884
12885        if cs.temporary {
12886            self.write_space();
12887            self.write_keyword("TEMPORARY");
12888        }
12889
12890        self.write_space();
12891        self.write_keyword("SEQUENCE");
12892
12893        if cs.if_not_exists {
12894            self.write_space();
12895            self.write_keyword("IF NOT EXISTS");
12896        }
12897
12898        self.write_space();
12899        self.generate_table(&cs.name)?;
12900
12901        // Output AS <type> if present
12902        if let Some(as_type) = &cs.as_type {
12903            self.write_space();
12904            self.write_keyword("AS");
12905            self.write_space();
12906            self.generate_data_type(as_type)?;
12907        }
12908
12909        // Output COMMENT first (Snowflake convention: COMMENT comes before other properties)
12910        if let Some(comment) = &cs.comment {
12911            self.write_space();
12912            self.write_keyword("COMMENT");
12913            self.write("=");
12914            self.generate_string_literal(comment)?;
12915        }
12916
12917        // If property_order is available, use it to preserve original order
12918        if !cs.property_order.is_empty() {
12919            for prop in &cs.property_order {
12920                match prop {
12921                    SeqPropKind::Start => {
12922                        if let Some(start) = cs.start {
12923                            self.write_space();
12924                            self.write_keyword("START WITH");
12925                            self.write(&format!(" {}", start));
12926                        }
12927                    }
12928                    SeqPropKind::Increment => {
12929                        if let Some(inc) = cs.increment {
12930                            self.write_space();
12931                            self.write_keyword("INCREMENT BY");
12932                            self.write(&format!(" {}", inc));
12933                        }
12934                    }
12935                    SeqPropKind::Minvalue => {
12936                        if let Some(min) = &cs.minvalue {
12937                            self.write_space();
12938                            match min {
12939                                SequenceBound::Value(v) => {
12940                                    self.write_keyword("MINVALUE");
12941                                    self.write(&format!(" {}", v));
12942                                }
12943                                SequenceBound::None => {
12944                                    self.write_keyword("NO MINVALUE");
12945                                }
12946                            }
12947                        }
12948                    }
12949                    SeqPropKind::Maxvalue => {
12950                        if let Some(max) = &cs.maxvalue {
12951                            self.write_space();
12952                            match max {
12953                                SequenceBound::Value(v) => {
12954                                    self.write_keyword("MAXVALUE");
12955                                    self.write(&format!(" {}", v));
12956                                }
12957                                SequenceBound::None => {
12958                                    self.write_keyword("NO MAXVALUE");
12959                                }
12960                            }
12961                        }
12962                    }
12963                    SeqPropKind::Cache => {
12964                        if let Some(cache) = cs.cache {
12965                            self.write_space();
12966                            self.write_keyword("CACHE");
12967                            self.write(&format!(" {}", cache));
12968                        }
12969                    }
12970                    SeqPropKind::NoCache => {
12971                        self.write_space();
12972                        self.write_keyword("NO CACHE");
12973                    }
12974                    SeqPropKind::NoCacheWord => {
12975                        self.write_space();
12976                        self.write_keyword("NOCACHE");
12977                    }
12978                    SeqPropKind::Cycle => {
12979                        self.write_space();
12980                        self.write_keyword("CYCLE");
12981                    }
12982                    SeqPropKind::NoCycle => {
12983                        self.write_space();
12984                        self.write_keyword("NO CYCLE");
12985                    }
12986                    SeqPropKind::NoCycleWord => {
12987                        self.write_space();
12988                        self.write_keyword("NOCYCLE");
12989                    }
12990                    SeqPropKind::OwnedBy => {
12991                        // Skip OWNED BY NONE (it's a no-op)
12992                        if !cs.owned_by_none {
12993                            if let Some(owned) = &cs.owned_by {
12994                                self.write_space();
12995                                self.write_keyword("OWNED BY");
12996                                self.write_space();
12997                                self.generate_table(owned)?;
12998                            }
12999                        }
13000                    }
13001                    SeqPropKind::Order => {
13002                        self.write_space();
13003                        self.write_keyword("ORDER");
13004                    }
13005                    SeqPropKind::NoOrder => {
13006                        self.write_space();
13007                        self.write_keyword("NOORDER");
13008                    }
13009                    SeqPropKind::Comment => {
13010                        // COMMENT is output above, before property_order iteration
13011                    }
13012                    SeqPropKind::Sharing => {
13013                        if let Some(val) = &cs.sharing {
13014                            self.write_space();
13015                            self.write(&format!("SHARING={}", val));
13016                        }
13017                    }
13018                    SeqPropKind::Keep => {
13019                        self.write_space();
13020                        self.write_keyword("KEEP");
13021                    }
13022                    SeqPropKind::NoKeep => {
13023                        self.write_space();
13024                        self.write_keyword("NOKEEP");
13025                    }
13026                    SeqPropKind::Scale => {
13027                        self.write_space();
13028                        self.write_keyword("SCALE");
13029                        if let Some(modifier) = &cs.scale_modifier {
13030                            if !modifier.is_empty() {
13031                                self.write_space();
13032                                self.write_keyword(modifier);
13033                            }
13034                        }
13035                    }
13036                    SeqPropKind::NoScale => {
13037                        self.write_space();
13038                        self.write_keyword("NOSCALE");
13039                    }
13040                    SeqPropKind::Shard => {
13041                        self.write_space();
13042                        self.write_keyword("SHARD");
13043                        if let Some(modifier) = &cs.shard_modifier {
13044                            if !modifier.is_empty() {
13045                                self.write_space();
13046                                self.write_keyword(modifier);
13047                            }
13048                        }
13049                    }
13050                    SeqPropKind::NoShard => {
13051                        self.write_space();
13052                        self.write_keyword("NOSHARD");
13053                    }
13054                    SeqPropKind::Session => {
13055                        self.write_space();
13056                        self.write_keyword("SESSION");
13057                    }
13058                    SeqPropKind::Global => {
13059                        self.write_space();
13060                        self.write_keyword("GLOBAL");
13061                    }
13062                    SeqPropKind::NoMinvalueWord => {
13063                        self.write_space();
13064                        self.write_keyword("NOMINVALUE");
13065                    }
13066                    SeqPropKind::NoMaxvalueWord => {
13067                        self.write_space();
13068                        self.write_keyword("NOMAXVALUE");
13069                    }
13070                }
13071            }
13072        } else {
13073            // Fallback: default order for backwards compatibility
13074            if let Some(inc) = cs.increment {
13075                self.write_space();
13076                self.write_keyword("INCREMENT BY");
13077                self.write(&format!(" {}", inc));
13078            }
13079
13080            if let Some(min) = &cs.minvalue {
13081                self.write_space();
13082                match min {
13083                    SequenceBound::Value(v) => {
13084                        self.write_keyword("MINVALUE");
13085                        self.write(&format!(" {}", v));
13086                    }
13087                    SequenceBound::None => {
13088                        self.write_keyword("NO MINVALUE");
13089                    }
13090                }
13091            }
13092
13093            if let Some(max) = &cs.maxvalue {
13094                self.write_space();
13095                match max {
13096                    SequenceBound::Value(v) => {
13097                        self.write_keyword("MAXVALUE");
13098                        self.write(&format!(" {}", v));
13099                    }
13100                    SequenceBound::None => {
13101                        self.write_keyword("NO MAXVALUE");
13102                    }
13103                }
13104            }
13105
13106            if let Some(start) = cs.start {
13107                self.write_space();
13108                self.write_keyword("START WITH");
13109                self.write(&format!(" {}", start));
13110            }
13111
13112            if let Some(cache) = cs.cache {
13113                self.write_space();
13114                self.write_keyword("CACHE");
13115                self.write(&format!(" {}", cache));
13116            }
13117
13118            if cs.cycle {
13119                self.write_space();
13120                self.write_keyword("CYCLE");
13121            }
13122
13123            if let Some(owned) = &cs.owned_by {
13124                self.write_space();
13125                self.write_keyword("OWNED BY");
13126                self.write_space();
13127                self.generate_table(owned)?;
13128            }
13129        }
13130
13131        Ok(())
13132    }
13133
13134    fn generate_drop_sequence(&mut self, ds: &DropSequence) -> Result<()> {
13135        self.write_keyword("DROP SEQUENCE");
13136
13137        if ds.if_exists {
13138            self.write_space();
13139            self.write_keyword("IF EXISTS");
13140        }
13141
13142        self.write_space();
13143        self.generate_table(&ds.name)?;
13144
13145        if ds.cascade {
13146            self.write_space();
13147            self.write_keyword("CASCADE");
13148        }
13149
13150        Ok(())
13151    }
13152
13153    fn generate_alter_sequence(&mut self, als: &AlterSequence) -> Result<()> {
13154        self.write_keyword("ALTER SEQUENCE");
13155
13156        if als.if_exists {
13157            self.write_space();
13158            self.write_keyword("IF EXISTS");
13159        }
13160
13161        self.write_space();
13162        self.generate_table(&als.name)?;
13163
13164        if let Some(inc) = als.increment {
13165            self.write_space();
13166            self.write_keyword("INCREMENT BY");
13167            self.write(&format!(" {}", inc));
13168        }
13169
13170        if let Some(min) = &als.minvalue {
13171            self.write_space();
13172            match min {
13173                SequenceBound::Value(v) => {
13174                    self.write_keyword("MINVALUE");
13175                    self.write(&format!(" {}", v));
13176                }
13177                SequenceBound::None => {
13178                    self.write_keyword("NO MINVALUE");
13179                }
13180            }
13181        }
13182
13183        if let Some(max) = &als.maxvalue {
13184            self.write_space();
13185            match max {
13186                SequenceBound::Value(v) => {
13187                    self.write_keyword("MAXVALUE");
13188                    self.write(&format!(" {}", v));
13189                }
13190                SequenceBound::None => {
13191                    self.write_keyword("NO MAXVALUE");
13192                }
13193            }
13194        }
13195
13196        if let Some(start) = als.start {
13197            self.write_space();
13198            self.write_keyword("START WITH");
13199            self.write(&format!(" {}", start));
13200        }
13201
13202        if let Some(restart) = &als.restart {
13203            self.write_space();
13204            self.write_keyword("RESTART");
13205            if let Some(val) = restart {
13206                self.write_keyword(" WITH");
13207                self.write(&format!(" {}", val));
13208            }
13209        }
13210
13211        if let Some(cache) = als.cache {
13212            self.write_space();
13213            self.write_keyword("CACHE");
13214            self.write(&format!(" {}", cache));
13215        }
13216
13217        if let Some(cycle) = als.cycle {
13218            self.write_space();
13219            if cycle {
13220                self.write_keyword("CYCLE");
13221            } else {
13222                self.write_keyword("NO CYCLE");
13223            }
13224        }
13225
13226        if let Some(owned) = &als.owned_by {
13227            self.write_space();
13228            self.write_keyword("OWNED BY");
13229            self.write_space();
13230            if let Some(table) = owned {
13231                self.generate_table(table)?;
13232            } else {
13233                self.write_keyword("NONE");
13234            }
13235        }
13236
13237        Ok(())
13238    }
13239
13240    fn generate_create_trigger(&mut self, ct: &CreateTrigger) -> Result<()> {
13241        self.write_keyword("CREATE");
13242
13243        if ct.or_replace {
13244            self.write_space();
13245            self.write_keyword("OR REPLACE");
13246        }
13247
13248        if ct.constraint {
13249            self.write_space();
13250            self.write_keyword("CONSTRAINT");
13251        }
13252
13253        self.write_space();
13254        self.write_keyword("TRIGGER");
13255        self.write_space();
13256        self.generate_identifier(&ct.name)?;
13257
13258        self.write_space();
13259        match ct.timing {
13260            TriggerTiming::Before => self.write_keyword("BEFORE"),
13261            TriggerTiming::After => self.write_keyword("AFTER"),
13262            TriggerTiming::InsteadOf => self.write_keyword("INSTEAD OF"),
13263        }
13264
13265        // Events
13266        for (i, event) in ct.events.iter().enumerate() {
13267            if i > 0 {
13268                self.write_keyword(" OR");
13269            }
13270            self.write_space();
13271            match event {
13272                TriggerEvent::Insert => self.write_keyword("INSERT"),
13273                TriggerEvent::Update(cols) => {
13274                    self.write_keyword("UPDATE");
13275                    if let Some(cols) = cols {
13276                        self.write_space();
13277                        self.write_keyword("OF");
13278                        for (j, col) in cols.iter().enumerate() {
13279                            if j > 0 {
13280                                self.write(",");
13281                            }
13282                            self.write_space();
13283                            self.generate_identifier(col)?;
13284                        }
13285                    }
13286                }
13287                TriggerEvent::Delete => self.write_keyword("DELETE"),
13288                TriggerEvent::Truncate => self.write_keyword("TRUNCATE"),
13289            }
13290        }
13291
13292        self.write_space();
13293        self.write_keyword("ON");
13294        self.write_space();
13295        self.generate_table(&ct.table)?;
13296
13297        // Referencing clause
13298        if let Some(ref_clause) = &ct.referencing {
13299            self.write_space();
13300            self.write_keyword("REFERENCING");
13301            if let Some(old_table) = &ref_clause.old_table {
13302                self.write_space();
13303                self.write_keyword("OLD TABLE AS");
13304                self.write_space();
13305                self.generate_identifier(old_table)?;
13306            }
13307            if let Some(new_table) = &ref_clause.new_table {
13308                self.write_space();
13309                self.write_keyword("NEW TABLE AS");
13310                self.write_space();
13311                self.generate_identifier(new_table)?;
13312            }
13313            if let Some(old_row) = &ref_clause.old_row {
13314                self.write_space();
13315                self.write_keyword("OLD ROW AS");
13316                self.write_space();
13317                self.generate_identifier(old_row)?;
13318            }
13319            if let Some(new_row) = &ref_clause.new_row {
13320                self.write_space();
13321                self.write_keyword("NEW ROW AS");
13322                self.write_space();
13323                self.generate_identifier(new_row)?;
13324            }
13325        }
13326
13327        // Deferrable options for constraint triggers (must come before FOR EACH)
13328        if let Some(deferrable) = ct.deferrable {
13329            self.write_space();
13330            if deferrable {
13331                self.write_keyword("DEFERRABLE");
13332            } else {
13333                self.write_keyword("NOT DEFERRABLE");
13334            }
13335        }
13336
13337        if let Some(initially) = ct.initially_deferred {
13338            self.write_space();
13339            self.write_keyword("INITIALLY");
13340            self.write_space();
13341            if initially {
13342                self.write_keyword("DEFERRED");
13343            } else {
13344                self.write_keyword("IMMEDIATE");
13345            }
13346        }
13347
13348        if let Some(for_each) = ct.for_each {
13349            self.write_space();
13350            self.write_keyword("FOR EACH");
13351            self.write_space();
13352            match for_each {
13353                TriggerForEach::Row => self.write_keyword("ROW"),
13354                TriggerForEach::Statement => self.write_keyword("STATEMENT"),
13355            }
13356        }
13357
13358        // When clause
13359        if let Some(when) = &ct.when {
13360            self.write_space();
13361            self.write_keyword("WHEN");
13362            if ct.when_paren {
13363                self.write(" (");
13364                self.generate_expression(when)?;
13365                self.write(")");
13366            } else {
13367                self.write_space();
13368                self.generate_expression(when)?;
13369            }
13370        }
13371
13372        // Body
13373        self.write_space();
13374        match &ct.body {
13375            TriggerBody::Execute { function, args } => {
13376                self.write_keyword("EXECUTE FUNCTION");
13377                self.write_space();
13378                self.generate_table(function)?;
13379                self.write("(");
13380                for (i, arg) in args.iter().enumerate() {
13381                    if i > 0 {
13382                        self.write(", ");
13383                    }
13384                    self.generate_expression(arg)?;
13385                }
13386                self.write(")");
13387            }
13388            TriggerBody::Block(block) => {
13389                self.write_keyword("BEGIN");
13390                self.write_space();
13391                self.write(block);
13392                self.write_space();
13393                self.write_keyword("END");
13394            }
13395        }
13396
13397        Ok(())
13398    }
13399
13400    fn generate_drop_trigger(&mut self, dt: &DropTrigger) -> Result<()> {
13401        self.write_keyword("DROP TRIGGER");
13402
13403        if dt.if_exists {
13404            self.write_space();
13405            self.write_keyword("IF EXISTS");
13406        }
13407
13408        self.write_space();
13409        self.generate_identifier(&dt.name)?;
13410
13411        if let Some(table) = &dt.table {
13412            self.write_space();
13413            self.write_keyword("ON");
13414            self.write_space();
13415            self.generate_table(table)?;
13416        }
13417
13418        if dt.cascade {
13419            self.write_space();
13420            self.write_keyword("CASCADE");
13421        }
13422
13423        Ok(())
13424    }
13425
13426    fn generate_create_type(&mut self, ct: &CreateType) -> Result<()> {
13427        self.write_keyword("CREATE TYPE");
13428
13429        if ct.if_not_exists {
13430            self.write_space();
13431            self.write_keyword("IF NOT EXISTS");
13432        }
13433
13434        self.write_space();
13435        self.generate_table(&ct.name)?;
13436
13437        self.write_space();
13438        self.write_keyword("AS");
13439        self.write_space();
13440
13441        match &ct.definition {
13442            TypeDefinition::Enum(values) => {
13443                self.write_keyword("ENUM");
13444                self.write(" (");
13445                for (i, val) in values.iter().enumerate() {
13446                    if i > 0 {
13447                        self.write(", ");
13448                    }
13449                    self.write(&format!("'{}'", val));
13450                }
13451                self.write(")");
13452            }
13453            TypeDefinition::Composite(attrs) => {
13454                self.write("(");
13455                for (i, attr) in attrs.iter().enumerate() {
13456                    if i > 0 {
13457                        self.write(", ");
13458                    }
13459                    self.generate_identifier(&attr.name)?;
13460                    self.write_space();
13461                    self.generate_data_type(&attr.data_type)?;
13462                    if let Some(collate) = &attr.collate {
13463                        self.write_space();
13464                        self.write_keyword("COLLATE");
13465                        self.write_space();
13466                        self.generate_identifier(collate)?;
13467                    }
13468                }
13469                self.write(")");
13470            }
13471            TypeDefinition::Range {
13472                subtype,
13473                subtype_diff,
13474                canonical,
13475            } => {
13476                self.write_keyword("RANGE");
13477                self.write(" (");
13478                self.write_keyword("SUBTYPE");
13479                self.write(" = ");
13480                self.generate_data_type(subtype)?;
13481                if let Some(diff) = subtype_diff {
13482                    self.write(", ");
13483                    self.write_keyword("SUBTYPE_DIFF");
13484                    self.write(" = ");
13485                    self.write(diff);
13486                }
13487                if let Some(canon) = canonical {
13488                    self.write(", ");
13489                    self.write_keyword("CANONICAL");
13490                    self.write(" = ");
13491                    self.write(canon);
13492                }
13493                self.write(")");
13494            }
13495            TypeDefinition::Base {
13496                input,
13497                output,
13498                internallength,
13499            } => {
13500                self.write("(");
13501                self.write_keyword("INPUT");
13502                self.write(" = ");
13503                self.write(input);
13504                self.write(", ");
13505                self.write_keyword("OUTPUT");
13506                self.write(" = ");
13507                self.write(output);
13508                if let Some(len) = internallength {
13509                    self.write(", ");
13510                    self.write_keyword("INTERNALLENGTH");
13511                    self.write(" = ");
13512                    self.write(&len.to_string());
13513                }
13514                self.write(")");
13515            }
13516            TypeDefinition::Domain {
13517                base_type,
13518                default,
13519                constraints,
13520            } => {
13521                self.generate_data_type(base_type)?;
13522                if let Some(def) = default {
13523                    self.write_space();
13524                    self.write_keyword("DEFAULT");
13525                    self.write_space();
13526                    self.generate_expression(def)?;
13527                }
13528                for constr in constraints {
13529                    self.write_space();
13530                    if let Some(name) = &constr.name {
13531                        self.write_keyword("CONSTRAINT");
13532                        self.write_space();
13533                        self.generate_identifier(name)?;
13534                        self.write_space();
13535                    }
13536                    self.write_keyword("CHECK");
13537                    self.write(" (");
13538                    self.generate_expression(&constr.check)?;
13539                    self.write(")");
13540                }
13541            }
13542        }
13543
13544        Ok(())
13545    }
13546
13547    fn generate_drop_type(&mut self, dt: &DropType) -> Result<()> {
13548        self.write_keyword("DROP TYPE");
13549
13550        if dt.if_exists {
13551            self.write_space();
13552            self.write_keyword("IF EXISTS");
13553        }
13554
13555        self.write_space();
13556        self.generate_table(&dt.name)?;
13557
13558        if dt.cascade {
13559            self.write_space();
13560            self.write_keyword("CASCADE");
13561        }
13562
13563        Ok(())
13564    }
13565
13566    fn generate_describe(&mut self, d: &Describe) -> Result<()> {
13567        // Athena: DESCRIBE uses Hive engine (backticks)
13568        let saved_athena_hive_context = self.athena_hive_context;
13569        if matches!(
13570            self.config.dialect,
13571            Some(crate::dialects::DialectType::Athena)
13572        ) {
13573            self.athena_hive_context = true;
13574        }
13575
13576        // Output leading comments before DESCRIBE
13577        for comment in &d.leading_comments {
13578            self.write_formatted_comment(comment);
13579            self.write(" ");
13580        }
13581
13582        self.write_keyword("DESCRIBE");
13583
13584        if d.extended {
13585            self.write_space();
13586            self.write_keyword("EXTENDED");
13587        } else if d.formatted {
13588            self.write_space();
13589            self.write_keyword("FORMATTED");
13590        }
13591
13592        // Output style like ANALYZE, HISTORY
13593        if let Some(ref style) = d.style {
13594            self.write_space();
13595            self.write_keyword(style);
13596        }
13597
13598        // Handle object kind (TABLE, VIEW) based on dialect
13599        let should_output_kind = match self.config.dialect {
13600            // Spark doesn't use TABLE/VIEW after DESCRIBE
13601            Some(DialectType::Spark) | Some(DialectType::Databricks) | Some(DialectType::Hive) => {
13602                false
13603            }
13604            // Snowflake always includes TABLE
13605            Some(DialectType::Snowflake) => true,
13606            _ => d.kind.is_some(),
13607        };
13608        if should_output_kind {
13609            if let Some(ref kind) = d.kind {
13610                self.write_space();
13611                self.write_keyword(kind);
13612            } else if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
13613                self.write_space();
13614                self.write_keyword("TABLE");
13615            }
13616        }
13617
13618        self.write_space();
13619        self.generate_expression(&d.target)?;
13620
13621        // Output PARTITION clause if present (the Partition expression outputs its own PARTITION keyword)
13622        if let Some(ref partition) = d.partition {
13623            self.write_space();
13624            self.generate_expression(partition)?;
13625        }
13626
13627        // Databricks: AS JSON
13628        if d.as_json {
13629            self.write_space();
13630            self.write_keyword("AS JSON");
13631        }
13632
13633        // Output properties like type=stage
13634        for (name, value) in &d.properties {
13635            self.write_space();
13636            self.write(name);
13637            self.write("=");
13638            self.write(value);
13639        }
13640
13641        // Restore Athena Hive context
13642        self.athena_hive_context = saved_athena_hive_context;
13643
13644        Ok(())
13645    }
13646
13647    /// Generate SHOW statement (Snowflake, MySQL, etc.)
13648    /// SHOW [TERSE] <object_type> [HISTORY] [LIKE pattern] [IN <scope>] [STARTS WITH pattern] [LIMIT n] [FROM object]
13649    fn generate_show(&mut self, s: &Show) -> Result<()> {
13650        self.write_keyword("SHOW");
13651        self.write_space();
13652
13653        // TERSE keyword - but not for PRIMARY KEYS, UNIQUE KEYS, IMPORTED KEYS
13654        // where TERSE is syntactically valid but has no effect on output
13655        let show_terse = s.terse
13656            && !matches!(
13657                s.this.as_str(),
13658                "PRIMARY KEYS" | "UNIQUE KEYS" | "IMPORTED KEYS"
13659            );
13660        if show_terse {
13661            self.write_keyword("TERSE");
13662            self.write_space();
13663        }
13664
13665        // Object type (USERS, TABLES, DATABASES, etc.)
13666        self.write_keyword(&s.this);
13667
13668        // Target identifier (MySQL: engine name in SHOW ENGINE, preserved case)
13669        if let Some(ref target_expr) = s.target {
13670            self.write_space();
13671            self.generate_expression(target_expr)?;
13672        }
13673
13674        // HISTORY keyword
13675        if s.history {
13676            self.write_space();
13677            self.write_keyword("HISTORY");
13678        }
13679
13680        // FOR target (MySQL: SHOW GRANTS FOR foo, SHOW PROFILE ... FOR QUERY 5)
13681        if let Some(ref for_target) = s.for_target {
13682            self.write_space();
13683            self.write_keyword("FOR");
13684            self.write_space();
13685            self.generate_expression(for_target)?;
13686        }
13687
13688        // Determine ordering based on dialect:
13689        // - Snowflake: LIKE, IN, STARTS WITH, LIMIT, FROM
13690        // - MySQL: IN, FROM, LIKE (when FROM is present)
13691        use crate::dialects::DialectType;
13692        let is_snowflake = matches!(self.config.dialect, Some(DialectType::Snowflake));
13693
13694        if !is_snowflake && s.from.is_some() {
13695            // MySQL ordering: IN, FROM, LIKE
13696
13697            // IN scope_kind [scope]
13698            if let Some(ref scope_kind) = s.scope_kind {
13699                self.write_space();
13700                self.write_keyword("IN");
13701                self.write_space();
13702                self.write_keyword(scope_kind);
13703                if let Some(ref scope) = s.scope {
13704                    self.write_space();
13705                    self.generate_expression(scope)?;
13706                }
13707            } else if let Some(ref scope) = s.scope {
13708                self.write_space();
13709                self.write_keyword("IN");
13710                self.write_space();
13711                self.generate_expression(scope)?;
13712            }
13713
13714            // FROM clause
13715            if let Some(ref from) = s.from {
13716                self.write_space();
13717                self.write_keyword("FROM");
13718                self.write_space();
13719                self.generate_expression(from)?;
13720            }
13721
13722            // Second FROM clause (db name)
13723            if let Some(ref db) = s.db {
13724                self.write_space();
13725                self.write_keyword("FROM");
13726                self.write_space();
13727                self.generate_expression(db)?;
13728            }
13729
13730            // LIKE pattern
13731            if let Some(ref like) = s.like {
13732                self.write_space();
13733                self.write_keyword("LIKE");
13734                self.write_space();
13735                self.generate_expression(like)?;
13736            }
13737        } else {
13738            // Snowflake ordering: LIKE, IN, STARTS WITH, LIMIT, FROM
13739
13740            // LIKE pattern
13741            if let Some(ref like) = s.like {
13742                self.write_space();
13743                self.write_keyword("LIKE");
13744                self.write_space();
13745                self.generate_expression(like)?;
13746            }
13747
13748            // IN scope_kind [scope]
13749            if let Some(ref scope_kind) = s.scope_kind {
13750                self.write_space();
13751                self.write_keyword("IN");
13752                self.write_space();
13753                self.write_keyword(scope_kind);
13754                if let Some(ref scope) = s.scope {
13755                    self.write_space();
13756                    self.generate_expression(scope)?;
13757                }
13758            } else if let Some(ref scope) = s.scope {
13759                self.write_space();
13760                self.write_keyword("IN");
13761                self.write_space();
13762                self.generate_expression(scope)?;
13763            }
13764        }
13765
13766        // STARTS WITH pattern
13767        if let Some(ref starts_with) = s.starts_with {
13768            self.write_space();
13769            self.write_keyword("STARTS WITH");
13770            self.write_space();
13771            self.generate_expression(starts_with)?;
13772        }
13773
13774        // LIMIT clause
13775        if let Some(ref limit) = s.limit {
13776            self.write_space();
13777            self.generate_limit(limit)?;
13778        }
13779
13780        // FROM clause (for Snowflake, FROM comes after STARTS WITH and LIMIT)
13781        if is_snowflake {
13782            if let Some(ref from) = s.from {
13783                self.write_space();
13784                self.write_keyword("FROM");
13785                self.write_space();
13786                self.generate_expression(from)?;
13787            }
13788        }
13789
13790        // WHERE clause (MySQL: SHOW STATUS WHERE condition)
13791        if let Some(ref where_clause) = s.where_clause {
13792            self.write_space();
13793            self.write_keyword("WHERE");
13794            self.write_space();
13795            self.generate_expression(where_clause)?;
13796        }
13797
13798        // MUTEX/STATUS suffix (MySQL: SHOW ENGINE foo STATUS/MUTEX)
13799        if let Some(is_mutex) = s.mutex {
13800            self.write_space();
13801            if is_mutex {
13802                self.write_keyword("MUTEX");
13803            } else {
13804                self.write_keyword("STATUS");
13805            }
13806        }
13807
13808        // WITH PRIVILEGES clause (Snowflake: SHOW ... WITH PRIVILEGES USAGE, MODIFY)
13809        if !s.privileges.is_empty() {
13810            self.write_space();
13811            self.write_keyword("WITH PRIVILEGES");
13812            self.write_space();
13813            for (i, priv_name) in s.privileges.iter().enumerate() {
13814                if i > 0 {
13815                    self.write(", ");
13816                }
13817                self.write_keyword(priv_name);
13818            }
13819        }
13820
13821        Ok(())
13822    }
13823
13824    // ==================== End DDL Generation ====================
13825
13826    fn generate_literal(&mut self, lit: &Literal) -> Result<()> {
13827        use crate::dialects::DialectType;
13828        match lit {
13829            Literal::String(s) => {
13830                self.generate_string_literal(s)?;
13831            }
13832            Literal::Number(n) => {
13833                if matches!(self.config.dialect, Some(DialectType::MySQL))
13834                    && n.len() > 2
13835                    && (n.starts_with("0x") || n.starts_with("0X"))
13836                    && !n[2..].chars().all(|c| c.is_ascii_hexdigit())
13837                {
13838                    return self.generate_identifier(&Identifier {
13839                        name: n.clone(),
13840                        quoted: true,
13841                        trailing_comments: Vec::new(),
13842                        span: None,
13843                    });
13844                }
13845                // Strip underscore digit separators (e.g., 1_000_000 -> 1000000)
13846                // for dialects that don't support them (MySQL interprets as identifier).
13847                // ClickHouse, DuckDB, PostgreSQL, and Hive/Spark/Databricks support them.
13848                let n = if n.contains('_')
13849                    && !matches!(
13850                        self.config.dialect,
13851                        Some(DialectType::ClickHouse)
13852                            | Some(DialectType::DuckDB)
13853                            | Some(DialectType::PostgreSQL)
13854                            | Some(DialectType::Hive)
13855                            | Some(DialectType::Spark)
13856                            | Some(DialectType::Databricks)
13857                    ) {
13858                    std::borrow::Cow::Owned(n.replace('_', ""))
13859                } else {
13860                    std::borrow::Cow::Borrowed(n.as_str())
13861                };
13862                // Normalize numbers starting with decimal point to have leading zero
13863                // e.g., .25 -> 0.25 (matches sqlglot behavior)
13864                if n.starts_with('.') {
13865                    self.write("0");
13866                    self.write(&n);
13867                } else if n.starts_with("-.") {
13868                    // Handle negative numbers like -.25 -> -0.25
13869                    self.write("-0");
13870                    self.write(&n[1..]);
13871                } else {
13872                    self.write(&n);
13873                }
13874            }
13875            Literal::HexString(h) => {
13876                // Most dialects use lowercase x'...' for hex literals; Spark/Databricks/Teradata use uppercase X'...'
13877                match self.config.dialect {
13878                    Some(DialectType::Spark)
13879                    | Some(DialectType::Databricks)
13880                    | Some(DialectType::Teradata) => self.write("X'"),
13881                    _ => self.write("x'"),
13882                }
13883                self.write(h);
13884                self.write("'");
13885            }
13886            Literal::HexNumber(h) => {
13887                // Hex number (0xA) - integer in hex notation (from BigQuery)
13888                // For BigQuery, TSQL, Fabric output as 0xHEX (native hex notation)
13889                // For other dialects, convert to decimal integer
13890                match self.config.dialect {
13891                    Some(DialectType::BigQuery)
13892                    | Some(DialectType::TSQL)
13893                    | Some(DialectType::Fabric) => {
13894                        self.write("0x");
13895                        self.write(h);
13896                    }
13897                    _ => {
13898                        // Convert hex to decimal
13899                        if let Ok(val) = u64::from_str_radix(h, 16) {
13900                            self.write(&val.to_string());
13901                        } else {
13902                            // Fallback: keep as 0x notation
13903                            self.write("0x");
13904                            self.write(h);
13905                        }
13906                    }
13907                }
13908            }
13909            Literal::BitString(b) => {
13910                // Bit string B'0101...'
13911                self.write("B'");
13912                self.write(b);
13913                self.write("'");
13914            }
13915            Literal::ByteString(b) => {
13916                // Byte string b'...' (BigQuery style)
13917                self.write("b'");
13918                // Escape special characters for output
13919                self.write_escaped_byte_string(b);
13920                self.write("'");
13921            }
13922            Literal::NationalString(s) => {
13923                // N'string' is supported by TSQL, Oracle, MySQL, and generic SQL
13924                // Other dialects strip the N prefix and output as regular string
13925                let keep_n_prefix = matches!(
13926                    self.config.dialect,
13927                    Some(DialectType::TSQL)
13928                        | Some(DialectType::Oracle)
13929                        | Some(DialectType::MySQL)
13930                        | None
13931                );
13932                if keep_n_prefix {
13933                    self.write("N'");
13934                } else {
13935                    self.write("'");
13936                }
13937                self.write(s);
13938                self.write("'");
13939            }
13940            Literal::Date(d) => {
13941                self.generate_date_literal(d)?;
13942            }
13943            Literal::Time(t) => {
13944                self.generate_time_literal(t)?;
13945            }
13946            Literal::Timestamp(ts) => {
13947                self.generate_timestamp_literal(ts)?;
13948            }
13949            Literal::Datetime(dt) => {
13950                self.generate_datetime_literal(dt)?;
13951            }
13952            Literal::TripleQuotedString(s, _quote_char) => {
13953                // For BigQuery and other dialects that don't support triple-quote, normalize to regular strings
13954                if matches!(
13955                    self.config.dialect,
13956                    Some(crate::dialects::DialectType::BigQuery)
13957                        | Some(crate::dialects::DialectType::DuckDB)
13958                        | Some(crate::dialects::DialectType::Snowflake)
13959                        | Some(crate::dialects::DialectType::Spark)
13960                        | Some(crate::dialects::DialectType::Hive)
13961                        | Some(crate::dialects::DialectType::Presto)
13962                        | Some(crate::dialects::DialectType::Trino)
13963                        | Some(crate::dialects::DialectType::PostgreSQL)
13964                        | Some(crate::dialects::DialectType::MySQL)
13965                        | Some(crate::dialects::DialectType::Redshift)
13966                        | Some(crate::dialects::DialectType::TSQL)
13967                        | Some(crate::dialects::DialectType::Oracle)
13968                        | Some(crate::dialects::DialectType::ClickHouse)
13969                        | Some(crate::dialects::DialectType::Databricks)
13970                        | Some(crate::dialects::DialectType::SQLite)
13971                ) {
13972                    self.generate_string_literal(s)?;
13973                } else {
13974                    // Preserve triple-quoted string syntax for generic/unknown dialects
13975                    let quotes = format!("{0}{0}{0}", _quote_char);
13976                    self.write(&quotes);
13977                    self.write(s);
13978                    self.write(&quotes);
13979                }
13980            }
13981            Literal::EscapeString(s) => {
13982                // PostgreSQL escape string: e'...' or E'...'
13983                // Token text format is "e:content" or "E:content"
13984                // Normalize escape sequences: \' -> '' (standard SQL doubled quote)
13985                use crate::dialects::DialectType;
13986                let content = if let Some(c) = s.strip_prefix("e:") {
13987                    c
13988                } else if let Some(c) = s.strip_prefix("E:") {
13989                    c
13990                } else {
13991                    s.as_str()
13992                };
13993
13994                // MySQL: output the content without quotes or prefix
13995                if matches!(
13996                    self.config.dialect,
13997                    Some(DialectType::MySQL) | Some(DialectType::TiDB)
13998                ) {
13999                    self.write(content);
14000                } else {
14001                    // Some dialects use lowercase e' prefix
14002                    let prefix = if matches!(
14003                        self.config.dialect,
14004                        Some(DialectType::SingleStore)
14005                            | Some(DialectType::DuckDB)
14006                            | Some(DialectType::PostgreSQL)
14007                            | Some(DialectType::CockroachDB)
14008                            | Some(DialectType::Materialize)
14009                            | Some(DialectType::RisingWave)
14010                    ) {
14011                        "e'"
14012                    } else {
14013                        "E'"
14014                    };
14015
14016                    // Normalize \' to '' for output
14017                    let normalized = content.replace("\\'", "''");
14018                    self.write(prefix);
14019                    self.write(&normalized);
14020                    self.write("'");
14021                }
14022            }
14023            Literal::DollarString(s) => {
14024                // Convert dollar-quoted strings to single-quoted strings
14025                // (like Python sqlglot's rawstring_sql)
14026                use crate::dialects::DialectType;
14027                // Extract content from tag\x00content format
14028                let (_tag, content) = crate::tokens::parse_dollar_string_token(s);
14029                // Step 1: Escape backslashes if the dialect uses backslash as a string escape
14030                let escape_backslash = matches!(self.config.dialect, Some(DialectType::Snowflake));
14031                // Step 2: Determine quote escaping style
14032                // Snowflake: ' -> \' (backslash escape)
14033                // PostgreSQL, DuckDB, others: ' -> '' (doubled quote)
14034                let use_backslash_quote =
14035                    matches!(self.config.dialect, Some(DialectType::Snowflake));
14036
14037                let mut escaped = String::with_capacity(content.len() + 4);
14038                for ch in content.chars() {
14039                    if escape_backslash && ch == '\\' {
14040                        // Escape backslash first (before quote escaping)
14041                        escaped.push('\\');
14042                        escaped.push('\\');
14043                    } else if ch == '\'' {
14044                        if use_backslash_quote {
14045                            escaped.push('\\');
14046                            escaped.push('\'');
14047                        } else {
14048                            escaped.push('\'');
14049                            escaped.push('\'');
14050                        }
14051                    } else {
14052                        escaped.push(ch);
14053                    }
14054                }
14055                self.write("'");
14056                self.write(&escaped);
14057                self.write("'");
14058            }
14059            Literal::RawString(s) => {
14060                // Raw strings (r"..." or r'...') contain literal backslashes.
14061                // When converting to a regular string, this follows Python sqlglot's rawstring_sql:
14062                // 1. If \\ is in STRING_ESCAPES, double all backslashes
14063                // 2. Apply ESCAPED_SEQUENCES for special chars (but NOT for backslash itself)
14064                // 3. Escape quotes using STRING_ESCAPES[0] + quote_char
14065                use crate::dialects::DialectType;
14066
14067                // Dialects where \\ is in STRING_ESCAPES (backslashes need doubling)
14068                let escape_backslash = matches!(
14069                    self.config.dialect,
14070                    Some(DialectType::BigQuery)
14071                        | Some(DialectType::MySQL)
14072                        | Some(DialectType::SingleStore)
14073                        | Some(DialectType::TiDB)
14074                        | Some(DialectType::Hive)
14075                        | Some(DialectType::Spark)
14076                        | Some(DialectType::Databricks)
14077                        | Some(DialectType::Drill)
14078                        | Some(DialectType::Snowflake)
14079                        | Some(DialectType::Redshift)
14080                        | Some(DialectType::ClickHouse)
14081                );
14082
14083                // Dialects where backslash is the PRIMARY string escape (STRING_ESCAPES[0] = "\\")
14084                // These escape quotes as \' instead of ''
14085                let backslash_escapes_quote = matches!(
14086                    self.config.dialect,
14087                    Some(DialectType::BigQuery)
14088                        | Some(DialectType::Hive)
14089                        | Some(DialectType::Spark)
14090                        | Some(DialectType::Databricks)
14091                        | Some(DialectType::Drill)
14092                        | Some(DialectType::Snowflake)
14093                        | Some(DialectType::Redshift)
14094                );
14095
14096                // Whether this dialect supports escaped sequences (ESCAPED_SEQUENCES mapping)
14097                // This is True when \\ is in STRING_ESCAPES (same as escape_backslash)
14098                let supports_escape_sequences = escape_backslash;
14099
14100                let mut escaped = String::with_capacity(s.len() + 4);
14101                for ch in s.chars() {
14102                    if escape_backslash && ch == '\\' {
14103                        // Double the backslash for the target dialect
14104                        escaped.push('\\');
14105                        escaped.push('\\');
14106                    } else if ch == '\'' {
14107                        if backslash_escapes_quote {
14108                            // Use backslash to escape the quote: \'
14109                            escaped.push('\\');
14110                            escaped.push('\'');
14111                        } else {
14112                            // Use SQL standard quote doubling: ''
14113                            escaped.push('\'');
14114                            escaped.push('\'');
14115                        }
14116                    } else if supports_escape_sequences {
14117                        // Apply ESCAPED_SEQUENCES mapping for special chars
14118                        // (escape_backslash=False in rawstring_sql, so \\ is NOT escaped here)
14119                        match ch {
14120                            '\n' => {
14121                                escaped.push('\\');
14122                                escaped.push('n');
14123                            }
14124                            '\r' => {
14125                                escaped.push('\\');
14126                                escaped.push('r');
14127                            }
14128                            '\t' => {
14129                                escaped.push('\\');
14130                                escaped.push('t');
14131                            }
14132                            '\x07' => {
14133                                escaped.push('\\');
14134                                escaped.push('a');
14135                            }
14136                            '\x08' => {
14137                                escaped.push('\\');
14138                                escaped.push('b');
14139                            }
14140                            '\x0C' => {
14141                                escaped.push('\\');
14142                                escaped.push('f');
14143                            }
14144                            '\x0B' => {
14145                                escaped.push('\\');
14146                                escaped.push('v');
14147                            }
14148                            _ => escaped.push(ch),
14149                        }
14150                    } else {
14151                        escaped.push(ch);
14152                    }
14153                }
14154                self.write("'");
14155                self.write(&escaped);
14156                self.write("'");
14157            }
14158        }
14159        Ok(())
14160    }
14161
14162    /// Generate a DATE literal with dialect-specific formatting
14163    fn generate_date_literal(&mut self, d: &str) -> Result<()> {
14164        use crate::dialects::DialectType;
14165
14166        match self.config.dialect {
14167            // SQL Server uses CONVERT or CAST
14168            Some(DialectType::TSQL) => {
14169                self.write("CAST('");
14170                self.write(d);
14171                self.write("' AS DATE)");
14172            }
14173            // BigQuery uses CAST syntax for type literals
14174            // DATE 'value' -> CAST('value' AS DATE)
14175            Some(DialectType::BigQuery) => {
14176                self.write("CAST('");
14177                self.write(d);
14178                self.write("' AS DATE)");
14179            }
14180            // Exasol uses CAST syntax for DATE literals
14181            // DATE 'value' -> CAST('value' AS DATE)
14182            Some(DialectType::Exasol) => {
14183                self.write("CAST('");
14184                self.write(d);
14185                self.write("' AS DATE)");
14186            }
14187            // Snowflake uses CAST syntax for DATE literals
14188            // DATE 'value' -> CAST('value' AS DATE)
14189            Some(DialectType::Snowflake) => {
14190                self.write("CAST('");
14191                self.write(d);
14192                self.write("' AS DATE)");
14193            }
14194            // PostgreSQL, MySQL, Redshift: DATE 'value' -> CAST('value' AS DATE)
14195            Some(DialectType::PostgreSQL)
14196            | Some(DialectType::MySQL)
14197            | Some(DialectType::SingleStore)
14198            | Some(DialectType::TiDB)
14199            | Some(DialectType::Redshift) => {
14200                self.write("CAST('");
14201                self.write(d);
14202                self.write("' AS DATE)");
14203            }
14204            // DuckDB, Presto, Trino, Spark: DATE 'value' -> CAST('value' AS DATE)
14205            Some(DialectType::DuckDB)
14206            | Some(DialectType::Presto)
14207            | Some(DialectType::Trino)
14208            | Some(DialectType::Athena)
14209            | Some(DialectType::Spark)
14210            | Some(DialectType::Databricks)
14211            | Some(DialectType::Hive) => {
14212                self.write("CAST('");
14213                self.write(d);
14214                self.write("' AS DATE)");
14215            }
14216            // Oracle: DATE 'value' -> TO_DATE('value', 'YYYY-MM-DD')
14217            Some(DialectType::Oracle) => {
14218                self.write("TO_DATE('");
14219                self.write(d);
14220                self.write("', 'YYYY-MM-DD')");
14221            }
14222            // Standard SQL: DATE '...'
14223            _ => {
14224                self.write_keyword("DATE");
14225                self.write(" '");
14226                self.write(d);
14227                self.write("'");
14228            }
14229        }
14230        Ok(())
14231    }
14232
14233    /// Generate a TIME literal with dialect-specific formatting
14234    fn generate_time_literal(&mut self, t: &str) -> Result<()> {
14235        use crate::dialects::DialectType;
14236
14237        match self.config.dialect {
14238            // SQL Server uses CONVERT or CAST
14239            Some(DialectType::TSQL) => {
14240                self.write("CAST('");
14241                self.write(t);
14242                self.write("' AS TIME)");
14243            }
14244            // Standard SQL: TIME '...'
14245            _ => {
14246                self.write_keyword("TIME");
14247                self.write(" '");
14248                self.write(t);
14249                self.write("'");
14250            }
14251        }
14252        Ok(())
14253    }
14254
14255    /// Generate a date expression for Dremio, converting DATE literals to CAST
14256    fn generate_dremio_date_expression(&mut self, expr: &Expression) -> Result<()> {
14257        use crate::expressions::Literal;
14258
14259        match expr {
14260            Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Date(_)) => {
14261                let Literal::Date(d) = lit.as_ref() else { unreachable!() };
14262                // DATE 'value' -> CAST('value' AS DATE)
14263                self.write("CAST('");
14264                self.write(d);
14265                self.write("' AS DATE)");
14266            }
14267            _ => {
14268                // For all other expressions, generate normally
14269                self.generate_expression(expr)?;
14270            }
14271        }
14272        Ok(())
14273    }
14274
14275    /// Generate a TIMESTAMP literal with dialect-specific formatting
14276    fn generate_timestamp_literal(&mut self, ts: &str) -> Result<()> {
14277        use crate::dialects::DialectType;
14278
14279        match self.config.dialect {
14280            // SQL Server uses CONVERT or CAST
14281            Some(DialectType::TSQL) => {
14282                self.write("CAST('");
14283                self.write(ts);
14284                self.write("' AS DATETIME2)");
14285            }
14286            // BigQuery uses CAST syntax for type literals
14287            // TIMESTAMP 'value' -> CAST('value' AS TIMESTAMP)
14288            Some(DialectType::BigQuery) => {
14289                self.write("CAST('");
14290                self.write(ts);
14291                self.write("' AS TIMESTAMP)");
14292            }
14293            // Snowflake uses CAST syntax for TIMESTAMP literals
14294            // TIMESTAMP 'value' -> CAST('value' AS TIMESTAMP)
14295            Some(DialectType::Snowflake) => {
14296                self.write("CAST('");
14297                self.write(ts);
14298                self.write("' AS TIMESTAMP)");
14299            }
14300            // Dremio uses CAST syntax for TIMESTAMP literals
14301            // TIMESTAMP 'value' -> CAST('value' AS TIMESTAMP)
14302            Some(DialectType::Dremio) => {
14303                self.write("CAST('");
14304                self.write(ts);
14305                self.write("' AS TIMESTAMP)");
14306            }
14307            // Exasol uses CAST syntax for TIMESTAMP literals
14308            // TIMESTAMP 'value' -> CAST('value' AS TIMESTAMP)
14309            Some(DialectType::Exasol) => {
14310                self.write("CAST('");
14311                self.write(ts);
14312                self.write("' AS TIMESTAMP)");
14313            }
14314            // Oracle prefers TO_TIMESTAMP function call
14315            // TIMESTAMP 'value' -> TO_TIMESTAMP('value', 'YYYY-MM-DD HH24:MI:SS.FF6')
14316            Some(DialectType::Oracle) => {
14317                self.write("TO_TIMESTAMP('");
14318                self.write(ts);
14319                self.write("', 'YYYY-MM-DD HH24:MI:SS.FF6')");
14320            }
14321            // Presto/Trino: always use CAST for TIMESTAMP literals
14322            Some(DialectType::Presto) | Some(DialectType::Trino) => {
14323                if Self::timestamp_has_timezone(ts) {
14324                    self.write("CAST('");
14325                    self.write(ts);
14326                    self.write("' AS TIMESTAMP WITH TIME ZONE)");
14327                } else {
14328                    self.write("CAST('");
14329                    self.write(ts);
14330                    self.write("' AS TIMESTAMP)");
14331                }
14332            }
14333            // ClickHouse: CAST('...' AS Nullable(DateTime))
14334            Some(DialectType::ClickHouse) => {
14335                self.write("CAST('");
14336                self.write(ts);
14337                self.write("' AS Nullable(DateTime))");
14338            }
14339            // Spark: CAST('...' AS TIMESTAMP)
14340            Some(DialectType::Spark) => {
14341                self.write("CAST('");
14342                self.write(ts);
14343                self.write("' AS TIMESTAMP)");
14344            }
14345            // Redshift: CAST('...' AS TIMESTAMP) for regular timestamps,
14346            // but TIMESTAMP '...' for special values like 'epoch'
14347            Some(DialectType::Redshift) => {
14348                if ts == "epoch" {
14349                    self.write_keyword("TIMESTAMP");
14350                    self.write(" '");
14351                    self.write(ts);
14352                    self.write("'");
14353                } else {
14354                    self.write("CAST('");
14355                    self.write(ts);
14356                    self.write("' AS TIMESTAMP)");
14357                }
14358            }
14359            // PostgreSQL, Hive, DuckDB, etc.: CAST('...' AS TIMESTAMP)
14360            Some(DialectType::PostgreSQL)
14361            | Some(DialectType::Hive)
14362            | Some(DialectType::SQLite)
14363            | Some(DialectType::DuckDB)
14364            | Some(DialectType::Athena)
14365            | Some(DialectType::Drill)
14366            | Some(DialectType::Teradata) => {
14367                self.write("CAST('");
14368                self.write(ts);
14369                self.write("' AS TIMESTAMP)");
14370            }
14371            // MySQL/StarRocks: CAST('...' AS DATETIME)
14372            Some(DialectType::MySQL) | Some(DialectType::StarRocks) | Some(DialectType::Doris) => {
14373                self.write("CAST('");
14374                self.write(ts);
14375                self.write("' AS DATETIME)");
14376            }
14377            // Databricks: CAST('...' AS TIMESTAMP_NTZ)
14378            Some(DialectType::Databricks) => {
14379                self.write("CAST('");
14380                self.write(ts);
14381                self.write("' AS TIMESTAMP_NTZ)");
14382            }
14383            // Standard SQL: TIMESTAMP '...'
14384            _ => {
14385                self.write_keyword("TIMESTAMP");
14386                self.write(" '");
14387                self.write(ts);
14388                self.write("'");
14389            }
14390        }
14391        Ok(())
14392    }
14393
14394    /// Check if a timestamp string contains a timezone identifier
14395    /// This detects IANA timezone names like Europe/Prague, America/New_York, etc.
14396    fn timestamp_has_timezone(ts: &str) -> bool {
14397        // Check for common IANA timezone patterns: Continent/City format
14398        // Examples: Europe/Prague, America/New_York, Asia/Tokyo, etc.
14399        // Also handles: UTC, GMT, Etc/GMT+0, etc.
14400        let ts_lower = ts.to_ascii_lowercase();
14401
14402        // Check for Continent/City pattern (most common)
14403        let continent_prefixes = [
14404            "africa/",
14405            "america/",
14406            "antarctica/",
14407            "arctic/",
14408            "asia/",
14409            "atlantic/",
14410            "australia/",
14411            "europe/",
14412            "indian/",
14413            "pacific/",
14414            "etc/",
14415            "brazil/",
14416            "canada/",
14417            "chile/",
14418            "mexico/",
14419            "us/",
14420        ];
14421
14422        for prefix in &continent_prefixes {
14423            if ts_lower.contains(prefix) {
14424                return true;
14425            }
14426        }
14427
14428        // Check for standalone timezone abbreviations at the end
14429        // These typically appear after the time portion
14430        let tz_abbrevs = [
14431            " utc", " gmt", " cet", " cest", " eet", " eest", " wet", " west", " est", " edt",
14432            " cst", " cdt", " mst", " mdt", " pst", " pdt", " ist", " bst", " jst", " kst", " hkt",
14433            " sgt", " aest", " aedt", " acst", " acdt", " awst",
14434        ];
14435
14436        for abbrev in &tz_abbrevs {
14437            if ts_lower.ends_with(abbrev) {
14438                return true;
14439            }
14440        }
14441
14442        // Check for numeric timezone offsets: +N, -N, +NN:NN, -NN:NN
14443        // Examples: "2012-10-31 01:00 -2", "2012-10-31 01:00 +02:00"
14444        // Look for pattern: space followed by + or - and digits (optionally with :)
14445        let trimmed = ts.trim();
14446        if let Some(last_space) = trimmed.rfind(' ') {
14447            let suffix = &trimmed[last_space + 1..];
14448            if (suffix.starts_with('+') || suffix.starts_with('-')) && suffix.len() > 1 {
14449                // Check if rest is numeric (possibly with : for hh:mm format)
14450                let rest = &suffix[1..];
14451                if rest.chars().all(|c| c.is_ascii_digit() || c == ':') {
14452                    return true;
14453                }
14454            }
14455        }
14456
14457        false
14458    }
14459
14460    /// Generate a DATETIME literal with dialect-specific formatting
14461    fn generate_datetime_literal(&mut self, dt: &str) -> Result<()> {
14462        use crate::dialects::DialectType;
14463
14464        match self.config.dialect {
14465            // BigQuery uses CAST syntax for type literals
14466            // DATETIME 'value' -> CAST('value' AS DATETIME)
14467            Some(DialectType::BigQuery) => {
14468                self.write("CAST('");
14469                self.write(dt);
14470                self.write("' AS DATETIME)");
14471            }
14472            // DuckDB: DATETIME -> CAST('value' AS TIMESTAMP)
14473            Some(DialectType::DuckDB) => {
14474                self.write("CAST('");
14475                self.write(dt);
14476                self.write("' AS TIMESTAMP)");
14477            }
14478            // DATETIME is primarily a BigQuery type
14479            // Output as DATETIME '...' for dialects that support it
14480            _ => {
14481                self.write_keyword("DATETIME");
14482                self.write(" '");
14483                self.write(dt);
14484                self.write("'");
14485            }
14486        }
14487        Ok(())
14488    }
14489
14490    /// Generate a string literal with dialect-specific escaping
14491    fn generate_string_literal(&mut self, s: &str) -> Result<()> {
14492        use crate::dialects::DialectType;
14493
14494        match self.config.dialect {
14495            // MySQL/Hive: Uses SQL standard quote escaping ('') for quotes,
14496            // and backslash escaping for special characters like newlines
14497            // Hive STRING_ESCAPES = ["\\"] - uses backslash escapes
14498            Some(DialectType::Hive) | Some(DialectType::Spark) | Some(DialectType::Databricks) => {
14499                // Hive/Spark use backslash escaping for quotes (\') and special chars
14500                self.write("'");
14501                for c in s.chars() {
14502                    match c {
14503                        '\'' => self.write("\\'"),
14504                        '\\' => self.write("\\\\"),
14505                        '\n' => self.write("\\n"),
14506                        '\r' => self.write("\\r"),
14507                        '\t' => self.write("\\t"),
14508                        '\0' => self.write("\\0"),
14509                        _ => self.output.push(c),
14510                    }
14511                }
14512                self.write("'");
14513            }
14514            Some(DialectType::Drill) => {
14515                // Drill uses SQL-standard quote doubling ('') for quotes,
14516                // but backslash escaping for special characters
14517                self.write("'");
14518                for c in s.chars() {
14519                    match c {
14520                        '\'' => self.write("''"),
14521                        '\\' => self.write("\\\\"),
14522                        '\n' => self.write("\\n"),
14523                        '\r' => self.write("\\r"),
14524                        '\t' => self.write("\\t"),
14525                        '\0' => self.write("\\0"),
14526                        _ => self.output.push(c),
14527                    }
14528                }
14529                self.write("'");
14530            }
14531            Some(DialectType::MySQL) | Some(DialectType::SingleStore) | Some(DialectType::TiDB) => {
14532                self.write("'");
14533                for c in s.chars() {
14534                    match c {
14535                        // MySQL uses SQL standard quote doubling
14536                        '\'' => self.write("''"),
14537                        '\\' => self.write("\\\\"),
14538                        '\n' => self.write("\\n"),
14539                        '\r' => self.write("\\r"),
14540                        '\t' => self.write("\\t"),
14541                        // sqlglot writes a literal NUL for this case
14542                        '\0' => self.output.push('\0'),
14543                        _ => self.output.push(c),
14544                    }
14545                }
14546                self.write("'");
14547            }
14548            // BigQuery: Uses backslash escaping
14549            Some(DialectType::BigQuery) => {
14550                self.write("'");
14551                for c in s.chars() {
14552                    match c {
14553                        '\'' => self.write("\\'"),
14554                        '\\' => self.write("\\\\"),
14555                        '\n' => self.write("\\n"),
14556                        '\r' => self.write("\\r"),
14557                        '\t' => self.write("\\t"),
14558                        '\0' => self.write("\\0"),
14559                        '\x07' => self.write("\\a"),
14560                        '\x08' => self.write("\\b"),
14561                        '\x0C' => self.write("\\f"),
14562                        '\x0B' => self.write("\\v"),
14563                        _ => self.output.push(c),
14564                    }
14565                }
14566                self.write("'");
14567            }
14568            // Athena: Uses different escaping for DDL (Hive) vs DML (Trino)
14569            // In Hive context (DDL): backslash escaping for single quotes (\') and backslashes (\\)
14570            // In Trino context (DML): SQL-standard escaping ('') and literal backslashes
14571            Some(DialectType::Athena) => {
14572                if self.athena_hive_context {
14573                    // Hive-style: backslash escaping
14574                    self.write("'");
14575                    for c in s.chars() {
14576                        match c {
14577                            '\'' => self.write("\\'"),
14578                            '\\' => self.write("\\\\"),
14579                            '\n' => self.write("\\n"),
14580                            '\r' => self.write("\\r"),
14581                            '\t' => self.write("\\t"),
14582                            '\0' => self.write("\\0"),
14583                            _ => self.output.push(c),
14584                        }
14585                    }
14586                    self.write("'");
14587                } else {
14588                    // Trino-style: SQL-standard escaping, preserve backslashes
14589                    self.write("'");
14590                    for c in s.chars() {
14591                        match c {
14592                            '\'' => self.write("''"),
14593                            // Preserve backslashes literally (no re-escaping)
14594                            _ => self.output.push(c),
14595                        }
14596                    }
14597                    self.write("'");
14598                }
14599            }
14600            // Snowflake: Uses backslash escaping (STRING_ESCAPES = ["\\", "'"])
14601            // The tokenizer preserves backslash escape sequences literally (e.g., input '\\'
14602            // becomes string value '\\'), so we should NOT re-escape backslashes.
14603            // We only need to escape single quotes.
14604            Some(DialectType::Snowflake) => {
14605                self.write("'");
14606                for c in s.chars() {
14607                    match c {
14608                        '\'' => self.write("\\'"),
14609                        // Backslashes are already escaped in the tokenized string, don't re-escape
14610                        // Only escape special characters that might not have been escaped
14611                        '\n' => self.write("\\n"),
14612                        '\r' => self.write("\\r"),
14613                        '\t' => self.write("\\t"),
14614                        _ => self.output.push(c),
14615                    }
14616                }
14617                self.write("'");
14618            }
14619            // PostgreSQL: Output special characters as literal chars in strings (no E-string prefix)
14620            Some(DialectType::PostgreSQL) => {
14621                self.write("'");
14622                for c in s.chars() {
14623                    match c {
14624                        '\'' => self.write("''"),
14625                        _ => self.output.push(c),
14626                    }
14627                }
14628                self.write("'");
14629            }
14630            // Redshift: Uses backslash escaping for single quotes
14631            Some(DialectType::Redshift) => {
14632                self.write("'");
14633                for c in s.chars() {
14634                    match c {
14635                        '\'' => self.write("\\'"),
14636                        _ => self.output.push(c),
14637                    }
14638                }
14639                self.write("'");
14640            }
14641            // Oracle: Uses standard double single-quote escaping
14642            Some(DialectType::Oracle) => {
14643                self.write("'");
14644                for ch in s.chars() {
14645                    if ch == '\'' { self.output.push_str("''"); } else { self.output.push(ch); }
14646                }
14647                self.write("'");
14648            }
14649            // ClickHouse: Uses SQL-standard quote doubling ('') for quotes,
14650            // backslash escaping for backslashes and special characters
14651            Some(DialectType::ClickHouse) => {
14652                self.write("'");
14653                for c in s.chars() {
14654                    match c {
14655                        '\'' => self.write("''"),
14656                        '\\' => self.write("\\\\"),
14657                        '\n' => self.write("\\n"),
14658                        '\r' => self.write("\\r"),
14659                        '\t' => self.write("\\t"),
14660                        '\0' => self.write("\\0"),
14661                        '\x07' => self.write("\\a"),
14662                        '\x08' => self.write("\\b"),
14663                        '\x0C' => self.write("\\f"),
14664                        '\x0B' => self.write("\\v"),
14665                        // Non-printable characters: emit as \xNN hex escapes
14666                        c if c.is_control() || (c as u32) < 0x20 => {
14667                            let byte = c as u32;
14668                            if byte < 256 {
14669                                self.write(&format!("\\x{:02X}", byte));
14670                            } else {
14671                                self.output.push(c);
14672                            }
14673                        }
14674                        _ => self.output.push(c),
14675                    }
14676                }
14677                self.write("'");
14678            }
14679            // Default: SQL standard double single quotes (works for most dialects)
14680            // PostgreSQL, Snowflake, DuckDB, TSQL, etc.
14681            _ => {
14682                self.write("'");
14683                for ch in s.chars() {
14684                    if ch == '\'' { self.output.push_str("''"); } else { self.output.push(ch); }
14685                }
14686                self.write("'");
14687            }
14688        }
14689        Ok(())
14690    }
14691
14692    /// Write a byte string with proper escaping for BigQuery-style byte literals
14693    /// Escapes characters as \xNN hex escapes where needed
14694    fn write_escaped_byte_string(&mut self, s: &str) {
14695        for c in s.chars() {
14696            match c {
14697                // Escape single quotes
14698                '\'' => self.write("\\'"),
14699                // Escape backslashes
14700                '\\' => self.write("\\\\"),
14701                // Keep all printable characters (including non-ASCII) as-is
14702                _ if !c.is_control() => self.output.push(c),
14703                // Escape control characters as hex
14704                _ => {
14705                    let byte = c as u32;
14706                    if byte < 256 {
14707                        self.write(&format!("\\x{:02x}", byte));
14708                    } else {
14709                        // For unicode characters, write each UTF-8 byte
14710                        for b in c.to_string().as_bytes() {
14711                            self.write(&format!("\\x{:02x}", b));
14712                        }
14713                    }
14714                }
14715            }
14716        }
14717    }
14718
14719    fn generate_boolean(&mut self, b: &BooleanLiteral) -> Result<()> {
14720        use crate::dialects::DialectType;
14721
14722        // Different dialects have different boolean literal formats
14723        match self.config.dialect {
14724            // SQL Server typically uses 1/0 for boolean literals in many contexts
14725            // However, TRUE/FALSE also works in modern versions
14726            Some(DialectType::TSQL) => {
14727                self.write(if b.value { "1" } else { "0" });
14728            }
14729            // Oracle traditionally uses 1/0 (no native boolean until recent versions)
14730            Some(DialectType::Oracle) => {
14731                self.write(if b.value { "1" } else { "0" });
14732            }
14733            // MySQL accepts TRUE/FALSE as aliases for 1/0
14734            Some(DialectType::MySQL) => {
14735                self.write_keyword(if b.value { "TRUE" } else { "FALSE" });
14736            }
14737            // Most other dialects support TRUE/FALSE
14738            _ => {
14739                self.write_keyword(if b.value { "TRUE" } else { "FALSE" });
14740            }
14741        }
14742        Ok(())
14743    }
14744
14745    /// Generate an identifier that's used as an alias name
14746    /// This quotes reserved keywords in addition to already-quoted identifiers
14747    fn generate_alias_identifier(&mut self, id: &Identifier) -> Result<()> {
14748        let name = &id.name;
14749        let quote_style = &self.config.identifier_quote_style;
14750
14751        // For aliases, quote if:
14752        // 1. The identifier was explicitly quoted in the source
14753        // 2. The identifier is a reserved keyword for the current dialect
14754        let needs_quoting = id.quoted || self.is_reserved_keyword(name);
14755
14756        // Normalize identifier if configured
14757        let output_name = if self.config.normalize_identifiers && !id.quoted {
14758            name.to_ascii_lowercase()
14759        } else {
14760            name.to_string()
14761        };
14762
14763        if needs_quoting {
14764            // Escape any quote characters within the identifier
14765            let escaped_name = if quote_style.start == quote_style.end {
14766                output_name.replace(
14767                    quote_style.end,
14768                    &format!("{}{}", quote_style.end, quote_style.end),
14769                )
14770            } else {
14771                output_name.replace(
14772                    quote_style.end,
14773                    &format!("{}{}", quote_style.end, quote_style.end),
14774                )
14775            };
14776            self.write(&format!(
14777                "{}{}{}",
14778                quote_style.start, escaped_name, quote_style.end
14779            ));
14780        } else {
14781            self.write(&output_name);
14782        }
14783
14784        // Output trailing comments
14785        for comment in &id.trailing_comments {
14786            self.write(" ");
14787            self.write_formatted_comment(comment);
14788        }
14789        Ok(())
14790    }
14791
14792    fn generate_identifier(&mut self, id: &Identifier) -> Result<()> {
14793        use crate::dialects::DialectType;
14794
14795        let name = &id.name;
14796
14797        // For Athena, use backticks in Hive context, double quotes in Trino context
14798        let quote_style = if matches!(self.config.dialect, Some(DialectType::Athena))
14799            && self.athena_hive_context
14800        {
14801            &IdentifierQuoteStyle::BACKTICK
14802        } else {
14803            &self.config.identifier_quote_style
14804        };
14805
14806        // Quote if:
14807        // 1. The identifier was explicitly quoted in the source
14808        // 2. The identifier is a reserved keyword for the current dialect
14809        // 3. The config says to always quote identifiers (e.g., Athena/Presto)
14810        // This matches Python sqlglot's identifier_sql behavior
14811        // Also quote identifiers starting with digits if the target dialect doesn't support them
14812        let starts_with_digit = name.chars().next().map_or(false, |c| c.is_ascii_digit());
14813        let needs_digit_quoting = starts_with_digit
14814            && !self.config.identifiers_can_start_with_digit
14815            && self.config.dialect.is_some();
14816        let mysql_invalid_hex_identifier = matches!(self.config.dialect, Some(DialectType::MySQL))
14817            && name.len() > 2
14818            && (name.starts_with("0x") || name.starts_with("0X"))
14819            && !name[2..].chars().all(|c| c.is_ascii_hexdigit());
14820        let needs_quoting = id.quoted
14821            || self.is_reserved_keyword(name)
14822            || self.config.always_quote_identifiers
14823            || needs_digit_quoting
14824            || mysql_invalid_hex_identifier;
14825
14826        // Check for MySQL index column prefix length: name(16) or name(16) ASC/DESC
14827        // When quoted, we need to output `name`(16) not `name(16)`
14828        let (base_name, suffix) = if needs_quoting {
14829            // Try to extract prefix length from identifier: name(number) or name(number) ASC/DESC
14830            if let Some(paren_pos) = name.find('(') {
14831                let base = &name[..paren_pos];
14832                let rest = &name[paren_pos..];
14833                // Verify it looks like (digits) or (digits) ASC/DESC
14834                if rest.starts_with('(')
14835                    && (rest.ends_with(')') || rest.ends_with(") ASC") || rest.ends_with(") DESC"))
14836                {
14837                    // Check if content between parens is all digits
14838                    let close_paren = rest.find(')').unwrap_or(rest.len());
14839                    let inside = &rest[1..close_paren];
14840                    if inside.chars().all(|c| c.is_ascii_digit()) {
14841                        (base.to_string(), rest.to_string())
14842                    } else {
14843                        (name.to_string(), String::new())
14844                    }
14845                } else {
14846                    (name.to_string(), String::new())
14847                }
14848            } else if name.ends_with(" ASC") {
14849                let base = &name[..name.len() - 4];
14850                (base.to_string(), " ASC".to_string())
14851            } else if name.ends_with(" DESC") {
14852                let base = &name[..name.len() - 5];
14853                (base.to_string(), " DESC".to_string())
14854            } else {
14855                (name.to_string(), String::new())
14856            }
14857        } else {
14858            (name.to_string(), String::new())
14859        };
14860
14861        // Normalize identifier if configured, with special handling for Exasol
14862        // Exasol uses UPPERCASE normalization strategy, so reserved keywords that need quoting
14863        // should be uppercased when not already quoted (to match Python sqlglot behavior)
14864        let output_name = if self.config.normalize_identifiers && !id.quoted {
14865            base_name.to_ascii_lowercase()
14866        } else if matches!(self.config.dialect, Some(DialectType::Exasol))
14867            && !id.quoted
14868            && self.is_reserved_keyword(name)
14869        {
14870            // Exasol: uppercase reserved keywords when quoting them
14871            // This matches Python sqlglot's behavior with NORMALIZATION_STRATEGY = UPPERCASE
14872            base_name.to_ascii_uppercase()
14873        } else {
14874            base_name
14875        };
14876
14877        if needs_quoting {
14878            // Escape any quote characters within the identifier
14879            let escaped_name = if quote_style.start == quote_style.end {
14880                // Same start/end char (e.g., " or `) - double the quote char
14881                output_name.replace(
14882                    quote_style.end,
14883                    &format!("{}{}", quote_style.end, quote_style.end),
14884                )
14885            } else {
14886                // Different start/end (e.g., [ and ]) - escape only the end char
14887                output_name.replace(
14888                    quote_style.end,
14889                    &format!("{}{}", quote_style.end, quote_style.end),
14890                )
14891            };
14892            self.write(&format!(
14893                "{}{}{}{}",
14894                quote_style.start, escaped_name, quote_style.end, suffix
14895            ));
14896        } else {
14897            self.write(&output_name);
14898        }
14899
14900        // Output trailing comments
14901        for comment in &id.trailing_comments {
14902            self.write(" ");
14903            self.write_formatted_comment(comment);
14904        }
14905        Ok(())
14906    }
14907
14908    fn generate_column(&mut self, col: &Column) -> Result<()> {
14909        use crate::dialects::DialectType;
14910
14911        if let Some(table) = &col.table {
14912            // Exasol special case: LOCAL as column table prefix should NOT be quoted
14913            // LOCAL is a special keyword in Exasol for referencing aliases from the current scope
14914            // Only applies when: dialect is Exasol, name is "LOCAL" (case-insensitive), and not already quoted
14915            let is_exasol_local_prefix = matches!(self.config.dialect, Some(DialectType::Exasol))
14916                && !table.quoted
14917                && table.name.eq_ignore_ascii_case("LOCAL");
14918
14919            if is_exasol_local_prefix {
14920                // Write LOCAL unquoted (this is special Exasol syntax, not a table reference)
14921                self.write("LOCAL");
14922            } else {
14923                self.generate_identifier(table)?;
14924            }
14925            self.write(".");
14926        }
14927        self.generate_identifier(&col.name)?;
14928        // Oracle-style join marker (+)
14929        // Only output if dialect supports it (Oracle, Exasol)
14930        if col.join_mark && self.config.supports_column_join_marks {
14931            self.write(" (+)");
14932        }
14933        // Output trailing comments
14934        for comment in &col.trailing_comments {
14935            self.write_space();
14936            self.write_formatted_comment(comment);
14937        }
14938        Ok(())
14939    }
14940
14941    /// Generate a pseudocolumn (Oracle ROWNUM, ROWID, LEVEL, etc.)
14942    /// Pseudocolumns should NEVER be quoted, as quoting breaks them in Oracle
14943    fn generate_pseudocolumn(&mut self, pc: &Pseudocolumn) -> Result<()> {
14944        use crate::dialects::DialectType;
14945        use crate::expressions::PseudocolumnType;
14946
14947        // SYSDATE -> CURRENT_TIMESTAMP for non-Oracle/Redshift dialects
14948        if pc.kind == PseudocolumnType::Sysdate
14949            && !matches!(
14950                self.config.dialect,
14951                Some(DialectType::Oracle) | Some(DialectType::Redshift) | None
14952            )
14953        {
14954            self.write_keyword("CURRENT_TIMESTAMP");
14955            // Add () for dialects that expect it
14956            if matches!(
14957                self.config.dialect,
14958                Some(DialectType::MySQL)
14959                    | Some(DialectType::ClickHouse)
14960                    | Some(DialectType::Spark)
14961                    | Some(DialectType::Databricks)
14962                    | Some(DialectType::Hive)
14963            ) {
14964                self.write("()");
14965            }
14966        } else {
14967            self.write(pc.kind.as_str());
14968        }
14969        Ok(())
14970    }
14971
14972    /// Generate CONNECT BY clause (Oracle hierarchical queries)
14973    fn generate_connect(&mut self, connect: &Connect) -> Result<()> {
14974        use crate::dialects::DialectType;
14975
14976        // Generate native CONNECT BY for Oracle and Snowflake
14977        // For other dialects, add a comment noting manual conversion needed
14978        let supports_connect_by = matches!(
14979            self.config.dialect,
14980            Some(DialectType::Oracle) | Some(DialectType::Snowflake)
14981        );
14982
14983        if !supports_connect_by && self.config.dialect.is_some() {
14984            // Add comment for unsupported dialects
14985            if self.config.pretty {
14986                self.write_newline();
14987            } else {
14988                self.write_space();
14989            }
14990            self.write_unsupported_comment(
14991                "CONNECT BY requires manual conversion to recursive CTE",
14992            )?;
14993        }
14994
14995        // Generate START WITH if present (before CONNECT BY)
14996        if let Some(start) = &connect.start {
14997            if self.config.pretty {
14998                self.write_newline();
14999            } else {
15000                self.write_space();
15001            }
15002            self.write_keyword("START WITH");
15003            self.write_space();
15004            self.generate_expression(start)?;
15005        }
15006
15007        // Generate CONNECT BY
15008        if self.config.pretty {
15009            self.write_newline();
15010        } else {
15011            self.write_space();
15012        }
15013        self.write_keyword("CONNECT BY");
15014        if connect.nocycle {
15015            self.write_space();
15016            self.write_keyword("NOCYCLE");
15017        }
15018        self.write_space();
15019        self.generate_expression(&connect.connect)?;
15020
15021        Ok(())
15022    }
15023
15024    /// Generate Connect expression (for Expression::Connect variant)
15025    fn generate_connect_expr(&mut self, connect: &Connect) -> Result<()> {
15026        self.generate_connect(connect)
15027    }
15028
15029    /// Generate PRIOR expression
15030    fn generate_prior(&mut self, prior: &Prior) -> Result<()> {
15031        self.write_keyword("PRIOR");
15032        self.write_space();
15033        self.generate_expression(&prior.this)?;
15034        Ok(())
15035    }
15036
15037    /// Generate CONNECT_BY_ROOT function
15038    /// Syntax: CONNECT_BY_ROOT column (no parentheses)
15039    fn generate_connect_by_root(&mut self, cbr: &ConnectByRoot) -> Result<()> {
15040        self.write_keyword("CONNECT_BY_ROOT");
15041        self.write_space();
15042        self.generate_expression(&cbr.this)?;
15043        Ok(())
15044    }
15045
15046    /// Generate MATCH_RECOGNIZE clause
15047    fn generate_match_recognize(&mut self, mr: &MatchRecognize) -> Result<()> {
15048        use crate::dialects::DialectType;
15049
15050        // MATCH_RECOGNIZE is supported in Oracle, Snowflake, Presto, and Trino
15051        let supports_match_recognize = matches!(
15052            self.config.dialect,
15053            Some(DialectType::Oracle)
15054                | Some(DialectType::Snowflake)
15055                | Some(DialectType::Presto)
15056                | Some(DialectType::Trino)
15057        );
15058
15059        // Generate the source table first
15060        if let Some(source) = &mr.this {
15061            self.generate_expression(source)?;
15062        }
15063
15064        if !supports_match_recognize {
15065            self.write_unsupported_comment("MATCH_RECOGNIZE not supported in this dialect")?;
15066            return Ok(());
15067        }
15068
15069        // In pretty mode, MATCH_RECOGNIZE should be on a new line
15070        if self.config.pretty {
15071            self.write_newline();
15072        } else {
15073            self.write_space();
15074        }
15075
15076        self.write_keyword("MATCH_RECOGNIZE");
15077        self.write(" (");
15078
15079        if self.config.pretty {
15080            self.indent_level += 1;
15081        }
15082
15083        let mut needs_separator = false;
15084
15085        // PARTITION BY
15086        if let Some(partition_by) = &mr.partition_by {
15087            if !partition_by.is_empty() {
15088                if self.config.pretty {
15089                    self.write_newline();
15090                    self.write_indent();
15091                }
15092                self.write_keyword("PARTITION BY");
15093                self.write_space();
15094                for (i, expr) in partition_by.iter().enumerate() {
15095                    if i > 0 {
15096                        self.write(", ");
15097                    }
15098                    self.generate_expression(expr)?;
15099                }
15100                needs_separator = true;
15101            }
15102        }
15103
15104        // ORDER BY
15105        if let Some(order_by) = &mr.order_by {
15106            if !order_by.is_empty() {
15107                if needs_separator {
15108                    if self.config.pretty {
15109                        self.write_newline();
15110                        self.write_indent();
15111                    } else {
15112                        self.write_space();
15113                    }
15114                } else if self.config.pretty {
15115                    self.write_newline();
15116                    self.write_indent();
15117                }
15118                self.write_keyword("ORDER BY");
15119                // In pretty mode, put each ORDER BY column on a new indented line
15120                if self.config.pretty {
15121                    self.indent_level += 1;
15122                    for (i, ordered) in order_by.iter().enumerate() {
15123                        if i > 0 {
15124                            self.write(",");
15125                        }
15126                        self.write_newline();
15127                        self.write_indent();
15128                        self.generate_ordered(ordered)?;
15129                    }
15130                    self.indent_level -= 1;
15131                } else {
15132                    self.write_space();
15133                    for (i, ordered) in order_by.iter().enumerate() {
15134                        if i > 0 {
15135                            self.write(", ");
15136                        }
15137                        self.generate_ordered(ordered)?;
15138                    }
15139                }
15140                needs_separator = true;
15141            }
15142        }
15143
15144        // MEASURES
15145        if let Some(measures) = &mr.measures {
15146            if !measures.is_empty() {
15147                if needs_separator {
15148                    if self.config.pretty {
15149                        self.write_newline();
15150                        self.write_indent();
15151                    } else {
15152                        self.write_space();
15153                    }
15154                } else if self.config.pretty {
15155                    self.write_newline();
15156                    self.write_indent();
15157                }
15158                self.write_keyword("MEASURES");
15159                // In pretty mode, put each MEASURE on a new indented line
15160                if self.config.pretty {
15161                    self.indent_level += 1;
15162                    for (i, measure) in measures.iter().enumerate() {
15163                        if i > 0 {
15164                            self.write(",");
15165                        }
15166                        self.write_newline();
15167                        self.write_indent();
15168                        // Handle RUNNING/FINAL prefix
15169                        if let Some(semantics) = &measure.window_frame {
15170                            match semantics {
15171                                MatchRecognizeSemantics::Running => {
15172                                    self.write_keyword("RUNNING");
15173                                    self.write_space();
15174                                }
15175                                MatchRecognizeSemantics::Final => {
15176                                    self.write_keyword("FINAL");
15177                                    self.write_space();
15178                                }
15179                            }
15180                        }
15181                        self.generate_expression(&measure.this)?;
15182                    }
15183                    self.indent_level -= 1;
15184                } else {
15185                    self.write_space();
15186                    for (i, measure) in measures.iter().enumerate() {
15187                        if i > 0 {
15188                            self.write(", ");
15189                        }
15190                        // Handle RUNNING/FINAL prefix
15191                        if let Some(semantics) = &measure.window_frame {
15192                            match semantics {
15193                                MatchRecognizeSemantics::Running => {
15194                                    self.write_keyword("RUNNING");
15195                                    self.write_space();
15196                                }
15197                                MatchRecognizeSemantics::Final => {
15198                                    self.write_keyword("FINAL");
15199                                    self.write_space();
15200                                }
15201                            }
15202                        }
15203                        self.generate_expression(&measure.this)?;
15204                    }
15205                }
15206                needs_separator = true;
15207            }
15208        }
15209
15210        // Row semantics (ONE ROW PER MATCH, ALL ROWS PER MATCH, etc.)
15211        if let Some(rows) = &mr.rows {
15212            if needs_separator {
15213                if self.config.pretty {
15214                    self.write_newline();
15215                    self.write_indent();
15216                } else {
15217                    self.write_space();
15218                }
15219            } else if self.config.pretty {
15220                self.write_newline();
15221                self.write_indent();
15222            }
15223            match rows {
15224                MatchRecognizeRows::OneRowPerMatch => {
15225                    self.write_keyword("ONE ROW PER MATCH");
15226                }
15227                MatchRecognizeRows::AllRowsPerMatch => {
15228                    self.write_keyword("ALL ROWS PER MATCH");
15229                }
15230                MatchRecognizeRows::AllRowsPerMatchShowEmptyMatches => {
15231                    self.write_keyword("ALL ROWS PER MATCH SHOW EMPTY MATCHES");
15232                }
15233                MatchRecognizeRows::AllRowsPerMatchOmitEmptyMatches => {
15234                    self.write_keyword("ALL ROWS PER MATCH OMIT EMPTY MATCHES");
15235                }
15236                MatchRecognizeRows::AllRowsPerMatchWithUnmatchedRows => {
15237                    self.write_keyword("ALL ROWS PER MATCH WITH UNMATCHED ROWS");
15238                }
15239            }
15240            needs_separator = true;
15241        }
15242
15243        // AFTER MATCH SKIP
15244        if let Some(after) = &mr.after {
15245            if needs_separator {
15246                if self.config.pretty {
15247                    self.write_newline();
15248                    self.write_indent();
15249                } else {
15250                    self.write_space();
15251                }
15252            } else if self.config.pretty {
15253                self.write_newline();
15254                self.write_indent();
15255            }
15256            match after {
15257                MatchRecognizeAfter::PastLastRow => {
15258                    self.write_keyword("AFTER MATCH SKIP PAST LAST ROW");
15259                }
15260                MatchRecognizeAfter::ToNextRow => {
15261                    self.write_keyword("AFTER MATCH SKIP TO NEXT ROW");
15262                }
15263                MatchRecognizeAfter::ToFirst(ident) => {
15264                    self.write_keyword("AFTER MATCH SKIP TO FIRST");
15265                    self.write_space();
15266                    self.generate_identifier(ident)?;
15267                }
15268                MatchRecognizeAfter::ToLast(ident) => {
15269                    self.write_keyword("AFTER MATCH SKIP TO LAST");
15270                    self.write_space();
15271                    self.generate_identifier(ident)?;
15272                }
15273            }
15274            needs_separator = true;
15275        }
15276
15277        // PATTERN
15278        if let Some(pattern) = &mr.pattern {
15279            if needs_separator {
15280                if self.config.pretty {
15281                    self.write_newline();
15282                    self.write_indent();
15283                } else {
15284                    self.write_space();
15285                }
15286            } else if self.config.pretty {
15287                self.write_newline();
15288                self.write_indent();
15289            }
15290            self.write_keyword("PATTERN");
15291            self.write_space();
15292            self.write("(");
15293            self.write(pattern);
15294            self.write(")");
15295            needs_separator = true;
15296        }
15297
15298        // DEFINE
15299        if let Some(define) = &mr.define {
15300            if !define.is_empty() {
15301                if needs_separator {
15302                    if self.config.pretty {
15303                        self.write_newline();
15304                        self.write_indent();
15305                    } else {
15306                        self.write_space();
15307                    }
15308                } else if self.config.pretty {
15309                    self.write_newline();
15310                    self.write_indent();
15311                }
15312                self.write_keyword("DEFINE");
15313                // In pretty mode, put each DEFINE on a new indented line
15314                if self.config.pretty {
15315                    self.indent_level += 1;
15316                    for (i, (name, expr)) in define.iter().enumerate() {
15317                        if i > 0 {
15318                            self.write(",");
15319                        }
15320                        self.write_newline();
15321                        self.write_indent();
15322                        self.generate_identifier(name)?;
15323                        self.write(" AS ");
15324                        self.generate_expression(expr)?;
15325                    }
15326                    self.indent_level -= 1;
15327                } else {
15328                    self.write_space();
15329                    for (i, (name, expr)) in define.iter().enumerate() {
15330                        if i > 0 {
15331                            self.write(", ");
15332                        }
15333                        self.generate_identifier(name)?;
15334                        self.write(" AS ");
15335                        self.generate_expression(expr)?;
15336                    }
15337                }
15338            }
15339        }
15340
15341        if self.config.pretty {
15342            self.indent_level -= 1;
15343            self.write_newline();
15344        }
15345        self.write(")");
15346
15347        // Alias - only include AS if it was explicitly present in the input
15348        if let Some(alias) = &mr.alias {
15349            self.write(" ");
15350            if mr.alias_explicit_as {
15351                self.write_keyword("AS");
15352                self.write(" ");
15353            }
15354            self.generate_identifier(alias)?;
15355        }
15356
15357        Ok(())
15358    }
15359
15360    /// Generate a query hint /*+ ... */
15361    fn generate_hint(&mut self, hint: &Hint) -> Result<()> {
15362        use crate::dialects::DialectType;
15363
15364        // Output hints for dialects that support them, or when no dialect is specified (identity tests)
15365        let supports_hints = matches!(
15366            self.config.dialect,
15367            None |  // No dialect = preserve everything
15368            Some(DialectType::Oracle) | Some(DialectType::MySQL) |
15369            Some(DialectType::Spark) | Some(DialectType::Hive) |
15370            Some(DialectType::Databricks) | Some(DialectType::PostgreSQL)
15371        );
15372
15373        if !supports_hints || hint.expressions.is_empty() {
15374            return Ok(());
15375        }
15376
15377        // First, expand raw hint text into individual hint strings
15378        // This handles the case where the parser stored multiple hints as a single raw string
15379        let mut hint_strings: Vec<String> = Vec::new();
15380        for expr in &hint.expressions {
15381            match expr {
15382                HintExpression::Raw(text) => {
15383                    // Parse raw hint text into individual hint function calls
15384                    let parsed = self.parse_raw_hint_text(text);
15385                    hint_strings.extend(parsed);
15386                }
15387                _ => {
15388                    hint_strings.push(self.hint_expression_to_string(expr)?);
15389                }
15390            }
15391        }
15392
15393        // In pretty mode with multiple hints, always use multiline format
15394        // This matches Python sqlglot's behavior where expressions() with default dynamic=False
15395        // always joins with newlines in pretty mode
15396        let use_multiline = self.config.pretty && hint_strings.len() > 1;
15397
15398        if use_multiline {
15399            // Pretty print with each hint on its own line
15400            self.write(" /*+ ");
15401            for (i, hint_str) in hint_strings.iter().enumerate() {
15402                if i > 0 {
15403                    self.write_newline();
15404                    self.write("  "); // 2-space indent within hint block
15405                }
15406                self.write(hint_str);
15407            }
15408            self.write(" */");
15409        } else {
15410            // Single line format
15411            self.write(" /*+ ");
15412            let sep = match self.config.dialect {
15413                Some(DialectType::Spark) | Some(DialectType::Databricks) => ", ",
15414                _ => " ",
15415            };
15416            for (i, hint_str) in hint_strings.iter().enumerate() {
15417                if i > 0 {
15418                    self.write(sep);
15419                }
15420                self.write(hint_str);
15421            }
15422            self.write(" */");
15423        }
15424
15425        Ok(())
15426    }
15427
15428    /// Parse raw hint text into individual hint function calls
15429    /// e.g., "LEADING(a b) USE_NL(c)" -> ["LEADING(a b)", "USE_NL(c)"]
15430    /// If the hint contains unparseable content (like SQL keywords), return as single raw string
15431    fn parse_raw_hint_text(&self, text: &str) -> Vec<String> {
15432        let mut results = Vec::new();
15433        let mut chars = text.chars().peekable();
15434        let mut current = String::new();
15435        let mut paren_depth = 0;
15436        let mut has_unparseable_content = false;
15437        let mut position_after_last_function = 0;
15438        let mut char_position = 0;
15439
15440        while let Some(c) = chars.next() {
15441            char_position += c.len_utf8();
15442            match c {
15443                '(' => {
15444                    paren_depth += 1;
15445                    current.push(c);
15446                }
15447                ')' => {
15448                    paren_depth -= 1;
15449                    current.push(c);
15450                    // When we close the outer parenthesis, we've completed a hint function
15451                    if paren_depth == 0 {
15452                        let trimmed = current.trim().to_string();
15453                        if !trimmed.is_empty() {
15454                            // Format this hint for pretty printing if needed
15455                            let formatted = self.format_hint_function(&trimmed);
15456                            results.push(formatted);
15457                        }
15458                        current.clear();
15459                        position_after_last_function = char_position;
15460                    }
15461                }
15462                ' ' | '\t' | '\n' | ',' if paren_depth == 0 => {
15463                    // Space/comma/whitespace outside parentheses - skip
15464                }
15465                _ if paren_depth == 0 => {
15466                    // Character outside parentheses - accumulate for potential hint name
15467                    current.push(c);
15468                }
15469                _ => {
15470                    current.push(c);
15471                }
15472            }
15473        }
15474
15475        // Check if there's remaining text after the last function call
15476        let remaining_text = text[position_after_last_function..].trim();
15477        if !remaining_text.is_empty() {
15478            // Check if it looks like valid hint function names
15479            // Valid hint identifiers typically are uppercase alphanumeric with underscores
15480            // If we see multiple words without parens, it's likely unparseable
15481            let words: Vec<&str> = remaining_text.split_whitespace().collect();
15482            let looks_like_hint_functions = words.iter().all(|word| {
15483                // A valid hint name followed by opening paren, or a standalone uppercase identifier
15484                word.contains('(') || (word.chars().all(|c| c.is_ascii_uppercase() || c == '_'))
15485            });
15486
15487            if !looks_like_hint_functions && words.len() > 1 {
15488                has_unparseable_content = true;
15489            }
15490        }
15491
15492        // If we detected unparseable content (like SQL keywords), return the whole hint as-is
15493        if has_unparseable_content {
15494            return vec![text.trim().to_string()];
15495        }
15496
15497        // If we couldn't parse anything, return the original text as a single hint
15498        if results.is_empty() {
15499            results.push(text.trim().to_string());
15500        }
15501
15502        results
15503    }
15504
15505    /// Format a hint function for pretty printing
15506    /// e.g., "LEADING(aaa bbb ccc ddd)" -> multiline if args are too wide
15507    fn format_hint_function(&self, hint: &str) -> String {
15508        if !self.config.pretty {
15509            return hint.to_string();
15510        }
15511
15512        // Try to parse NAME(args) pattern
15513        if let Some(paren_pos) = hint.find('(') {
15514            if hint.ends_with(')') {
15515                let name = &hint[..paren_pos];
15516                let args_str = &hint[paren_pos + 1..hint.len() - 1];
15517
15518                // Parse arguments (space-separated for Oracle hints)
15519                let args: Vec<&str> = args_str.split_whitespace().collect();
15520
15521                // Calculate total width of arguments
15522                let total_args_width: usize =
15523                    args.iter().map(|s| s.len()).sum::<usize>() + args.len().saturating_sub(1); // spaces between args
15524
15525                // If too wide, format on multiple lines
15526                if total_args_width > self.config.max_text_width && !args.is_empty() {
15527                    let mut result = format!("{}(\n", name);
15528                    for arg in &args {
15529                        result.push_str("    "); // 4-space indent for args
15530                        result.push_str(arg);
15531                        result.push('\n');
15532                    }
15533                    result.push_str("  )"); // 2-space indent for closing paren
15534                    return result;
15535                }
15536            }
15537        }
15538
15539        hint.to_string()
15540    }
15541
15542    /// Convert a hint expression to a string, handling multiline formatting for long arguments
15543    fn hint_expression_to_string(&mut self, expr: &HintExpression) -> Result<String> {
15544        match expr {
15545            HintExpression::Function { name, args } => {
15546                // Generate each argument to a string
15547                let arg_strings: Vec<String> = args
15548                    .iter()
15549                    .map(|arg| {
15550                        let mut gen = Generator::with_arc_config(self.config.clone());
15551                        gen.generate_expression(arg)?;
15552                        Ok(gen.output)
15553                    })
15554                    .collect::<Result<Vec<_>>>()?;
15555
15556                // Oracle hints use space-separated arguments, not comma-separated
15557                let total_args_width: usize = arg_strings.iter().map(|s| s.len()).sum::<usize>()
15558                    + arg_strings.len().saturating_sub(1); // spaces between args
15559
15560                // Check if function args need multiline formatting
15561                // Use too_wide check for argument formatting
15562                let args_multiline =
15563                    self.config.pretty && total_args_width > self.config.max_text_width;
15564
15565                if args_multiline && !arg_strings.is_empty() {
15566                    // Multiline format for long argument lists
15567                    let mut result = format!("{}(\n", name);
15568                    for arg_str in &arg_strings {
15569                        result.push_str("    "); // 4-space indent for args
15570                        result.push_str(arg_str);
15571                        result.push('\n');
15572                    }
15573                    result.push_str("  )"); // 2-space indent for closing paren
15574                    Ok(result)
15575                } else {
15576                    // Single line format with space-separated args (Oracle style)
15577                    let args_str = arg_strings.join(" ");
15578                    Ok(format!("{}({})", name, args_str))
15579                }
15580            }
15581            HintExpression::Identifier(name) => Ok(name.clone()),
15582            HintExpression::Raw(text) => {
15583                // For pretty printing, try to format the raw text
15584                if self.config.pretty {
15585                    Ok(self.format_hint_function(text))
15586                } else {
15587                    Ok(text.clone())
15588                }
15589            }
15590        }
15591    }
15592
15593    fn generate_table(&mut self, table: &TableRef) -> Result<()> {
15594        // PostgreSQL ONLY modifier: prevents scanning child tables
15595        if table.only {
15596            self.write_keyword("ONLY");
15597            self.write_space();
15598        }
15599
15600        // Check for Snowflake IDENTIFIER() function
15601        if let Some(ref identifier_func) = table.identifier_func {
15602            self.generate_expression(identifier_func)?;
15603        } else {
15604            if let Some(catalog) = &table.catalog {
15605                self.generate_identifier(catalog)?;
15606                self.write(".");
15607            }
15608            if let Some(schema) = &table.schema {
15609                self.generate_identifier(schema)?;
15610                self.write(".");
15611            }
15612            self.generate_identifier(&table.name)?;
15613        }
15614
15615        // Output Snowflake CHANGES clause (before partition, includes its own AT/BEFORE/END)
15616        if let Some(changes) = &table.changes {
15617            self.write(" ");
15618            self.generate_changes(changes)?;
15619        }
15620
15621        // Output MySQL PARTITION clause: t1 PARTITION(p0, p1)
15622        if !table.partitions.is_empty() {
15623            self.write_space();
15624            self.write_keyword("PARTITION");
15625            self.write("(");
15626            for (i, partition) in table.partitions.iter().enumerate() {
15627                if i > 0 {
15628                    self.write(", ");
15629                }
15630                self.generate_identifier(partition)?;
15631            }
15632            self.write(")");
15633        }
15634
15635        // Output time travel clause: BEFORE (STATEMENT => ...) or AT (TIMESTAMP => ...)
15636        // Skip if CHANGES clause is present (CHANGES includes its own time travel)
15637        if table.changes.is_none() {
15638            if let Some(when) = &table.when {
15639                self.write_space();
15640                self.generate_historical_data(when)?;
15641            }
15642        }
15643
15644        // Output TSQL FOR SYSTEM_TIME temporal clause (before alias, except BigQuery)
15645        let system_time_post_alias = matches!(self.config.dialect, Some(DialectType::BigQuery));
15646        if !system_time_post_alias {
15647            if let Some(ref system_time) = table.system_time {
15648                self.write_space();
15649                self.write(system_time);
15650            }
15651        }
15652
15653        // Output Presto/Trino time travel: FOR VERSION AS OF / FOR TIMESTAMP AS OF
15654        if let Some(ref version) = table.version {
15655            self.write_space();
15656            self.generate_version(version)?;
15657        }
15658
15659        // When alias_post_tablesample is true, the order is: table TABLESAMPLE (...) alias
15660        // When alias_post_tablesample is false (default), the order is: table alias TABLESAMPLE (...)
15661        // Oracle, Hive, Spark use ALIAS_POST_TABLESAMPLE = true (alias comes after sample)
15662        let alias_post_tablesample = self.config.alias_post_tablesample;
15663
15664        if alias_post_tablesample {
15665            // TABLESAMPLE before alias (Oracle, Hive, Spark)
15666            self.generate_table_sample_clause(table)?;
15667        }
15668
15669        // Output table hints (TSQL: WITH (TABLOCK, INDEX(myindex), ...))
15670        // For SQLite, INDEXED BY hints come after the alias, so skip here
15671        let is_sqlite_hint = matches!(self.config.dialect, Some(DialectType::SQLite))
15672            && table.hints.iter().any(|h| {
15673                if let Expression::Identifier(id) = h {
15674                    id.name.starts_with("INDEXED BY") || id.name == "NOT INDEXED"
15675                } else {
15676                    false
15677                }
15678            });
15679        if !table.hints.is_empty() && !is_sqlite_hint {
15680            for hint in &table.hints {
15681                self.write_space();
15682                self.generate_expression(hint)?;
15683            }
15684        }
15685
15686        if let Some(alias) = &table.alias {
15687            self.write_space();
15688            // Output AS if it was explicitly present in the input, OR for certain dialects/cases
15689            // Generic mode and most dialects always use AS for table aliases
15690            let always_use_as = self.config.dialect.is_none()
15691                || matches!(
15692                    self.config.dialect,
15693                    Some(DialectType::Generic)
15694                        | Some(DialectType::PostgreSQL)
15695                        | Some(DialectType::Redshift)
15696                        | Some(DialectType::Snowflake)
15697                        | Some(DialectType::BigQuery)
15698                        | Some(DialectType::DuckDB)
15699                        | Some(DialectType::Presto)
15700                        | Some(DialectType::Trino)
15701                        | Some(DialectType::TSQL)
15702                        | Some(DialectType::Fabric)
15703                        | Some(DialectType::MySQL)
15704                        | Some(DialectType::Spark)
15705                        | Some(DialectType::Hive)
15706                        | Some(DialectType::SQLite)
15707                        | Some(DialectType::Drill)
15708                );
15709            let is_stage_ref = table.name.name.starts_with('@');
15710            // Oracle never uses AS for table aliases
15711            let suppress_as = matches!(self.config.dialect, Some(DialectType::Oracle));
15712            if !suppress_as && (table.alias_explicit_as || always_use_as || is_stage_ref) {
15713                self.write_keyword("AS");
15714                self.write_space();
15715            }
15716            self.generate_identifier(alias)?;
15717
15718            // Output column aliases if present: AS t(c1, c2)
15719            // Skip for dialects that don't support table alias columns (BigQuery, SQLite)
15720            if !table.column_aliases.is_empty() && self.config.supports_table_alias_columns {
15721                self.write("(");
15722                for (i, col_alias) in table.column_aliases.iter().enumerate() {
15723                    if i > 0 {
15724                        self.write(", ");
15725                    }
15726                    self.generate_identifier(col_alias)?;
15727                }
15728                self.write(")");
15729            }
15730        }
15731
15732        // BigQuery: FOR SYSTEM_TIME AS OF after alias
15733        if system_time_post_alias {
15734            if let Some(ref system_time) = table.system_time {
15735                self.write_space();
15736                self.write(system_time);
15737            }
15738        }
15739
15740        // For default behavior (alias_post_tablesample = false), output TABLESAMPLE after alias
15741        if !alias_post_tablesample {
15742            self.generate_table_sample_clause(table)?;
15743        }
15744
15745        // Output SQLite INDEXED BY / NOT INDEXED hints after alias
15746        if is_sqlite_hint {
15747            for hint in &table.hints {
15748                self.write_space();
15749                self.generate_expression(hint)?;
15750            }
15751        }
15752
15753        // ClickHouse FINAL modifier
15754        if table.final_ && matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
15755            self.write_space();
15756            self.write_keyword("FINAL");
15757        }
15758
15759        // Output trailing comments
15760        for comment in &table.trailing_comments {
15761            self.write_space();
15762            self.write_formatted_comment(comment);
15763        }
15764
15765        Ok(())
15766    }
15767
15768    /// Helper to output TABLESAMPLE clause for a table reference
15769    fn generate_table_sample_clause(&mut self, table: &TableRef) -> Result<()> {
15770        if let Some(ref ts) = table.table_sample {
15771            self.write_space();
15772            if ts.is_using_sample {
15773                self.write_keyword("USING SAMPLE");
15774            } else {
15775                // Use the configured tablesample keyword (e.g., "TABLESAMPLE" or "SAMPLE")
15776                self.write_keyword(self.config.tablesample_keywords);
15777            }
15778            self.generate_sample_body(ts)?;
15779            // Seed for table-level sample - use dialect's configured keyword
15780            if let Some(ref seed) = ts.seed {
15781                self.write_space();
15782                self.write_keyword(self.config.tablesample_seed_keyword);
15783                self.write(" (");
15784                self.generate_expression(seed)?;
15785                self.write(")");
15786            }
15787        }
15788        Ok(())
15789    }
15790
15791    fn generate_stage_reference(&mut self, sr: &StageReference) -> Result<()> {
15792        // Output: '@stage_name/path' if quoted, or @stage_name/path otherwise
15793        // Optionally followed by (FILE_FORMAT => 'fmt', PATTERN => '*.csv')
15794
15795        if sr.quoted {
15796            self.write("'");
15797        }
15798
15799        self.write(&sr.name);
15800        if let Some(path) = &sr.path {
15801            self.write(path);
15802        }
15803
15804        if sr.quoted {
15805            self.write("'");
15806        }
15807
15808        // Output FILE_FORMAT and PATTERN if present
15809        let has_options = sr.file_format.is_some() || sr.pattern.is_some();
15810        if has_options {
15811            self.write(" (");
15812            let mut first = true;
15813
15814            if let Some(file_format) = &sr.file_format {
15815                if !first {
15816                    self.write(", ");
15817                }
15818                self.write_keyword("FILE_FORMAT");
15819                self.write(" => ");
15820                self.generate_expression(file_format)?;
15821                first = false;
15822            }
15823
15824            if let Some(pattern) = &sr.pattern {
15825                if !first {
15826                    self.write(", ");
15827                }
15828                self.write_keyword("PATTERN");
15829                self.write(" => '");
15830                self.write(pattern);
15831                self.write("'");
15832            }
15833
15834            self.write(")");
15835        }
15836        Ok(())
15837    }
15838
15839    fn generate_star(&mut self, star: &Star) -> Result<()> {
15840        use crate::dialects::DialectType;
15841
15842        if let Some(table) = &star.table {
15843            self.generate_identifier(table)?;
15844            self.write(".");
15845        }
15846        self.write("*");
15847
15848        // Generate EXCLUDE/EXCEPT clause based on dialect
15849        if let Some(except) = &star.except {
15850            if !except.is_empty() {
15851                self.write_space();
15852                // Use dialect-appropriate keyword
15853                match self.config.dialect {
15854                    Some(DialectType::BigQuery) => self.write_keyword("EXCEPT"),
15855                    Some(DialectType::DuckDB) | Some(DialectType::Snowflake) => {
15856                        self.write_keyword("EXCLUDE")
15857                    }
15858                    _ => self.write_keyword("EXCEPT"), // Default to EXCEPT
15859                }
15860                self.write(" (");
15861                for (i, col) in except.iter().enumerate() {
15862                    if i > 0 {
15863                        self.write(", ");
15864                    }
15865                    self.generate_identifier(col)?;
15866                }
15867                self.write(")");
15868            }
15869        }
15870
15871        // Generate REPLACE clause
15872        if let Some(replace) = &star.replace {
15873            if !replace.is_empty() {
15874                self.write_space();
15875                self.write_keyword("REPLACE");
15876                self.write(" (");
15877                for (i, alias) in replace.iter().enumerate() {
15878                    if i > 0 {
15879                        self.write(", ");
15880                    }
15881                    self.generate_expression(&alias.this)?;
15882                    self.write_space();
15883                    self.write_keyword("AS");
15884                    self.write_space();
15885                    self.generate_identifier(&alias.alias)?;
15886                }
15887                self.write(")");
15888            }
15889        }
15890
15891        // Generate RENAME clause (Snowflake specific)
15892        if let Some(rename) = &star.rename {
15893            if !rename.is_empty() {
15894                self.write_space();
15895                self.write_keyword("RENAME");
15896                self.write(" (");
15897                for (i, (old_name, new_name)) in rename.iter().enumerate() {
15898                    if i > 0 {
15899                        self.write(", ");
15900                    }
15901                    self.generate_identifier(old_name)?;
15902                    self.write_space();
15903                    self.write_keyword("AS");
15904                    self.write_space();
15905                    self.generate_identifier(new_name)?;
15906                }
15907                self.write(")");
15908            }
15909        }
15910
15911        // Output trailing comments
15912        for comment in &star.trailing_comments {
15913            self.write_space();
15914            self.write_formatted_comment(comment);
15915        }
15916
15917        Ok(())
15918    }
15919
15920    /// Generate Snowflake braced wildcard syntax: {*}, {tbl.*}, {* EXCLUDE (...)}, {* ILIKE '...'}
15921    fn generate_braced_wildcard(&mut self, expr: &Expression) -> Result<()> {
15922        self.write("{");
15923        match expr {
15924            Expression::Star(star) => {
15925                // Generate the star (table.* or just * with optional EXCLUDE)
15926                self.generate_star(star)?;
15927            }
15928            Expression::ILike(ilike) => {
15929                // {* ILIKE 'pattern'} syntax
15930                self.generate_expression(&ilike.left)?;
15931                self.write_space();
15932                self.write_keyword("ILIKE");
15933                self.write_space();
15934                self.generate_expression(&ilike.right)?;
15935            }
15936            _ => {
15937                self.generate_expression(expr)?;
15938            }
15939        }
15940        self.write("}");
15941        Ok(())
15942    }
15943
15944    fn generate_alias(&mut self, alias: &Alias) -> Result<()> {
15945        // Generate inner expression, but skip trailing comments if they're in pre_alias_comments
15946        // to avoid duplication (comments are captured as both Column.trailing_comments
15947        // and Alias.pre_alias_comments during parsing)
15948        match &alias.this {
15949            Expression::Column(col) => {
15950                // Generate column without trailing comments - they're in pre_alias_comments
15951                if let Some(table) = &col.table {
15952                    self.generate_identifier(table)?;
15953                    self.write(".");
15954                }
15955                self.generate_identifier(&col.name)?;
15956            }
15957            _ => {
15958                self.generate_expression(&alias.this)?;
15959            }
15960        }
15961
15962        // Handle pre-alias comments: when there are no trailing_comments, sqlglot
15963        // moves pre-alias comments to after the alias. When there are also trailing_comments,
15964        // keep pre-alias comments in their original position (between expression and AS).
15965        if !alias.pre_alias_comments.is_empty() && !alias.trailing_comments.is_empty() {
15966            for comment in &alias.pre_alias_comments {
15967                self.write_space();
15968                self.write_formatted_comment(comment);
15969            }
15970        }
15971
15972        use crate::dialects::DialectType;
15973
15974        // Determine if we should skip AS keyword for table-valued function aliases
15975        // Oracle and some other dialects don't use AS for table aliases
15976        // Note: We specifically use TableFromRows here, NOT Function, because Function
15977        // matches regular functions like MATCH_NUMBER() which should include the AS keyword.
15978        // TableFromRows represents TABLE(expr) constructs which are actual table-valued functions.
15979        let is_table_source = matches!(
15980            &alias.this,
15981            Expression::JSONTable(_)
15982                | Expression::XMLTable(_)
15983                | Expression::TableFromRows(_)
15984                | Expression::Unnest(_)
15985                | Expression::MatchRecognize(_)
15986                | Expression::Select(_)
15987                | Expression::Subquery(_)
15988                | Expression::Paren(_)
15989        );
15990        let dialect_skips_table_alias_as = matches!(self.config.dialect, Some(DialectType::Oracle));
15991        let skip_as = is_table_source && dialect_skips_table_alias_as;
15992
15993        self.write_space();
15994        if !skip_as {
15995            self.write_keyword("AS");
15996            self.write_space();
15997        }
15998
15999        // BigQuery doesn't support column aliases in table aliases: AS t(c1, c2)
16000        let skip_column_aliases = matches!(self.config.dialect, Some(DialectType::BigQuery));
16001
16002        // Check if we have column aliases only (no table alias name)
16003        if alias.alias.is_empty() && !alias.column_aliases.is_empty() && !skip_column_aliases {
16004            // Generate AS (col1, col2, ...)
16005            self.write("(");
16006            for (i, col_alias) in alias.column_aliases.iter().enumerate() {
16007                if i > 0 {
16008                    self.write(", ");
16009                }
16010                self.generate_alias_identifier(col_alias)?;
16011            }
16012            self.write(")");
16013        } else if !alias.column_aliases.is_empty() && !skip_column_aliases {
16014            // Generate AS alias(col1, col2, ...)
16015            self.generate_alias_identifier(&alias.alias)?;
16016            self.write("(");
16017            for (i, col_alias) in alias.column_aliases.iter().enumerate() {
16018                if i > 0 {
16019                    self.write(", ");
16020                }
16021                self.generate_alias_identifier(col_alias)?;
16022            }
16023            self.write(")");
16024        } else {
16025            // Simple alias (or BigQuery without column aliases)
16026            self.generate_alias_identifier(&alias.alias)?;
16027        }
16028
16029        // Output trailing comments (comments after the alias)
16030        for comment in &alias.trailing_comments {
16031            self.write_space();
16032            self.write_formatted_comment(comment);
16033        }
16034
16035        // Output pre-alias comments: when there are no trailing_comments, sqlglot
16036        // moves pre-alias comments to after the alias. When there are trailing_comments,
16037        // the pre-alias comments were already lost (consumed as column trailing comments
16038        // that were then used as pre_alias_comments). We always emit them after alias.
16039        if alias.trailing_comments.is_empty() {
16040            for comment in &alias.pre_alias_comments {
16041                self.write_space();
16042                self.write_formatted_comment(comment);
16043            }
16044        }
16045
16046        Ok(())
16047    }
16048
16049    fn generate_cast(&mut self, cast: &Cast) -> Result<()> {
16050        use crate::dialects::DialectType;
16051
16052        // SingleStore uses :> syntax
16053        if matches!(self.config.dialect, Some(DialectType::SingleStore)) {
16054            self.generate_expression(&cast.this)?;
16055            self.write(" :> ");
16056            self.generate_data_type(&cast.to)?;
16057            return Ok(());
16058        }
16059
16060        // Teradata: CAST(x AS FORMAT 'fmt') (no data type)
16061        if matches!(self.config.dialect, Some(DialectType::Teradata)) {
16062            let is_unknown_type = matches!(cast.to, DataType::Unknown)
16063                || matches!(cast.to, DataType::Custom { ref name } if name.is_empty());
16064            if is_unknown_type {
16065                if let Some(format) = &cast.format {
16066                    self.write_keyword("CAST");
16067                    self.write("(");
16068                    self.generate_expression(&cast.this)?;
16069                    self.write_space();
16070                    self.write_keyword("AS");
16071                    self.write_space();
16072                    self.write_keyword("FORMAT");
16073                    self.write_space();
16074                    self.generate_expression(format)?;
16075                    self.write(")");
16076                    return Ok(());
16077                }
16078            }
16079        }
16080
16081        // Oracle: CAST(x AS DATE/TIMESTAMP ..., 'format') -> TO_DATE/TO_TIMESTAMP(x, 'format')
16082        // This follows Python sqlglot's behavior of transforming CAST with format to native functions
16083        if matches!(self.config.dialect, Some(DialectType::Oracle)) {
16084            if let Some(format) = &cast.format {
16085                // Check if target type is DATE or TIMESTAMP
16086                let is_date = matches!(cast.to, DataType::Date);
16087                let is_timestamp = matches!(cast.to, DataType::Timestamp { .. });
16088
16089                if is_date || is_timestamp {
16090                    let func_name = if is_date { "TO_DATE" } else { "TO_TIMESTAMP" };
16091                    self.write_keyword(func_name);
16092                    self.write("(");
16093                    self.generate_expression(&cast.this)?;
16094                    self.write(", ");
16095
16096                    // Normalize format string for Oracle (HH -> HH12)
16097                    // Oracle HH is 12-hour format, same as HH12. For clarity, Python sqlglot uses HH12.
16098                    if let Expression::Literal(lit) = format.as_ref() {
16099                        if let Literal::String(fmt_str) = lit.as_ref() {
16100                        let normalized = self.normalize_oracle_format(fmt_str);
16101                        self.write("'");
16102                        self.write(&normalized);
16103                        self.write("'");
16104                    }
16105                    } else {
16106                        self.generate_expression(format)?;
16107                    }
16108
16109                    self.write(")");
16110                    return Ok(());
16111                }
16112            }
16113        }
16114
16115        // BigQuery: CAST(ARRAY[...] AS ARRAY<T>) -> ARRAY<T>[...]
16116        // This preserves sqlglot's typed inline array literal output.
16117        if matches!(self.config.dialect, Some(DialectType::BigQuery)) {
16118            if let Expression::Array(arr) = &cast.this {
16119                self.generate_data_type(&cast.to)?;
16120                // Output just the bracket content [values] without the ARRAY prefix
16121                self.write("[");
16122                for (i, expr) in arr.expressions.iter().enumerate() {
16123                    if i > 0 {
16124                        self.write(", ");
16125                    }
16126                    self.generate_expression(expr)?;
16127                }
16128                self.write("]");
16129                return Ok(());
16130            }
16131            if matches!(&cast.this, Expression::ArrayFunc(_)) {
16132                self.generate_data_type(&cast.to)?;
16133                self.generate_expression(&cast.this)?;
16134                return Ok(());
16135            }
16136        }
16137
16138        // DuckDB/Presto/Trino: When CAST(Struct([unnamed]) AS STRUCT(...)),
16139        // convert the inner Struct to ROW(values...) format
16140        if matches!(
16141            self.config.dialect,
16142            Some(DialectType::DuckDB) | Some(DialectType::Presto) | Some(DialectType::Trino)
16143        ) {
16144            if let Expression::Struct(ref s) = cast.this {
16145                let all_unnamed = s.fields.iter().all(|(name, _)| name.is_none());
16146                if all_unnamed && matches!(cast.to, DataType::Struct { .. }) {
16147                    self.write_keyword("CAST");
16148                    self.write("(");
16149                    self.generate_struct_as_row(s)?;
16150                    self.write_space();
16151                    self.write_keyword("AS");
16152                    self.write_space();
16153                    self.generate_data_type(&cast.to)?;
16154                    self.write(")");
16155                    return Ok(());
16156                }
16157            }
16158        }
16159
16160        // Determine if we should use :: syntax based on dialect
16161        // PostgreSQL prefers :: for identity, most others prefer CAST()
16162        let use_double_colon = cast.double_colon_syntax && self.dialect_prefers_double_colon();
16163
16164        if use_double_colon {
16165            // PostgreSQL :: syntax: expr::type
16166            self.generate_expression(&cast.this)?;
16167            self.write("::");
16168            self.generate_data_type(&cast.to)?;
16169        } else {
16170            // Standard CAST() syntax
16171            self.write_keyword("CAST");
16172            self.write("(");
16173            self.generate_expression(&cast.this)?;
16174            self.write_space();
16175            self.write_keyword("AS");
16176            self.write_space();
16177            // For MySQL/SingleStore/TiDB, map text/blob variant types to CHAR in CAST
16178            // This matches Python sqlglot's CAST_MAPPING behavior
16179            if matches!(
16180                self.config.dialect,
16181                Some(DialectType::MySQL) | Some(DialectType::SingleStore) | Some(DialectType::TiDB)
16182            ) {
16183                match &cast.to {
16184                    DataType::Custom { ref name } => {
16185                        if name.eq_ignore_ascii_case("LONGTEXT")
16186                            || name.eq_ignore_ascii_case("MEDIUMTEXT")
16187                            || name.eq_ignore_ascii_case("TINYTEXT")
16188                            || name.eq_ignore_ascii_case("LONGBLOB")
16189                            || name.eq_ignore_ascii_case("MEDIUMBLOB")
16190                            || name.eq_ignore_ascii_case("TINYBLOB")
16191                        {
16192                            self.write_keyword("CHAR");
16193                        } else {
16194                            self.generate_data_type(&cast.to)?;
16195                        }
16196                    }
16197                    DataType::VarChar { length, .. } => {
16198                        // MySQL CAST: VARCHAR -> CHAR
16199                        self.write_keyword("CHAR");
16200                        if let Some(n) = length {
16201                            self.write(&format!("({})", n));
16202                        }
16203                    }
16204                    DataType::Text => {
16205                        // MySQL CAST: TEXT -> CHAR
16206                        self.write_keyword("CHAR");
16207                    }
16208                    DataType::Timestamp {
16209                        precision,
16210                        timezone: false,
16211                    } => {
16212                        // MySQL CAST: TIMESTAMP -> DATETIME
16213                        self.write_keyword("DATETIME");
16214                        if let Some(p) = precision {
16215                            self.write(&format!("({})", p));
16216                        }
16217                    }
16218                    _ => {
16219                        self.generate_data_type(&cast.to)?;
16220                    }
16221                }
16222            } else if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
16223                // Snowflake CAST: STRING -> VARCHAR
16224                match &cast.to {
16225                    DataType::String { length } => {
16226                        self.write_keyword("VARCHAR");
16227                        if let Some(n) = length {
16228                            self.write(&format!("({})", n));
16229                        }
16230                    }
16231                    _ => {
16232                        self.generate_data_type(&cast.to)?;
16233                    }
16234                }
16235            } else {
16236                self.generate_data_type(&cast.to)?;
16237            }
16238
16239            // Output DEFAULT ... ON CONVERSION ERROR clause if present (Oracle)
16240            if let Some(default) = &cast.default {
16241                self.write_space();
16242                self.write_keyword("DEFAULT");
16243                self.write_space();
16244                self.generate_expression(default)?;
16245                self.write_space();
16246                self.write_keyword("ON");
16247                self.write_space();
16248                self.write_keyword("CONVERSION");
16249                self.write_space();
16250                self.write_keyword("ERROR");
16251            }
16252
16253            // Output FORMAT clause if present (BigQuery: CAST(x AS STRING FORMAT 'format'))
16254            // For Oracle with comma-separated format: CAST(x AS DATE DEFAULT NULL ON CONVERSION ERROR, 'format')
16255            if let Some(format) = &cast.format {
16256                // Check if Oracle dialect - use comma syntax
16257                if matches!(
16258                    self.config.dialect,
16259                    Some(crate::dialects::DialectType::Oracle)
16260                ) {
16261                    self.write(", ");
16262                } else {
16263                    self.write_space();
16264                    self.write_keyword("FORMAT");
16265                    self.write_space();
16266                }
16267                self.generate_expression(format)?;
16268            }
16269
16270            self.write(")");
16271            // Output trailing comments
16272            for comment in &cast.trailing_comments {
16273                self.write_space();
16274                self.write_formatted_comment(comment);
16275            }
16276        }
16277        Ok(())
16278    }
16279
16280    /// Generate a Struct as ROW(values...) format, recursively converting inner Struct to ROW too.
16281    /// Used for DuckDB/Presto/Trino CAST(Struct AS STRUCT(...)) context.
16282    fn generate_struct_as_row(&mut self, s: &crate::expressions::Struct) -> Result<()> {
16283        self.write_keyword("ROW");
16284        self.write("(");
16285        for (i, (_, expr)) in s.fields.iter().enumerate() {
16286            if i > 0 {
16287                self.write(", ");
16288            }
16289            // Recursively convert inner Struct to ROW format
16290            if let Expression::Struct(ref inner_s) = expr {
16291                self.generate_struct_as_row(inner_s)?;
16292            } else {
16293                self.generate_expression(expr)?;
16294            }
16295        }
16296        self.write(")");
16297        Ok(())
16298    }
16299
16300    /// Normalize Oracle date/time format strings
16301    /// HH -> HH12 (both are 12-hour format, but Python sqlglot prefers explicit HH12)
16302    fn normalize_oracle_format(&self, format: &str) -> String {
16303        // Replace standalone HH with HH12 (but not HH12 or HH24)
16304        // We need to be careful not to replace HH12 -> HH1212 or HH24 -> HH1224
16305        let mut result = String::new();
16306        let chars: Vec<char> = format.chars().collect();
16307        let mut i = 0;
16308
16309        while i < chars.len() {
16310            if i + 1 < chars.len() && chars[i] == 'H' && chars[i + 1] == 'H' {
16311                // Check what follows HH
16312                if i + 2 < chars.len() {
16313                    let next = chars[i + 2];
16314                    if next == '1' || next == '2' {
16315                        // This is HH12 or HH24, keep as is
16316                        result.push('H');
16317                        result.push('H');
16318                        i += 2;
16319                        continue;
16320                    }
16321                }
16322                // Standalone HH -> HH12
16323                result.push_str("HH12");
16324                i += 2;
16325            } else {
16326                result.push(chars[i]);
16327                i += 1;
16328            }
16329        }
16330
16331        result
16332    }
16333
16334    /// Check if the current dialect prefers :: cast syntax
16335    /// Note: Python sqlglot normalizes all :: to CAST() for output, even for PostgreSQL
16336    /// So we return false for all dialects to match Python sqlglot's behavior
16337    fn dialect_prefers_double_colon(&self) -> bool {
16338        // Python sqlglot normalizes :: syntax to CAST() for all dialects
16339        // Even PostgreSQL outputs CAST() not ::
16340        false
16341    }
16342
16343    /// Generate MOD function - uses % operator for Snowflake/MySQL/Presto/Trino, MOD() for others
16344    fn generate_mod_func(&mut self, f: &crate::expressions::BinaryFunc) -> Result<()> {
16345        use crate::dialects::DialectType;
16346
16347        // Snowflake, MySQL, Presto, Trino, PostgreSQL, and DuckDB prefer x % y instead of MOD(x, y)
16348        let use_percent_operator = matches!(
16349            self.config.dialect,
16350            Some(DialectType::Snowflake)
16351                | Some(DialectType::MySQL)
16352                | Some(DialectType::Presto)
16353                | Some(DialectType::Trino)
16354                | Some(DialectType::PostgreSQL)
16355                | Some(DialectType::DuckDB)
16356                | Some(DialectType::Hive)
16357                | Some(DialectType::Spark)
16358                | Some(DialectType::Databricks)
16359                | Some(DialectType::Athena)
16360        );
16361
16362        if use_percent_operator {
16363            // Wrap complex expressions in parens to preserve precedence
16364            // Since % has higher precedence than +/-, we need parens for Add/Sub on either side
16365            let needs_paren = |e: &Expression| matches!(e, Expression::Add(_) | Expression::Sub(_));
16366            if needs_paren(&f.this) {
16367                self.write("(");
16368                self.generate_expression(&f.this)?;
16369                self.write(")");
16370            } else {
16371                self.generate_expression(&f.this)?;
16372            }
16373            self.write(" % ");
16374            if needs_paren(&f.expression) {
16375                self.write("(");
16376                self.generate_expression(&f.expression)?;
16377                self.write(")");
16378            } else {
16379                self.generate_expression(&f.expression)?;
16380            }
16381            Ok(())
16382        } else {
16383            self.generate_binary_func("MOD", &f.this, &f.expression)
16384        }
16385    }
16386
16387    /// Generate IFNULL - uses COALESCE for Snowflake, IFNULL for others
16388    fn generate_ifnull(&mut self, f: &crate::expressions::BinaryFunc) -> Result<()> {
16389        use crate::dialects::DialectType;
16390
16391        // Snowflake normalizes IFNULL to COALESCE
16392        let func_name = match self.config.dialect {
16393            Some(DialectType::Snowflake) => "COALESCE",
16394            _ => "IFNULL",
16395        };
16396
16397        self.generate_binary_func(func_name, &f.this, &f.expression)
16398    }
16399
16400    /// Generate NVL - preserves original name if available, otherwise uses dialect-specific output
16401    fn generate_nvl(&mut self, f: &crate::expressions::BinaryFunc) -> Result<()> {
16402        // Use original function name if preserved (for identity tests)
16403        if let Some(ref original_name) = f.original_name {
16404            return self.generate_binary_func(original_name, &f.this, &f.expression);
16405        }
16406
16407        // Otherwise, use dialect-specific function names
16408        use crate::dialects::DialectType;
16409        let func_name = match self.config.dialect {
16410            Some(DialectType::Snowflake)
16411            | Some(DialectType::ClickHouse)
16412            | Some(DialectType::PostgreSQL)
16413            | Some(DialectType::Presto)
16414            | Some(DialectType::Trino)
16415            | Some(DialectType::Athena)
16416            | Some(DialectType::DuckDB)
16417            | Some(DialectType::BigQuery)
16418            | Some(DialectType::Spark)
16419            | Some(DialectType::Databricks)
16420            | Some(DialectType::Hive) => "COALESCE",
16421            Some(DialectType::MySQL)
16422            | Some(DialectType::Doris)
16423            | Some(DialectType::StarRocks)
16424            | Some(DialectType::SingleStore)
16425            | Some(DialectType::TiDB) => "IFNULL",
16426            _ => "NVL",
16427        };
16428
16429        self.generate_binary_func(func_name, &f.this, &f.expression)
16430    }
16431
16432    /// Generate STDDEV_SAMP - uses STDDEV for Snowflake, STDDEV_SAMP for others
16433    fn generate_stddev_samp(&mut self, f: &crate::expressions::AggFunc) -> Result<()> {
16434        use crate::dialects::DialectType;
16435
16436        // Snowflake normalizes STDDEV_SAMP to STDDEV
16437        let func_name = match self.config.dialect {
16438            Some(DialectType::Snowflake) => "STDDEV",
16439            _ => "STDDEV_SAMP",
16440        };
16441
16442        self.generate_agg_func(func_name, f)
16443    }
16444
16445    fn generate_collation(&mut self, coll: &CollationExpr) -> Result<()> {
16446        self.generate_expression(&coll.this)?;
16447        self.write_space();
16448        self.write_keyword("COLLATE");
16449        self.write_space();
16450        if coll.quoted {
16451            // Single-quoted string: COLLATE 'de_DE'
16452            self.write("'");
16453            self.write(&coll.collation);
16454            self.write("'");
16455        } else if coll.double_quoted {
16456            // Double-quoted identifier: COLLATE "de_DE"
16457            self.write("\"");
16458            self.write(&coll.collation);
16459            self.write("\"");
16460        } else {
16461            // Unquoted identifier: COLLATE de_DE
16462            self.write(&coll.collation);
16463        }
16464        Ok(())
16465    }
16466
16467    fn generate_case(&mut self, case: &Case) -> Result<()> {
16468        // In pretty mode, decide whether to expand based on total text width
16469        let multiline_case = if self.config.pretty {
16470            // Build the flat representation to check width
16471            let mut statements: Vec<String> = Vec::new();
16472            let operand_str = if let Some(operand) = &case.operand {
16473                let s = self.generate_to_string(operand)?;
16474                statements.push(format!("CASE {}", s));
16475                s
16476            } else {
16477                statements.push("CASE".to_string());
16478                String::new()
16479            };
16480            let _ = operand_str;
16481            for (condition, result) in &case.whens {
16482                statements.push(format!("WHEN {}", self.generate_to_string(condition)?));
16483                statements.push(format!("THEN {}", self.generate_to_string(result)?));
16484            }
16485            if let Some(else_) = &case.else_ {
16486                statements.push(format!("ELSE {}", self.generate_to_string(else_)?));
16487            }
16488            statements.push("END".to_string());
16489            self.too_wide(&statements)
16490        } else {
16491            false
16492        };
16493
16494        self.write_keyword("CASE");
16495        if let Some(operand) = &case.operand {
16496            self.write_space();
16497            self.generate_expression(operand)?;
16498        }
16499        if multiline_case {
16500            self.indent_level += 1;
16501        }
16502        for (condition, result) in &case.whens {
16503            if multiline_case {
16504                self.write_newline();
16505                self.write_indent();
16506            } else {
16507                self.write_space();
16508            }
16509            self.write_keyword("WHEN");
16510            self.write_space();
16511            self.generate_expression(condition)?;
16512            if multiline_case {
16513                self.write_newline();
16514                self.write_indent();
16515            } else {
16516                self.write_space();
16517            }
16518            self.write_keyword("THEN");
16519            self.write_space();
16520            self.generate_expression(result)?;
16521        }
16522        if let Some(else_) = &case.else_ {
16523            if multiline_case {
16524                self.write_newline();
16525                self.write_indent();
16526            } else {
16527                self.write_space();
16528            }
16529            self.write_keyword("ELSE");
16530            self.write_space();
16531            self.generate_expression(else_)?;
16532        }
16533        if multiline_case {
16534            self.indent_level -= 1;
16535            self.write_newline();
16536            self.write_indent();
16537        } else {
16538            self.write_space();
16539        }
16540        self.write_keyword("END");
16541        // Emit any comments that were attached to the CASE keyword
16542        for comment in &case.comments {
16543            self.write(" ");
16544            self.write_formatted_comment(comment);
16545        }
16546        Ok(())
16547    }
16548
16549    fn generate_function(&mut self, func: &Function) -> Result<()> {
16550        // Normalize function name based on dialect settings
16551        let normalized_name = self.normalize_func_name(&func.name);
16552
16553        // DuckDB: ARRAY_CONSTRUCT_COMPACT(a, b, c) -> LIST_FILTER([a, b, c], _u -> NOT _u IS NULL)
16554        if matches!(self.config.dialect, Some(DialectType::DuckDB))
16555            && func.name.eq_ignore_ascii_case("ARRAY_CONSTRUCT_COMPACT")
16556        {
16557            self.write("LIST_FILTER(");
16558            self.write("[");
16559            for (i, arg) in func.args.iter().enumerate() {
16560                if i > 0 {
16561                    self.write(", ");
16562                }
16563                self.generate_expression(arg)?;
16564            }
16565            self.write("], _u -> NOT _u IS NULL)");
16566            return Ok(());
16567        }
16568
16569        // STRUCT function: BigQuery STRUCT('Alice' AS name, 85 AS score) -> dialect-specific
16570        if func.name.eq_ignore_ascii_case("STRUCT")
16571            && !matches!(
16572                self.config.dialect,
16573                Some(DialectType::BigQuery)
16574                    | Some(DialectType::Spark)
16575                    | Some(DialectType::Databricks)
16576                    | Some(DialectType::Hive)
16577                    | None
16578            )
16579        {
16580            return self.generate_struct_function_cross_dialect(func);
16581        }
16582
16583        // SingleStore: __SS_JSON_PATH_QMARK__(expr, key) -> expr::?key
16584        // This is an internal marker function for ::? JSON path syntax
16585        if func.name.eq_ignore_ascii_case("__SS_JSON_PATH_QMARK__") && func.args.len() == 2 {
16586            self.generate_expression(&func.args[0])?;
16587            self.write("::?");
16588            // Extract the key from the string literal
16589            if let Expression::Literal(lit) = &func.args[1] {
16590                if let crate::expressions::Literal::String(key) = lit.as_ref() {
16591                self.write(key);
16592            }
16593            } else {
16594                self.generate_expression(&func.args[1])?;
16595            }
16596            return Ok(());
16597        }
16598
16599        // PostgreSQL: __PG_BITWISE_XOR__(a, b) -> a # b
16600        if func.name.eq_ignore_ascii_case("__PG_BITWISE_XOR__") && func.args.len() == 2 {
16601            self.generate_expression(&func.args[0])?;
16602            self.write(" # ");
16603            self.generate_expression(&func.args[1])?;
16604            return Ok(());
16605        }
16606
16607        // Spark/Hive family: unwrap TRY(expr) since these dialects don't emit TRY as a scalar wrapper.
16608        if matches!(
16609            self.config.dialect,
16610            Some(DialectType::Spark | DialectType::Databricks | DialectType::Hive)
16611        ) && func.name.eq_ignore_ascii_case("TRY")
16612            && func.args.len() == 1
16613        {
16614            self.generate_expression(&func.args[0])?;
16615            return Ok(());
16616        }
16617
16618        // ClickHouse normalization: toStartOfDay(x) -> dateTrunc('DAY', x)
16619        if self.config.dialect == Some(DialectType::ClickHouse)
16620            && func.name.eq_ignore_ascii_case("TOSTARTOFDAY")
16621            && func.args.len() == 1
16622        {
16623            self.write("dateTrunc('DAY', ");
16624            self.generate_expression(&func.args[0])?;
16625            self.write(")");
16626            return Ok(());
16627        }
16628
16629        // Redshift: CONCAT(a, b, ...) -> a || b || ...
16630        if self.config.dialect == Some(DialectType::Redshift)
16631            && func.name.eq_ignore_ascii_case("CONCAT")
16632            && func.args.len() >= 2
16633        {
16634            for (i, arg) in func.args.iter().enumerate() {
16635                if i > 0 {
16636                    self.write(" || ");
16637                }
16638                self.generate_expression(arg)?;
16639            }
16640            return Ok(());
16641        }
16642
16643        // Redshift: CONCAT_WS(delim, a, b, c) -> a || delim || b || delim || c
16644        if self.config.dialect == Some(DialectType::Redshift)
16645            && func.name.eq_ignore_ascii_case("CONCAT_WS")
16646            && func.args.len() >= 2
16647        {
16648            let sep = &func.args[0];
16649            for (i, arg) in func.args.iter().skip(1).enumerate() {
16650                if i > 0 {
16651                    self.write(" || ");
16652                    self.generate_expression(sep)?;
16653                    self.write(" || ");
16654                }
16655                self.generate_expression(arg)?;
16656            }
16657            return Ok(());
16658        }
16659
16660        // Redshift: DATEDIFF/DATE_DIFF(unit, start, end) -> DATEDIFF(UNIT, start, end)
16661        // Unit should be unquoted uppercase identifier
16662        if self.config.dialect == Some(DialectType::Redshift)
16663            && (func.name.eq_ignore_ascii_case("DATEDIFF") || func.name.eq_ignore_ascii_case("DATE_DIFF"))
16664            && func.args.len() == 3
16665        {
16666            self.write_keyword("DATEDIFF");
16667            self.write("(");
16668            // First arg is unit - normalize to unquoted uppercase
16669            self.write_redshift_date_part(&func.args[0]);
16670            self.write(", ");
16671            self.generate_expression(&func.args[1])?;
16672            self.write(", ");
16673            self.generate_expression(&func.args[2])?;
16674            self.write(")");
16675            return Ok(());
16676        }
16677
16678        // Redshift: DATEADD/DATE_ADD(unit, interval, date) -> DATEADD(UNIT, interval, date)
16679        // Unit should be unquoted uppercase identifier
16680        if self.config.dialect == Some(DialectType::Redshift)
16681            && (func.name.eq_ignore_ascii_case("DATEADD") || func.name.eq_ignore_ascii_case("DATE_ADD"))
16682            && func.args.len() == 3
16683        {
16684            self.write_keyword("DATEADD");
16685            self.write("(");
16686            // First arg is unit - normalize to unquoted uppercase
16687            self.write_redshift_date_part(&func.args[0]);
16688            self.write(", ");
16689            self.generate_expression(&func.args[1])?;
16690            self.write(", ");
16691            self.generate_expression(&func.args[2])?;
16692            self.write(")");
16693            return Ok(());
16694        }
16695
16696        // UUID_STRING(args) from Snowflake -> dialect-specific UUID function (dropping args)
16697        if func.name.eq_ignore_ascii_case("UUID_STRING")
16698            && !matches!(self.config.dialect, Some(DialectType::Snowflake) | None)
16699        {
16700            let func_name = match self.config.dialect {
16701                Some(DialectType::PostgreSQL) | Some(DialectType::Redshift) => "GEN_RANDOM_UUID",
16702                Some(DialectType::BigQuery) => "GENERATE_UUID",
16703                _ => "UUID",
16704            };
16705            self.write_keyword(func_name);
16706            self.write("()");
16707            return Ok(());
16708        }
16709
16710        // Snowflake: GENERATOR(val) -> GENERATOR(ROWCOUNT => val)
16711        // GENERATOR(val1, val2) -> GENERATOR(ROWCOUNT => val1, TIMELIMIT => val2)
16712        // Positional args are mapped to named parameters.
16713        if matches!(self.config.dialect, Some(DialectType::Snowflake))
16714            && func.name.eq_ignore_ascii_case("GENERATOR")
16715        {
16716            let has_positional_args = !func.args.is_empty()
16717                && !matches!(&func.args[0], Expression::NamedArgument(_));
16718            if has_positional_args {
16719                let param_names = ["ROWCOUNT", "TIMELIMIT"];
16720                self.write_keyword("GENERATOR");
16721                self.write("(");
16722                for (i, arg) in func.args.iter().enumerate() {
16723                    if i > 0 {
16724                        self.write(", ");
16725                    }
16726                    if i < param_names.len() {
16727                        self.write_keyword(param_names[i]);
16728                        self.write(" => ");
16729                        self.generate_expression(arg)?;
16730                    } else {
16731                        self.generate_expression(arg)?;
16732                    }
16733                }
16734                self.write(")");
16735                return Ok(());
16736            }
16737        }
16738
16739        // Redshift: DATE_TRUNC('unit', date) -> DATE_TRUNC('UNIT', date)
16740        // Unit should be quoted uppercase string
16741        if self.config.dialect == Some(DialectType::Redshift)
16742            && func.name.eq_ignore_ascii_case("DATE_TRUNC")
16743            && func.args.len() == 2
16744        {
16745            self.write_keyword("DATE_TRUNC");
16746            self.write("(");
16747            // First arg is unit - normalize to quoted uppercase
16748            self.write_redshift_date_part_quoted(&func.args[0]);
16749            self.write(", ");
16750            self.generate_expression(&func.args[1])?;
16751            self.write(")");
16752            return Ok(());
16753        }
16754
16755        // TSQL/Fabric: DATE_PART -> DATEPART (no underscore)
16756        if matches!(
16757            self.config.dialect,
16758            Some(DialectType::TSQL) | Some(DialectType::Fabric)
16759        ) && (func.name.eq_ignore_ascii_case("DATE_PART") || func.name.eq_ignore_ascii_case("DATEPART"))
16760            && func.args.len() == 2
16761        {
16762            self.write_keyword("DATEPART");
16763            self.write("(");
16764            self.generate_expression(&func.args[0])?;
16765            self.write(", ");
16766            self.generate_expression(&func.args[1])?;
16767            self.write(")");
16768            return Ok(());
16769        }
16770
16771        // PostgreSQL/Redshift: DATE_PART(part, value) -> EXTRACT(part FROM value)
16772        if matches!(
16773            self.config.dialect,
16774            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
16775        ) && (func.name.eq_ignore_ascii_case("DATE_PART") || func.name.eq_ignore_ascii_case("DATEPART"))
16776            && func.args.len() == 2
16777        {
16778            self.write_keyword("EXTRACT");
16779            self.write("(");
16780            // Extract the datetime field - if it's a string literal, strip quotes to make it a keyword
16781            match &func.args[0] {
16782                Expression::Literal(lit) if matches!(lit.as_ref(), crate::expressions::Literal::String(_)) => {
16783                    let crate::expressions::Literal::String(s) = lit.as_ref() else { unreachable!() };
16784                    self.write(&s.to_ascii_lowercase());
16785                }
16786                _ => self.generate_expression(&func.args[0])?,
16787            }
16788            self.write_space();
16789            self.write_keyword("FROM");
16790            self.write_space();
16791            self.generate_expression(&func.args[1])?;
16792            self.write(")");
16793            return Ok(());
16794        }
16795
16796        // Dremio: DATE_PART(part, value) -> EXTRACT(part FROM value)
16797        // Also DATE literals in Dremio should be CAST(...AS DATE)
16798        if self.config.dialect == Some(DialectType::Dremio)
16799            && (func.name.eq_ignore_ascii_case("DATE_PART") || func.name.eq_ignore_ascii_case("DATEPART"))
16800            && func.args.len() == 2
16801        {
16802            self.write_keyword("EXTRACT");
16803            self.write("(");
16804            self.generate_expression(&func.args[0])?;
16805            self.write_space();
16806            self.write_keyword("FROM");
16807            self.write_space();
16808            // For Dremio, DATE literals should become CAST('value' AS DATE)
16809            self.generate_dremio_date_expression(&func.args[1])?;
16810            self.write(")");
16811            return Ok(());
16812        }
16813
16814        // Dremio: CURRENT_DATE_UTC() -> CURRENT_DATE_UTC (no parentheses)
16815        if self.config.dialect == Some(DialectType::Dremio)
16816            && func.name.eq_ignore_ascii_case("CURRENT_DATE_UTC")
16817            && func.args.is_empty()
16818        {
16819            self.write_keyword("CURRENT_DATE_UTC");
16820            return Ok(());
16821        }
16822
16823        // Dremio: DATETYPE(year, month, day) transformation
16824        // - If all args are integer literals: DATE('YYYY-MM-DD')
16825        // - If args are expressions: CAST(CONCAT(x, '-', y, '-', z) AS DATE)
16826        if self.config.dialect == Some(DialectType::Dremio)
16827            && func.name.eq_ignore_ascii_case("DATETYPE")
16828            && func.args.len() == 3
16829        {
16830            // Helper function to extract integer from number literal
16831            fn get_int_literal(expr: &Expression) -> Option<i64> {
16832                if let Expression::Literal(lit) = expr {
16833                    if let crate::expressions::Literal::Number(s) = lit.as_ref() {
16834                    s.parse::<i64>().ok()
16835                } else { None }
16836                } else {
16837                    None
16838                }
16839            }
16840
16841            // Check if all arguments are integer literals
16842            if let (Some(year), Some(month), Some(day)) = (
16843                get_int_literal(&func.args[0]),
16844                get_int_literal(&func.args[1]),
16845                get_int_literal(&func.args[2]),
16846            ) {
16847                // All are integer literals: DATE('YYYY-MM-DD')
16848                self.write_keyword("DATE");
16849                self.write(&format!("('{:04}-{:02}-{:02}')", year, month, day));
16850                return Ok(());
16851            }
16852
16853            // For expressions: CAST(CONCAT(x, '-', y, '-', z) AS DATE)
16854            self.write_keyword("CAST");
16855            self.write("(");
16856            self.write_keyword("CONCAT");
16857            self.write("(");
16858            self.generate_expression(&func.args[0])?;
16859            self.write(", '-', ");
16860            self.generate_expression(&func.args[1])?;
16861            self.write(", '-', ");
16862            self.generate_expression(&func.args[2])?;
16863            self.write(")");
16864            self.write_space();
16865            self.write_keyword("AS");
16866            self.write_space();
16867            self.write_keyword("DATE");
16868            self.write(")");
16869            return Ok(());
16870        }
16871
16872        // Presto/Trino: DATE_ADD('unit', interval, date) - wrap interval in CAST(...AS BIGINT)
16873        // when it's not an integer literal
16874        let is_presto_like = matches!(
16875            self.config.dialect,
16876            Some(DialectType::Presto) | Some(DialectType::Trino)
16877        );
16878        if is_presto_like && func.name.eq_ignore_ascii_case("DATE_ADD") && func.args.len() == 3 {
16879            self.write_keyword("DATE_ADD");
16880            self.write("(");
16881            // First arg: unit (pass through as-is, e.g., 'DAY')
16882            self.generate_expression(&func.args[0])?;
16883            self.write(", ");
16884            // Second arg: interval - wrap in CAST(...AS BIGINT) if it doesn't return integer type
16885            let interval = &func.args[1];
16886            let needs_cast = !self.returns_integer_type(interval);
16887            if needs_cast {
16888                self.write_keyword("CAST");
16889                self.write("(");
16890            }
16891            self.generate_expression(interval)?;
16892            if needs_cast {
16893                self.write_space();
16894                self.write_keyword("AS");
16895                self.write_space();
16896                self.write_keyword("BIGINT");
16897                self.write(")");
16898            }
16899            self.write(", ");
16900            // Third arg: date
16901            self.generate_expression(&func.args[2])?;
16902            self.write(")");
16903            return Ok(());
16904        }
16905
16906        // Use bracket syntax if the function was parsed with brackets (e.g., MAP[keys, values])
16907        let use_brackets = func.use_bracket_syntax;
16908
16909        // Special case: functions WITH ORDINALITY need special output order
16910        // Input: FUNC(args) WITH ORDINALITY
16911        // Stored as: name="FUNC WITH ORDINALITY", args=[...]
16912        // Output must be: FUNC(args) WITH ORDINALITY
16913        let has_ordinality = func.name.len() >= 16
16914            && func.name[func.name.len() - 16..].eq_ignore_ascii_case(" WITH ORDINALITY");
16915        let output_name = if has_ordinality {
16916            let base_name = &func.name[..func.name.len() - " WITH ORDINALITY".len()];
16917            self.normalize_func_name(base_name)
16918        } else {
16919            normalized_name.clone()
16920        };
16921
16922        // For qualified names (schema.function or object.method), preserve original case
16923        // because they can be case-sensitive (e.g., TSQL XML methods like .nodes(), .value())
16924        if func.name.contains('.') && !has_ordinality {
16925            // Don't normalize qualified functions - preserve original case
16926            // If the function was quoted (e.g., BigQuery `p.d.UdF`), wrap it in backticks
16927            if func.quoted {
16928                self.write("`");
16929                self.write(&func.name);
16930                self.write("`");
16931            } else {
16932                self.write(&func.name);
16933            }
16934        } else {
16935            self.write(&output_name);
16936        }
16937
16938        // If no_parens is true and there are no args, output just the function name
16939        // Unless the target dialect requires parens for this function
16940        let force_parens = func.no_parens && func.args.is_empty() && !func.distinct && {
16941            let needs_parens =
16942                if func.name.eq_ignore_ascii_case("CURRENT_USER")
16943                    || func.name.eq_ignore_ascii_case("SESSION_USER")
16944                    || func.name.eq_ignore_ascii_case("SYSTEM_USER")
16945                {
16946                    matches!(
16947                        self.config.dialect,
16948                        Some(DialectType::Snowflake)
16949                            | Some(DialectType::Spark)
16950                            | Some(DialectType::Databricks)
16951                            | Some(DialectType::Hive)
16952                    )
16953                } else {
16954                    false
16955                };
16956            !needs_parens
16957        };
16958        if force_parens {
16959            // Output trailing comments
16960            for comment in &func.trailing_comments {
16961                self.write_space();
16962                self.write_formatted_comment(comment);
16963            }
16964            return Ok(());
16965        }
16966
16967        // CUBE, ROLLUP, GROUPING SETS need a space before the parenthesis
16968        if func.name.eq_ignore_ascii_case("CUBE") || func.name.eq_ignore_ascii_case("ROLLUP") || func.name.eq_ignore_ascii_case("GROUPING SETS") {
16969            self.write(" (");
16970        } else if use_brackets {
16971            self.write("[");
16972        } else {
16973            self.write("(");
16974        }
16975        if func.distinct {
16976            self.write_keyword("DISTINCT");
16977            self.write_space();
16978        }
16979
16980        // Check if arguments should be split onto multiple lines (pretty + too wide)
16981        let compact_pretty_func = matches!(self.config.dialect, Some(DialectType::Snowflake))
16982            && (func.name.eq_ignore_ascii_case("TABLE") || func.name.eq_ignore_ascii_case("FLATTEN"));
16983        // GROUPING SETS, CUBE, ROLLUP always expand in pretty mode
16984        let is_grouping_func =
16985            func.name.eq_ignore_ascii_case("GROUPING SETS") || func.name.eq_ignore_ascii_case("CUBE") || func.name.eq_ignore_ascii_case("ROLLUP");
16986        let should_split = if self.config.pretty && !func.args.is_empty() && !compact_pretty_func {
16987            if is_grouping_func {
16988                true
16989            } else {
16990                // Pre-render arguments to check total width
16991                let mut expr_strings: Vec<String> = Vec::with_capacity(func.args.len());
16992                for arg in &func.args {
16993                    let mut temp_gen = Generator::with_arc_config(self.config.clone());
16994                    Arc::make_mut(&mut temp_gen.config).pretty = false; // Don't recurse into pretty
16995                    temp_gen.generate_expression(arg)?;
16996                    expr_strings.push(temp_gen.output);
16997                }
16998                self.too_wide(&expr_strings)
16999            }
17000        } else {
17001            false
17002        };
17003
17004        if should_split {
17005            // Split onto multiple lines
17006            self.write_newline();
17007            self.indent_level += 1;
17008            for (i, arg) in func.args.iter().enumerate() {
17009                self.write_indent();
17010                self.generate_expression(arg)?;
17011                if i + 1 < func.args.len() {
17012                    self.write(",");
17013                }
17014                self.write_newline();
17015            }
17016            self.indent_level -= 1;
17017            self.write_indent();
17018        } else {
17019            // All on one line
17020            for (i, arg) in func.args.iter().enumerate() {
17021                if i > 0 {
17022                    self.write(", ");
17023                }
17024                self.generate_expression(arg)?;
17025            }
17026        }
17027
17028        if use_brackets {
17029            self.write("]");
17030        } else {
17031            self.write(")");
17032        }
17033        // Append WITH ORDINALITY after closing paren for table-valued functions
17034        if has_ordinality {
17035            self.write_space();
17036            self.write_keyword("WITH ORDINALITY");
17037        }
17038        // Output trailing comments
17039        for comment in &func.trailing_comments {
17040            self.write_space();
17041            self.write_formatted_comment(comment);
17042        }
17043        Ok(())
17044    }
17045
17046    fn generate_function_emits(&mut self, fe: &FunctionEmits) -> Result<()> {
17047        self.generate_expression(&fe.this)?;
17048        self.write_keyword(" EMITS ");
17049        self.generate_expression(&fe.emits)?;
17050        Ok(())
17051    }
17052
17053    fn generate_aggregate_function(&mut self, func: &AggregateFunction) -> Result<()> {
17054        // Normalize function name based on dialect settings
17055        let mut normalized_name = self.normalize_func_name(&func.name);
17056
17057        // Dialect-specific name mappings for aggregate functions
17058        if func.name.eq_ignore_ascii_case("MAX_BY") || func.name.eq_ignore_ascii_case("MIN_BY") {
17059            let is_max = func.name.eq_ignore_ascii_case("MAX_BY");
17060            match self.config.dialect {
17061                Some(DialectType::ClickHouse) => {
17062                    normalized_name = if is_max {
17063                        Cow::Borrowed("argMax")
17064                    } else {
17065                        Cow::Borrowed("argMin")
17066                    };
17067                }
17068                Some(DialectType::DuckDB) => {
17069                    normalized_name = if is_max {
17070                        Cow::Borrowed("ARG_MAX")
17071                    } else {
17072                        Cow::Borrowed("ARG_MIN")
17073                    };
17074                }
17075                _ => {}
17076            }
17077        }
17078        self.write(normalized_name.as_ref());
17079        self.write("(");
17080        if func.distinct {
17081            self.write_keyword("DISTINCT");
17082            self.write_space();
17083        }
17084
17085        // Check if we need to transform multi-arg COUNT DISTINCT
17086        // When dialect doesn't support multi_arg_distinct, transform:
17087        // COUNT(DISTINCT a, b) -> COUNT(DISTINCT CASE WHEN a IS NULL THEN NULL WHEN b IS NULL THEN NULL ELSE (a, b) END)
17088        let is_count = normalized_name.eq_ignore_ascii_case("COUNT");
17089        let needs_multi_arg_transform =
17090            func.distinct && is_count && func.args.len() > 1 && !self.config.multi_arg_distinct;
17091
17092        if needs_multi_arg_transform {
17093            // Generate: CASE WHEN a IS NULL THEN NULL WHEN b IS NULL THEN NULL ELSE (a, b) END
17094            self.write_keyword("CASE");
17095            for arg in &func.args {
17096                self.write_space();
17097                self.write_keyword("WHEN");
17098                self.write_space();
17099                self.generate_expression(arg)?;
17100                self.write_space();
17101                self.write_keyword("IS NULL THEN NULL");
17102            }
17103            self.write_space();
17104            self.write_keyword("ELSE");
17105            self.write(" (");
17106            for (i, arg) in func.args.iter().enumerate() {
17107                if i > 0 {
17108                    self.write(", ");
17109                }
17110                self.generate_expression(arg)?;
17111            }
17112            self.write(")");
17113            self.write_space();
17114            self.write_keyword("END");
17115        } else {
17116            for (i, arg) in func.args.iter().enumerate() {
17117                if i > 0 {
17118                    self.write(", ");
17119                }
17120                self.generate_expression(arg)?;
17121            }
17122        }
17123
17124        // IGNORE NULLS / RESPECT NULLS inside parens (for BigQuery style or when config says in_func)
17125        if self.config.ignore_nulls_in_func
17126            && !matches!(self.config.dialect, Some(DialectType::DuckDB))
17127        {
17128            if let Some(ignore) = func.ignore_nulls {
17129                self.write_space();
17130                if ignore {
17131                    self.write_keyword("IGNORE NULLS");
17132                } else {
17133                    self.write_keyword("RESPECT NULLS");
17134                }
17135            }
17136        }
17137
17138        // ORDER BY inside aggregate
17139        if !func.order_by.is_empty() {
17140            self.write_space();
17141            self.write_keyword("ORDER BY");
17142            self.write_space();
17143            for (i, ord) in func.order_by.iter().enumerate() {
17144                if i > 0 {
17145                    self.write(", ");
17146                }
17147                self.generate_ordered(ord)?;
17148            }
17149        }
17150
17151        // LIMIT inside aggregate
17152        if let Some(limit) = &func.limit {
17153            self.write_space();
17154            self.write_keyword("LIMIT");
17155            self.write_space();
17156            // Check if this is a Tuple representing LIMIT offset, count
17157            if let Expression::Tuple(t) = limit.as_ref() {
17158                if t.expressions.len() == 2 {
17159                    self.generate_expression(&t.expressions[0])?;
17160                    self.write(", ");
17161                    self.generate_expression(&t.expressions[1])?;
17162                } else {
17163                    self.generate_expression(limit)?;
17164                }
17165            } else {
17166                self.generate_expression(limit)?;
17167            }
17168        }
17169
17170        self.write(")");
17171
17172        // IGNORE NULLS / RESPECT NULLS outside parens (standard style)
17173        if !self.config.ignore_nulls_in_func
17174            && !matches!(self.config.dialect, Some(DialectType::DuckDB))
17175        {
17176            if let Some(ignore) = func.ignore_nulls {
17177                self.write_space();
17178                if ignore {
17179                    self.write_keyword("IGNORE NULLS");
17180                } else {
17181                    self.write_keyword("RESPECT NULLS");
17182                }
17183            }
17184        }
17185
17186        if let Some(filter) = &func.filter {
17187            self.write_space();
17188            self.write_keyword("FILTER");
17189            self.write("(");
17190            self.write_keyword("WHERE");
17191            self.write_space();
17192            self.generate_expression(filter)?;
17193            self.write(")");
17194        }
17195
17196        Ok(())
17197    }
17198
17199    fn generate_window_function(&mut self, wf: &WindowFunction) -> Result<()> {
17200        self.generate_expression(&wf.this)?;
17201
17202        // Generate KEEP clause if present (Oracle KEEP (DENSE_RANK FIRST|LAST ORDER BY ...))
17203        if let Some(keep) = &wf.keep {
17204            self.write_space();
17205            self.write_keyword("KEEP");
17206            self.write(" (");
17207            self.write_keyword("DENSE_RANK");
17208            self.write_space();
17209            if keep.first {
17210                self.write_keyword("FIRST");
17211            } else {
17212                self.write_keyword("LAST");
17213            }
17214            self.write_space();
17215            self.write_keyword("ORDER BY");
17216            self.write_space();
17217            for (i, ord) in keep.order_by.iter().enumerate() {
17218                if i > 0 {
17219                    self.write(", ");
17220                }
17221                self.generate_ordered(ord)?;
17222            }
17223            self.write(")");
17224        }
17225
17226        // Check if there's any OVER clause content
17227        let has_over = !wf.over.partition_by.is_empty()
17228            || !wf.over.order_by.is_empty()
17229            || wf.over.frame.is_some()
17230            || wf.over.window_name.is_some();
17231
17232        // Only output OVER if there's actual window specification (not just KEEP alone)
17233        if has_over {
17234            self.write_space();
17235            self.write_keyword("OVER");
17236
17237            // Check if this is just a bare named window reference (no parens needed)
17238            let has_specs = !wf.over.partition_by.is_empty()
17239                || !wf.over.order_by.is_empty()
17240                || wf.over.frame.is_some();
17241
17242            if wf.over.window_name.is_some() && !has_specs {
17243                // OVER window_name (without parentheses)
17244                self.write_space();
17245                self.write(&wf.over.window_name.as_ref().unwrap().name);
17246            } else {
17247                // OVER (...) or OVER (window_name ...)
17248                self.write(" (");
17249                self.generate_over(&wf.over)?;
17250                self.write(")");
17251            }
17252        } else if wf.keep.is_none() {
17253            // No KEEP and no OVER content, but still a WindowFunction - output empty OVER ()
17254            self.write_space();
17255            self.write_keyword("OVER");
17256            self.write(" ()");
17257        }
17258
17259        Ok(())
17260    }
17261
17262    /// Generate WITHIN GROUP clause (for ordered-set aggregate functions)
17263    fn generate_within_group(&mut self, wg: &WithinGroup) -> Result<()> {
17264        self.generate_expression(&wg.this)?;
17265        self.write_space();
17266        self.write_keyword("WITHIN GROUP");
17267        self.write(" (");
17268        self.write_keyword("ORDER BY");
17269        self.write_space();
17270        for (i, ord) in wg.order_by.iter().enumerate() {
17271            if i > 0 {
17272                self.write(", ");
17273            }
17274            self.generate_ordered(ord)?;
17275        }
17276        self.write(")");
17277        Ok(())
17278    }
17279
17280    /// Generate the contents of an OVER clause (without parentheses)
17281    fn generate_over(&mut self, over: &Over) -> Result<()> {
17282        let mut has_content = false;
17283
17284        // Named window reference
17285        if let Some(name) = &over.window_name {
17286            self.write(&name.name);
17287            has_content = true;
17288        }
17289
17290        // PARTITION BY
17291        if !over.partition_by.is_empty() {
17292            if has_content {
17293                self.write_space();
17294            }
17295            self.write_keyword("PARTITION BY");
17296            self.write_space();
17297            for (i, expr) in over.partition_by.iter().enumerate() {
17298                if i > 0 {
17299                    self.write(", ");
17300                }
17301                self.generate_expression(expr)?;
17302            }
17303            has_content = true;
17304        }
17305
17306        // ORDER BY
17307        if !over.order_by.is_empty() {
17308            if has_content {
17309                self.write_space();
17310            }
17311            self.write_keyword("ORDER BY");
17312            self.write_space();
17313            for (i, ordered) in over.order_by.iter().enumerate() {
17314                if i > 0 {
17315                    self.write(", ");
17316                }
17317                self.generate_ordered(ordered)?;
17318            }
17319            has_content = true;
17320        }
17321
17322        // Window frame
17323        if let Some(frame) = &over.frame {
17324            if has_content {
17325                self.write_space();
17326            }
17327            self.generate_window_frame(frame)?;
17328        }
17329
17330        Ok(())
17331    }
17332
17333    fn generate_window_frame(&mut self, frame: &WindowFrame) -> Result<()> {
17334        // Exasol uses lowercase for frame kind (rows/range/groups)
17335        let lowercase_frame = self.config.lowercase_window_frame_keywords;
17336
17337        // Use preserved kind_text if available (for case preservation), unless lowercase override is active
17338        if !lowercase_frame {
17339            if let Some(kind_text) = &frame.kind_text {
17340                self.write(kind_text);
17341            } else {
17342                match frame.kind {
17343                    WindowFrameKind::Rows => self.write_keyword("ROWS"),
17344                    WindowFrameKind::Range => self.write_keyword("RANGE"),
17345                    WindowFrameKind::Groups => self.write_keyword("GROUPS"),
17346                }
17347            }
17348        } else {
17349            match frame.kind {
17350                WindowFrameKind::Rows => self.write("rows"),
17351                WindowFrameKind::Range => self.write("range"),
17352                WindowFrameKind::Groups => self.write("groups"),
17353            }
17354        }
17355
17356        // Use BETWEEN format only when there's an explicit end bound,
17357        // or when normalize_window_frame_between is enabled and the start is a directional bound
17358        self.write_space();
17359        let should_normalize = self.config.normalize_window_frame_between
17360            && frame.end.is_none()
17361            && matches!(
17362                frame.start,
17363                WindowFrameBound::Preceding(_)
17364                    | WindowFrameBound::Following(_)
17365                    | WindowFrameBound::UnboundedPreceding
17366                    | WindowFrameBound::UnboundedFollowing
17367            );
17368
17369        if let Some(end) = &frame.end {
17370            // BETWEEN format: RANGE BETWEEN start AND end
17371            self.write_keyword("BETWEEN");
17372            self.write_space();
17373            self.generate_window_frame_bound(&frame.start, frame.start_side_text.as_deref())?;
17374            self.write_space();
17375            self.write_keyword("AND");
17376            self.write_space();
17377            self.generate_window_frame_bound(end, frame.end_side_text.as_deref())?;
17378        } else if should_normalize {
17379            // Normalize single-bound to BETWEEN form: ROWS 1 PRECEDING → ROWS BETWEEN 1 PRECEDING AND CURRENT ROW
17380            self.write_keyword("BETWEEN");
17381            self.write_space();
17382            self.generate_window_frame_bound(&frame.start, frame.start_side_text.as_deref())?;
17383            self.write_space();
17384            self.write_keyword("AND");
17385            self.write_space();
17386            self.write_keyword("CURRENT ROW");
17387        } else {
17388            // Single bound format: RANGE CURRENT ROW
17389            self.generate_window_frame_bound(&frame.start, frame.start_side_text.as_deref())?;
17390        }
17391
17392        // EXCLUDE clause
17393        if let Some(exclude) = &frame.exclude {
17394            self.write_space();
17395            self.write_keyword("EXCLUDE");
17396            self.write_space();
17397            match exclude {
17398                WindowFrameExclude::CurrentRow => self.write_keyword("CURRENT ROW"),
17399                WindowFrameExclude::Group => self.write_keyword("GROUP"),
17400                WindowFrameExclude::Ties => self.write_keyword("TIES"),
17401                WindowFrameExclude::NoOthers => self.write_keyword("NO OTHERS"),
17402            }
17403        }
17404
17405        Ok(())
17406    }
17407
17408    fn generate_window_frame_bound(
17409        &mut self,
17410        bound: &WindowFrameBound,
17411        side_text: Option<&str>,
17412    ) -> Result<()> {
17413        // Exasol uses lowercase for preceding/following
17414        let lowercase_frame = self.config.lowercase_window_frame_keywords;
17415
17416        match bound {
17417            WindowFrameBound::CurrentRow => {
17418                self.write_keyword("CURRENT ROW");
17419            }
17420            WindowFrameBound::UnboundedPreceding => {
17421                self.write_keyword("UNBOUNDED");
17422                self.write_space();
17423                if lowercase_frame {
17424                    self.write("preceding");
17425                } else if let Some(text) = side_text {
17426                    self.write(text);
17427                } else {
17428                    self.write_keyword("PRECEDING");
17429                }
17430            }
17431            WindowFrameBound::UnboundedFollowing => {
17432                self.write_keyword("UNBOUNDED");
17433                self.write_space();
17434                if lowercase_frame {
17435                    self.write("following");
17436                } else if let Some(text) = side_text {
17437                    self.write(text);
17438                } else {
17439                    self.write_keyword("FOLLOWING");
17440                }
17441            }
17442            WindowFrameBound::Preceding(expr) => {
17443                self.generate_expression(expr)?;
17444                self.write_space();
17445                if lowercase_frame {
17446                    self.write("preceding");
17447                } else if let Some(text) = side_text {
17448                    self.write(text);
17449                } else {
17450                    self.write_keyword("PRECEDING");
17451                }
17452            }
17453            WindowFrameBound::Following(expr) => {
17454                self.generate_expression(expr)?;
17455                self.write_space();
17456                if lowercase_frame {
17457                    self.write("following");
17458                } else if let Some(text) = side_text {
17459                    self.write(text);
17460                } else {
17461                    self.write_keyword("FOLLOWING");
17462                }
17463            }
17464            WindowFrameBound::BarePreceding => {
17465                if lowercase_frame {
17466                    self.write("preceding");
17467                } else if let Some(text) = side_text {
17468                    self.write(text);
17469                } else {
17470                    self.write_keyword("PRECEDING");
17471                }
17472            }
17473            WindowFrameBound::BareFollowing => {
17474                if lowercase_frame {
17475                    self.write("following");
17476                } else if let Some(text) = side_text {
17477                    self.write(text);
17478                } else {
17479                    self.write_keyword("FOLLOWING");
17480                }
17481            }
17482            WindowFrameBound::Value(expr) => {
17483                // Bare numeric bound without PRECEDING/FOLLOWING
17484                self.generate_expression(expr)?;
17485            }
17486        }
17487        Ok(())
17488    }
17489
17490    fn generate_interval(&mut self, interval: &Interval) -> Result<()> {
17491        // For Oracle with ExprSpan: only output INTERVAL if `this` is a literal
17492        // (e.g., `(expr) DAY(9) TO SECOND(3)` should NOT have INTERVAL prefix)
17493        let skip_interval_keyword = matches!(self.config.dialect, Some(DialectType::Oracle))
17494            && matches!(&interval.unit, Some(IntervalUnitSpec::ExprSpan(_)))
17495            && !matches!(&interval.this, Some(Expression::Literal(_)));
17496
17497        // SINGLE_STRING_INTERVAL: combine value and unit into a single quoted string
17498        // e.g., INTERVAL '1' DAY -> INTERVAL '1 DAY'
17499        if self.config.single_string_interval {
17500            if let (
17501                Some(Expression::Literal(lit)),
17502                Some(IntervalUnitSpec::Simple {
17503                    ref unit,
17504                    ref use_plural,
17505                }),
17506            ) = (&interval.this, &interval.unit)
17507            {
17508                    if let Literal::String(ref val) = lit.as_ref() {
17509                self.write_keyword("INTERVAL");
17510                self.write_space();
17511                let effective_plural = *use_plural && self.config.interval_allows_plural_form;
17512                let unit_str = self.interval_unit_str(unit, effective_plural);
17513                self.write("'");
17514                self.write(val);
17515                self.write(" ");
17516                self.write(&unit_str);
17517                self.write("'");
17518                return Ok(());
17519            }
17520                }
17521        }
17522
17523        if !skip_interval_keyword {
17524            self.write_keyword("INTERVAL");
17525        }
17526
17527        // Generate value if present
17528        if let Some(ref value) = interval.this {
17529            if !skip_interval_keyword {
17530                self.write_space();
17531            }
17532            // If the value is a complex expression (not a literal/column/function call)
17533            // and there's a unit, wrap it in parentheses
17534            // e.g., INTERVAL (2 * 2) MONTH, INTERVAL (DAYOFMONTH(dt) - 1) DAY
17535            let needs_parens = interval.unit.is_some()
17536                && matches!(
17537                    value,
17538                    Expression::Add(_)
17539                        | Expression::Sub(_)
17540                        | Expression::Mul(_)
17541                        | Expression::Div(_)
17542                        | Expression::Mod(_)
17543                        | Expression::BitwiseAnd(_)
17544                        | Expression::BitwiseOr(_)
17545                        | Expression::BitwiseXor(_)
17546                );
17547            if needs_parens {
17548                self.write("(");
17549            }
17550            self.generate_expression(value)?;
17551            if needs_parens {
17552                self.write(")");
17553            }
17554        }
17555
17556        // Generate unit if present
17557        if let Some(ref unit_spec) = interval.unit {
17558            self.write_space();
17559            self.write_interval_unit_spec(unit_spec)?;
17560        }
17561
17562        Ok(())
17563    }
17564
17565    /// Return the string representation of an interval unit
17566    fn interval_unit_str(&self, unit: &IntervalUnit, use_plural: bool) -> &'static str {
17567        match (unit, use_plural) {
17568            (IntervalUnit::Year, false) => "YEAR",
17569            (IntervalUnit::Year, true) => "YEARS",
17570            (IntervalUnit::Quarter, false) => "QUARTER",
17571            (IntervalUnit::Quarter, true) => "QUARTERS",
17572            (IntervalUnit::Month, false) => "MONTH",
17573            (IntervalUnit::Month, true) => "MONTHS",
17574            (IntervalUnit::Week, false) => "WEEK",
17575            (IntervalUnit::Week, true) => "WEEKS",
17576            (IntervalUnit::Day, false) => "DAY",
17577            (IntervalUnit::Day, true) => "DAYS",
17578            (IntervalUnit::Hour, false) => "HOUR",
17579            (IntervalUnit::Hour, true) => "HOURS",
17580            (IntervalUnit::Minute, false) => "MINUTE",
17581            (IntervalUnit::Minute, true) => "MINUTES",
17582            (IntervalUnit::Second, false) => "SECOND",
17583            (IntervalUnit::Second, true) => "SECONDS",
17584            (IntervalUnit::Millisecond, false) => "MILLISECOND",
17585            (IntervalUnit::Millisecond, true) => "MILLISECONDS",
17586            (IntervalUnit::Microsecond, false) => "MICROSECOND",
17587            (IntervalUnit::Microsecond, true) => "MICROSECONDS",
17588            (IntervalUnit::Nanosecond, false) => "NANOSECOND",
17589            (IntervalUnit::Nanosecond, true) => "NANOSECONDS",
17590        }
17591    }
17592
17593    fn write_interval_unit_spec(&mut self, unit_spec: &IntervalUnitSpec) -> Result<()> {
17594        match unit_spec {
17595            IntervalUnitSpec::Simple { unit, use_plural } => {
17596                // If dialect doesn't allow plural forms, force singular
17597                let effective_plural = *use_plural && self.config.interval_allows_plural_form;
17598                self.write_simple_interval_unit(unit, effective_plural);
17599            }
17600            IntervalUnitSpec::Span(span) => {
17601                self.write_simple_interval_unit(&span.this, false);
17602                self.write_space();
17603                self.write_keyword("TO");
17604                self.write_space();
17605                self.write_simple_interval_unit(&span.expression, false);
17606            }
17607            IntervalUnitSpec::ExprSpan(span) => {
17608                // Expression-based interval span (e.g., DAY(9) TO SECOND(3))
17609                self.generate_expression(&span.this)?;
17610                self.write_space();
17611                self.write_keyword("TO");
17612                self.write_space();
17613                self.generate_expression(&span.expression)?;
17614            }
17615            IntervalUnitSpec::Expr(expr) => {
17616                self.generate_expression(expr)?;
17617            }
17618        }
17619        Ok(())
17620    }
17621
17622    fn write_simple_interval_unit(&mut self, unit: &IntervalUnit, use_plural: bool) {
17623        // Output interval unit, respecting plural preference
17624        match (unit, use_plural) {
17625            (IntervalUnit::Year, false) => self.write_keyword("YEAR"),
17626            (IntervalUnit::Year, true) => self.write_keyword("YEARS"),
17627            (IntervalUnit::Quarter, false) => self.write_keyword("QUARTER"),
17628            (IntervalUnit::Quarter, true) => self.write_keyword("QUARTERS"),
17629            (IntervalUnit::Month, false) => self.write_keyword("MONTH"),
17630            (IntervalUnit::Month, true) => self.write_keyword("MONTHS"),
17631            (IntervalUnit::Week, false) => self.write_keyword("WEEK"),
17632            (IntervalUnit::Week, true) => self.write_keyword("WEEKS"),
17633            (IntervalUnit::Day, false) => self.write_keyword("DAY"),
17634            (IntervalUnit::Day, true) => self.write_keyword("DAYS"),
17635            (IntervalUnit::Hour, false) => self.write_keyword("HOUR"),
17636            (IntervalUnit::Hour, true) => self.write_keyword("HOURS"),
17637            (IntervalUnit::Minute, false) => self.write_keyword("MINUTE"),
17638            (IntervalUnit::Minute, true) => self.write_keyword("MINUTES"),
17639            (IntervalUnit::Second, false) => self.write_keyword("SECOND"),
17640            (IntervalUnit::Second, true) => self.write_keyword("SECONDS"),
17641            (IntervalUnit::Millisecond, false) => self.write_keyword("MILLISECOND"),
17642            (IntervalUnit::Millisecond, true) => self.write_keyword("MILLISECONDS"),
17643            (IntervalUnit::Microsecond, false) => self.write_keyword("MICROSECOND"),
17644            (IntervalUnit::Microsecond, true) => self.write_keyword("MICROSECONDS"),
17645            (IntervalUnit::Nanosecond, false) => self.write_keyword("NANOSECOND"),
17646            (IntervalUnit::Nanosecond, true) => self.write_keyword("NANOSECONDS"),
17647        }
17648    }
17649
17650    /// Normalize a date part expression to unquoted uppercase for Redshift DATEDIFF/DATEADD
17651    /// Converts: 'day', 'days', day, days, DAY -> DAY (unquoted)
17652    fn write_redshift_date_part(&mut self, expr: &Expression) {
17653        let part_str = self.extract_date_part_string(expr);
17654        if let Some(part) = part_str {
17655            let normalized = self.normalize_date_part(&part);
17656            self.write_keyword(&normalized);
17657        } else {
17658            // If we can't extract a date part string, fall back to generating the expression
17659            let _ = self.generate_expression(expr);
17660        }
17661    }
17662
17663    /// Normalize a date part expression to quoted uppercase for Redshift DATE_TRUNC
17664    /// Converts: 'day', day, DAY -> 'DAY' (quoted)
17665    fn write_redshift_date_part_quoted(&mut self, expr: &Expression) {
17666        let part_str = self.extract_date_part_string(expr);
17667        if let Some(part) = part_str {
17668            let normalized = self.normalize_date_part(&part);
17669            self.write("'");
17670            self.write(&normalized);
17671            self.write("'");
17672        } else {
17673            // If we can't extract a date part string, fall back to generating the expression
17674            let _ = self.generate_expression(expr);
17675        }
17676    }
17677
17678    /// Extract date part string from expression (handles string literals and identifiers)
17679    fn extract_date_part_string(&self, expr: &Expression) -> Option<String> {
17680        match expr {
17681            Expression::Literal(lit) if matches!(lit.as_ref(), crate::expressions::Literal::String(_)) => { let crate::expressions::Literal::String(s) = lit.as_ref() else { unreachable!() }; Some(s.clone()) },
17682            Expression::Identifier(id) => Some(id.name.clone()),
17683            Expression::Column(col) if col.table.is_none() => {
17684                // Simple column reference without table prefix, treat as identifier
17685                Some(col.name.name.clone())
17686            }
17687            _ => None,
17688        }
17689    }
17690
17691    /// Normalize date part to uppercase singular form
17692    /// days -> DAY, months -> MONTH, etc.
17693    fn normalize_date_part(&self, part: &str) -> String {
17694        let mut buf = [0u8; 64];
17695        let lower: &str = if part.len() <= 64 {
17696            for (i, b) in part.bytes().enumerate() {
17697                buf[i] = b.to_ascii_lowercase();
17698            }
17699            std::str::from_utf8(&buf[..part.len()]).unwrap_or(part)
17700        } else {
17701            return part.to_ascii_uppercase();
17702        };
17703        match lower {
17704            "day" | "days" | "d" => "DAY".to_string(),
17705            "month" | "months" | "mon" | "mm" => "MONTH".to_string(),
17706            "year" | "years" | "y" | "yy" | "yyyy" => "YEAR".to_string(),
17707            "week" | "weeks" | "w" | "wk" => "WEEK".to_string(),
17708            "hour" | "hours" | "h" | "hh" => "HOUR".to_string(),
17709            "minute" | "minutes" | "m" | "mi" | "n" => "MINUTE".to_string(),
17710            "second" | "seconds" | "s" | "ss" => "SECOND".to_string(),
17711            "millisecond" | "milliseconds" | "ms" => "MILLISECOND".to_string(),
17712            "microsecond" | "microseconds" | "us" => "MICROSECOND".to_string(),
17713            "quarter" | "quarters" | "q" | "qq" => "QUARTER".to_string(),
17714            _ => part.to_ascii_uppercase(),
17715        }
17716    }
17717
17718    fn write_datetime_field(&mut self, field: &DateTimeField) {
17719        match field {
17720            DateTimeField::Year => self.write_keyword("YEAR"),
17721            DateTimeField::Month => self.write_keyword("MONTH"),
17722            DateTimeField::Day => self.write_keyword("DAY"),
17723            DateTimeField::Hour => self.write_keyword("HOUR"),
17724            DateTimeField::Minute => self.write_keyword("MINUTE"),
17725            DateTimeField::Second => self.write_keyword("SECOND"),
17726            DateTimeField::Millisecond => self.write_keyword("MILLISECOND"),
17727            DateTimeField::Microsecond => self.write_keyword("MICROSECOND"),
17728            DateTimeField::DayOfWeek => {
17729                let name = match self.config.dialect {
17730                    Some(DialectType::DuckDB) | Some(DialectType::Snowflake) => "DAYOFWEEK",
17731                    _ => "DOW",
17732                };
17733                self.write_keyword(name);
17734            }
17735            DateTimeField::DayOfYear => {
17736                let name = match self.config.dialect {
17737                    Some(DialectType::DuckDB) | Some(DialectType::Snowflake) => "DAYOFYEAR",
17738                    _ => "DOY",
17739                };
17740                self.write_keyword(name);
17741            }
17742            DateTimeField::Week => self.write_keyword("WEEK"),
17743            DateTimeField::WeekWithModifier(modifier) => {
17744                self.write_keyword("WEEK");
17745                self.write("(");
17746                self.write(modifier);
17747                self.write(")");
17748            }
17749            DateTimeField::Quarter => self.write_keyword("QUARTER"),
17750            DateTimeField::Epoch => self.write_keyword("EPOCH"),
17751            DateTimeField::Timezone => self.write_keyword("TIMEZONE"),
17752            DateTimeField::TimezoneHour => self.write_keyword("TIMEZONE_HOUR"),
17753            DateTimeField::TimezoneMinute => self.write_keyword("TIMEZONE_MINUTE"),
17754            DateTimeField::Date => self.write_keyword("DATE"),
17755            DateTimeField::Time => self.write_keyword("TIME"),
17756            DateTimeField::Custom(name) => self.write(name),
17757        }
17758    }
17759
17760    /// Write datetime field in lowercase (for Spark/Hive/Databricks)
17761    fn write_datetime_field_lower(&mut self, field: &DateTimeField) {
17762        match field {
17763            DateTimeField::Year => self.write("year"),
17764            DateTimeField::Month => self.write("month"),
17765            DateTimeField::Day => self.write("day"),
17766            DateTimeField::Hour => self.write("hour"),
17767            DateTimeField::Minute => self.write("minute"),
17768            DateTimeField::Second => self.write("second"),
17769            DateTimeField::Millisecond => self.write("millisecond"),
17770            DateTimeField::Microsecond => self.write("microsecond"),
17771            DateTimeField::DayOfWeek => self.write("dow"),
17772            DateTimeField::DayOfYear => self.write("doy"),
17773            DateTimeField::Week => self.write("week"),
17774            DateTimeField::WeekWithModifier(modifier) => {
17775                self.write("week(");
17776                self.write(modifier);
17777                self.write(")");
17778            }
17779            DateTimeField::Quarter => self.write("quarter"),
17780            DateTimeField::Epoch => self.write("epoch"),
17781            DateTimeField::Timezone => self.write("timezone"),
17782            DateTimeField::TimezoneHour => self.write("timezone_hour"),
17783            DateTimeField::TimezoneMinute => self.write("timezone_minute"),
17784            DateTimeField::Date => self.write("date"),
17785            DateTimeField::Time => self.write("time"),
17786            DateTimeField::Custom(name) => self.write(name),
17787        }
17788    }
17789
17790    // Helper function generators
17791
17792    fn generate_simple_func(&mut self, name: &str, arg: &Expression) -> Result<()> {
17793        self.write_keyword(name);
17794        self.write("(");
17795        self.generate_expression(arg)?;
17796        self.write(")");
17797        Ok(())
17798    }
17799
17800    /// Generate a unary function, using the original name if available for round-trip preservation
17801    fn generate_unary_func(
17802        &mut self,
17803        default_name: &str,
17804        f: &crate::expressions::UnaryFunc,
17805    ) -> Result<()> {
17806        let name = f.original_name.as_deref().unwrap_or(default_name);
17807        self.write_keyword(name);
17808        self.write("(");
17809        self.generate_expression(&f.this)?;
17810        self.write(")");
17811        Ok(())
17812    }
17813
17814    /// Generate SQRT/CBRT - always use function form (matches Python SQLGlot normalization)
17815    fn generate_sqrt_cbrt(
17816        &mut self,
17817        f: &crate::expressions::UnaryFunc,
17818        func_name: &str,
17819        _op: &str,
17820    ) -> Result<()> {
17821        // Python SQLGlot normalizes |/ and ||/ to SQRT() and CBRT()
17822        // Always use function syntax for consistency
17823        self.write_keyword(func_name);
17824        self.write("(");
17825        self.generate_expression(&f.this)?;
17826        self.write(")");
17827        Ok(())
17828    }
17829
17830    fn generate_binary_func(
17831        &mut self,
17832        name: &str,
17833        arg1: &Expression,
17834        arg2: &Expression,
17835    ) -> Result<()> {
17836        self.write_keyword(name);
17837        self.write("(");
17838        self.generate_expression(arg1)?;
17839        self.write(", ");
17840        self.generate_expression(arg2)?;
17841        self.write(")");
17842        Ok(())
17843    }
17844
17845    /// Generate CHAR/CHR function with optional USING charset
17846    /// e.g., CHAR(77, 77.3, '77.3' USING utf8mb4)
17847    /// e.g., CHR(187 USING NCHAR_CS) -- Oracle
17848    fn generate_char_func(&mut self, f: &crate::expressions::CharFunc) -> Result<()> {
17849        // Use stored name if available, otherwise default to CHAR
17850        let func_name = f.name.as_deref().unwrap_or("CHAR");
17851        self.write_keyword(func_name);
17852        self.write("(");
17853        for (i, arg) in f.args.iter().enumerate() {
17854            if i > 0 {
17855                self.write(", ");
17856            }
17857            self.generate_expression(arg)?;
17858        }
17859        if let Some(ref charset) = f.charset {
17860            self.write(" ");
17861            self.write_keyword("USING");
17862            self.write(" ");
17863            self.write(charset);
17864        }
17865        self.write(")");
17866        Ok(())
17867    }
17868
17869    fn generate_power(&mut self, f: &BinaryFunc) -> Result<()> {
17870        use crate::dialects::DialectType;
17871
17872        match self.config.dialect {
17873            Some(DialectType::Teradata) => {
17874                // Teradata uses ** operator for exponentiation
17875                self.generate_expression(&f.this)?;
17876                self.write(" ** ");
17877                self.generate_expression(&f.expression)?;
17878                Ok(())
17879            }
17880            _ => {
17881                // Other dialects use POWER function
17882                self.generate_binary_func("POWER", &f.this, &f.expression)
17883            }
17884        }
17885    }
17886
17887    fn generate_vararg_func(&mut self, name: &str, args: &[Expression]) -> Result<()> {
17888        self.write_func_name(name);
17889        self.write("(");
17890        for (i, arg) in args.iter().enumerate() {
17891            if i > 0 {
17892                self.write(", ");
17893            }
17894            self.generate_expression(arg)?;
17895        }
17896        self.write(")");
17897        Ok(())
17898    }
17899
17900    // String function generators
17901
17902    fn generate_concat_ws(&mut self, f: &ConcatWs) -> Result<()> {
17903        self.write_keyword("CONCAT_WS");
17904        self.write("(");
17905        self.generate_expression(&f.separator)?;
17906        for expr in &f.expressions {
17907            self.write(", ");
17908            self.generate_expression(expr)?;
17909        }
17910        self.write(")");
17911        Ok(())
17912    }
17913
17914    fn collect_concat_operands<'a>(expr: &'a Expression, out: &mut Vec<&'a Expression>) {
17915        if let Expression::Concat(op) = expr {
17916            Self::collect_concat_operands(&op.left, out);
17917            Self::collect_concat_operands(&op.right, out);
17918        } else {
17919            out.push(expr);
17920        }
17921    }
17922
17923    fn generate_mysql_concat_from_concat(&mut self, op: &BinaryOp) -> Result<()> {
17924        let mut operands = Vec::new();
17925        Self::collect_concat_operands(&op.left, &mut operands);
17926        Self::collect_concat_operands(&op.right, &mut operands);
17927
17928        self.write_keyword("CONCAT");
17929        self.write("(");
17930        for (i, operand) in operands.iter().enumerate() {
17931            if i > 0 {
17932                self.write(", ");
17933            }
17934            self.generate_expression(operand)?;
17935        }
17936        self.write(")");
17937        Ok(())
17938    }
17939
17940    fn collect_dpipe_operands<'a>(expr: &'a Expression, out: &mut Vec<&'a Expression>) {
17941        if let Expression::DPipe(dpipe) = expr {
17942            Self::collect_dpipe_operands(&dpipe.this, out);
17943            Self::collect_dpipe_operands(&dpipe.expression, out);
17944        } else {
17945            out.push(expr);
17946        }
17947    }
17948
17949    fn generate_mysql_concat_from_dpipe(&mut self, e: &DPipe) -> Result<()> {
17950        let mut operands = Vec::new();
17951        Self::collect_dpipe_operands(&e.this, &mut operands);
17952        Self::collect_dpipe_operands(&e.expression, &mut operands);
17953
17954        self.write_keyword("CONCAT");
17955        self.write("(");
17956        for (i, operand) in operands.iter().enumerate() {
17957            if i > 0 {
17958                self.write(", ");
17959            }
17960            self.generate_expression(operand)?;
17961        }
17962        self.write(")");
17963        Ok(())
17964    }
17965
17966    fn generate_substring(&mut self, f: &SubstringFunc) -> Result<()> {
17967        // Oracle uses SUBSTR; most others use SUBSTRING
17968        let is_oracle = matches!(self.config.dialect, Some(DialectType::Oracle));
17969        if is_oracle {
17970            self.write_keyword("SUBSTR");
17971        } else {
17972            self.write_keyword("SUBSTRING");
17973        }
17974        self.write("(");
17975        self.generate_expression(&f.this)?;
17976        // PostgreSQL always uses FROM/FOR syntax
17977        let force_from_for = matches!(self.config.dialect, Some(DialectType::PostgreSQL));
17978        // Spark/Hive use comma syntax, not FROM/FOR syntax
17979        let use_comma_syntax = matches!(
17980            self.config.dialect,
17981            Some(DialectType::Spark) | Some(DialectType::Hive) | Some(DialectType::Databricks)
17982        );
17983        if (f.from_for_syntax || force_from_for) && !use_comma_syntax {
17984            // SQL standard syntax: SUBSTRING(str FROM pos FOR len)
17985            self.write_space();
17986            self.write_keyword("FROM");
17987            self.write_space();
17988            self.generate_expression(&f.start)?;
17989            if let Some(length) = &f.length {
17990                self.write_space();
17991                self.write_keyword("FOR");
17992                self.write_space();
17993                self.generate_expression(length)?;
17994            }
17995        } else {
17996            // Comma-separated syntax: SUBSTRING(str, pos, len) or SUBSTR(str, pos, len)
17997            self.write(", ");
17998            self.generate_expression(&f.start)?;
17999            if let Some(length) = &f.length {
18000                self.write(", ");
18001                self.generate_expression(length)?;
18002            }
18003        }
18004        self.write(")");
18005        Ok(())
18006    }
18007
18008    fn generate_overlay(&mut self, f: &OverlayFunc) -> Result<()> {
18009        self.write_keyword("OVERLAY");
18010        self.write("(");
18011        self.generate_expression(&f.this)?;
18012        self.write_space();
18013        self.write_keyword("PLACING");
18014        self.write_space();
18015        self.generate_expression(&f.replacement)?;
18016        self.write_space();
18017        self.write_keyword("FROM");
18018        self.write_space();
18019        self.generate_expression(&f.from)?;
18020        if let Some(length) = &f.length {
18021            self.write_space();
18022            self.write_keyword("FOR");
18023            self.write_space();
18024            self.generate_expression(length)?;
18025        }
18026        self.write(")");
18027        Ok(())
18028    }
18029
18030    fn generate_trim(&mut self, f: &TrimFunc) -> Result<()> {
18031        // Special case: TRIM(LEADING str) -> LTRIM(str), TRIM(TRAILING str) -> RTRIM(str)
18032        // when no characters are specified (PostgreSQL style)
18033        if f.position_explicit && f.characters.is_none() {
18034            match f.position {
18035                TrimPosition::Leading => {
18036                    self.write_keyword("LTRIM");
18037                    self.write("(");
18038                    self.generate_expression(&f.this)?;
18039                    self.write(")");
18040                    return Ok(());
18041                }
18042                TrimPosition::Trailing => {
18043                    self.write_keyword("RTRIM");
18044                    self.write("(");
18045                    self.generate_expression(&f.this)?;
18046                    self.write(")");
18047                    return Ok(());
18048                }
18049                TrimPosition::Both => {
18050                    // TRIM(BOTH str) -> BTRIM(str) in PostgreSQL, but TRIM(str) is more standard
18051                    // Fall through to standard TRIM handling
18052                }
18053            }
18054        }
18055
18056        self.write_keyword("TRIM");
18057        self.write("(");
18058        // When BOTH is specified without trim characters, simplify to just TRIM(str)
18059        // Force standard syntax for dialects that require it (Hive, Spark, Databricks, ClickHouse)
18060        let force_standard = f.characters.is_some()
18061            && !f.sql_standard_syntax
18062            && matches!(
18063                self.config.dialect,
18064                Some(DialectType::Hive)
18065                    | Some(DialectType::Spark)
18066                    | Some(DialectType::Databricks)
18067                    | Some(DialectType::ClickHouse)
18068            );
18069        let use_standard = (f.sql_standard_syntax || force_standard)
18070            && !(f.position_explicit
18071                && f.characters.is_none()
18072                && matches!(f.position, TrimPosition::Both));
18073        if use_standard {
18074            // SQL standard syntax: TRIM(BOTH chars FROM str)
18075            // Only output position if it was explicitly specified
18076            if f.position_explicit {
18077                match f.position {
18078                    TrimPosition::Both => self.write_keyword("BOTH"),
18079                    TrimPosition::Leading => self.write_keyword("LEADING"),
18080                    TrimPosition::Trailing => self.write_keyword("TRAILING"),
18081                }
18082                self.write_space();
18083            }
18084            if let Some(chars) = &f.characters {
18085                self.generate_expression(chars)?;
18086                self.write_space();
18087            }
18088            self.write_keyword("FROM");
18089            self.write_space();
18090            self.generate_expression(&f.this)?;
18091        } else {
18092            // Simple function syntax: TRIM(str) or TRIM(str, chars)
18093            self.generate_expression(&f.this)?;
18094            if let Some(chars) = &f.characters {
18095                self.write(", ");
18096                self.generate_expression(chars)?;
18097            }
18098        }
18099        self.write(")");
18100        Ok(())
18101    }
18102
18103    fn generate_replace(&mut self, f: &ReplaceFunc) -> Result<()> {
18104        self.write_keyword("REPLACE");
18105        self.write("(");
18106        self.generate_expression(&f.this)?;
18107        self.write(", ");
18108        self.generate_expression(&f.old)?;
18109        self.write(", ");
18110        self.generate_expression(&f.new)?;
18111        self.write(")");
18112        Ok(())
18113    }
18114
18115    fn generate_left_right(&mut self, name: &str, f: &LeftRightFunc) -> Result<()> {
18116        self.write_keyword(name);
18117        self.write("(");
18118        self.generate_expression(&f.this)?;
18119        self.write(", ");
18120        self.generate_expression(&f.length)?;
18121        self.write(")");
18122        Ok(())
18123    }
18124
18125    fn generate_repeat(&mut self, f: &RepeatFunc) -> Result<()> {
18126        self.write_keyword("REPEAT");
18127        self.write("(");
18128        self.generate_expression(&f.this)?;
18129        self.write(", ");
18130        self.generate_expression(&f.times)?;
18131        self.write(")");
18132        Ok(())
18133    }
18134
18135    fn generate_pad(&mut self, name: &str, f: &PadFunc) -> Result<()> {
18136        self.write_keyword(name);
18137        self.write("(");
18138        self.generate_expression(&f.this)?;
18139        self.write(", ");
18140        self.generate_expression(&f.length)?;
18141        if let Some(fill) = &f.fill {
18142            self.write(", ");
18143            self.generate_expression(fill)?;
18144        }
18145        self.write(")");
18146        Ok(())
18147    }
18148
18149    fn generate_split(&mut self, f: &SplitFunc) -> Result<()> {
18150        self.write_keyword("SPLIT");
18151        self.write("(");
18152        self.generate_expression(&f.this)?;
18153        self.write(", ");
18154        self.generate_expression(&f.delimiter)?;
18155        self.write(")");
18156        Ok(())
18157    }
18158
18159    fn generate_regexp_like(&mut self, f: &RegexpFunc) -> Result<()> {
18160        use crate::dialects::DialectType;
18161        // PostgreSQL uses ~ operator for regex matching
18162        if matches!(self.config.dialect, Some(DialectType::PostgreSQL)) && f.flags.is_none() {
18163            self.generate_expression(&f.this)?;
18164            self.write(" ~ ");
18165            self.generate_expression(&f.pattern)?;
18166        } else if matches!(self.config.dialect, Some(DialectType::Exasol)) && f.flags.is_none() {
18167            // Exasol uses REGEXP_LIKE as infix binary operator
18168            self.generate_expression(&f.this)?;
18169            self.write_keyword(" REGEXP_LIKE ");
18170            self.generate_expression(&f.pattern)?;
18171        } else if matches!(
18172            self.config.dialect,
18173            Some(DialectType::SingleStore)
18174                | Some(DialectType::Spark)
18175                | Some(DialectType::Hive)
18176                | Some(DialectType::Databricks)
18177        ) && f.flags.is_none()
18178        {
18179            // SingleStore/Spark/Hive/Databricks use RLIKE infix operator
18180            self.generate_expression(&f.this)?;
18181            self.write_keyword(" RLIKE ");
18182            self.generate_expression(&f.pattern)?;
18183        } else if matches!(self.config.dialect, Some(DialectType::StarRocks)) {
18184            // StarRocks uses REGEXP function syntax
18185            self.write_keyword("REGEXP");
18186            self.write("(");
18187            self.generate_expression(&f.this)?;
18188            self.write(", ");
18189            self.generate_expression(&f.pattern)?;
18190            if let Some(flags) = &f.flags {
18191                self.write(", ");
18192                self.generate_expression(flags)?;
18193            }
18194            self.write(")");
18195        } else {
18196            self.write_keyword("REGEXP_LIKE");
18197            self.write("(");
18198            self.generate_expression(&f.this)?;
18199            self.write(", ");
18200            self.generate_expression(&f.pattern)?;
18201            if let Some(flags) = &f.flags {
18202                self.write(", ");
18203                self.generate_expression(flags)?;
18204            }
18205            self.write(")");
18206        }
18207        Ok(())
18208    }
18209
18210    fn generate_regexp_replace(&mut self, f: &RegexpReplaceFunc) -> Result<()> {
18211        self.write_keyword("REGEXP_REPLACE");
18212        self.write("(");
18213        self.generate_expression(&f.this)?;
18214        self.write(", ");
18215        self.generate_expression(&f.pattern)?;
18216        self.write(", ");
18217        self.generate_expression(&f.replacement)?;
18218        if let Some(flags) = &f.flags {
18219            self.write(", ");
18220            self.generate_expression(flags)?;
18221        }
18222        self.write(")");
18223        Ok(())
18224    }
18225
18226    fn generate_regexp_extract(&mut self, f: &RegexpExtractFunc) -> Result<()> {
18227        self.write_keyword("REGEXP_EXTRACT");
18228        self.write("(");
18229        self.generate_expression(&f.this)?;
18230        self.write(", ");
18231        self.generate_expression(&f.pattern)?;
18232        if let Some(group) = &f.group {
18233            self.write(", ");
18234            self.generate_expression(group)?;
18235        }
18236        self.write(")");
18237        Ok(())
18238    }
18239
18240    // Math function generators
18241
18242    fn generate_round(&mut self, f: &RoundFunc) -> Result<()> {
18243        self.write_keyword("ROUND");
18244        self.write("(");
18245        self.generate_expression(&f.this)?;
18246        if let Some(decimals) = &f.decimals {
18247            self.write(", ");
18248            self.generate_expression(decimals)?;
18249        }
18250        self.write(")");
18251        Ok(())
18252    }
18253
18254    fn generate_floor(&mut self, f: &FloorFunc) -> Result<()> {
18255        self.write_keyword("FLOOR");
18256        self.write("(");
18257        self.generate_expression(&f.this)?;
18258        // Handle Druid-style FLOOR(time TO unit) syntax
18259        if let Some(to) = &f.to {
18260            self.write(" ");
18261            self.write_keyword("TO");
18262            self.write(" ");
18263            self.generate_expression(to)?;
18264        } else if let Some(scale) = &f.scale {
18265            self.write(", ");
18266            self.generate_expression(scale)?;
18267        }
18268        self.write(")");
18269        Ok(())
18270    }
18271
18272    fn generate_ceil(&mut self, f: &CeilFunc) -> Result<()> {
18273        self.write_keyword("CEIL");
18274        self.write("(");
18275        self.generate_expression(&f.this)?;
18276        // Handle Druid-style CEIL(time TO unit) syntax
18277        if let Some(to) = &f.to {
18278            self.write(" ");
18279            self.write_keyword("TO");
18280            self.write(" ");
18281            self.generate_expression(to)?;
18282        } else if let Some(decimals) = &f.decimals {
18283            self.write(", ");
18284            self.generate_expression(decimals)?;
18285        }
18286        self.write(")");
18287        Ok(())
18288    }
18289
18290    fn generate_log(&mut self, f: &LogFunc) -> Result<()> {
18291        use crate::expressions::Literal;
18292
18293        if let Some(base) = &f.base {
18294            // Check for LOG_BASE_FIRST = None dialects (Presto, Trino, ClickHouse, Athena)
18295            // These dialects use LOG2()/LOG10() instead of LOG(base, value)
18296            if self.is_log_base_none() {
18297                if matches!(base, Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(s) if s == "2")) {
18298                    self.write_func_name("LOG2");
18299                    self.write("(");
18300                    self.generate_expression(&f.this)?;
18301                    self.write(")");
18302                    return Ok(());
18303                } else if matches!(base, Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(s) if s == "10")) {
18304                    self.write_func_name("LOG10");
18305                    self.write("(");
18306                    self.generate_expression(&f.this)?;
18307                    self.write(")");
18308                    return Ok(());
18309                }
18310                // Other bases: fall through to LOG(base, value) — best effort
18311            }
18312
18313            self.write_func_name("LOG");
18314            self.write("(");
18315            if self.is_log_value_first() {
18316                // BigQuery, TSQL, Tableau, Fabric: LOG(value, base)
18317                self.generate_expression(&f.this)?;
18318                self.write(", ");
18319                self.generate_expression(base)?;
18320            } else {
18321                // Default (PostgreSQL, etc.): LOG(base, value)
18322                self.generate_expression(base)?;
18323                self.write(", ");
18324                self.generate_expression(&f.this)?;
18325            }
18326            self.write(")");
18327        } else {
18328            // Single arg: LOG(x) — unspecified base (log base 10 in default dialect)
18329            self.write_func_name("LOG");
18330            self.write("(");
18331            self.generate_expression(&f.this)?;
18332            self.write(")");
18333        }
18334        Ok(())
18335    }
18336
18337    /// Whether the target dialect uses LOG(value, base) order (value first).
18338    /// BigQuery, TSQL, Tableau, Fabric use LOG(value, base).
18339    fn is_log_value_first(&self) -> bool {
18340        use crate::dialects::DialectType;
18341        matches!(
18342            self.config.dialect,
18343            Some(DialectType::BigQuery)
18344                | Some(DialectType::TSQL)
18345                | Some(DialectType::Tableau)
18346                | Some(DialectType::Fabric)
18347        )
18348    }
18349
18350    /// Whether the target dialect has LOG_BASE_FIRST = None (uses LOG2/LOG10 instead).
18351    /// Presto, Trino, ClickHouse, Athena.
18352    fn is_log_base_none(&self) -> bool {
18353        use crate::dialects::DialectType;
18354        matches!(
18355            self.config.dialect,
18356            Some(DialectType::Presto)
18357                | Some(DialectType::Trino)
18358                | Some(DialectType::ClickHouse)
18359                | Some(DialectType::Athena)
18360        )
18361    }
18362
18363    // Date/time function generators
18364
18365    fn generate_current_time(&mut self, f: &CurrentTime) -> Result<()> {
18366        self.write_keyword("CURRENT_TIME");
18367        if let Some(precision) = f.precision {
18368            self.write(&format!("({})", precision));
18369        } else if matches!(
18370            self.config.dialect,
18371            Some(crate::dialects::DialectType::MySQL)
18372                | Some(crate::dialects::DialectType::SingleStore)
18373                | Some(crate::dialects::DialectType::TiDB)
18374        ) {
18375            self.write("()");
18376        }
18377        Ok(())
18378    }
18379
18380    fn generate_current_timestamp(&mut self, f: &CurrentTimestamp) -> Result<()> {
18381        use crate::dialects::DialectType;
18382
18383        // Oracle/Redshift SYSDATE handling
18384        if f.sysdate {
18385            match self.config.dialect {
18386                Some(DialectType::Oracle) | Some(DialectType::Redshift) => {
18387                    self.write_keyword("SYSDATE");
18388                    return Ok(());
18389                }
18390                Some(DialectType::Snowflake) => {
18391                    // Snowflake uses SYSDATE() function
18392                    self.write_keyword("SYSDATE");
18393                    self.write("()");
18394                    return Ok(());
18395                }
18396                _ => {
18397                    // Other dialects use CURRENT_TIMESTAMP for SYSDATE
18398                }
18399            }
18400        }
18401
18402        self.write_keyword("CURRENT_TIMESTAMP");
18403        // MySQL, Spark, Hive always use CURRENT_TIMESTAMP() with parentheses
18404        if let Some(precision) = f.precision {
18405            self.write(&format!("({})", precision));
18406        } else if matches!(
18407            self.config.dialect,
18408            Some(crate::dialects::DialectType::MySQL)
18409                | Some(crate::dialects::DialectType::SingleStore)
18410                | Some(crate::dialects::DialectType::TiDB)
18411                | Some(crate::dialects::DialectType::Spark)
18412                | Some(crate::dialects::DialectType::Hive)
18413                | Some(crate::dialects::DialectType::Databricks)
18414                | Some(crate::dialects::DialectType::ClickHouse)
18415                | Some(crate::dialects::DialectType::BigQuery)
18416                | Some(crate::dialects::DialectType::Snowflake)
18417                | Some(crate::dialects::DialectType::Exasol)
18418        ) {
18419            self.write("()");
18420        }
18421        Ok(())
18422    }
18423
18424    fn generate_at_time_zone(&mut self, f: &AtTimeZone) -> Result<()> {
18425        // Exasol uses CONVERT_TZ(timestamp, 'UTC', zone) instead of AT TIME ZONE
18426        if self.config.dialect == Some(DialectType::Exasol) {
18427            self.write_keyword("CONVERT_TZ");
18428            self.write("(");
18429            self.generate_expression(&f.this)?;
18430            self.write(", 'UTC', ");
18431            self.generate_expression(&f.zone)?;
18432            self.write(")");
18433            return Ok(());
18434        }
18435
18436        self.generate_expression(&f.this)?;
18437        self.write_space();
18438        self.write_keyword("AT TIME ZONE");
18439        self.write_space();
18440        self.generate_expression(&f.zone)?;
18441        Ok(())
18442    }
18443
18444    fn generate_date_add(&mut self, f: &DateAddFunc, name: &str) -> Result<()> {
18445        use crate::dialects::DialectType;
18446
18447        // Presto/Trino use DATE_ADD('unit', interval, date) format
18448        // with the interval cast to BIGINT when needed
18449        let is_presto_like = matches!(
18450            self.config.dialect,
18451            Some(DialectType::Presto) | Some(DialectType::Trino)
18452        );
18453
18454        if is_presto_like {
18455            self.write_keyword(name);
18456            self.write("(");
18457            // Unit as string literal
18458            self.write("'");
18459            self.write_simple_interval_unit(&f.unit, false);
18460            self.write("'");
18461            self.write(", ");
18462            // Interval - wrap in CAST(...AS BIGINT) if it doesn't return integer type
18463            let needs_cast = !self.returns_integer_type(&f.interval);
18464            if needs_cast {
18465                self.write_keyword("CAST");
18466                self.write("(");
18467            }
18468            self.generate_expression(&f.interval)?;
18469            if needs_cast {
18470                self.write_space();
18471                self.write_keyword("AS");
18472                self.write_space();
18473                self.write_keyword("BIGINT");
18474                self.write(")");
18475            }
18476            self.write(", ");
18477            self.generate_expression(&f.this)?;
18478            self.write(")");
18479        } else {
18480            self.write_keyword(name);
18481            self.write("(");
18482            self.generate_expression(&f.this)?;
18483            self.write(", ");
18484            self.write_keyword("INTERVAL");
18485            self.write_space();
18486            self.generate_expression(&f.interval)?;
18487            self.write_space();
18488            self.write_simple_interval_unit(&f.unit, false); // Use singular form for DATEADD
18489            self.write(")");
18490        }
18491        Ok(())
18492    }
18493
18494    /// Check if an expression returns an integer type (doesn't need cast to BIGINT in Presto DATE_ADD)
18495    /// This is a heuristic to avoid full type inference
18496    fn returns_integer_type(&self, expr: &Expression) -> bool {
18497        use crate::expressions::{DataType, Literal};
18498        match expr {
18499            // Integer literals (no decimal point)
18500            Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(_)) => { let Literal::Number(n) = lit.as_ref() else { unreachable!() }; !n.contains('.') },
18501
18502            // FLOOR(x) returns integer if x is integer
18503            Expression::Floor(f) => self.returns_integer_type(&f.this),
18504
18505            // ROUND(x) returns integer if x is integer
18506            Expression::Round(f) => {
18507                // Only if no decimals arg or it's returning an integer
18508                f.decimals.is_none() && self.returns_integer_type(&f.this)
18509            }
18510
18511            // SIGN returns integer if input is integer
18512            Expression::Sign(f) => self.returns_integer_type(&f.this),
18513
18514            // ABS returns the same type as input
18515            Expression::Abs(f) => self.returns_integer_type(&f.this),
18516
18517            // Arithmetic operations on integers return integers
18518            Expression::Mul(op) => {
18519                self.returns_integer_type(&op.left) && self.returns_integer_type(&op.right)
18520            }
18521            Expression::Add(op) => {
18522                self.returns_integer_type(&op.left) && self.returns_integer_type(&op.right)
18523            }
18524            Expression::Sub(op) => {
18525                self.returns_integer_type(&op.left) && self.returns_integer_type(&op.right)
18526            }
18527            Expression::Mod(op) => self.returns_integer_type(&op.left),
18528
18529            // CAST(x AS BIGINT/INT/INTEGER/SMALLINT/TINYINT) returns integer
18530            Expression::Cast(c) => matches!(
18531                &c.to,
18532                DataType::BigInt { .. }
18533                    | DataType::Int { .. }
18534                    | DataType::SmallInt { .. }
18535                    | DataType::TinyInt { .. }
18536            ),
18537
18538            // Negation: -x returns integer if x is integer
18539            Expression::Neg(op) => self.returns_integer_type(&op.this),
18540
18541            // Parenthesized expression
18542            Expression::Paren(p) => self.returns_integer_type(&p.this),
18543
18544            // Column references and most expressions are assumed to need casting
18545            // since we don't have full type information
18546            _ => false,
18547        }
18548    }
18549
18550    fn generate_datediff(&mut self, f: &DateDiffFunc) -> Result<()> {
18551        self.write_keyword("DATEDIFF");
18552        self.write("(");
18553        if let Some(unit) = &f.unit {
18554            self.write_simple_interval_unit(unit, false); // Use singular form for DATEDIFF
18555            self.write(", ");
18556        }
18557        self.generate_expression(&f.this)?;
18558        self.write(", ");
18559        self.generate_expression(&f.expression)?;
18560        self.write(")");
18561        Ok(())
18562    }
18563
18564    fn generate_date_trunc(&mut self, f: &DateTruncFunc) -> Result<()> {
18565        self.write_keyword("DATE_TRUNC");
18566        self.write("('");
18567        self.write_datetime_field(&f.unit);
18568        self.write("', ");
18569        self.generate_expression(&f.this)?;
18570        self.write(")");
18571        Ok(())
18572    }
18573
18574    fn generate_last_day(&mut self, f: &LastDayFunc) -> Result<()> {
18575        use crate::dialects::DialectType;
18576        use crate::expressions::DateTimeField;
18577
18578        self.write_keyword("LAST_DAY");
18579        self.write("(");
18580        self.generate_expression(&f.this)?;
18581        if let Some(unit) = &f.unit {
18582            self.write(", ");
18583            // BigQuery: strip week-start modifier from WEEK(SUNDAY), WEEK(MONDAY), etc.
18584            // WEEK(SUNDAY) -> WEEK
18585            if matches!(self.config.dialect, Some(DialectType::BigQuery)) {
18586                if let DateTimeField::WeekWithModifier(_) = unit {
18587                    self.write_keyword("WEEK");
18588                } else {
18589                    self.write_datetime_field(unit);
18590                }
18591            } else {
18592                self.write_datetime_field(unit);
18593            }
18594        }
18595        self.write(")");
18596        Ok(())
18597    }
18598
18599    fn generate_extract(&mut self, f: &ExtractFunc) -> Result<()> {
18600        // TSQL/Fabric use DATEPART(part, expr) instead of EXTRACT(part FROM expr)
18601        if matches!(
18602            self.config.dialect,
18603            Some(DialectType::TSQL) | Some(DialectType::Fabric)
18604        ) {
18605            self.write_keyword("DATEPART");
18606            self.write("(");
18607            self.write_datetime_field(&f.field);
18608            self.write(", ");
18609            self.generate_expression(&f.this)?;
18610            self.write(")");
18611            return Ok(());
18612        }
18613        self.write_keyword("EXTRACT");
18614        self.write("(");
18615        // Hive/Spark use lowercase datetime fields in EXTRACT
18616        if matches!(
18617            self.config.dialect,
18618            Some(DialectType::Hive) | Some(DialectType::Spark) | Some(DialectType::Databricks)
18619        ) {
18620            self.write_datetime_field_lower(&f.field);
18621        } else {
18622            self.write_datetime_field(&f.field);
18623        }
18624        self.write_space();
18625        self.write_keyword("FROM");
18626        self.write_space();
18627        self.generate_expression(&f.this)?;
18628        self.write(")");
18629        Ok(())
18630    }
18631
18632    fn generate_to_date(&mut self, f: &ToDateFunc) -> Result<()> {
18633        self.write_keyword("TO_DATE");
18634        self.write("(");
18635        self.generate_expression(&f.this)?;
18636        if let Some(format) = &f.format {
18637            self.write(", ");
18638            self.generate_expression(format)?;
18639        }
18640        self.write(")");
18641        Ok(())
18642    }
18643
18644    fn generate_to_timestamp(&mut self, f: &ToTimestampFunc) -> Result<()> {
18645        self.write_keyword("TO_TIMESTAMP");
18646        self.write("(");
18647        self.generate_expression(&f.this)?;
18648        if let Some(format) = &f.format {
18649            self.write(", ");
18650            self.generate_expression(format)?;
18651        }
18652        self.write(")");
18653        Ok(())
18654    }
18655
18656    // Control flow function generators
18657
18658    fn generate_if_func(&mut self, f: &IfFunc) -> Result<()> {
18659        use crate::dialects::DialectType;
18660
18661        // Generic mode: normalize IF to CASE WHEN
18662        if self.config.dialect.is_none() || self.config.dialect == Some(DialectType::Generic) {
18663            self.write_keyword("CASE WHEN");
18664            self.write_space();
18665            self.generate_expression(&f.condition)?;
18666            self.write_space();
18667            self.write_keyword("THEN");
18668            self.write_space();
18669            self.generate_expression(&f.true_value)?;
18670            if let Some(false_val) = &f.false_value {
18671                self.write_space();
18672                self.write_keyword("ELSE");
18673                self.write_space();
18674                self.generate_expression(false_val)?;
18675            }
18676            self.write_space();
18677            self.write_keyword("END");
18678            return Ok(());
18679        }
18680
18681        // Exasol uses IF condition THEN true_value ELSE false_value ENDIF syntax
18682        if self.config.dialect == Some(DialectType::Exasol) {
18683            self.write_keyword("IF");
18684            self.write_space();
18685            self.generate_expression(&f.condition)?;
18686            self.write_space();
18687            self.write_keyword("THEN");
18688            self.write_space();
18689            self.generate_expression(&f.true_value)?;
18690            if let Some(false_val) = &f.false_value {
18691                self.write_space();
18692                self.write_keyword("ELSE");
18693                self.write_space();
18694                self.generate_expression(false_val)?;
18695            }
18696            self.write_space();
18697            self.write_keyword("ENDIF");
18698            return Ok(());
18699        }
18700
18701        // Choose function name based on target dialect
18702        let func_name = match self.config.dialect {
18703            Some(DialectType::Snowflake) => "IFF",
18704            Some(DialectType::SQLite) | Some(DialectType::TSQL) => "IIF",
18705            Some(DialectType::Drill) => "`IF`",
18706            _ => "IF",
18707        };
18708        self.write(func_name);
18709        self.write("(");
18710        self.generate_expression(&f.condition)?;
18711        self.write(", ");
18712        self.generate_expression(&f.true_value)?;
18713        if let Some(false_val) = &f.false_value {
18714            self.write(", ");
18715            self.generate_expression(false_val)?;
18716        }
18717        self.write(")");
18718        Ok(())
18719    }
18720
18721    fn generate_nvl2(&mut self, f: &Nvl2Func) -> Result<()> {
18722        self.write_keyword("NVL2");
18723        self.write("(");
18724        self.generate_expression(&f.this)?;
18725        self.write(", ");
18726        self.generate_expression(&f.true_value)?;
18727        self.write(", ");
18728        self.generate_expression(&f.false_value)?;
18729        self.write(")");
18730        Ok(())
18731    }
18732
18733    // Typed aggregate function generators
18734
18735    fn generate_count(&mut self, f: &CountFunc) -> Result<()> {
18736        // Use normalize_functions for COUNT to respect ClickHouse case preservation
18737        let count_name = match self.config.normalize_functions {
18738            NormalizeFunctions::Upper => "COUNT".to_string(),
18739            NormalizeFunctions::Lower => "count".to_string(),
18740            NormalizeFunctions::None => f
18741                .original_name
18742                .clone()
18743                .unwrap_or_else(|| "COUNT".to_string()),
18744        };
18745        self.write(&count_name);
18746        self.write("(");
18747        if f.distinct {
18748            self.write_keyword("DISTINCT");
18749            self.write_space();
18750        }
18751        if f.star {
18752            self.write("*");
18753        } else if let Some(ref expr) = f.this {
18754            // For COUNT(DISTINCT a, b), unwrap the Tuple to avoid extra parentheses
18755            if let Expression::Tuple(tuple) = expr {
18756                // Check if we need to transform multi-arg COUNT DISTINCT
18757                // When dialect doesn't support multi_arg_distinct, transform:
18758                // COUNT(DISTINCT a, b) -> COUNT(DISTINCT CASE WHEN a IS NULL THEN NULL WHEN b IS NULL THEN NULL ELSE (a, b) END)
18759                let needs_transform =
18760                    f.distinct && tuple.expressions.len() > 1 && !self.config.multi_arg_distinct;
18761
18762                if needs_transform {
18763                    // Generate: CASE WHEN a IS NULL THEN NULL WHEN b IS NULL THEN NULL ELSE (a, b) END
18764                    self.write_keyword("CASE");
18765                    for e in &tuple.expressions {
18766                        self.write_space();
18767                        self.write_keyword("WHEN");
18768                        self.write_space();
18769                        self.generate_expression(e)?;
18770                        self.write_space();
18771                        self.write_keyword("IS NULL THEN NULL");
18772                    }
18773                    self.write_space();
18774                    self.write_keyword("ELSE");
18775                    self.write(" (");
18776                    for (i, e) in tuple.expressions.iter().enumerate() {
18777                        if i > 0 {
18778                            self.write(", ");
18779                        }
18780                        self.generate_expression(e)?;
18781                    }
18782                    self.write(")");
18783                    self.write_space();
18784                    self.write_keyword("END");
18785                } else {
18786                    for (i, e) in tuple.expressions.iter().enumerate() {
18787                        if i > 0 {
18788                            self.write(", ");
18789                        }
18790                        self.generate_expression(e)?;
18791                    }
18792                }
18793            } else {
18794                self.generate_expression(expr)?;
18795            }
18796        }
18797        // RESPECT NULLS / IGNORE NULLS
18798        if let Some(ignore) = f.ignore_nulls {
18799            self.write_space();
18800            if ignore {
18801                self.write_keyword("IGNORE NULLS");
18802            } else {
18803                self.write_keyword("RESPECT NULLS");
18804            }
18805        }
18806        self.write(")");
18807        if let Some(ref filter) = f.filter {
18808            self.write_space();
18809            self.write_keyword("FILTER");
18810            self.write("(");
18811            self.write_keyword("WHERE");
18812            self.write_space();
18813            self.generate_expression(filter)?;
18814            self.write(")");
18815        }
18816        Ok(())
18817    }
18818
18819    fn generate_agg_func(&mut self, name: &str, f: &AggFunc) -> Result<()> {
18820        // Apply function name normalization based on config
18821        let func_name: Cow<'_, str> = match self.config.normalize_functions {
18822            NormalizeFunctions::Upper => Cow::Owned(name.to_ascii_uppercase()),
18823            NormalizeFunctions::Lower => Cow::Owned(name.to_ascii_lowercase()),
18824            NormalizeFunctions::None => {
18825                // Use the original function name from parsing if available,
18826                // otherwise fall back to lowercase of the hardcoded constant
18827                if let Some(ref original) = f.name {
18828                    Cow::Owned(original.clone())
18829                } else {
18830                    Cow::Owned(name.to_ascii_lowercase())
18831                }
18832            }
18833        };
18834        self.write(func_name.as_ref());
18835        self.write("(");
18836        if f.distinct {
18837            self.write_keyword("DISTINCT");
18838            self.write_space();
18839        }
18840        // Skip generating the expression if it's a NULL placeholder for zero-arg aggregates like MODE()
18841        if !matches!(f.this, Expression::Null(_)) {
18842            self.generate_expression(&f.this)?;
18843        }
18844        // Generate IGNORE NULLS / RESPECT NULLS inside parens if config says so (BigQuery style)
18845        // DuckDB doesn't support IGNORE NULLS / RESPECT NULLS in aggregate functions - skip it
18846        if self.config.ignore_nulls_in_func
18847            && !matches!(self.config.dialect, Some(DialectType::DuckDB))
18848        {
18849            match f.ignore_nulls {
18850                Some(true) => {
18851                    self.write_space();
18852                    self.write_keyword("IGNORE NULLS");
18853                }
18854                Some(false) => {
18855                    self.write_space();
18856                    self.write_keyword("RESPECT NULLS");
18857                }
18858                None => {}
18859            }
18860        }
18861        // Generate HAVING MAX/MIN if present (BigQuery syntax)
18862        // e.g., ANY_VALUE(fruit HAVING MAX sold)
18863        if let Some((ref expr, is_max)) = f.having_max {
18864            self.write_space();
18865            self.write_keyword("HAVING");
18866            self.write_space();
18867            if is_max {
18868                self.write_keyword("MAX");
18869            } else {
18870                self.write_keyword("MIN");
18871            }
18872            self.write_space();
18873            self.generate_expression(expr)?;
18874        }
18875        // Generate ORDER BY if present (for aggregates like ARRAY_AGG(x ORDER BY y))
18876        if !f.order_by.is_empty() {
18877            self.write_space();
18878            self.write_keyword("ORDER BY");
18879            self.write_space();
18880            for (i, ord) in f.order_by.iter().enumerate() {
18881                if i > 0 {
18882                    self.write(", ");
18883                }
18884                self.generate_ordered(ord)?;
18885            }
18886        }
18887        // Generate LIMIT if present (for aggregates like ARRAY_AGG(x ORDER BY y LIMIT 2))
18888        if let Some(ref limit) = f.limit {
18889            self.write_space();
18890            self.write_keyword("LIMIT");
18891            self.write_space();
18892            // Check if this is a Tuple representing LIMIT offset, count
18893            if let Expression::Tuple(t) = limit.as_ref() {
18894                if t.expressions.len() == 2 {
18895                    self.generate_expression(&t.expressions[0])?;
18896                    self.write(", ");
18897                    self.generate_expression(&t.expressions[1])?;
18898                } else {
18899                    self.generate_expression(limit)?;
18900                }
18901            } else {
18902                self.generate_expression(limit)?;
18903            }
18904        }
18905        self.write(")");
18906        // Generate IGNORE NULLS / RESPECT NULLS outside parens if config says so (standard style)
18907        // DuckDB doesn't support IGNORE NULLS / RESPECT NULLS in aggregate functions - skip it
18908        if !self.config.ignore_nulls_in_func
18909            && !matches!(self.config.dialect, Some(DialectType::DuckDB))
18910        {
18911            match f.ignore_nulls {
18912                Some(true) => {
18913                    self.write_space();
18914                    self.write_keyword("IGNORE NULLS");
18915                }
18916                Some(false) => {
18917                    self.write_space();
18918                    self.write_keyword("RESPECT NULLS");
18919                }
18920                None => {}
18921            }
18922        }
18923        if let Some(ref filter) = f.filter {
18924            self.write_space();
18925            self.write_keyword("FILTER");
18926            self.write("(");
18927            self.write_keyword("WHERE");
18928            self.write_space();
18929            self.generate_expression(filter)?;
18930            self.write(")");
18931        }
18932        Ok(())
18933    }
18934
18935    /// Generate FIRST/LAST aggregate functions with Hive/Spark2-style boolean argument
18936    /// for IGNORE NULLS. In Hive/Spark2, `FIRST(col) IGNORE NULLS` is written as `FIRST(col, TRUE)`.
18937    fn generate_agg_func_with_ignore_nulls_bool(
18938        &mut self,
18939        name: &str,
18940        f: &AggFunc,
18941    ) -> Result<()> {
18942        // For Hive/Spark2 dialects, convert IGNORE NULLS to boolean TRUE argument
18943        if matches!(
18944            self.config.dialect,
18945            Some(DialectType::Hive)
18946        ) && f.ignore_nulls == Some(true)
18947        {
18948            // Create a modified copy without ignore_nulls, add TRUE as part of the output
18949            let func_name: Cow<'_, str> = match self.config.normalize_functions {
18950                NormalizeFunctions::Upper => Cow::Owned(name.to_ascii_uppercase()),
18951                NormalizeFunctions::Lower => Cow::Owned(name.to_ascii_lowercase()),
18952                NormalizeFunctions::None => {
18953                    if let Some(ref original) = f.name {
18954                        Cow::Owned(original.clone())
18955                    } else {
18956                        Cow::Owned(name.to_ascii_lowercase())
18957                    }
18958                }
18959            };
18960            self.write(func_name.as_ref());
18961            self.write("(");
18962            if f.distinct {
18963                self.write_keyword("DISTINCT");
18964                self.write_space();
18965            }
18966            if !matches!(f.this, Expression::Null(_)) {
18967                self.generate_expression(&f.this)?;
18968            }
18969            self.write(", ");
18970            self.write_keyword("TRUE");
18971            self.write(")");
18972            return Ok(());
18973        }
18974        self.generate_agg_func(name, f)
18975    }
18976
18977    fn generate_group_concat(&mut self, f: &GroupConcatFunc) -> Result<()> {
18978        self.write_keyword("GROUP_CONCAT");
18979        self.write("(");
18980        if f.distinct {
18981            self.write_keyword("DISTINCT");
18982            self.write_space();
18983        }
18984        self.generate_expression(&f.this)?;
18985        if let Some(ref order_by) = f.order_by {
18986            self.write_space();
18987            self.write_keyword("ORDER BY");
18988            self.write_space();
18989            for (i, ord) in order_by.iter().enumerate() {
18990                if i > 0 {
18991                    self.write(", ");
18992                }
18993                self.generate_ordered(ord)?;
18994            }
18995        }
18996        if let Some(ref sep) = f.separator {
18997            // SQLite uses GROUP_CONCAT(x, sep) syntax (comma-separated)
18998            // MySQL and others use GROUP_CONCAT(x SEPARATOR sep) syntax
18999            if matches!(
19000                self.config.dialect,
19001                Some(crate::dialects::DialectType::SQLite)
19002            ) {
19003                self.write(", ");
19004                self.generate_expression(sep)?;
19005            } else {
19006                self.write_space();
19007                self.write_keyword("SEPARATOR");
19008                self.write_space();
19009                self.generate_expression(sep)?;
19010            }
19011        }
19012        self.write(")");
19013        if let Some(ref filter) = f.filter {
19014            self.write_space();
19015            self.write_keyword("FILTER");
19016            self.write("(");
19017            self.write_keyword("WHERE");
19018            self.write_space();
19019            self.generate_expression(filter)?;
19020            self.write(")");
19021        }
19022        Ok(())
19023    }
19024
19025    fn generate_string_agg(&mut self, f: &StringAggFunc) -> Result<()> {
19026        let is_tsql = matches!(
19027            self.config.dialect,
19028            Some(crate::dialects::DialectType::TSQL)
19029        );
19030        self.write_keyword("STRING_AGG");
19031        self.write("(");
19032        if f.distinct {
19033            self.write_keyword("DISTINCT");
19034            self.write_space();
19035        }
19036        self.generate_expression(&f.this)?;
19037        if let Some(ref separator) = f.separator {
19038            self.write(", ");
19039            self.generate_expression(separator)?;
19040        }
19041        // For TSQL, ORDER BY goes in WITHIN GROUP clause after the closing paren
19042        if !is_tsql {
19043            if let Some(ref order_by) = f.order_by {
19044                self.write_space();
19045                self.write_keyword("ORDER BY");
19046                self.write_space();
19047                for (i, ord) in order_by.iter().enumerate() {
19048                    if i > 0 {
19049                        self.write(", ");
19050                    }
19051                    self.generate_ordered(ord)?;
19052                }
19053            }
19054        }
19055        if let Some(ref limit) = f.limit {
19056            self.write_space();
19057            self.write_keyword("LIMIT");
19058            self.write_space();
19059            self.generate_expression(limit)?;
19060        }
19061        self.write(")");
19062        // TSQL uses WITHIN GROUP (ORDER BY ...) after the function call
19063        if is_tsql {
19064            if let Some(ref order_by) = f.order_by {
19065                self.write_space();
19066                self.write_keyword("WITHIN GROUP");
19067                self.write(" (");
19068                self.write_keyword("ORDER BY");
19069                self.write_space();
19070                for (i, ord) in order_by.iter().enumerate() {
19071                    if i > 0 {
19072                        self.write(", ");
19073                    }
19074                    self.generate_ordered(ord)?;
19075                }
19076                self.write(")");
19077            }
19078        }
19079        if let Some(ref filter) = f.filter {
19080            self.write_space();
19081            self.write_keyword("FILTER");
19082            self.write("(");
19083            self.write_keyword("WHERE");
19084            self.write_space();
19085            self.generate_expression(filter)?;
19086            self.write(")");
19087        }
19088        Ok(())
19089    }
19090
19091    fn generate_listagg(&mut self, f: &ListAggFunc) -> Result<()> {
19092        use crate::dialects::DialectType;
19093        self.write_keyword("LISTAGG");
19094        self.write("(");
19095        if f.distinct {
19096            self.write_keyword("DISTINCT");
19097            self.write_space();
19098        }
19099        self.generate_expression(&f.this)?;
19100        if let Some(ref sep) = f.separator {
19101            self.write(", ");
19102            self.generate_expression(sep)?;
19103        } else if matches!(
19104            self.config.dialect,
19105            Some(DialectType::Trino) | Some(DialectType::Presto)
19106        ) {
19107            // Trino/Presto require explicit separator; default to ','
19108            self.write(", ','");
19109        }
19110        if let Some(ref overflow) = f.on_overflow {
19111            self.write_space();
19112            self.write_keyword("ON OVERFLOW");
19113            self.write_space();
19114            match overflow {
19115                ListAggOverflow::Error => self.write_keyword("ERROR"),
19116                ListAggOverflow::Truncate { filler, with_count } => {
19117                    self.write_keyword("TRUNCATE");
19118                    if let Some(ref fill) = filler {
19119                        self.write_space();
19120                        self.generate_expression(fill)?;
19121                    }
19122                    if *with_count {
19123                        self.write_space();
19124                        self.write_keyword("WITH COUNT");
19125                    } else {
19126                        self.write_space();
19127                        self.write_keyword("WITHOUT COUNT");
19128                    }
19129                }
19130            }
19131        }
19132        self.write(")");
19133        if let Some(ref order_by) = f.order_by {
19134            self.write_space();
19135            self.write_keyword("WITHIN GROUP");
19136            self.write(" (");
19137            self.write_keyword("ORDER BY");
19138            self.write_space();
19139            for (i, ord) in order_by.iter().enumerate() {
19140                if i > 0 {
19141                    self.write(", ");
19142                }
19143                self.generate_ordered(ord)?;
19144            }
19145            self.write(")");
19146        }
19147        if let Some(ref filter) = f.filter {
19148            self.write_space();
19149            self.write_keyword("FILTER");
19150            self.write("(");
19151            self.write_keyword("WHERE");
19152            self.write_space();
19153            self.generate_expression(filter)?;
19154            self.write(")");
19155        }
19156        Ok(())
19157    }
19158
19159    fn generate_sum_if(&mut self, f: &SumIfFunc) -> Result<()> {
19160        self.write_keyword("SUM_IF");
19161        self.write("(");
19162        self.generate_expression(&f.this)?;
19163        self.write(", ");
19164        self.generate_expression(&f.condition)?;
19165        self.write(")");
19166        if let Some(ref filter) = f.filter {
19167            self.write_space();
19168            self.write_keyword("FILTER");
19169            self.write("(");
19170            self.write_keyword("WHERE");
19171            self.write_space();
19172            self.generate_expression(filter)?;
19173            self.write(")");
19174        }
19175        Ok(())
19176    }
19177
19178    fn generate_approx_percentile(&mut self, f: &ApproxPercentileFunc) -> Result<()> {
19179        self.write_keyword("APPROX_PERCENTILE");
19180        self.write("(");
19181        self.generate_expression(&f.this)?;
19182        self.write(", ");
19183        self.generate_expression(&f.percentile)?;
19184        if let Some(ref acc) = f.accuracy {
19185            self.write(", ");
19186            self.generate_expression(acc)?;
19187        }
19188        self.write(")");
19189        if let Some(ref filter) = f.filter {
19190            self.write_space();
19191            self.write_keyword("FILTER");
19192            self.write("(");
19193            self.write_keyword("WHERE");
19194            self.write_space();
19195            self.generate_expression(filter)?;
19196            self.write(")");
19197        }
19198        Ok(())
19199    }
19200
19201    fn generate_percentile(&mut self, name: &str, f: &PercentileFunc) -> Result<()> {
19202        self.write_keyword(name);
19203        self.write("(");
19204        self.generate_expression(&f.percentile)?;
19205        self.write(")");
19206        if let Some(ref order_by) = f.order_by {
19207            self.write_space();
19208            self.write_keyword("WITHIN GROUP");
19209            self.write(" (");
19210            self.write_keyword("ORDER BY");
19211            self.write_space();
19212            self.generate_expression(&f.this)?;
19213            for ord in order_by.iter() {
19214                if ord.desc {
19215                    self.write_space();
19216                    self.write_keyword("DESC");
19217                }
19218            }
19219            self.write(")");
19220        }
19221        if let Some(ref filter) = f.filter {
19222            self.write_space();
19223            self.write_keyword("FILTER");
19224            self.write("(");
19225            self.write_keyword("WHERE");
19226            self.write_space();
19227            self.generate_expression(filter)?;
19228            self.write(")");
19229        }
19230        Ok(())
19231    }
19232
19233    // Window function generators
19234
19235    fn generate_ntile(&mut self, f: &NTileFunc) -> Result<()> {
19236        self.write_keyword("NTILE");
19237        self.write("(");
19238        if let Some(num_buckets) = &f.num_buckets {
19239            self.generate_expression(num_buckets)?;
19240        }
19241        if let Some(order_by) = &f.order_by {
19242            self.write_keyword(" ORDER BY ");
19243            for (i, ob) in order_by.iter().enumerate() {
19244                if i > 0 {
19245                    self.write(", ");
19246                }
19247                self.generate_ordered(ob)?;
19248            }
19249        }
19250        self.write(")");
19251        Ok(())
19252    }
19253
19254    fn generate_lead_lag(&mut self, name: &str, f: &LeadLagFunc) -> Result<()> {
19255        self.write_keyword(name);
19256        self.write("(");
19257        self.generate_expression(&f.this)?;
19258        if let Some(ref offset) = f.offset {
19259            self.write(", ");
19260            self.generate_expression(offset)?;
19261            if let Some(ref default) = f.default {
19262                self.write(", ");
19263                self.generate_expression(default)?;
19264            }
19265        }
19266        // IGNORE NULLS / RESPECT NULLS inside parens for dialects like BigQuery
19267        if self.config.ignore_nulls_in_func {
19268            match f.ignore_nulls {
19269                Some(true) => {
19270                    self.write_space();
19271                    self.write_keyword("IGNORE NULLS");
19272                }
19273                Some(false) => {
19274                    self.write_space();
19275                    self.write_keyword("RESPECT NULLS");
19276                }
19277                None => {}
19278            }
19279        }
19280        self.write(")");
19281        // IGNORE NULLS / RESPECT NULLS outside parens for other dialects
19282        if !self.config.ignore_nulls_in_func {
19283            match f.ignore_nulls {
19284                Some(true) => {
19285                    self.write_space();
19286                    self.write_keyword("IGNORE NULLS");
19287                }
19288                Some(false) => {
19289                    self.write_space();
19290                    self.write_keyword("RESPECT NULLS");
19291                }
19292                None => {}
19293            }
19294        }
19295        Ok(())
19296    }
19297
19298    fn generate_value_func(&mut self, name: &str, f: &ValueFunc) -> Result<()> {
19299        self.write_keyword(name);
19300        self.write("(");
19301        self.generate_expression(&f.this)?;
19302        // ORDER BY inside parens (e.g., DuckDB: LAST_VALUE(x ORDER BY x))
19303        if !f.order_by.is_empty() {
19304            self.write_space();
19305            self.write_keyword("ORDER BY");
19306            self.write_space();
19307            for (i, ordered) in f.order_by.iter().enumerate() {
19308                if i > 0 {
19309                    self.write(", ");
19310                }
19311                self.generate_ordered(ordered)?;
19312            }
19313        }
19314        // IGNORE NULLS / RESPECT NULLS inside parens for dialects like BigQuery, DuckDB
19315        if self.config.ignore_nulls_in_func {
19316            match f.ignore_nulls {
19317                Some(true) => {
19318                    self.write_space();
19319                    self.write_keyword("IGNORE NULLS");
19320                }
19321                Some(false) => {
19322                    self.write_space();
19323                    self.write_keyword("RESPECT NULLS");
19324                }
19325                None => {}
19326            }
19327        }
19328        self.write(")");
19329        // IGNORE NULLS / RESPECT NULLS outside parens for other dialects
19330        if !self.config.ignore_nulls_in_func {
19331            match f.ignore_nulls {
19332                Some(true) => {
19333                    self.write_space();
19334                    self.write_keyword("IGNORE NULLS");
19335                }
19336                Some(false) => {
19337                    self.write_space();
19338                    self.write_keyword("RESPECT NULLS");
19339                }
19340                None => {}
19341            }
19342        }
19343        Ok(())
19344    }
19345
19346    /// Generate FIRST_VALUE/LAST_VALUE with Hive/Spark2-style boolean argument for IGNORE NULLS.
19347    /// In Hive/Spark2, `FIRST_VALUE(col) IGNORE NULLS` is written as `FIRST_VALUE(col, TRUE)`.
19348    fn generate_value_func_with_ignore_nulls_bool(
19349        &mut self,
19350        name: &str,
19351        f: &ValueFunc,
19352    ) -> Result<()> {
19353        if matches!(
19354            self.config.dialect,
19355            Some(DialectType::Hive)
19356        ) && f.ignore_nulls == Some(true)
19357        {
19358            self.write_keyword(name);
19359            self.write("(");
19360            self.generate_expression(&f.this)?;
19361            self.write(", ");
19362            self.write_keyword("TRUE");
19363            self.write(")");
19364            return Ok(());
19365        }
19366        self.generate_value_func(name, f)
19367    }
19368
19369    fn generate_nth_value(&mut self, f: &NthValueFunc) -> Result<()> {
19370        self.write_keyword("NTH_VALUE");
19371        self.write("(");
19372        self.generate_expression(&f.this)?;
19373        self.write(", ");
19374        self.generate_expression(&f.offset)?;
19375        // IGNORE NULLS / RESPECT NULLS inside parens for dialects like BigQuery, DuckDB
19376        if self.config.ignore_nulls_in_func {
19377            match f.ignore_nulls {
19378                Some(true) => {
19379                    self.write_space();
19380                    self.write_keyword("IGNORE NULLS");
19381                }
19382                Some(false) => {
19383                    self.write_space();
19384                    self.write_keyword("RESPECT NULLS");
19385                }
19386                None => {}
19387            }
19388        }
19389        self.write(")");
19390        // FROM FIRST / FROM LAST (Snowflake-specific, before IGNORE/RESPECT NULLS)
19391        if matches!(
19392            self.config.dialect,
19393            Some(crate::dialects::DialectType::Snowflake)
19394        ) {
19395            match f.from_first {
19396                Some(true) => {
19397                    self.write_space();
19398                    self.write_keyword("FROM FIRST");
19399                }
19400                Some(false) => {
19401                    self.write_space();
19402                    self.write_keyword("FROM LAST");
19403                }
19404                None => {}
19405            }
19406        }
19407        // IGNORE NULLS / RESPECT NULLS outside parens for other dialects
19408        if !self.config.ignore_nulls_in_func {
19409            match f.ignore_nulls {
19410                Some(true) => {
19411                    self.write_space();
19412                    self.write_keyword("IGNORE NULLS");
19413                }
19414                Some(false) => {
19415                    self.write_space();
19416                    self.write_keyword("RESPECT NULLS");
19417                }
19418                None => {}
19419            }
19420        }
19421        Ok(())
19422    }
19423
19424    // Additional string function generators
19425
19426    fn generate_position(&mut self, f: &PositionFunc) -> Result<()> {
19427        // Standard syntax: POSITION(substr IN str)
19428        // ClickHouse prefers comma syntax with reversed arg order: POSITION(str, substr[, start])
19429        if matches!(
19430            self.config.dialect,
19431            Some(crate::dialects::DialectType::ClickHouse)
19432        ) {
19433            self.write_keyword("POSITION");
19434            self.write("(");
19435            self.generate_expression(&f.string)?;
19436            self.write(", ");
19437            self.generate_expression(&f.substring)?;
19438            if let Some(ref start) = f.start {
19439                self.write(", ");
19440                self.generate_expression(start)?;
19441            }
19442            self.write(")");
19443            return Ok(());
19444        }
19445
19446        self.write_keyword("POSITION");
19447        self.write("(");
19448        self.generate_expression(&f.substring)?;
19449        self.write_space();
19450        self.write_keyword("IN");
19451        self.write_space();
19452        self.generate_expression(&f.string)?;
19453        if let Some(ref start) = f.start {
19454            self.write(", ");
19455            self.generate_expression(start)?;
19456        }
19457        self.write(")");
19458        Ok(())
19459    }
19460
19461    // Additional math function generators
19462
19463    fn generate_rand(&mut self, f: &Rand) -> Result<()> {
19464        // Teradata RANDOM(lower, upper)
19465        if f.lower.is_some() || f.upper.is_some() {
19466            self.write_keyword("RANDOM");
19467            self.write("(");
19468            if let Some(ref lower) = f.lower {
19469                self.generate_expression(lower)?;
19470            }
19471            if let Some(ref upper) = f.upper {
19472                self.write(", ");
19473                self.generate_expression(upper)?;
19474            }
19475            self.write(")");
19476            return Ok(());
19477        }
19478        // Snowflake uses RANDOM instead of RAND, DuckDB uses RANDOM without seed
19479        let func_name = match self.config.dialect {
19480            Some(crate::dialects::DialectType::Snowflake)
19481            | Some(crate::dialects::DialectType::DuckDB) => "RANDOM",
19482            _ => "RAND",
19483        };
19484        self.write_keyword(func_name);
19485        self.write("(");
19486        // DuckDB doesn't support seeded RANDOM, so skip the seed
19487        if !matches!(
19488            self.config.dialect,
19489            Some(crate::dialects::DialectType::DuckDB)
19490        ) {
19491            if let Some(ref seed) = f.seed {
19492                self.generate_expression(seed)?;
19493            }
19494        }
19495        self.write(")");
19496        Ok(())
19497    }
19498
19499    fn generate_truncate_func(&mut self, f: &TruncateFunc) -> Result<()> {
19500        self.write_keyword("TRUNCATE");
19501        self.write("(");
19502        self.generate_expression(&f.this)?;
19503        if let Some(ref decimals) = f.decimals {
19504            self.write(", ");
19505            self.generate_expression(decimals)?;
19506        }
19507        self.write(")");
19508        Ok(())
19509    }
19510
19511    // Control flow generators
19512
19513    fn generate_decode(&mut self, f: &DecodeFunc) -> Result<()> {
19514        self.write_keyword("DECODE");
19515        self.write("(");
19516        self.generate_expression(&f.this)?;
19517        for (search, result) in &f.search_results {
19518            self.write(", ");
19519            self.generate_expression(search)?;
19520            self.write(", ");
19521            self.generate_expression(result)?;
19522        }
19523        if let Some(ref default) = f.default {
19524            self.write(", ");
19525            self.generate_expression(default)?;
19526        }
19527        self.write(")");
19528        Ok(())
19529    }
19530
19531    // Date/time function generators
19532
19533    fn generate_date_format(&mut self, name: &str, f: &DateFormatFunc) -> Result<()> {
19534        self.write_keyword(name);
19535        self.write("(");
19536        self.generate_expression(&f.this)?;
19537        self.write(", ");
19538        self.generate_expression(&f.format)?;
19539        self.write(")");
19540        Ok(())
19541    }
19542
19543    fn generate_from_unixtime(&mut self, f: &FromUnixtimeFunc) -> Result<()> {
19544        self.write_keyword("FROM_UNIXTIME");
19545        self.write("(");
19546        self.generate_expression(&f.this)?;
19547        if let Some(ref format) = f.format {
19548            self.write(", ");
19549            self.generate_expression(format)?;
19550        }
19551        self.write(")");
19552        Ok(())
19553    }
19554
19555    fn generate_unix_timestamp(&mut self, f: &UnixTimestampFunc) -> Result<()> {
19556        self.write_keyword("UNIX_TIMESTAMP");
19557        self.write("(");
19558        if let Some(ref expr) = f.this {
19559            self.generate_expression(expr)?;
19560            if let Some(ref format) = f.format {
19561                self.write(", ");
19562                self.generate_expression(format)?;
19563            }
19564        } else if matches!(
19565            self.config.dialect,
19566            Some(DialectType::Spark) | Some(DialectType::Hive) | Some(DialectType::Databricks)
19567        ) {
19568            // Spark/Hive: UNIX_TIMESTAMP() -> UNIX_TIMESTAMP(CURRENT_TIMESTAMP())
19569            self.write_keyword("CURRENT_TIMESTAMP");
19570            self.write("()");
19571        }
19572        self.write(")");
19573        Ok(())
19574    }
19575
19576    fn generate_make_date(&mut self, f: &MakeDateFunc) -> Result<()> {
19577        self.write_keyword("MAKE_DATE");
19578        self.write("(");
19579        self.generate_expression(&f.year)?;
19580        self.write(", ");
19581        self.generate_expression(&f.month)?;
19582        self.write(", ");
19583        self.generate_expression(&f.day)?;
19584        self.write(")");
19585        Ok(())
19586    }
19587
19588    fn generate_make_timestamp(&mut self, f: &MakeTimestampFunc) -> Result<()> {
19589        self.write_keyword("MAKE_TIMESTAMP");
19590        self.write("(");
19591        self.generate_expression(&f.year)?;
19592        self.write(", ");
19593        self.generate_expression(&f.month)?;
19594        self.write(", ");
19595        self.generate_expression(&f.day)?;
19596        self.write(", ");
19597        self.generate_expression(&f.hour)?;
19598        self.write(", ");
19599        self.generate_expression(&f.minute)?;
19600        self.write(", ");
19601        self.generate_expression(&f.second)?;
19602        if let Some(ref tz) = f.timezone {
19603            self.write(", ");
19604            self.generate_expression(tz)?;
19605        }
19606        self.write(")");
19607        Ok(())
19608    }
19609
19610    /// Extract field names from a struct expression (either Struct or Function named STRUCT with Alias args)
19611    fn extract_struct_field_names(expr: &Expression) -> Option<Vec<String>> {
19612        match expr {
19613            Expression::Struct(s) => {
19614                if s.fields.iter().all(|(name, _)| name.is_some()) {
19615                    Some(
19616                        s.fields
19617                            .iter()
19618                            .map(|(name, _)| name.as_deref().unwrap_or("").to_string())
19619                            .collect(),
19620                    )
19621                } else {
19622                    None
19623                }
19624            }
19625            Expression::Function(f) if f.name.eq_ignore_ascii_case("STRUCT") => {
19626                // Check if all args are Alias (named fields)
19627                if f.args.iter().all(|a| matches!(a, Expression::Alias(_))) {
19628                    Some(
19629                        f.args
19630                            .iter()
19631                            .filter_map(|a| {
19632                                if let Expression::Alias(alias) = a {
19633                                    Some(alias.alias.name.clone())
19634                                } else {
19635                                    None
19636                                }
19637                            })
19638                            .collect(),
19639                    )
19640                } else {
19641                    None
19642                }
19643            }
19644            _ => None,
19645        }
19646    }
19647
19648    /// Check if a struct expression has any unnamed fields
19649    fn struct_has_unnamed_fields(expr: &Expression) -> bool {
19650        match expr {
19651            Expression::Struct(s) => s.fields.iter().any(|(name, _)| name.is_none()),
19652            Expression::Function(f) if f.name.eq_ignore_ascii_case("STRUCT") => {
19653                f.args.iter().any(|a| !matches!(a, Expression::Alias(_)))
19654            }
19655            _ => false,
19656        }
19657    }
19658
19659    /// Get the field count of a struct expression
19660    fn struct_field_count(expr: &Expression) -> usize {
19661        match expr {
19662            Expression::Struct(s) => s.fields.len(),
19663            Expression::Function(f) if f.name.eq_ignore_ascii_case("STRUCT") => f.args.len(),
19664            _ => 0,
19665        }
19666    }
19667
19668    /// Apply field names to an unnamed struct expression, producing a new expression with names
19669    fn apply_struct_field_names(expr: &Expression, field_names: &[String]) -> Expression {
19670        match expr {
19671            Expression::Struct(s) => {
19672                let mut new_fields = Vec::with_capacity(s.fields.len());
19673                for (i, (name, value)) in s.fields.iter().enumerate() {
19674                    if name.is_none() && i < field_names.len() {
19675                        new_fields.push((Some(field_names[i].clone()), value.clone()));
19676                    } else {
19677                        new_fields.push((name.clone(), value.clone()));
19678                    }
19679                }
19680                Expression::Struct(Box::new(crate::expressions::Struct { fields: new_fields }))
19681            }
19682            Expression::Function(f) if f.name.eq_ignore_ascii_case("STRUCT") => {
19683                let mut new_args = Vec::with_capacity(f.args.len());
19684                for (i, arg) in f.args.iter().enumerate() {
19685                    if !matches!(arg, Expression::Alias(_)) && i < field_names.len() {
19686                        // Wrap the value in an Alias with the inherited name
19687                        new_args.push(Expression::Alias(Box::new(crate::expressions::Alias {
19688                            this: arg.clone(),
19689                            alias: crate::expressions::Identifier::new(field_names[i].clone()),
19690                            column_aliases: Vec::new(),
19691                            pre_alias_comments: Vec::new(),
19692                            trailing_comments: Vec::new(),
19693                            inferred_type: None,
19694                        })));
19695                    } else {
19696                        new_args.push(arg.clone());
19697                    }
19698                }
19699                Expression::Function(Box::new(crate::expressions::Function {
19700                    name: f.name.clone(),
19701                    args: new_args,
19702                    distinct: f.distinct,
19703                    trailing_comments: f.trailing_comments.clone(),
19704                    use_bracket_syntax: f.use_bracket_syntax,
19705                    no_parens: f.no_parens,
19706                    quoted: f.quoted,
19707                    span: None,
19708                    inferred_type: None,
19709                }))
19710            }
19711            _ => expr.clone(),
19712        }
19713    }
19714
19715    /// Propagate struct field names from the first struct in an array to subsequent unnamed structs.
19716    /// This implements BigQuery's implicit field name inheritance for struct arrays.
19717    /// Handles both Expression::Struct and Expression::Function named "STRUCT".
19718    fn inherit_struct_field_names(expressions: &[Expression]) -> Vec<Expression> {
19719        let first = match expressions.first() {
19720            Some(e) => e,
19721            None => return expressions.to_vec(),
19722        };
19723
19724        let field_names = match Self::extract_struct_field_names(first) {
19725            Some(names) if !names.is_empty() => names,
19726            _ => return expressions.to_vec(),
19727        };
19728
19729        let mut result = Vec::with_capacity(expressions.len());
19730        for (idx, expr) in expressions.iter().enumerate() {
19731            if idx == 0 {
19732                result.push(expr.clone());
19733                continue;
19734            }
19735            // Check if this is a struct with unnamed fields that needs name propagation
19736            if Self::struct_field_count(expr) == field_names.len()
19737                && Self::struct_has_unnamed_fields(expr)
19738            {
19739                result.push(Self::apply_struct_field_names(expr, &field_names));
19740            } else {
19741                result.push(expr.clone());
19742            }
19743        }
19744        result
19745    }
19746
19747    // Array function generators
19748
19749    fn generate_array_constructor(&mut self, f: &ArrayConstructor) -> Result<()> {
19750        // Apply struct name inheritance for target dialects that need it
19751        // (DuckDB, Spark, Databricks, Hive, Snowflake, Presto, Trino)
19752        let needs_inheritance = matches!(
19753            self.config.dialect,
19754            Some(DialectType::DuckDB)
19755                | Some(DialectType::Spark)
19756                | Some(DialectType::Databricks)
19757                | Some(DialectType::Hive)
19758                | Some(DialectType::Snowflake)
19759                | Some(DialectType::Presto)
19760                | Some(DialectType::Trino)
19761        );
19762        let propagated: Vec<Expression>;
19763        let expressions = if needs_inheritance && f.expressions.len() > 1 {
19764            propagated = Self::inherit_struct_field_names(&f.expressions);
19765            &propagated
19766        } else {
19767            &f.expressions
19768        };
19769
19770        // Check if elements should be split onto multiple lines (pretty + too wide)
19771        let should_split = if self.config.pretty && !expressions.is_empty() {
19772            let mut expr_strings: Vec<String> = Vec::with_capacity(expressions.len());
19773            for expr in expressions {
19774                let mut temp_gen = Generator::with_arc_config(self.config.clone());
19775                Arc::make_mut(&mut temp_gen.config).pretty = false;
19776                temp_gen.generate_expression(expr)?;
19777                expr_strings.push(temp_gen.output);
19778            }
19779            self.too_wide(&expr_strings)
19780        } else {
19781            false
19782        };
19783
19784        if f.bracket_notation {
19785            // For Spark/Databricks, use ARRAY(...) with parens
19786            // For Presto/Trino/PostgreSQL, use ARRAY[...] with keyword prefix
19787            // For others (DuckDB, Snowflake), use bare [...]
19788            let (open, close) = match self.config.dialect {
19789                None
19790                | Some(DialectType::Generic)
19791                | Some(DialectType::Spark)
19792                | Some(DialectType::Databricks)
19793                | Some(DialectType::Hive) => {
19794                    self.write_keyword("ARRAY");
19795                    ("(", ")")
19796                }
19797                Some(DialectType::Presto)
19798                | Some(DialectType::Trino)
19799                | Some(DialectType::PostgreSQL)
19800                | Some(DialectType::Redshift)
19801                | Some(DialectType::Materialize)
19802                | Some(DialectType::RisingWave)
19803                | Some(DialectType::CockroachDB) => {
19804                    self.write_keyword("ARRAY");
19805                    ("[", "]")
19806                }
19807                _ => ("[", "]"),
19808            };
19809            self.write(open);
19810            if should_split {
19811                self.write_newline();
19812                self.indent_level += 1;
19813                for (i, expr) in expressions.iter().enumerate() {
19814                    self.write_indent();
19815                    self.generate_expression(expr)?;
19816                    if i + 1 < expressions.len() {
19817                        self.write(",");
19818                    }
19819                    self.write_newline();
19820                }
19821                self.indent_level -= 1;
19822                self.write_indent();
19823            } else {
19824                for (i, expr) in expressions.iter().enumerate() {
19825                    if i > 0 {
19826                        self.write(", ");
19827                    }
19828                    self.generate_expression(expr)?;
19829                }
19830            }
19831            self.write(close);
19832        } else {
19833            // Use LIST keyword if that was the original syntax (DuckDB)
19834            if f.use_list_keyword {
19835                self.write_keyword("LIST");
19836            } else {
19837                self.write_keyword("ARRAY");
19838            }
19839            // For Spark/Hive, always use ARRAY(...) with parens
19840            // Also use parens for BigQuery when the array contains a subquery (ARRAY(SELECT ...))
19841            let has_subquery = expressions
19842                .iter()
19843                .any(|e| matches!(e, Expression::Select(_)));
19844            let (open, close) = if matches!(
19845                self.config.dialect,
19846                Some(DialectType::Spark) | Some(DialectType::Databricks) | Some(DialectType::Hive)
19847            ) || (matches!(self.config.dialect, Some(DialectType::BigQuery))
19848                && has_subquery)
19849            {
19850                ("(", ")")
19851            } else {
19852                ("[", "]")
19853            };
19854            self.write(open);
19855            if should_split {
19856                self.write_newline();
19857                self.indent_level += 1;
19858                for (i, expr) in expressions.iter().enumerate() {
19859                    self.write_indent();
19860                    self.generate_expression(expr)?;
19861                    if i + 1 < expressions.len() {
19862                        self.write(",");
19863                    }
19864                    self.write_newline();
19865                }
19866                self.indent_level -= 1;
19867                self.write_indent();
19868            } else {
19869                for (i, expr) in expressions.iter().enumerate() {
19870                    if i > 0 {
19871                        self.write(", ");
19872                    }
19873                    self.generate_expression(expr)?;
19874                }
19875            }
19876            self.write(close);
19877        }
19878        Ok(())
19879    }
19880
19881    fn generate_array_sort(&mut self, f: &ArraySortFunc) -> Result<()> {
19882        self.write_keyword("ARRAY_SORT");
19883        self.write("(");
19884        self.generate_expression(&f.this)?;
19885        if let Some(ref comp) = f.comparator {
19886            self.write(", ");
19887            self.generate_expression(comp)?;
19888        }
19889        self.write(")");
19890        Ok(())
19891    }
19892
19893    fn generate_array_join(&mut self, name: &str, f: &ArrayJoinFunc) -> Result<()> {
19894        self.write_keyword(name);
19895        self.write("(");
19896        self.generate_expression(&f.this)?;
19897        self.write(", ");
19898        self.generate_expression(&f.separator)?;
19899        if let Some(ref null_rep) = f.null_replacement {
19900            self.write(", ");
19901            self.generate_expression(null_rep)?;
19902        }
19903        self.write(")");
19904        Ok(())
19905    }
19906
19907    fn generate_unnest(&mut self, f: &UnnestFunc) -> Result<()> {
19908        self.write_keyword("UNNEST");
19909        self.write("(");
19910        self.generate_expression(&f.this)?;
19911        for extra in &f.expressions {
19912            self.write(", ");
19913            self.generate_expression(extra)?;
19914        }
19915        self.write(")");
19916        if f.with_ordinality {
19917            self.write_space();
19918            if self.config.unnest_with_ordinality {
19919                // Presto/Trino: UNNEST(arr) WITH ORDINALITY [AS alias]
19920                self.write_keyword("WITH ORDINALITY");
19921            } else if f.offset_alias.is_some() {
19922                // BigQuery: UNNEST(arr) [AS col] WITH OFFSET AS pos
19923                // Alias (if any) comes BEFORE WITH OFFSET
19924                if let Some(ref alias) = f.alias {
19925                    self.write_keyword("AS");
19926                    self.write_space();
19927                    self.generate_identifier(alias)?;
19928                    self.write_space();
19929                }
19930                self.write_keyword("WITH OFFSET");
19931                if let Some(ref offset_alias) = f.offset_alias {
19932                    self.write_space();
19933                    self.write_keyword("AS");
19934                    self.write_space();
19935                    self.generate_identifier(offset_alias)?;
19936                }
19937            } else {
19938                // WITH OFFSET (BigQuery identity) - add default "AS offset" if no explicit alias
19939                self.write_keyword("WITH OFFSET");
19940                if f.alias.is_none() {
19941                    self.write(" AS offset");
19942                }
19943            }
19944        }
19945        if let Some(ref alias) = f.alias {
19946            // Add alias for: non-WITH-OFFSET cases, Presto/Trino WITH ORDINALITY, or BigQuery WITH OFFSET + alias (no offset_alias)
19947            let should_add_alias = if !f.with_ordinality {
19948                true
19949            } else if self.config.unnest_with_ordinality {
19950                // Presto/Trino: alias comes after WITH ORDINALITY
19951                true
19952            } else if f.offset_alias.is_some() {
19953                // BigQuery expansion: alias already handled above
19954                false
19955            } else {
19956                // BigQuery WITH OFFSET + alias but no offset_alias: alias comes after
19957                true
19958            };
19959            if should_add_alias {
19960                self.write_space();
19961                self.write_keyword("AS");
19962                self.write_space();
19963                self.generate_identifier(alias)?;
19964            }
19965        }
19966        Ok(())
19967    }
19968
19969    fn generate_array_filter(&mut self, f: &ArrayFilterFunc) -> Result<()> {
19970        self.write_keyword("FILTER");
19971        self.write("(");
19972        self.generate_expression(&f.this)?;
19973        self.write(", ");
19974        self.generate_expression(&f.filter)?;
19975        self.write(")");
19976        Ok(())
19977    }
19978
19979    fn generate_array_transform(&mut self, f: &ArrayTransformFunc) -> Result<()> {
19980        self.write_keyword("TRANSFORM");
19981        self.write("(");
19982        self.generate_expression(&f.this)?;
19983        self.write(", ");
19984        self.generate_expression(&f.transform)?;
19985        self.write(")");
19986        Ok(())
19987    }
19988
19989    fn generate_sequence(&mut self, name: &str, f: &SequenceFunc) -> Result<()> {
19990        self.write_keyword(name);
19991        self.write("(");
19992        self.generate_expression(&f.start)?;
19993        self.write(", ");
19994        self.generate_expression(&f.stop)?;
19995        if let Some(ref step) = f.step {
19996            self.write(", ");
19997            self.generate_expression(step)?;
19998        }
19999        self.write(")");
20000        Ok(())
20001    }
20002
20003    // Struct function generators
20004
20005    fn generate_struct_constructor(&mut self, f: &StructConstructor) -> Result<()> {
20006        self.write_keyword("STRUCT");
20007        self.write("(");
20008        for (i, (name, expr)) in f.fields.iter().enumerate() {
20009            if i > 0 {
20010                self.write(", ");
20011            }
20012            if let Some(ref id) = name {
20013                self.generate_identifier(id)?;
20014                self.write(" ");
20015                self.write_keyword("AS");
20016                self.write(" ");
20017            }
20018            self.generate_expression(expr)?;
20019        }
20020        self.write(")");
20021        Ok(())
20022    }
20023
20024    /// Convert BigQuery STRUCT function (parsed as Function with Alias args) to target dialect
20025    fn generate_struct_function_cross_dialect(&mut self, func: &Function) -> Result<()> {
20026        // Extract named/unnamed fields from function args
20027        // Args are either Alias(this=value, alias=name) for named or plain expressions for unnamed
20028        let mut names: Vec<Option<String>> = Vec::new();
20029        let mut values: Vec<&Expression> = Vec::new();
20030        let mut all_named = true;
20031
20032        for arg in &func.args {
20033            match arg {
20034                Expression::Alias(a) => {
20035                    names.push(Some(a.alias.name.clone()));
20036                    values.push(&a.this);
20037                }
20038                _ => {
20039                    names.push(None);
20040                    values.push(arg);
20041                    all_named = false;
20042                }
20043            }
20044        }
20045
20046        if matches!(self.config.dialect, Some(DialectType::DuckDB)) {
20047            // DuckDB: {'name': value, ...} for named, {'_0': value, ...} for unnamed
20048            self.write("{");
20049            for (i, (name, value)) in names.iter().zip(values.iter()).enumerate() {
20050                if i > 0 {
20051                    self.write(", ");
20052                }
20053                if let Some(n) = name {
20054                    self.write("'");
20055                    self.write(n);
20056                    self.write("'");
20057                } else {
20058                    self.write("'_");
20059                    self.write(&i.to_string());
20060                    self.write("'");
20061                }
20062                self.write(": ");
20063                self.generate_expression(value)?;
20064            }
20065            self.write("}");
20066            return Ok(());
20067        }
20068
20069        if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
20070            // Snowflake: OBJECT_CONSTRUCT('name', value, ...)
20071            self.write_keyword("OBJECT_CONSTRUCT");
20072            self.write("(");
20073            for (i, (name, value)) in names.iter().zip(values.iter()).enumerate() {
20074                if i > 0 {
20075                    self.write(", ");
20076                }
20077                if let Some(n) = name {
20078                    self.write("'");
20079                    self.write(n);
20080                    self.write("'");
20081                } else {
20082                    self.write("'_");
20083                    self.write(&i.to_string());
20084                    self.write("'");
20085                }
20086                self.write(", ");
20087                self.generate_expression(value)?;
20088            }
20089            self.write(")");
20090            return Ok(());
20091        }
20092
20093        if matches!(
20094            self.config.dialect,
20095            Some(DialectType::Presto) | Some(DialectType::Trino)
20096        ) {
20097            if all_named && !names.is_empty() {
20098                // Presto/Trino: CAST(ROW(values...) AS ROW(name TYPE, ...))
20099                // Need to infer types from values
20100                self.write_keyword("CAST");
20101                self.write("(");
20102                self.write_keyword("ROW");
20103                self.write("(");
20104                for (i, value) in values.iter().enumerate() {
20105                    if i > 0 {
20106                        self.write(", ");
20107                    }
20108                    self.generate_expression(value)?;
20109                }
20110                self.write(")");
20111                self.write(" ");
20112                self.write_keyword("AS");
20113                self.write(" ");
20114                self.write_keyword("ROW");
20115                self.write("(");
20116                for (i, (name, value)) in names.iter().zip(values.iter()).enumerate() {
20117                    if i > 0 {
20118                        self.write(", ");
20119                    }
20120                    if let Some(n) = name {
20121                        self.write(n);
20122                    }
20123                    self.write(" ");
20124                    let type_str = Self::infer_sql_type_for_presto(value);
20125                    self.write_keyword(&type_str);
20126                }
20127                self.write(")");
20128                self.write(")");
20129            } else {
20130                // Unnamed: ROW(values...)
20131                self.write_keyword("ROW");
20132                self.write("(");
20133                for (i, value) in values.iter().enumerate() {
20134                    if i > 0 {
20135                        self.write(", ");
20136                    }
20137                    self.generate_expression(value)?;
20138                }
20139                self.write(")");
20140            }
20141            return Ok(());
20142        }
20143
20144        // Default: ROW(values...) for other dialects
20145        self.write_keyword("ROW");
20146        self.write("(");
20147        for (i, value) in values.iter().enumerate() {
20148            if i > 0 {
20149                self.write(", ");
20150            }
20151            self.generate_expression(value)?;
20152        }
20153        self.write(")");
20154        Ok(())
20155    }
20156
20157    /// Infer SQL type name for a Presto/Trino ROW CAST from a literal expression
20158    fn infer_sql_type_for_presto(expr: &Expression) -> String {
20159        match expr {
20160            Expression::Literal(lit) if matches!(lit.as_ref(), crate::expressions::Literal::String(_)) => "VARCHAR".to_string(),
20161            Expression::Literal(lit) if matches!(lit.as_ref(), crate::expressions::Literal::Number(_)) => {
20162                let crate::expressions::Literal::Number(n) = lit.as_ref() else { unreachable!() };
20163                if n.contains('.') {
20164                    "DOUBLE".to_string()
20165                } else {
20166                    "INTEGER".to_string()
20167                }
20168            }
20169            Expression::Boolean(_) => "BOOLEAN".to_string(),
20170            Expression::Literal(lit) if matches!(lit.as_ref(), crate::expressions::Literal::Date(_)) => "DATE".to_string(),
20171            Expression::Literal(lit) if matches!(lit.as_ref(), crate::expressions::Literal::Timestamp(_)) => {
20172                "TIMESTAMP".to_string()
20173            }
20174            Expression::Literal(lit) if matches!(lit.as_ref(), crate::expressions::Literal::Datetime(_)) => {
20175                "TIMESTAMP".to_string()
20176            }
20177            Expression::Array(_) | Expression::ArrayFunc(_) => {
20178                // Try to infer element type from first element
20179                "ARRAY(VARCHAR)".to_string()
20180            }
20181            // For nested structs - generate a nested ROW type by inspecting fields
20182            Expression::Struct(_) | Expression::StructFunc(_) => "ROW".to_string(),
20183            Expression::Function(f) => {
20184                if f.name.eq_ignore_ascii_case("STRUCT") {
20185                    "ROW".to_string()
20186                } else if f.name.eq_ignore_ascii_case("CURRENT_DATE") {
20187                    "DATE".to_string()
20188                } else if f.name.eq_ignore_ascii_case("CURRENT_TIMESTAMP") || f.name.eq_ignore_ascii_case("NOW") {
20189                    "TIMESTAMP".to_string()
20190                } else {
20191                    "VARCHAR".to_string()
20192                }
20193            }
20194            _ => "VARCHAR".to_string(),
20195        }
20196    }
20197
20198    fn generate_struct_extract(&mut self, f: &StructExtractFunc) -> Result<()> {
20199        // DuckDB uses STRUCT_EXTRACT function syntax
20200        if matches!(self.config.dialect, Some(DialectType::DuckDB)) {
20201            self.write_keyword("STRUCT_EXTRACT");
20202            self.write("(");
20203            self.generate_expression(&f.this)?;
20204            self.write(", ");
20205            // Output field name as string literal
20206            self.write("'");
20207            self.write(&f.field.name);
20208            self.write("'");
20209            self.write(")");
20210            return Ok(());
20211        }
20212        self.generate_expression(&f.this)?;
20213        self.write(".");
20214        self.generate_identifier(&f.field)
20215    }
20216
20217    fn generate_named_struct(&mut self, f: &NamedStructFunc) -> Result<()> {
20218        self.write_keyword("NAMED_STRUCT");
20219        self.write("(");
20220        for (i, (name, value)) in f.pairs.iter().enumerate() {
20221            if i > 0 {
20222                self.write(", ");
20223            }
20224            self.generate_expression(name)?;
20225            self.write(", ");
20226            self.generate_expression(value)?;
20227        }
20228        self.write(")");
20229        Ok(())
20230    }
20231
20232    // Map function generators
20233
20234    fn generate_map_constructor(&mut self, f: &MapConstructor) -> Result<()> {
20235        if f.curly_brace_syntax {
20236            // Curly brace syntax: MAP {'a': 1, 'b': 2} or just {'a': 1, 'b': 2}
20237            if f.with_map_keyword {
20238                self.write_keyword("MAP");
20239                self.write(" ");
20240            }
20241            self.write("{");
20242            for (i, (key, val)) in f.keys.iter().zip(f.values.iter()).enumerate() {
20243                if i > 0 {
20244                    self.write(", ");
20245                }
20246                self.generate_expression(key)?;
20247                self.write(": ");
20248                self.generate_expression(val)?;
20249            }
20250            self.write("}");
20251        } else {
20252            // MAP function syntax: MAP(ARRAY[keys], ARRAY[values])
20253            self.write_keyword("MAP");
20254            self.write("(");
20255            self.write_keyword("ARRAY");
20256            self.write("[");
20257            for (i, key) in f.keys.iter().enumerate() {
20258                if i > 0 {
20259                    self.write(", ");
20260                }
20261                self.generate_expression(key)?;
20262            }
20263            self.write("], ");
20264            self.write_keyword("ARRAY");
20265            self.write("[");
20266            for (i, val) in f.values.iter().enumerate() {
20267                if i > 0 {
20268                    self.write(", ");
20269                }
20270                self.generate_expression(val)?;
20271            }
20272            self.write("])");
20273        }
20274        Ok(())
20275    }
20276
20277    fn generate_transform_func(&mut self, name: &str, f: &TransformFunc) -> Result<()> {
20278        self.write_keyword(name);
20279        self.write("(");
20280        self.generate_expression(&f.this)?;
20281        self.write(", ");
20282        self.generate_expression(&f.transform)?;
20283        self.write(")");
20284        Ok(())
20285    }
20286
20287    // JSON function generators
20288
20289    fn generate_json_extract(&mut self, name: &str, f: &JsonExtractFunc) -> Result<()> {
20290        use crate::dialects::DialectType;
20291
20292        // Check if we should use arrow syntax (-> or ->>)
20293        let use_arrow = f.arrow_syntax && self.dialect_supports_json_arrow();
20294
20295        if use_arrow {
20296            // Output arrow syntax: expr -> path or expr ->> path
20297            self.generate_expression(&f.this)?;
20298            if name == "JSON_EXTRACT_SCALAR" || name == "JSON_EXTRACT_PATH_TEXT" {
20299                self.write(" ->> ");
20300            } else {
20301                self.write(" -> ");
20302            }
20303            self.generate_expression(&f.path)?;
20304            return Ok(());
20305        }
20306
20307        // PostgreSQL uses #>> operator for JSONB path text extraction (only when hash_arrow_syntax is true)
20308        if f.hash_arrow_syntax
20309            && matches!(
20310                self.config.dialect,
20311                Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
20312            )
20313        {
20314            self.generate_expression(&f.this)?;
20315            self.write(" #>> ");
20316            self.generate_expression(&f.path)?;
20317            return Ok(());
20318        }
20319
20320        // For PostgreSQL/Redshift, use JSON_EXTRACT_PATH / JSON_EXTRACT_PATH_TEXT for extraction without arrow syntax
20321        // Redshift maps everything to JSON_EXTRACT_PATH_TEXT since it doesn't have JSON_EXTRACT_PATH
20322        let func_name = if matches!(self.config.dialect, Some(DialectType::Redshift)) {
20323            match name {
20324                "JSON_EXTRACT_SCALAR"
20325                | "JSON_EXTRACT_PATH_TEXT"
20326                | "JSON_EXTRACT"
20327                | "JSON_EXTRACT_PATH" => "JSON_EXTRACT_PATH_TEXT",
20328                _ => name,
20329            }
20330        } else if matches!(self.config.dialect, Some(DialectType::PostgreSQL)) {
20331            match name {
20332                "JSON_EXTRACT_SCALAR" | "JSON_EXTRACT_PATH_TEXT" => "JSON_EXTRACT_PATH_TEXT",
20333                "JSON_EXTRACT" | "JSON_EXTRACT_PATH" => "JSON_EXTRACT_PATH",
20334                _ => name,
20335            }
20336        } else {
20337            name
20338        };
20339
20340        self.write_keyword(func_name);
20341        self.write("(");
20342        // For Redshift, strip CAST(... AS JSON) wrapper from the expression
20343        if matches!(self.config.dialect, Some(DialectType::Redshift)) {
20344            if let Expression::Cast(ref cast) = f.this {
20345                if matches!(cast.to, crate::expressions::DataType::Json) {
20346                    self.generate_expression(&cast.this)?;
20347                } else {
20348                    self.generate_expression(&f.this)?;
20349                }
20350            } else {
20351                self.generate_expression(&f.this)?;
20352            }
20353        } else {
20354            self.generate_expression(&f.this)?;
20355        }
20356        // For PostgreSQL/Redshift JSON_EXTRACT_PATH/JSON_EXTRACT_PATH_TEXT,
20357        // decompose JSON path into separate string arguments
20358        if matches!(
20359            self.config.dialect,
20360            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
20361        ) && (func_name == "JSON_EXTRACT_PATH" || func_name == "JSON_EXTRACT_PATH_TEXT")
20362        {
20363            if let Expression::Literal(ref lit) = f.path {
20364                if let Literal::String(ref s) = lit.as_ref() {
20365                let parts = Self::decompose_json_path(s);
20366                for part in &parts {
20367                    self.write(", '");
20368                    self.write(part);
20369                    self.write("'");
20370                }
20371            }
20372            } else {
20373                self.write(", ");
20374                self.generate_expression(&f.path)?;
20375            }
20376        } else {
20377            self.write(", ");
20378            self.generate_expression(&f.path)?;
20379        }
20380
20381        // Output JSON_QUERY/JSON_VALUE options (Trino/Presto style)
20382        // These go BEFORE the closing parenthesis
20383        if let Some(ref wrapper) = f.wrapper_option {
20384            self.write_space();
20385            self.write_keyword(wrapper);
20386        }
20387        if let Some(ref quotes) = f.quotes_option {
20388            self.write_space();
20389            self.write_keyword(quotes);
20390            if f.on_scalar_string {
20391                self.write_space();
20392                self.write_keyword("ON SCALAR STRING");
20393            }
20394        }
20395        if let Some(ref on_err) = f.on_error {
20396            self.write_space();
20397            self.write_keyword(on_err);
20398        }
20399        if let Some(ref ret_type) = f.returning {
20400            self.write_space();
20401            self.write_keyword("RETURNING");
20402            self.write_space();
20403            self.generate_data_type(ret_type)?;
20404        }
20405
20406        self.write(")");
20407        Ok(())
20408    }
20409
20410    /// Check if the current dialect supports JSON arrow operators (-> and ->>)
20411    fn dialect_supports_json_arrow(&self) -> bool {
20412        use crate::dialects::DialectType;
20413        match self.config.dialect {
20414            // PostgreSQL, MySQL, DuckDB support -> and ->> operators
20415            Some(DialectType::PostgreSQL) => true,
20416            Some(DialectType::MySQL) => true,
20417            Some(DialectType::DuckDB) => true,
20418            Some(DialectType::CockroachDB) => true,
20419            Some(DialectType::StarRocks) => true,
20420            Some(DialectType::SQLite) => true,
20421            // Other dialects use function syntax
20422            _ => false,
20423        }
20424    }
20425
20426    fn generate_json_path(&mut self, name: &str, f: &JsonPathFunc) -> Result<()> {
20427        use crate::dialects::DialectType;
20428
20429        // PostgreSQL uses #> operator for JSONB path extraction
20430        if matches!(
20431            self.config.dialect,
20432            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
20433        ) && name == "JSON_EXTRACT_PATH"
20434        {
20435            self.generate_expression(&f.this)?;
20436            self.write(" #> ");
20437            if f.paths.len() == 1 {
20438                self.generate_expression(&f.paths[0])?;
20439            } else {
20440                // Multiple paths: ARRAY[path1, path2, ...]
20441                self.write_keyword("ARRAY");
20442                self.write("[");
20443                for (i, path) in f.paths.iter().enumerate() {
20444                    if i > 0 {
20445                        self.write(", ");
20446                    }
20447                    self.generate_expression(path)?;
20448                }
20449                self.write("]");
20450            }
20451            return Ok(());
20452        }
20453
20454        self.write_keyword(name);
20455        self.write("(");
20456        self.generate_expression(&f.this)?;
20457        for path in &f.paths {
20458            self.write(", ");
20459            self.generate_expression(path)?;
20460        }
20461        self.write(")");
20462        Ok(())
20463    }
20464
20465    fn generate_json_object(&mut self, f: &JsonObjectFunc) -> Result<()> {
20466        use crate::dialects::DialectType;
20467
20468        self.write_keyword("JSON_OBJECT");
20469        self.write("(");
20470        if f.star {
20471            self.write("*");
20472        } else {
20473            // BigQuery, MySQL, and SQLite use comma syntax: JSON_OBJECT('key', value)
20474            // Standard SQL uses colon syntax: JSON_OBJECT('key': value)
20475            // Also respect the json_key_value_pair_sep config
20476            let use_comma_syntax = self.config.json_key_value_pair_sep == ","
20477                || matches!(
20478                    self.config.dialect,
20479                    Some(DialectType::BigQuery)
20480                        | Some(DialectType::MySQL)
20481                        | Some(DialectType::SQLite)
20482                );
20483
20484            for (i, (key, value)) in f.pairs.iter().enumerate() {
20485                if i > 0 {
20486                    self.write(", ");
20487                }
20488                self.generate_expression(key)?;
20489                if use_comma_syntax {
20490                    self.write(", ");
20491                } else {
20492                    self.write(": ");
20493                }
20494                self.generate_expression(value)?;
20495            }
20496        }
20497        if let Some(null_handling) = f.null_handling {
20498            self.write_space();
20499            match null_handling {
20500                JsonNullHandling::NullOnNull => self.write_keyword("NULL ON NULL"),
20501                JsonNullHandling::AbsentOnNull => self.write_keyword("ABSENT ON NULL"),
20502            }
20503        }
20504        if f.with_unique_keys {
20505            self.write_space();
20506            self.write_keyword("WITH UNIQUE KEYS");
20507        }
20508        if let Some(ref ret_type) = f.returning_type {
20509            self.write_space();
20510            self.write_keyword("RETURNING");
20511            self.write_space();
20512            self.generate_data_type(ret_type)?;
20513            if f.format_json {
20514                self.write_space();
20515                self.write_keyword("FORMAT JSON");
20516            }
20517            if let Some(ref enc) = f.encoding {
20518                self.write_space();
20519                self.write_keyword("ENCODING");
20520                self.write_space();
20521                self.write(enc);
20522            }
20523        }
20524        self.write(")");
20525        Ok(())
20526    }
20527
20528    fn generate_json_modify(&mut self, name: &str, f: &JsonModifyFunc) -> Result<()> {
20529        self.write_keyword(name);
20530        self.write("(");
20531        self.generate_expression(&f.this)?;
20532        for (path, value) in &f.path_values {
20533            self.write(", ");
20534            self.generate_expression(path)?;
20535            self.write(", ");
20536            self.generate_expression(value)?;
20537        }
20538        self.write(")");
20539        Ok(())
20540    }
20541
20542    fn generate_json_array_agg(&mut self, f: &JsonArrayAggFunc) -> Result<()> {
20543        self.write_keyword("JSON_ARRAYAGG");
20544        self.write("(");
20545        self.generate_expression(&f.this)?;
20546        if let Some(ref order_by) = f.order_by {
20547            self.write_space();
20548            self.write_keyword("ORDER BY");
20549            self.write_space();
20550            for (i, ord) in order_by.iter().enumerate() {
20551                if i > 0 {
20552                    self.write(", ");
20553                }
20554                self.generate_ordered(ord)?;
20555            }
20556        }
20557        if let Some(null_handling) = f.null_handling {
20558            self.write_space();
20559            match null_handling {
20560                JsonNullHandling::NullOnNull => self.write_keyword("NULL ON NULL"),
20561                JsonNullHandling::AbsentOnNull => self.write_keyword("ABSENT ON NULL"),
20562            }
20563        }
20564        self.write(")");
20565        if let Some(ref filter) = f.filter {
20566            self.write_space();
20567            self.write_keyword("FILTER");
20568            self.write("(");
20569            self.write_keyword("WHERE");
20570            self.write_space();
20571            self.generate_expression(filter)?;
20572            self.write(")");
20573        }
20574        Ok(())
20575    }
20576
20577    fn generate_json_object_agg(&mut self, f: &JsonObjectAggFunc) -> Result<()> {
20578        self.write_keyword("JSON_OBJECTAGG");
20579        self.write("(");
20580        self.generate_expression(&f.key)?;
20581        self.write(": ");
20582        self.generate_expression(&f.value)?;
20583        if let Some(null_handling) = f.null_handling {
20584            self.write_space();
20585            match null_handling {
20586                JsonNullHandling::NullOnNull => self.write_keyword("NULL ON NULL"),
20587                JsonNullHandling::AbsentOnNull => self.write_keyword("ABSENT ON NULL"),
20588            }
20589        }
20590        self.write(")");
20591        if let Some(ref filter) = f.filter {
20592            self.write_space();
20593            self.write_keyword("FILTER");
20594            self.write("(");
20595            self.write_keyword("WHERE");
20596            self.write_space();
20597            self.generate_expression(filter)?;
20598            self.write(")");
20599        }
20600        Ok(())
20601    }
20602
20603    // Type casting/conversion generators
20604
20605    fn generate_convert(&mut self, f: &ConvertFunc) -> Result<()> {
20606        use crate::dialects::DialectType;
20607
20608        // Redshift: CONVERT(type, expr) -> CAST(expr AS type)
20609        if self.config.dialect == Some(DialectType::Redshift) {
20610            self.write_keyword("CAST");
20611            self.write("(");
20612            self.generate_expression(&f.this)?;
20613            self.write_space();
20614            self.write_keyword("AS");
20615            self.write_space();
20616            self.generate_data_type(&f.to)?;
20617            self.write(")");
20618            return Ok(());
20619        }
20620
20621        self.write_keyword("CONVERT");
20622        self.write("(");
20623        self.generate_data_type(&f.to)?;
20624        self.write(", ");
20625        self.generate_expression(&f.this)?;
20626        if let Some(ref style) = f.style {
20627            self.write(", ");
20628            self.generate_expression(style)?;
20629        }
20630        self.write(")");
20631        Ok(())
20632    }
20633
20634    // Additional expression generators
20635
20636    fn generate_lambda(&mut self, f: &LambdaExpr) -> Result<()> {
20637        if f.colon {
20638            // DuckDB syntax: LAMBDA x : expr
20639            self.write_keyword("LAMBDA");
20640            self.write_space();
20641            for (i, param) in f.parameters.iter().enumerate() {
20642                if i > 0 {
20643                    self.write(", ");
20644                }
20645                self.generate_identifier(param)?;
20646            }
20647            self.write(" : ");
20648        } else {
20649            // Standard syntax: x -> expr or (x, y) -> expr
20650            if f.parameters.len() == 1 {
20651                self.generate_identifier(&f.parameters[0])?;
20652            } else {
20653                self.write("(");
20654                for (i, param) in f.parameters.iter().enumerate() {
20655                    if i > 0 {
20656                        self.write(", ");
20657                    }
20658                    self.generate_identifier(param)?;
20659                }
20660                self.write(")");
20661            }
20662            self.write(" -> ");
20663        }
20664        self.generate_expression(&f.body)
20665    }
20666
20667    fn generate_named_argument(&mut self, f: &NamedArgument) -> Result<()> {
20668        self.generate_identifier(&f.name)?;
20669        match f.separator {
20670            NamedArgSeparator::DArrow => self.write(" => "),
20671            NamedArgSeparator::ColonEq => self.write(" := "),
20672            NamedArgSeparator::Eq => self.write(" = "),
20673        }
20674        self.generate_expression(&f.value)
20675    }
20676
20677    fn generate_table_argument(&mut self, f: &TableArgument) -> Result<()> {
20678        self.write_keyword(&f.prefix);
20679        self.write(" ");
20680        self.generate_expression(&f.this)
20681    }
20682
20683    fn generate_parameter(&mut self, f: &Parameter) -> Result<()> {
20684        match f.style {
20685            ParameterStyle::Question => self.write("?"),
20686            ParameterStyle::Dollar => {
20687                self.write("$");
20688                if let Some(idx) = f.index {
20689                    self.write(&idx.to_string());
20690                } else if let Some(ref name) = f.name {
20691                    // Session variable like $x or $query_id
20692                    self.write(name);
20693                }
20694            }
20695            ParameterStyle::DollarBrace => {
20696                // Template variable like ${x} or ${hiveconf:name} (Databricks, Hive)
20697                self.write("${");
20698                if let Some(ref name) = f.name {
20699                    self.write(name);
20700                }
20701                if let Some(ref expr) = f.expression {
20702                    self.write(":");
20703                    self.write(expr);
20704                }
20705                self.write("}");
20706            }
20707            ParameterStyle::Colon => {
20708                self.write(":");
20709                if let Some(idx) = f.index {
20710                    self.write(&idx.to_string());
20711                } else if let Some(ref name) = f.name {
20712                    self.write(name);
20713                }
20714            }
20715            ParameterStyle::At => {
20716                self.write("@");
20717                if let Some(ref name) = f.name {
20718                    if f.string_quoted {
20719                        self.write("'");
20720                        self.write(name);
20721                        self.write("'");
20722                    } else if f.quoted {
20723                        self.write("\"");
20724                        self.write(name);
20725                        self.write("\"");
20726                    } else {
20727                        self.write(name);
20728                    }
20729                }
20730            }
20731            ParameterStyle::DoubleAt => {
20732                self.write("@@");
20733                if let Some(ref name) = f.name {
20734                    self.write(name);
20735                }
20736            }
20737            ParameterStyle::DoubleDollar => {
20738                self.write("$$");
20739                if let Some(ref name) = f.name {
20740                    self.write(name);
20741                }
20742            }
20743            ParameterStyle::Percent => {
20744                if let Some(ref name) = f.name {
20745                    // %(name)s format
20746                    self.write("%(");
20747                    self.write(name);
20748                    self.write(")s");
20749                } else {
20750                    // %s format
20751                    self.write("%s");
20752                }
20753            }
20754            ParameterStyle::Brace => {
20755                // Spark/Databricks widget template variable: {name}
20756                // ClickHouse query parameter may include kind: {name: Type}
20757                self.write("{");
20758                if let Some(ref name) = f.name {
20759                    self.write(name);
20760                }
20761                if let Some(ref expr) = f.expression {
20762                    self.write(": ");
20763                    self.write(expr);
20764                }
20765                self.write("}");
20766            }
20767        }
20768        Ok(())
20769    }
20770
20771    fn generate_placeholder(&mut self, f: &Placeholder) -> Result<()> {
20772        self.write("?");
20773        if let Some(idx) = f.index {
20774            self.write(&idx.to_string());
20775        }
20776        Ok(())
20777    }
20778
20779    fn generate_sql_comment(&mut self, f: &SqlComment) -> Result<()> {
20780        if f.is_block {
20781            self.write("/*");
20782            self.write(&f.text);
20783            self.write("*/");
20784        } else {
20785            self.write("--");
20786            self.write(&f.text);
20787        }
20788        Ok(())
20789    }
20790
20791    // Additional predicate generators
20792
20793    fn generate_similar_to(&mut self, f: &SimilarToExpr) -> Result<()> {
20794        self.generate_expression(&f.this)?;
20795        if f.not {
20796            self.write_space();
20797            self.write_keyword("NOT");
20798        }
20799        self.write_space();
20800        self.write_keyword("SIMILAR TO");
20801        self.write_space();
20802        self.generate_expression(&f.pattern)?;
20803        if let Some(ref escape) = f.escape {
20804            self.write_space();
20805            self.write_keyword("ESCAPE");
20806            self.write_space();
20807            self.generate_expression(escape)?;
20808        }
20809        Ok(())
20810    }
20811
20812    fn generate_quantified(&mut self, name: &str, f: &QuantifiedExpr) -> Result<()> {
20813        self.generate_expression(&f.this)?;
20814        self.write_space();
20815        // Output comparison operator if present
20816        if let Some(op) = &f.op {
20817            match op {
20818                QuantifiedOp::Eq => self.write("="),
20819                QuantifiedOp::Neq => self.write("<>"),
20820                QuantifiedOp::Lt => self.write("<"),
20821                QuantifiedOp::Lte => self.write("<="),
20822                QuantifiedOp::Gt => self.write(">"),
20823                QuantifiedOp::Gte => self.write(">="),
20824            }
20825            self.write_space();
20826        }
20827        self.write_keyword(name);
20828
20829        // If the child is a Subquery, it provides its own parens — output with space
20830        if matches!(&f.subquery, Expression::Subquery(_)) {
20831            self.write_space();
20832            self.generate_expression(&f.subquery)?;
20833        } else {
20834            self.write("(");
20835
20836            let is_statement = matches!(
20837                &f.subquery,
20838                Expression::Select(_)
20839                    | Expression::Union(_)
20840                    | Expression::Intersect(_)
20841                    | Expression::Except(_)
20842            );
20843
20844            if self.config.pretty && is_statement {
20845                self.write_newline();
20846                self.indent_level += 1;
20847                self.write_indent();
20848            }
20849            self.generate_expression(&f.subquery)?;
20850            if self.config.pretty && is_statement {
20851                self.write_newline();
20852                self.indent_level -= 1;
20853                self.write_indent();
20854            }
20855            self.write(")");
20856        }
20857        Ok(())
20858    }
20859
20860    fn generate_overlaps(&mut self, f: &OverlapsExpr) -> Result<()> {
20861        // Check if this is a simple binary form (this OVERLAPS expression)
20862        if let (Some(this), Some(expr)) = (&f.this, &f.expression) {
20863            self.generate_expression(this)?;
20864            self.write_space();
20865            self.write_keyword("OVERLAPS");
20866            self.write_space();
20867            self.generate_expression(expr)?;
20868        } else if let (Some(ls), Some(le), Some(rs), Some(re)) =
20869            (&f.left_start, &f.left_end, &f.right_start, &f.right_end)
20870        {
20871            // Full ANSI form: (a, b) OVERLAPS (c, d)
20872            self.write("(");
20873            self.generate_expression(ls)?;
20874            self.write(", ");
20875            self.generate_expression(le)?;
20876            self.write(")");
20877            self.write_space();
20878            self.write_keyword("OVERLAPS");
20879            self.write_space();
20880            self.write("(");
20881            self.generate_expression(rs)?;
20882            self.write(", ");
20883            self.generate_expression(re)?;
20884            self.write(")");
20885        }
20886        Ok(())
20887    }
20888
20889    // Type conversion generators
20890
20891    fn generate_try_cast(&mut self, cast: &Cast) -> Result<()> {
20892        use crate::dialects::DialectType;
20893
20894        // SingleStore uses !:> syntax for try cast
20895        if matches!(self.config.dialect, Some(DialectType::SingleStore)) {
20896            self.generate_expression(&cast.this)?;
20897            self.write(" !:> ");
20898            self.generate_data_type(&cast.to)?;
20899            return Ok(());
20900        }
20901
20902        // Teradata uses TRYCAST (no underscore)
20903        if matches!(self.config.dialect, Some(DialectType::Teradata)) {
20904            self.write_keyword("TRYCAST");
20905            self.write("(");
20906            self.generate_expression(&cast.this)?;
20907            self.write_space();
20908            self.write_keyword("AS");
20909            self.write_space();
20910            self.generate_data_type(&cast.to)?;
20911            self.write(")");
20912            return Ok(());
20913        }
20914
20915        // Dialects without TRY_CAST: generate as regular CAST
20916        let keyword = if matches!(
20917            self.config.dialect,
20918            Some(DialectType::Hive)
20919                | Some(DialectType::MySQL)
20920                | Some(DialectType::SQLite)
20921                | Some(DialectType::Oracle)
20922                | Some(DialectType::ClickHouse)
20923                | Some(DialectType::Redshift)
20924                | Some(DialectType::PostgreSQL)
20925                | Some(DialectType::StarRocks)
20926                | Some(DialectType::Doris)
20927        ) {
20928            "CAST"
20929        } else {
20930            "TRY_CAST"
20931        };
20932
20933        self.write_keyword(keyword);
20934        self.write("(");
20935        self.generate_expression(&cast.this)?;
20936        self.write_space();
20937        self.write_keyword("AS");
20938        self.write_space();
20939        self.generate_data_type(&cast.to)?;
20940
20941        // Output FORMAT clause if present
20942        if let Some(format) = &cast.format {
20943            self.write_space();
20944            self.write_keyword("FORMAT");
20945            self.write_space();
20946            self.generate_expression(format)?;
20947        }
20948
20949        self.write(")");
20950        Ok(())
20951    }
20952
20953    fn generate_safe_cast(&mut self, cast: &Cast) -> Result<()> {
20954        self.write_keyword("SAFE_CAST");
20955        self.write("(");
20956        self.generate_expression(&cast.this)?;
20957        self.write_space();
20958        self.write_keyword("AS");
20959        self.write_space();
20960        self.generate_data_type(&cast.to)?;
20961
20962        // Output FORMAT clause if present
20963        if let Some(format) = &cast.format {
20964            self.write_space();
20965            self.write_keyword("FORMAT");
20966            self.write_space();
20967            self.generate_expression(format)?;
20968        }
20969
20970        self.write(")");
20971        Ok(())
20972    }
20973
20974    // Array/struct/map access generators
20975
20976    fn generate_subscript(&mut self, s: &Subscript) -> Result<()> {
20977        // Wrap the base expression in parentheses when it uses arrow syntax (->)
20978        // which has lower precedence than bracket subscript ([]).
20979        // E.g., (t.v -> '$.a')[s.x] instead of t.v -> '$.a'[s.x]
20980        let needs_parens = matches!(&s.this, Expression::JsonExtract(ref f) if f.arrow_syntax);
20981        if needs_parens {
20982            self.write("(");
20983        }
20984        self.generate_expression(&s.this)?;
20985        if needs_parens {
20986            self.write(")");
20987        }
20988        self.write("[");
20989        self.generate_expression(&s.index)?;
20990        self.write("]");
20991        Ok(())
20992    }
20993
20994    fn generate_dot_access(&mut self, d: &DotAccess) -> Result<()> {
20995        self.generate_expression(&d.this)?;
20996        // Snowflake uses : (colon) for first-level struct/object field access on CAST/column expressions
20997        // e.g., CAST(col AS OBJECT(fld1 OBJECT(fld2 INT))):fld1.fld2
20998        let use_colon = matches!(self.config.dialect, Some(DialectType::Snowflake))
20999            && matches!(
21000                &d.this,
21001                Expression::Cast(_) | Expression::SafeCast(_) | Expression::TryCast(_)
21002            );
21003        if use_colon {
21004            self.write(":");
21005        } else {
21006            self.write(".");
21007        }
21008        self.generate_identifier(&d.field)
21009    }
21010
21011    fn generate_method_call(&mut self, m: &MethodCall) -> Result<()> {
21012        self.generate_expression(&m.this)?;
21013        self.write(".");
21014        // Method names after a dot should not be quoted based on reserved keywords
21015        // Only quote if explicitly marked as quoted in the AST
21016        if m.method.quoted {
21017            let q = self.config.identifier_quote;
21018            self.write(&format!("{}{}{}", q, m.method.name, q));
21019        } else {
21020            self.write(&m.method.name);
21021        }
21022        self.write("(");
21023        for (i, arg) in m.args.iter().enumerate() {
21024            if i > 0 {
21025                self.write(", ");
21026            }
21027            self.generate_expression(arg)?;
21028        }
21029        self.write(")");
21030        Ok(())
21031    }
21032
21033    fn generate_array_slice(&mut self, s: &ArraySlice) -> Result<()> {
21034        // Check if we need to wrap the inner expression in parentheses
21035        // JSON arrow expressions have lower precedence than array subscript
21036        let needs_parens = matches!(
21037            &s.this,
21038            Expression::JsonExtract(f) if f.arrow_syntax
21039        ) || matches!(
21040            &s.this,
21041            Expression::JsonExtractScalar(f) if f.arrow_syntax
21042        );
21043
21044        if needs_parens {
21045            self.write("(");
21046        }
21047        self.generate_expression(&s.this)?;
21048        if needs_parens {
21049            self.write(")");
21050        }
21051        self.write("[");
21052        if let Some(start) = &s.start {
21053            self.generate_expression(start)?;
21054        }
21055        self.write(":");
21056        if let Some(end) = &s.end {
21057            self.generate_expression(end)?;
21058        }
21059        self.write("]");
21060        Ok(())
21061    }
21062
21063    fn generate_binary_op(&mut self, op: &BinaryOp, operator: &str) -> Result<()> {
21064        // Generate left expression, but skip trailing comments if they're already in left_comments
21065        // to avoid duplication (comments are captured as both expr.trailing_comments
21066        // and BinaryOp.left_comments during parsing)
21067        match &op.left {
21068            Expression::Column(col) => {
21069                // Generate column with trailing comments but skip them if they're
21070                // already captured in BinaryOp.left_comments to avoid duplication
21071                if let Some(table) = &col.table {
21072                    self.generate_identifier(table)?;
21073                    self.write(".");
21074                }
21075                self.generate_identifier(&col.name)?;
21076                // Oracle-style join marker (+)
21077                if col.join_mark && self.config.supports_column_join_marks {
21078                    self.write(" (+)");
21079                }
21080                // Output column trailing comments if they're not already in left_comments
21081                if op.left_comments.is_empty() {
21082                    for comment in &col.trailing_comments {
21083                        self.write_space();
21084                        self.write_formatted_comment(comment);
21085                    }
21086                }
21087            }
21088            Expression::Add(inner_op)
21089            | Expression::Sub(inner_op)
21090            | Expression::Mul(inner_op)
21091            | Expression::Div(inner_op)
21092            | Expression::Concat(inner_op) => {
21093                // Generate binary op without its trailing comments
21094                self.generate_binary_op_no_trailing(inner_op, match &op.left {
21095                    Expression::Add(_) => "+",
21096                    Expression::Sub(_) => "-",
21097                    Expression::Mul(_) => "*",
21098                    Expression::Div(_) => "/",
21099                    Expression::Concat(_) => "||",
21100                    _ => unreachable!("op.left variant already matched by outer arm as Add/Sub/Mul/Div/Concat"),
21101                })?;
21102            }
21103            _ => {
21104                self.generate_expression(&op.left)?;
21105            }
21106        }
21107        // Output comments after left operand
21108        for comment in &op.left_comments {
21109            self.write_space();
21110            self.write_formatted_comment(comment);
21111        }
21112        if self.config.pretty
21113            && matches!(self.config.dialect, Some(DialectType::Snowflake))
21114            && (operator == "AND" || operator == "OR")
21115        {
21116            self.write_newline();
21117            self.write_indent();
21118            self.write_keyword(operator);
21119        } else {
21120            self.write_space();
21121            if operator.chars().all(|c| c.is_alphabetic()) {
21122                self.write_keyword(operator);
21123            } else {
21124                self.write(operator);
21125            }
21126        }
21127        // Output comments after operator (before right operand)
21128        for comment in &op.operator_comments {
21129            self.write_space();
21130            self.write_formatted_comment(comment);
21131        }
21132        self.write_space();
21133        self.generate_expression(&op.right)?;
21134        // Output trailing comments after right operand
21135        for comment in &op.trailing_comments {
21136            self.write_space();
21137            self.write_formatted_comment(comment);
21138        }
21139        Ok(())
21140    }
21141
21142    fn generate_connector_op(&mut self, op: &BinaryOp, connector: ConnectorOperator) -> Result<()> {
21143        let keyword = connector.keyword();
21144        let Some(terms) = self.flatten_connector_terms(op, connector) else {
21145            return self.generate_binary_op(op, keyword);
21146        };
21147
21148        self.generate_expression(terms[0])?;
21149        for term in terms.iter().skip(1) {
21150            if self.config.pretty && matches!(self.config.dialect, Some(DialectType::Snowflake)) {
21151                self.write_newline();
21152                self.write_indent();
21153                self.write_keyword(keyword);
21154            } else {
21155                self.write_space();
21156                self.write_keyword(keyword);
21157            }
21158            self.write_space();
21159            self.generate_expression(term)?;
21160        }
21161
21162        Ok(())
21163    }
21164
21165    fn flatten_connector_terms<'a>(
21166        &self,
21167        root: &'a BinaryOp,
21168        connector: ConnectorOperator,
21169    ) -> Option<Vec<&'a Expression>> {
21170        if !root.left_comments.is_empty()
21171            || !root.operator_comments.is_empty()
21172            || !root.trailing_comments.is_empty()
21173        {
21174            return None;
21175        }
21176
21177        let mut terms = Vec::new();
21178        let mut stack: Vec<&Expression> = vec![&root.right, &root.left];
21179
21180        while let Some(expr) = stack.pop() {
21181            match (connector, expr) {
21182                (ConnectorOperator::And, Expression::And(inner))
21183                    if inner.left_comments.is_empty()
21184                        && inner.operator_comments.is_empty()
21185                        && inner.trailing_comments.is_empty() =>
21186                {
21187                    stack.push(&inner.right);
21188                    stack.push(&inner.left);
21189                }
21190                (ConnectorOperator::Or, Expression::Or(inner))
21191                    if inner.left_comments.is_empty()
21192                        && inner.operator_comments.is_empty()
21193                        && inner.trailing_comments.is_empty() =>
21194                {
21195                    stack.push(&inner.right);
21196                    stack.push(&inner.left);
21197                }
21198                _ => terms.push(expr),
21199            }
21200        }
21201
21202        if terms.len() > 1 {
21203            Some(terms)
21204        } else {
21205            None
21206        }
21207    }
21208
21209    /// Generate LIKE/ILIKE operation with optional ESCAPE clause
21210    fn generate_like_op(&mut self, op: &LikeOp, operator: &str) -> Result<()> {
21211        self.generate_expression(&op.left)?;
21212        self.write_space();
21213        // Drill backtick-quotes ILIKE
21214        if operator == "ILIKE" && matches!(self.config.dialect, Some(DialectType::Drill)) {
21215            self.write("`ILIKE`");
21216        } else {
21217            self.write_keyword(operator);
21218        }
21219        if let Some(quantifier) = &op.quantifier {
21220            self.write_space();
21221            self.write_keyword(quantifier);
21222        }
21223        self.write_space();
21224        self.generate_expression(&op.right)?;
21225        if let Some(escape) = &op.escape {
21226            self.write_space();
21227            self.write_keyword("ESCAPE");
21228            self.write_space();
21229            self.generate_expression(escape)?;
21230        }
21231        Ok(())
21232    }
21233
21234    /// Generate null-safe equality
21235    /// MySQL uses <=>, other dialects use IS NOT DISTINCT FROM
21236    fn generate_null_safe_eq(&mut self, op: &BinaryOp) -> Result<()> {
21237        use crate::dialects::DialectType;
21238        self.generate_expression(&op.left)?;
21239        self.write_space();
21240        if matches!(self.config.dialect, Some(DialectType::MySQL)) {
21241            self.write("<=>");
21242        } else {
21243            self.write_keyword("IS NOT DISTINCT FROM");
21244        }
21245        self.write_space();
21246        self.generate_expression(&op.right)?;
21247        Ok(())
21248    }
21249
21250    /// Generate IS DISTINCT FROM (null-safe inequality)
21251    fn generate_null_safe_neq(&mut self, op: &BinaryOp) -> Result<()> {
21252        self.generate_expression(&op.left)?;
21253        self.write_space();
21254        self.write_keyword("IS DISTINCT FROM");
21255        self.write_space();
21256        self.generate_expression(&op.right)?;
21257        Ok(())
21258    }
21259
21260    /// Generate binary op without trailing comments (used when nested inside another binary op)
21261    fn generate_binary_op_no_trailing(&mut self, op: &BinaryOp, operator: &str) -> Result<()> {
21262        // Generate left expression, but skip trailing comments
21263        match &op.left {
21264            Expression::Column(col) => {
21265                if let Some(table) = &col.table {
21266                    self.generate_identifier(table)?;
21267                    self.write(".");
21268                }
21269                self.generate_identifier(&col.name)?;
21270                // Oracle-style join marker (+)
21271                if col.join_mark && self.config.supports_column_join_marks {
21272                    self.write(" (+)");
21273                }
21274            }
21275            Expression::Add(inner_op)
21276            | Expression::Sub(inner_op)
21277            | Expression::Mul(inner_op)
21278            | Expression::Div(inner_op)
21279            | Expression::Concat(inner_op) => {
21280                self.generate_binary_op_no_trailing(inner_op, match &op.left {
21281                    Expression::Add(_) => "+",
21282                    Expression::Sub(_) => "-",
21283                    Expression::Mul(_) => "*",
21284                    Expression::Div(_) => "/",
21285                    Expression::Concat(_) => "||",
21286                    _ => unreachable!("op.left variant already matched by outer arm as Add/Sub/Mul/Div/Concat"),
21287                })?;
21288            }
21289            _ => {
21290                self.generate_expression(&op.left)?;
21291            }
21292        }
21293        // Output left_comments
21294        for comment in &op.left_comments {
21295            self.write_space();
21296            self.write_formatted_comment(comment);
21297        }
21298        self.write_space();
21299        if operator.chars().all(|c| c.is_alphabetic()) {
21300            self.write_keyword(operator);
21301        } else {
21302            self.write(operator);
21303        }
21304        // Output operator_comments
21305        for comment in &op.operator_comments {
21306            self.write_space();
21307            self.write_formatted_comment(comment);
21308        }
21309        self.write_space();
21310        // Generate right expression, but skip trailing comments if it's a Column
21311        // (the parent's left_comments will output them)
21312        match &op.right {
21313            Expression::Column(col) => {
21314                if let Some(table) = &col.table {
21315                    self.generate_identifier(table)?;
21316                    self.write(".");
21317                }
21318                self.generate_identifier(&col.name)?;
21319                // Oracle-style join marker (+)
21320                if col.join_mark && self.config.supports_column_join_marks {
21321                    self.write(" (+)");
21322                }
21323            }
21324            _ => {
21325                self.generate_expression(&op.right)?;
21326            }
21327        }
21328        // Skip trailing_comments - parent will handle them via its left_comments
21329        Ok(())
21330    }
21331
21332    fn generate_unary_op(&mut self, op: &UnaryOp, operator: &str) -> Result<()> {
21333        if operator.chars().all(|c| c.is_alphabetic()) {
21334            self.write_keyword(operator);
21335            self.write_space();
21336        } else {
21337            self.write(operator);
21338            // Add space between consecutive unary operators (e.g., "- -5" not "--5")
21339            if matches!(&op.this, Expression::Neg(_) | Expression::BitwiseNot(_)) {
21340                self.write_space();
21341            }
21342        }
21343        self.generate_expression(&op.this)
21344    }
21345
21346    fn generate_in(&mut self, in_expr: &In) -> Result<()> {
21347        // Generic mode supports two styles for negated IN:
21348        // - Prefix: NOT a IN (...)
21349        // - Infix:  a NOT IN (...)
21350        let is_generic =
21351            self.config.dialect.is_none() || self.config.dialect == Some(DialectType::Generic);
21352        let use_prefix_not =
21353            in_expr.not && is_generic && self.config.not_in_style == NotInStyle::Prefix;
21354        if use_prefix_not {
21355            self.write_keyword("NOT");
21356            self.write_space();
21357        }
21358        self.generate_expression(&in_expr.this)?;
21359        if in_expr.global {
21360            self.write_space();
21361            self.write_keyword("GLOBAL");
21362        }
21363        if in_expr.not && !use_prefix_not {
21364            self.write_space();
21365            self.write_keyword("NOT");
21366        }
21367        self.write_space();
21368        self.write_keyword("IN");
21369
21370        // BigQuery: IN UNNEST(expr)
21371        if let Some(unnest_expr) = &in_expr.unnest {
21372            self.write_space();
21373            self.write_keyword("UNNEST");
21374            self.write("(");
21375            self.generate_expression(unnest_expr)?;
21376            self.write(")");
21377            return Ok(());
21378        }
21379
21380        if let Some(query) = &in_expr.query {
21381            // Check if this is a bare identifier (PIVOT FOR foo IN y_enum)
21382            // vs a subquery (col IN (SELECT ...))
21383            let is_bare = in_expr.expressions.is_empty()
21384                && !matches!(
21385                    query,
21386                    Expression::Select(_)
21387                        | Expression::Union(_)
21388                        | Expression::Intersect(_)
21389                        | Expression::Except(_)
21390                        | Expression::Subquery(_)
21391                );
21392            if is_bare {
21393                // Bare identifier: no parentheses
21394                self.write_space();
21395                self.generate_expression(query)?;
21396            } else {
21397                // Subquery: with parentheses
21398                self.write(" (");
21399                let is_statement = matches!(
21400                    query,
21401                    Expression::Select(_)
21402                        | Expression::Union(_)
21403                        | Expression::Intersect(_)
21404                        | Expression::Except(_)
21405                        | Expression::Subquery(_)
21406                );
21407                if self.config.pretty && is_statement {
21408                    self.write_newline();
21409                    self.indent_level += 1;
21410                    self.write_indent();
21411                }
21412                self.generate_expression(query)?;
21413                if self.config.pretty && is_statement {
21414                    self.write_newline();
21415                    self.indent_level -= 1;
21416                    self.write_indent();
21417                }
21418                self.write(")");
21419            }
21420        } else {
21421            // DuckDB: IN without parentheses for single expression that is NOT a literal
21422            // (array/list membership like 'red' IN tbl.flags)
21423            // ClickHouse: IN without parentheses for single non-array expressions
21424            let is_duckdb = matches!(
21425                self.config.dialect,
21426                Some(crate::dialects::DialectType::DuckDB)
21427            );
21428            let is_clickhouse = matches!(
21429                self.config.dialect,
21430                Some(crate::dialects::DialectType::ClickHouse)
21431            );
21432            let single_expr = in_expr.expressions.len() == 1;
21433            if is_clickhouse && single_expr {
21434                if let Expression::Array(arr) = &in_expr.expressions[0] {
21435                    // ClickHouse: x IN [1, 2] -> x IN (1, 2)
21436                    self.write(" (");
21437                    for (i, expr) in arr.expressions.iter().enumerate() {
21438                        if i > 0 {
21439                            self.write(", ");
21440                        }
21441                        self.generate_expression(expr)?;
21442                    }
21443                    self.write(")");
21444                } else {
21445                    self.write_space();
21446                    self.generate_expression(&in_expr.expressions[0])?;
21447                }
21448            } else {
21449                let is_bare_ref = single_expr
21450                    && matches!(
21451                        &in_expr.expressions[0],
21452                        Expression::Column(_) | Expression::Identifier(_) | Expression::Dot(_)
21453                    );
21454                if (is_duckdb && is_bare_ref) || (in_expr.is_field && single_expr) {
21455                    // Bare field reference (no parens in source): IN identifier
21456                    // Also DuckDB: IN without parentheses for array/list membership
21457                    self.write_space();
21458                    self.generate_expression(&in_expr.expressions[0])?;
21459                } else {
21460                    // Standard IN (list)
21461                    self.write(" (");
21462                    for (i, expr) in in_expr.expressions.iter().enumerate() {
21463                        if i > 0 {
21464                            self.write(", ");
21465                        }
21466                        self.generate_expression(expr)?;
21467                    }
21468                    self.write(")");
21469                }
21470            }
21471        }
21472
21473        Ok(())
21474    }
21475
21476    fn generate_between(&mut self, between: &Between) -> Result<()> {
21477        // Generic mode: normalize NOT BETWEEN to prefix form: NOT a BETWEEN b AND c
21478        let use_prefix_not = between.not
21479            && (self.config.dialect.is_none() || self.config.dialect == Some(DialectType::Generic));
21480        if use_prefix_not {
21481            self.write_keyword("NOT");
21482            self.write_space();
21483        }
21484        self.generate_expression(&between.this)?;
21485        if between.not && !use_prefix_not {
21486            self.write_space();
21487            self.write_keyword("NOT");
21488        }
21489        self.write_space();
21490        self.write_keyword("BETWEEN");
21491        // Emit SYMMETRIC/ASYMMETRIC if present
21492        if let Some(sym) = between.symmetric {
21493            if sym {
21494                self.write(" SYMMETRIC");
21495            } else {
21496                self.write(" ASYMMETRIC");
21497            }
21498        }
21499        self.write_space();
21500        self.generate_expression(&between.low)?;
21501        self.write_space();
21502        self.write_keyword("AND");
21503        self.write_space();
21504        self.generate_expression(&between.high)
21505    }
21506
21507    fn generate_is_null(&mut self, is_null: &IsNull) -> Result<()> {
21508        // Generic mode: normalize IS NOT NULL to prefix form: NOT x IS NULL
21509        let use_prefix_not = is_null.not
21510            && (self.config.dialect.is_none()
21511                || self.config.dialect == Some(DialectType::Generic)
21512                || is_null.postfix_form);
21513        if use_prefix_not {
21514            // NOT x IS NULL (generic normalization and NOTNULL postfix form)
21515            self.write_keyword("NOT");
21516            self.write_space();
21517            self.generate_expression(&is_null.this)?;
21518            self.write_space();
21519            self.write_keyword("IS");
21520            self.write_space();
21521            self.write_keyword("NULL");
21522        } else {
21523            self.generate_expression(&is_null.this)?;
21524            self.write_space();
21525            self.write_keyword("IS");
21526            if is_null.not {
21527                self.write_space();
21528                self.write_keyword("NOT");
21529            }
21530            self.write_space();
21531            self.write_keyword("NULL");
21532        }
21533        Ok(())
21534    }
21535
21536    fn generate_is_true(&mut self, is_true: &IsTrueFalse) -> Result<()> {
21537        self.generate_expression(&is_true.this)?;
21538        self.write_space();
21539        self.write_keyword("IS");
21540        if is_true.not {
21541            self.write_space();
21542            self.write_keyword("NOT");
21543        }
21544        self.write_space();
21545        self.write_keyword("TRUE");
21546        Ok(())
21547    }
21548
21549    fn generate_is_false(&mut self, is_false: &IsTrueFalse) -> Result<()> {
21550        self.generate_expression(&is_false.this)?;
21551        self.write_space();
21552        self.write_keyword("IS");
21553        if is_false.not {
21554            self.write_space();
21555            self.write_keyword("NOT");
21556        }
21557        self.write_space();
21558        self.write_keyword("FALSE");
21559        Ok(())
21560    }
21561
21562    fn generate_is_json(&mut self, is_json: &IsJson) -> Result<()> {
21563        self.generate_expression(&is_json.this)?;
21564        self.write_space();
21565        self.write_keyword("IS");
21566        if is_json.negated {
21567            self.write_space();
21568            self.write_keyword("NOT");
21569        }
21570        self.write_space();
21571        self.write_keyword("JSON");
21572
21573        // Output JSON type if specified (VALUE, SCALAR, OBJECT, ARRAY)
21574        if let Some(ref json_type) = is_json.json_type {
21575            self.write_space();
21576            self.write_keyword(json_type);
21577        }
21578
21579        // Output key uniqueness constraint if specified
21580        match &is_json.unique_keys {
21581            Some(JsonUniqueKeys::With) => {
21582                self.write_space();
21583                self.write_keyword("WITH UNIQUE KEYS");
21584            }
21585            Some(JsonUniqueKeys::Without) => {
21586                self.write_space();
21587                self.write_keyword("WITHOUT UNIQUE KEYS");
21588            }
21589            Some(JsonUniqueKeys::Shorthand) => {
21590                self.write_space();
21591                self.write_keyword("UNIQUE KEYS");
21592            }
21593            None => {}
21594        }
21595
21596        Ok(())
21597    }
21598
21599    fn generate_is(&mut self, is_expr: &BinaryOp) -> Result<()> {
21600        self.generate_expression(&is_expr.left)?;
21601        self.write_space();
21602        self.write_keyword("IS");
21603        self.write_space();
21604        self.generate_expression(&is_expr.right)
21605    }
21606
21607    fn generate_exists(&mut self, exists: &Exists) -> Result<()> {
21608        if exists.not {
21609            self.write_keyword("NOT");
21610            self.write_space();
21611        }
21612        self.write_keyword("EXISTS");
21613        self.write("(");
21614        let is_statement = matches!(
21615            &exists.this,
21616            Expression::Select(_)
21617                | Expression::Union(_)
21618                | Expression::Intersect(_)
21619                | Expression::Except(_)
21620        );
21621        if self.config.pretty && is_statement {
21622            self.write_newline();
21623            self.indent_level += 1;
21624            self.write_indent();
21625            self.generate_expression(&exists.this)?;
21626            self.write_newline();
21627            self.indent_level -= 1;
21628            self.write_indent();
21629            self.write(")");
21630        } else {
21631            self.generate_expression(&exists.this)?;
21632            self.write(")");
21633        }
21634        Ok(())
21635    }
21636
21637    fn generate_member_of(&mut self, op: &BinaryOp) -> Result<()> {
21638        self.generate_expression(&op.left)?;
21639        self.write_space();
21640        self.write_keyword("MEMBER OF");
21641        self.write("(");
21642        self.generate_expression(&op.right)?;
21643        self.write(")");
21644        Ok(())
21645    }
21646
21647    fn generate_subquery(&mut self, subquery: &Subquery) -> Result<()> {
21648        if subquery.lateral {
21649            self.write_keyword("LATERAL");
21650            self.write_space();
21651        }
21652
21653        // If the inner expression is a Paren wrapping a statement, don't add extra parentheses
21654        // This handles cases like ((SELECT 1)) LIMIT 1 where we wrap Paren in Subquery
21655        // to carry the LIMIT modifier without adding more parens
21656        let skip_outer_parens = if let Expression::Paren(ref p) = &subquery.this {
21657            matches!(
21658                &p.this,
21659                Expression::Select(_)
21660                    | Expression::Union(_)
21661                    | Expression::Intersect(_)
21662                    | Expression::Except(_)
21663                    | Expression::Subquery(_)
21664            )
21665        } else {
21666            false
21667        };
21668
21669        // Check if inner expression is a statement for pretty formatting
21670        let is_statement = matches!(
21671            &subquery.this,
21672            Expression::Select(_)
21673                | Expression::Union(_)
21674                | Expression::Intersect(_)
21675                | Expression::Except(_)
21676                | Expression::Merge(_)
21677        );
21678
21679        if !skip_outer_parens {
21680            self.write("(");
21681            if self.config.pretty && is_statement {
21682                self.write_newline();
21683                self.indent_level += 1;
21684                self.write_indent();
21685            }
21686        }
21687        self.generate_expression(&subquery.this)?;
21688
21689        // Generate ORDER BY, LIMIT, OFFSET based on modifiers_inside flag
21690        if subquery.modifiers_inside {
21691            // Generate modifiers INSIDE the parentheses: (SELECT ... LIMIT 1)
21692            if let Some(order_by) = &subquery.order_by {
21693                self.write_space();
21694                self.write_keyword("ORDER BY");
21695                self.write_space();
21696                for (i, ord) in order_by.expressions.iter().enumerate() {
21697                    if i > 0 {
21698                        self.write(", ");
21699                    }
21700                    self.generate_ordered(ord)?;
21701                }
21702            }
21703
21704            if let Some(limit) = &subquery.limit {
21705                self.write_space();
21706                self.write_keyword("LIMIT");
21707                self.write_space();
21708                self.generate_expression(&limit.this)?;
21709                if limit.percent {
21710                    self.write_space();
21711                    self.write_keyword("PERCENT");
21712                }
21713            }
21714
21715            if let Some(offset) = &subquery.offset {
21716                self.write_space();
21717                self.write_keyword("OFFSET");
21718                self.write_space();
21719                self.generate_expression(&offset.this)?;
21720            }
21721        }
21722
21723        if !skip_outer_parens {
21724            if self.config.pretty && is_statement {
21725                self.write_newline();
21726                self.indent_level -= 1;
21727                self.write_indent();
21728            }
21729            self.write(")");
21730        }
21731
21732        // Generate modifiers OUTSIDE the parentheses: (SELECT ...) LIMIT 1
21733        if !subquery.modifiers_inside {
21734            if let Some(order_by) = &subquery.order_by {
21735                self.write_space();
21736                self.write_keyword("ORDER BY");
21737                self.write_space();
21738                for (i, ord) in order_by.expressions.iter().enumerate() {
21739                    if i > 0 {
21740                        self.write(", ");
21741                    }
21742                    self.generate_ordered(ord)?;
21743                }
21744            }
21745
21746            if let Some(limit) = &subquery.limit {
21747                self.write_space();
21748                self.write_keyword("LIMIT");
21749                self.write_space();
21750                self.generate_expression(&limit.this)?;
21751                if limit.percent {
21752                    self.write_space();
21753                    self.write_keyword("PERCENT");
21754                }
21755            }
21756
21757            if let Some(offset) = &subquery.offset {
21758                self.write_space();
21759                self.write_keyword("OFFSET");
21760                self.write_space();
21761                self.generate_expression(&offset.this)?;
21762            }
21763
21764            // Generate DISTRIBUTE BY (Hive/Spark)
21765            if let Some(distribute_by) = &subquery.distribute_by {
21766                self.write_space();
21767                self.write_keyword("DISTRIBUTE BY");
21768                self.write_space();
21769                for (i, expr) in distribute_by.expressions.iter().enumerate() {
21770                    if i > 0 {
21771                        self.write(", ");
21772                    }
21773                    self.generate_expression(expr)?;
21774                }
21775            }
21776
21777            // Generate SORT BY (Hive/Spark)
21778            if let Some(sort_by) = &subquery.sort_by {
21779                self.write_space();
21780                self.write_keyword("SORT BY");
21781                self.write_space();
21782                for (i, ord) in sort_by.expressions.iter().enumerate() {
21783                    if i > 0 {
21784                        self.write(", ");
21785                    }
21786                    self.generate_ordered(ord)?;
21787                }
21788            }
21789
21790            // Generate CLUSTER BY (Hive/Spark)
21791            if let Some(cluster_by) = &subquery.cluster_by {
21792                self.write_space();
21793                self.write_keyword("CLUSTER BY");
21794                self.write_space();
21795                for (i, ord) in cluster_by.expressions.iter().enumerate() {
21796                    if i > 0 {
21797                        self.write(", ");
21798                    }
21799                    self.generate_ordered(ord)?;
21800                }
21801            }
21802        }
21803
21804        if let Some(alias) = &subquery.alias {
21805            self.write_space();
21806            // Oracle doesn't use AS for subquery aliases
21807            let skip_as = matches!(
21808                self.config.dialect,
21809                Some(crate::dialects::DialectType::Oracle)
21810            );
21811            if !skip_as {
21812                self.write_keyword("AS");
21813                self.write_space();
21814            }
21815            self.generate_identifier(alias)?;
21816            if !subquery.column_aliases.is_empty() {
21817                self.write("(");
21818                for (i, col) in subquery.column_aliases.iter().enumerate() {
21819                    if i > 0 {
21820                        self.write(", ");
21821                    }
21822                    self.generate_identifier(col)?;
21823                }
21824                self.write(")");
21825            }
21826        }
21827        // Output trailing comments
21828        for comment in &subquery.trailing_comments {
21829            self.write(" ");
21830            self.write_formatted_comment(comment);
21831        }
21832        Ok(())
21833    }
21834
21835    fn generate_pivot(&mut self, pivot: &Pivot) -> Result<()> {
21836        // Generate WITH clause if present
21837        if let Some(ref with) = pivot.with {
21838            self.generate_with(with)?;
21839            self.write_space();
21840        }
21841
21842        let direction = if pivot.unpivot { "UNPIVOT" } else { "PIVOT" };
21843
21844        // Check for Redshift UNPIVOT in FROM clause:
21845        // UNPIVOT expr [AS val AT attr]
21846        // This is when unpivot=true, expressions is empty, fields is empty, and this is not Null
21847        let is_redshift_unpivot = pivot.unpivot
21848            && pivot.expressions.is_empty()
21849            && pivot.fields.is_empty()
21850            && pivot.using.is_empty()
21851            && pivot.into.is_none()
21852            && !matches!(&pivot.this, Expression::Null(_));
21853
21854        if is_redshift_unpivot {
21855            // Redshift UNPIVOT: UNPIVOT expr [AS alias]
21856            self.write_keyword("UNPIVOT");
21857            self.write_space();
21858            self.generate_expression(&pivot.this)?;
21859            // Alias - for Redshift it can be "val AT attr" format
21860            if let Some(alias) = &pivot.alias {
21861                self.write_space();
21862                self.write_keyword("AS");
21863                self.write_space();
21864                // The alias might contain " AT " for the attr part
21865                self.write(&alias.name);
21866            }
21867            return Ok(());
21868        }
21869
21870        // Check if this is a DuckDB simplified pivot (has `using` or `into`, or no `fields`)
21871        let is_simplified = !pivot.using.is_empty()
21872            || pivot.into.is_some()
21873            || (pivot.fields.is_empty()
21874                && !pivot.expressions.is_empty()
21875                && !matches!(&pivot.this, Expression::Null(_)));
21876
21877        if is_simplified {
21878            // DuckDB simplified syntax:
21879            //   PIVOT table ON cols [IN (...)] USING agg [AS alias], ... [GROUP BY ...]
21880            //   UNPIVOT table ON cols INTO NAME col VALUE col
21881            self.write_keyword(direction);
21882            self.write_space();
21883            self.generate_expression(&pivot.this)?;
21884
21885            if !pivot.expressions.is_empty() {
21886                self.write_space();
21887                self.write_keyword("ON");
21888                self.write_space();
21889                for (i, expr) in pivot.expressions.iter().enumerate() {
21890                    if i > 0 {
21891                        self.write(", ");
21892                    }
21893                    self.generate_expression(expr)?;
21894                }
21895            }
21896
21897            // INTO (for UNPIVOT)
21898            if let Some(into) = &pivot.into {
21899                self.write_space();
21900                self.write_keyword("INTO");
21901                self.write_space();
21902                self.generate_expression(into)?;
21903            }
21904
21905            // USING (for PIVOT)
21906            if !pivot.using.is_empty() {
21907                self.write_space();
21908                self.write_keyword("USING");
21909                self.write_space();
21910                for (i, expr) in pivot.using.iter().enumerate() {
21911                    if i > 0 {
21912                        self.write(", ");
21913                    }
21914                    self.generate_expression(expr)?;
21915                }
21916            }
21917
21918            // GROUP BY
21919            if let Some(group) = &pivot.group {
21920                self.write_space();
21921                self.generate_expression(group)?;
21922            }
21923        } else {
21924            // Standard syntax:
21925            //   table PIVOT(agg [AS alias], ... FOR col IN (val [AS alias], ...) [GROUP BY ...])
21926            //   table UNPIVOT(value_col FOR name_col IN (col1, col2, ...))
21927            // Only output the table expression if it's not a Null (null is used when PIVOT comes after JOIN ON)
21928            if !matches!(&pivot.this, Expression::Null(_)) {
21929                self.generate_expression(&pivot.this)?;
21930                self.write_space();
21931            }
21932            self.write_keyword(direction);
21933            self.write("(");
21934
21935            // Aggregation expressions
21936            for (i, expr) in pivot.expressions.iter().enumerate() {
21937                if i > 0 {
21938                    self.write(", ");
21939                }
21940                self.generate_expression(expr)?;
21941            }
21942
21943            // FOR...IN fields
21944            if !pivot.fields.is_empty() {
21945                if !pivot.expressions.is_empty() {
21946                    self.write_space();
21947                }
21948                self.write_keyword("FOR");
21949                self.write_space();
21950                for (i, field) in pivot.fields.iter().enumerate() {
21951                    if i > 0 {
21952                        self.write_space();
21953                    }
21954                    // field is an In expression: column IN (values)
21955                    self.generate_expression(field)?;
21956                }
21957            }
21958
21959            // DEFAULT ON NULL
21960            if let Some(default_val) = &pivot.default_on_null {
21961                self.write_space();
21962                self.write_keyword("DEFAULT ON NULL");
21963                self.write(" (");
21964                self.generate_expression(default_val)?;
21965                self.write(")");
21966            }
21967
21968            // GROUP BY inside PIVOT parens
21969            if let Some(group) = &pivot.group {
21970                self.write_space();
21971                self.generate_expression(group)?;
21972            }
21973
21974            self.write(")");
21975        }
21976
21977        // Alias
21978        if let Some(alias) = &pivot.alias {
21979            self.write_space();
21980            self.write_keyword("AS");
21981            self.write_space();
21982            self.generate_identifier(alias)?;
21983        }
21984
21985        Ok(())
21986    }
21987
21988    fn generate_unpivot(&mut self, unpivot: &Unpivot) -> Result<()> {
21989        self.generate_expression(&unpivot.this)?;
21990        self.write_space();
21991        self.write_keyword("UNPIVOT");
21992        // Output INCLUDE NULLS or EXCLUDE NULLS if specified
21993        if let Some(include) = unpivot.include_nulls {
21994            self.write_space();
21995            if include {
21996                self.write_keyword("INCLUDE NULLS");
21997            } else {
21998                self.write_keyword("EXCLUDE NULLS");
21999            }
22000            self.write_space();
22001        }
22002        self.write("(");
22003        if unpivot.value_column_parenthesized {
22004            self.write("(");
22005        }
22006        self.generate_identifier(&unpivot.value_column)?;
22007        // Output additional value columns if present
22008        for extra_col in &unpivot.extra_value_columns {
22009            self.write(", ");
22010            self.generate_identifier(extra_col)?;
22011        }
22012        if unpivot.value_column_parenthesized {
22013            self.write(")");
22014        }
22015        self.write_space();
22016        self.write_keyword("FOR");
22017        self.write_space();
22018        self.generate_identifier(&unpivot.name_column)?;
22019        self.write_space();
22020        self.write_keyword("IN");
22021        self.write(" (");
22022        for (i, col) in unpivot.columns.iter().enumerate() {
22023            if i > 0 {
22024                self.write(", ");
22025            }
22026            self.generate_expression(col)?;
22027        }
22028        self.write("))");
22029        if let Some(alias) = &unpivot.alias {
22030            self.write_space();
22031            self.write_keyword("AS");
22032            self.write_space();
22033            self.generate_identifier(alias)?;
22034        }
22035        Ok(())
22036    }
22037
22038    fn generate_values(&mut self, values: &Values) -> Result<()> {
22039        self.write_keyword("VALUES");
22040        for (i, row) in values.expressions.iter().enumerate() {
22041            if i > 0 {
22042                self.write(",");
22043            }
22044            self.write(" (");
22045            for (j, expr) in row.expressions.iter().enumerate() {
22046                if j > 0 {
22047                    self.write(", ");
22048                }
22049                self.generate_expression(expr)?;
22050            }
22051            self.write(")");
22052        }
22053        if let Some(alias) = &values.alias {
22054            self.write_space();
22055            self.write_keyword("AS");
22056            self.write_space();
22057            self.generate_identifier(alias)?;
22058            if !values.column_aliases.is_empty() {
22059                self.write("(");
22060                for (i, col) in values.column_aliases.iter().enumerate() {
22061                    if i > 0 {
22062                        self.write(", ");
22063                    }
22064                    self.generate_identifier(col)?;
22065                }
22066                self.write(")");
22067            }
22068        }
22069        Ok(())
22070    }
22071
22072    fn generate_array(&mut self, arr: &Array) -> Result<()> {
22073        // Apply struct name inheritance for target dialects that need it
22074        let needs_inheritance = matches!(
22075            self.config.dialect,
22076            Some(DialectType::DuckDB)
22077                | Some(DialectType::Spark)
22078                | Some(DialectType::Databricks)
22079                | Some(DialectType::Hive)
22080                | Some(DialectType::Snowflake)
22081                | Some(DialectType::Presto)
22082                | Some(DialectType::Trino)
22083        );
22084        let propagated: Vec<Expression>;
22085        let expressions = if needs_inheritance && arr.expressions.len() > 1 {
22086            propagated = Self::inherit_struct_field_names(&arr.expressions);
22087            &propagated
22088        } else {
22089            &arr.expressions
22090        };
22091
22092        // Generic mode: ARRAY(1, 2, 3) with parentheses
22093        // Dialect mode: ARRAY[1, 2, 3] with brackets (or just [1, 2, 3] if array_bracket_only)
22094        let use_parens =
22095            self.config.dialect.is_none() || self.config.dialect == Some(DialectType::Generic);
22096        if !self.config.array_bracket_only {
22097            self.write_keyword("ARRAY");
22098        }
22099        if use_parens {
22100            self.write("(");
22101        } else {
22102            self.write("[");
22103        }
22104        for (i, expr) in expressions.iter().enumerate() {
22105            if i > 0 {
22106                self.write(", ");
22107            }
22108            self.generate_expression(expr)?;
22109        }
22110        if use_parens {
22111            self.write(")");
22112        } else {
22113            self.write("]");
22114        }
22115        Ok(())
22116    }
22117
22118    fn generate_tuple(&mut self, tuple: &Tuple) -> Result<()> {
22119        // Special case: Tuple(function/expr, TableAlias) pattern for table functions with typed aliases
22120        // Used for PostgreSQL functions like JSON_TO_RECORDSET: FUNC(args) AS alias(col1 type1, col2 type2)
22121        if tuple.expressions.len() == 2 {
22122            if let Expression::TableAlias(_) = &tuple.expressions[1] {
22123                // First element is the function/expression, second is the TableAlias
22124                self.generate_expression(&tuple.expressions[0])?;
22125                self.write_space();
22126                self.write_keyword("AS");
22127                self.write_space();
22128                self.generate_expression(&tuple.expressions[1])?;
22129                return Ok(());
22130            }
22131        }
22132
22133        // In pretty mode, format long tuples with each element on a new line
22134        // Only expand if total width exceeds threshold
22135        let expand_tuple = if self.config.pretty && tuple.expressions.len() > 1 {
22136            let mut expr_strings: Vec<String> = Vec::with_capacity(tuple.expressions.len());
22137            for expr in &tuple.expressions {
22138                expr_strings.push(self.generate_to_string(expr)?);
22139            }
22140            self.too_wide(&expr_strings)
22141        } else {
22142            false
22143        };
22144
22145        if expand_tuple {
22146            self.write("(");
22147            self.write_newline();
22148            self.indent_level += 1;
22149            for (i, expr) in tuple.expressions.iter().enumerate() {
22150                if i > 0 {
22151                    self.write(",");
22152                    self.write_newline();
22153                }
22154                self.write_indent();
22155                self.generate_expression(expr)?;
22156            }
22157            self.indent_level -= 1;
22158            self.write_newline();
22159            self.write_indent();
22160            self.write(")");
22161        } else {
22162            self.write("(");
22163            for (i, expr) in tuple.expressions.iter().enumerate() {
22164                if i > 0 {
22165                    self.write(", ");
22166                }
22167                self.generate_expression(expr)?;
22168            }
22169            self.write(")");
22170        }
22171        Ok(())
22172    }
22173
22174    fn generate_pipe_operator(&mut self, pipe: &PipeOperator) -> Result<()> {
22175        self.generate_expression(&pipe.this)?;
22176        self.write(" |> ");
22177        self.generate_expression(&pipe.expression)?;
22178        Ok(())
22179    }
22180
22181    fn generate_ordered(&mut self, ordered: &Ordered) -> Result<()> {
22182        self.generate_expression(&ordered.this)?;
22183        if ordered.desc {
22184            self.write_space();
22185            self.write_keyword("DESC");
22186        } else if ordered.explicit_asc {
22187            self.write_space();
22188            self.write_keyword("ASC");
22189        }
22190        if let Some(nulls_first) = ordered.nulls_first {
22191            // Determine if we should skip outputting NULLS FIRST/LAST when it's the default
22192            // for the dialect. Different dialects have different NULL ordering defaults:
22193            //
22194            // nulls_are_large (Oracle, Postgres, Snowflake, etc.):
22195            //   - ASC: NULLS LAST is default (omit NULLS LAST for ASC)
22196            //   - DESC: NULLS FIRST is default (omit NULLS FIRST for DESC)
22197            //
22198            // nulls_are_small (Spark, Hive, BigQuery, most others):
22199            //   - ASC: NULLS FIRST is default
22200            //   - DESC: NULLS LAST is default
22201            //
22202            // nulls_are_last (DuckDB, Presto, Trino, Dremio, etc.):
22203            //   - NULLS LAST is always the default regardless of sort direction
22204            let is_asc = !ordered.desc;
22205            let is_nulls_are_large = matches!(
22206                self.config.dialect,
22207                Some(DialectType::Oracle)
22208                    | Some(DialectType::PostgreSQL)
22209                    | Some(DialectType::Redshift)
22210                    | Some(DialectType::Snowflake)
22211            );
22212            let is_nulls_are_last = matches!(
22213                self.config.dialect,
22214                Some(DialectType::Dremio)
22215                    | Some(DialectType::DuckDB)
22216                    | Some(DialectType::Presto)
22217                    | Some(DialectType::Trino)
22218                    | Some(DialectType::Athena)
22219                    | Some(DialectType::ClickHouse)
22220                    | Some(DialectType::Drill)
22221                    | Some(DialectType::Exasol)
22222            );
22223
22224            // Check if the NULLS ordering matches the default for this dialect
22225            let is_default_nulls = if is_nulls_are_large {
22226                // For nulls_are_large: ASC + NULLS LAST or DESC + NULLS FIRST is default
22227                (is_asc && !nulls_first) || (!is_asc && nulls_first)
22228            } else if is_nulls_are_last {
22229                // For nulls_are_last: NULLS LAST is always default
22230                !nulls_first
22231            } else {
22232                false
22233            };
22234
22235            if !is_default_nulls {
22236                self.write_space();
22237                self.write_keyword("NULLS");
22238                self.write_space();
22239                self.write_keyword(if nulls_first { "FIRST" } else { "LAST" });
22240            }
22241        }
22242        // WITH FILL clause (ClickHouse)
22243        if let Some(ref with_fill) = ordered.with_fill {
22244            self.write_space();
22245            self.generate_with_fill(with_fill)?;
22246        }
22247        Ok(())
22248    }
22249
22250    /// Write a ClickHouse type string, wrapping in Nullable unless in map key context.
22251    fn write_clickhouse_type(&mut self, type_str: &str) {
22252        if self.clickhouse_nullable_depth < 0 {
22253            // Map key context: don't wrap in Nullable
22254            self.write(type_str);
22255        } else {
22256            self.write(&format!("Nullable({})", type_str));
22257        }
22258    }
22259
22260    fn generate_data_type(&mut self, dt: &DataType) -> Result<()> {
22261        use crate::dialects::DialectType;
22262
22263        match dt {
22264            DataType::Boolean => {
22265                // Dialect-specific boolean type mappings
22266                match self.config.dialect {
22267                    Some(DialectType::TSQL) => self.write_keyword("BIT"),
22268                    Some(DialectType::MySQL) => self.write_keyword("BOOLEAN"), // alias for TINYINT(1)
22269                    Some(DialectType::Oracle) => {
22270                        // Oracle 23c+ supports BOOLEAN, older versions use NUMBER(1)
22271                        self.write_keyword("NUMBER(1)")
22272                    }
22273                    Some(DialectType::ClickHouse) => self.write("Bool"), // ClickHouse uses Bool (case-sensitive)
22274                    _ => self.write_keyword("BOOLEAN"),
22275                }
22276            }
22277            DataType::TinyInt { length } => {
22278                // PostgreSQL, Oracle, and Exasol don't have TINYINT, use SMALLINT
22279                // Dremio maps TINYINT to INT
22280                // ClickHouse maps TINYINT to Int8
22281                match self.config.dialect {
22282                    Some(DialectType::PostgreSQL)
22283                    | Some(DialectType::Redshift)
22284                    | Some(DialectType::Oracle)
22285                    | Some(DialectType::Exasol) => {
22286                        self.write_keyword("SMALLINT");
22287                    }
22288                    Some(DialectType::Teradata) => {
22289                        // Teradata uses BYTEINT for smallest integer
22290                        self.write_keyword("BYTEINT");
22291                    }
22292                    Some(DialectType::Dremio) => {
22293                        // Dremio maps TINYINT to INT
22294                        self.write_keyword("INT");
22295                    }
22296                    Some(DialectType::ClickHouse) => {
22297                        self.write_clickhouse_type("Int8");
22298                    }
22299                    _ => {
22300                        self.write_keyword("TINYINT");
22301                    }
22302                }
22303                if let Some(n) = length {
22304                    if !matches!(
22305                        self.config.dialect,
22306                        Some(DialectType::Dremio) | Some(DialectType::ClickHouse)
22307                    ) {
22308                        self.write(&format!("({})", n));
22309                    }
22310                }
22311            }
22312            DataType::SmallInt { length } => {
22313                // Dremio maps SMALLINT to INT, SQLite/Drill maps SMALLINT to INTEGER
22314                match self.config.dialect {
22315                    Some(DialectType::Dremio) => {
22316                        self.write_keyword("INT");
22317                    }
22318                    Some(DialectType::SQLite) | Some(DialectType::Drill) => {
22319                        self.write_keyword("INTEGER");
22320                    }
22321                    Some(DialectType::BigQuery) => {
22322                        self.write_keyword("INT64");
22323                    }
22324                    Some(DialectType::ClickHouse) => {
22325                        self.write_clickhouse_type("Int16");
22326                    }
22327                    _ => {
22328                        self.write_keyword("SMALLINT");
22329                        if let Some(n) = length {
22330                            self.write(&format!("({})", n));
22331                        }
22332                    }
22333                }
22334            }
22335            DataType::Int {
22336                length,
22337                integer_spelling: _,
22338            } => {
22339                // BigQuery uses INT64 for INT
22340                if matches!(self.config.dialect, Some(DialectType::BigQuery)) {
22341                    self.write_keyword("INT64");
22342                } else if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
22343                    self.write_clickhouse_type("Int32");
22344                } else {
22345                    // TSQL, Presto, Trino, SQLite, Redshift use INTEGER as the canonical form
22346                    let use_integer = match self.config.dialect {
22347                        Some(DialectType::TSQL)
22348                        | Some(DialectType::Fabric)
22349                        | Some(DialectType::Presto)
22350                        | Some(DialectType::Trino)
22351                        | Some(DialectType::SQLite)
22352                        | Some(DialectType::Redshift) => true,
22353                        _ => false,
22354                    };
22355                    if use_integer {
22356                        self.write_keyword("INTEGER");
22357                    } else {
22358                        self.write_keyword("INT");
22359                    }
22360                    if let Some(n) = length {
22361                        self.write(&format!("({})", n));
22362                    }
22363                }
22364            }
22365            DataType::BigInt { length } => {
22366                // Dialect-specific bigint type mappings
22367                match self.config.dialect {
22368                    Some(DialectType::Oracle) => {
22369                        // Oracle doesn't have BIGINT, uses INT
22370                        self.write_keyword("INT");
22371                    }
22372                    Some(DialectType::ClickHouse) => {
22373                        self.write_clickhouse_type("Int64");
22374                    }
22375                    _ => {
22376                        self.write_keyword("BIGINT");
22377                        if let Some(n) = length {
22378                            self.write(&format!("({})", n));
22379                        }
22380                    }
22381                }
22382            }
22383            DataType::Float {
22384                precision,
22385                scale,
22386                real_spelling,
22387            } => {
22388                // Dialect-specific float type mappings
22389                // If real_spelling is true, preserve REAL; otherwise use dialect default
22390                // Spark/Hive don't support REAL, always use FLOAT
22391                if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
22392                    self.write_clickhouse_type("Float32");
22393                } else if *real_spelling
22394                    && !matches!(
22395                        self.config.dialect,
22396                        Some(DialectType::Spark)
22397                            | Some(DialectType::Databricks)
22398                            | Some(DialectType::Hive)
22399                            | Some(DialectType::Snowflake)
22400                            | Some(DialectType::MySQL)
22401                            | Some(DialectType::BigQuery)
22402                    )
22403                {
22404                    self.write_keyword("REAL")
22405                } else {
22406                    match self.config.dialect {
22407                        Some(DialectType::PostgreSQL) => self.write_keyword("REAL"),
22408                        Some(DialectType::BigQuery) => self.write_keyword("FLOAT64"),
22409                        _ => self.write_keyword("FLOAT"),
22410                    }
22411                }
22412                // MySQL supports FLOAT(precision) or FLOAT(precision, scale)
22413                // Spark/Hive don't support FLOAT(precision)
22414                if !matches!(
22415                    self.config.dialect,
22416                    Some(DialectType::Spark)
22417                        | Some(DialectType::Databricks)
22418                        | Some(DialectType::Hive)
22419                        | Some(DialectType::Presto)
22420                        | Some(DialectType::Trino)
22421                ) {
22422                    if let Some(p) = precision {
22423                        self.write(&format!("({}", p));
22424                        if let Some(s) = scale {
22425                            self.write(&format!(", {})", s));
22426                        } else {
22427                            self.write(")");
22428                        }
22429                    }
22430                }
22431            }
22432            DataType::Double { precision, scale } => {
22433                // Dialect-specific double type mappings
22434                match self.config.dialect {
22435                    Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
22436                        self.write_keyword("FLOAT")
22437                    } // SQL Server/Fabric FLOAT is double
22438                    Some(DialectType::Oracle) => self.write_keyword("DOUBLE PRECISION"),
22439                    Some(DialectType::ClickHouse) => self.write_clickhouse_type("Float64"),
22440                    Some(DialectType::BigQuery) => self.write_keyword("FLOAT64"),
22441                    Some(DialectType::SQLite) => self.write_keyword("REAL"),
22442                    Some(DialectType::PostgreSQL)
22443                    | Some(DialectType::Redshift)
22444                    | Some(DialectType::Teradata)
22445                    | Some(DialectType::Materialize) => self.write_keyword("DOUBLE PRECISION"),
22446                    _ => self.write_keyword("DOUBLE"),
22447                }
22448                // MySQL supports DOUBLE(precision, scale)
22449                if let Some(p) = precision {
22450                    self.write(&format!("({}", p));
22451                    if let Some(s) = scale {
22452                        self.write(&format!(", {})", s));
22453                    } else {
22454                        self.write(")");
22455                    }
22456                }
22457            }
22458            DataType::Decimal { precision, scale } => {
22459                // Dialect-specific decimal type mappings
22460                match self.config.dialect {
22461                    Some(DialectType::ClickHouse) => {
22462                        self.write("Decimal");
22463                        if let Some(p) = precision {
22464                            self.write(&format!("({}", p));
22465                            if let Some(s) = scale {
22466                                self.write(&format!(", {}", s));
22467                            }
22468                            self.write(")");
22469                        }
22470                    }
22471                    Some(DialectType::Oracle) => {
22472                        // Oracle uses NUMBER instead of DECIMAL
22473                        self.write_keyword("NUMBER");
22474                        if let Some(p) = precision {
22475                            self.write(&format!("({}", p));
22476                            if let Some(s) = scale {
22477                                self.write(&format!(", {}", s));
22478                            }
22479                            self.write(")");
22480                        }
22481                    }
22482                    Some(DialectType::BigQuery) => {
22483                        // BigQuery uses NUMERIC instead of DECIMAL
22484                        self.write_keyword("NUMERIC");
22485                        if let Some(p) = precision {
22486                            self.write(&format!("({}", p));
22487                            if let Some(s) = scale {
22488                                self.write(&format!(", {}", s));
22489                            }
22490                            self.write(")");
22491                        }
22492                    }
22493                    _ => {
22494                        self.write_keyword("DECIMAL");
22495                        if let Some(p) = precision {
22496                            self.write(&format!("({}", p));
22497                            if let Some(s) = scale {
22498                                self.write(&format!(", {}", s));
22499                            }
22500                            self.write(")");
22501                        }
22502                    }
22503                }
22504            }
22505            DataType::Char { length } => {
22506                // Dialect-specific char type mappings
22507                match self.config.dialect {
22508                    Some(DialectType::DuckDB) | Some(DialectType::SQLite) => {
22509                        // DuckDB/SQLite maps CHAR to TEXT
22510                        self.write_keyword("TEXT");
22511                    }
22512                    Some(DialectType::Hive)
22513                    | Some(DialectType::Spark)
22514                    | Some(DialectType::Databricks) => {
22515                        // Hive/Spark/Databricks maps CHAR to STRING (when no length)
22516                        // CHAR(n) with explicit length is kept as CHAR(n) for Spark/Databricks
22517                        if length.is_some()
22518                            && !matches!(self.config.dialect, Some(DialectType::Hive))
22519                        {
22520                            self.write_keyword("CHAR");
22521                            if let Some(n) = length {
22522                                self.write(&format!("({})", n));
22523                            }
22524                        } else {
22525                            self.write_keyword("STRING");
22526                        }
22527                    }
22528                    Some(DialectType::Dremio) => {
22529                        // Dremio maps CHAR to VARCHAR
22530                        self.write_keyword("VARCHAR");
22531                        if let Some(n) = length {
22532                            self.write(&format!("({})", n));
22533                        }
22534                    }
22535                    _ => {
22536                        self.write_keyword("CHAR");
22537                        if let Some(n) = length {
22538                            self.write(&format!("({})", n));
22539                        }
22540                    }
22541                }
22542            }
22543            DataType::VarChar {
22544                length,
22545                parenthesized_length,
22546            } => {
22547                // Dialect-specific varchar type mappings
22548                match self.config.dialect {
22549                    Some(DialectType::Oracle) => {
22550                        self.write_keyword("VARCHAR2");
22551                        if let Some(n) = length {
22552                            self.write(&format!("({})", n));
22553                        }
22554                    }
22555                    Some(DialectType::DuckDB) => {
22556                        // DuckDB maps VARCHAR to TEXT, preserving length
22557                        self.write_keyword("TEXT");
22558                        if let Some(n) = length {
22559                            self.write(&format!("({})", n));
22560                        }
22561                    }
22562                    Some(DialectType::SQLite) => {
22563                        // SQLite maps VARCHAR to TEXT, preserving length
22564                        self.write_keyword("TEXT");
22565                        if let Some(n) = length {
22566                            self.write(&format!("({})", n));
22567                        }
22568                    }
22569                    Some(DialectType::MySQL) if length.is_none() => {
22570                        // MySQL requires VARCHAR to have a size - if it doesn't, use TEXT
22571                        self.write_keyword("TEXT");
22572                    }
22573                    Some(DialectType::Hive)
22574                    | Some(DialectType::Spark)
22575                    | Some(DialectType::Databricks)
22576                        if length.is_none() =>
22577                    {
22578                        // Hive/Spark/Databricks: VARCHAR without length → STRING
22579                        self.write_keyword("STRING");
22580                    }
22581                    _ => {
22582                        self.write_keyword("VARCHAR");
22583                        if let Some(n) = length {
22584                            // Hive uses VARCHAR((n)) with extra parentheses in STRUCT definitions
22585                            if *parenthesized_length {
22586                                self.write(&format!("(({}))", n));
22587                            } else {
22588                                self.write(&format!("({})", n));
22589                            }
22590                        }
22591                    }
22592                }
22593            }
22594            DataType::Text => {
22595                // Dialect-specific text type mappings
22596                match self.config.dialect {
22597                    Some(DialectType::Oracle) => self.write_keyword("CLOB"),
22598                    Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
22599                        self.write_keyword("VARCHAR(MAX)")
22600                    }
22601                    Some(DialectType::BigQuery) => self.write_keyword("STRING"),
22602                    Some(DialectType::Snowflake)
22603                    | Some(DialectType::Dremio)
22604                    | Some(DialectType::Drill) => self.write_keyword("VARCHAR"),
22605                    Some(DialectType::Exasol) => self.write_keyword("LONG VARCHAR"),
22606                    Some(DialectType::Presto)
22607                    | Some(DialectType::Trino)
22608                    | Some(DialectType::Athena) => self.write_keyword("VARCHAR"),
22609                    Some(DialectType::Spark)
22610                    | Some(DialectType::Databricks)
22611                    | Some(DialectType::Hive) => self.write_keyword("STRING"),
22612                    Some(DialectType::Redshift) => self.write_keyword("VARCHAR(MAX)"),
22613                    Some(DialectType::StarRocks) | Some(DialectType::Doris) => {
22614                        self.write_keyword("STRING")
22615                    }
22616                    Some(DialectType::ClickHouse) => self.write_clickhouse_type("String"),
22617                    _ => self.write_keyword("TEXT"),
22618                }
22619            }
22620            DataType::TextWithLength { length } => {
22621                // TEXT(n) - dialect-specific type with length
22622                match self.config.dialect {
22623                    Some(DialectType::Oracle) => self.write(&format!("CLOB({})", length)),
22624                    Some(DialectType::Hive)
22625                    | Some(DialectType::Spark)
22626                    | Some(DialectType::Databricks) => {
22627                        self.write(&format!("VARCHAR({})", length));
22628                    }
22629                    Some(DialectType::Redshift) => self.write(&format!("VARCHAR({})", length)),
22630                    Some(DialectType::BigQuery) => self.write(&format!("STRING({})", length)),
22631                    Some(DialectType::Snowflake)
22632                    | Some(DialectType::Presto)
22633                    | Some(DialectType::Trino)
22634                    | Some(DialectType::Athena)
22635                    | Some(DialectType::Drill)
22636                    | Some(DialectType::Dremio) => {
22637                        self.write(&format!("VARCHAR({})", length));
22638                    }
22639                    Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
22640                        self.write(&format!("VARCHAR({})", length))
22641                    }
22642                    Some(DialectType::StarRocks) | Some(DialectType::Doris) => {
22643                        self.write(&format!("STRING({})", length))
22644                    }
22645                    Some(DialectType::ClickHouse) => self.write_clickhouse_type("String"),
22646                    _ => self.write(&format!("TEXT({})", length)),
22647                }
22648            }
22649            DataType::String { length } => {
22650                // STRING type with optional length (BigQuery STRING(n))
22651                match self.config.dialect {
22652                    Some(DialectType::ClickHouse) => {
22653                        // ClickHouse uses String with specific casing
22654                        self.write("String");
22655                        if let Some(n) = length {
22656                            self.write(&format!("({})", n));
22657                        }
22658                    }
22659                    Some(DialectType::BigQuery)
22660                    | Some(DialectType::Hive)
22661                    | Some(DialectType::Spark)
22662                    | Some(DialectType::Databricks)
22663                    | Some(DialectType::StarRocks)
22664                    | Some(DialectType::Doris) => {
22665                        self.write_keyword("STRING");
22666                        if let Some(n) = length {
22667                            self.write(&format!("({})", n));
22668                        }
22669                    }
22670                    Some(DialectType::PostgreSQL) => {
22671                        // PostgreSQL doesn't have STRING - use VARCHAR or TEXT
22672                        if let Some(n) = length {
22673                            self.write_keyword("VARCHAR");
22674                            self.write(&format!("({})", n));
22675                        } else {
22676                            self.write_keyword("TEXT");
22677                        }
22678                    }
22679                    Some(DialectType::Redshift) => {
22680                        // Redshift: STRING -> VARCHAR(MAX)
22681                        if let Some(n) = length {
22682                            self.write_keyword("VARCHAR");
22683                            self.write(&format!("({})", n));
22684                        } else {
22685                            self.write_keyword("VARCHAR(MAX)");
22686                        }
22687                    }
22688                    Some(DialectType::MySQL) => {
22689                        // MySQL doesn't have STRING - use VARCHAR or TEXT
22690                        if let Some(n) = length {
22691                            self.write_keyword("VARCHAR");
22692                            self.write(&format!("({})", n));
22693                        } else {
22694                            self.write_keyword("TEXT");
22695                        }
22696                    }
22697                    Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
22698                        // TSQL: STRING -> VARCHAR(MAX)
22699                        if let Some(n) = length {
22700                            self.write_keyword("VARCHAR");
22701                            self.write(&format!("({})", n));
22702                        } else {
22703                            self.write_keyword("VARCHAR(MAX)");
22704                        }
22705                    }
22706                    Some(DialectType::Oracle) => {
22707                        // Oracle: STRING -> CLOB
22708                        self.write_keyword("CLOB");
22709                    }
22710                    Some(DialectType::DuckDB) | Some(DialectType::Materialize) => {
22711                        // DuckDB/Materialize uses TEXT for string types
22712                        self.write_keyword("TEXT");
22713                        if let Some(n) = length {
22714                            self.write(&format!("({})", n));
22715                        }
22716                    }
22717                    Some(DialectType::Presto)
22718                    | Some(DialectType::Trino)
22719                    | Some(DialectType::Drill)
22720                    | Some(DialectType::Dremio) => {
22721                        // Presto/Trino/Drill use VARCHAR for string types
22722                        self.write_keyword("VARCHAR");
22723                        if let Some(n) = length {
22724                            self.write(&format!("({})", n));
22725                        }
22726                    }
22727                    Some(DialectType::Snowflake) => {
22728                        // Snowflake: STRING stays as STRING (identity/DDL)
22729                        // CAST context STRING -> VARCHAR is handled in generate_cast
22730                        self.write_keyword("STRING");
22731                        if let Some(n) = length {
22732                            self.write(&format!("({})", n));
22733                        }
22734                    }
22735                    _ => {
22736                        // Default: output STRING with optional length
22737                        self.write_keyword("STRING");
22738                        if let Some(n) = length {
22739                            self.write(&format!("({})", n));
22740                        }
22741                    }
22742                }
22743            }
22744            DataType::Binary { length } => {
22745                // Dialect-specific binary type mappings
22746                match self.config.dialect {
22747                    Some(DialectType::PostgreSQL) | Some(DialectType::Materialize) => {
22748                        self.write_keyword("BYTEA");
22749                        if let Some(n) = length {
22750                            self.write(&format!("({})", n));
22751                        }
22752                    }
22753                    Some(DialectType::Redshift) => {
22754                        self.write_keyword("VARBYTE");
22755                        if let Some(n) = length {
22756                            self.write(&format!("({})", n));
22757                        }
22758                    }
22759                    Some(DialectType::DuckDB)
22760                    | Some(DialectType::SQLite)
22761                    | Some(DialectType::Oracle) => {
22762                        // DuckDB/SQLite/Oracle maps BINARY to BLOB
22763                        self.write_keyword("BLOB");
22764                        if let Some(n) = length {
22765                            self.write(&format!("({})", n));
22766                        }
22767                    }
22768                    Some(DialectType::Presto)
22769                    | Some(DialectType::Trino)
22770                    | Some(DialectType::Athena)
22771                    | Some(DialectType::Drill)
22772                    | Some(DialectType::Dremio) => {
22773                        // These dialects map BINARY to VARBINARY
22774                        self.write_keyword("VARBINARY");
22775                        if let Some(n) = length {
22776                            self.write(&format!("({})", n));
22777                        }
22778                    }
22779                    Some(DialectType::ClickHouse) => {
22780                        // ClickHouse: wrap BINARY in Nullable (unless map key context)
22781                        if self.clickhouse_nullable_depth < 0 {
22782                            self.write("BINARY");
22783                        } else {
22784                            self.write("Nullable(BINARY");
22785                        }
22786                        if let Some(n) = length {
22787                            self.write(&format!("({})", n));
22788                        }
22789                        if self.clickhouse_nullable_depth >= 0 {
22790                            self.write(")");
22791                        }
22792                    }
22793                    _ => {
22794                        self.write_keyword("BINARY");
22795                        if let Some(n) = length {
22796                            self.write(&format!("({})", n));
22797                        }
22798                    }
22799                }
22800            }
22801            DataType::VarBinary { length } => {
22802                // Dialect-specific varbinary type mappings
22803                match self.config.dialect {
22804                    Some(DialectType::PostgreSQL) | Some(DialectType::Materialize) => {
22805                        self.write_keyword("BYTEA");
22806                        if let Some(n) = length {
22807                            self.write(&format!("({})", n));
22808                        }
22809                    }
22810                    Some(DialectType::Redshift) => {
22811                        self.write_keyword("VARBYTE");
22812                        if let Some(n) = length {
22813                            self.write(&format!("({})", n));
22814                        }
22815                    }
22816                    Some(DialectType::DuckDB)
22817                    | Some(DialectType::SQLite)
22818                    | Some(DialectType::Oracle) => {
22819                        // DuckDB/SQLite/Oracle maps VARBINARY to BLOB
22820                        self.write_keyword("BLOB");
22821                        if let Some(n) = length {
22822                            self.write(&format!("({})", n));
22823                        }
22824                    }
22825                    Some(DialectType::Exasol) => {
22826                        // Exasol maps VARBINARY to VARCHAR
22827                        self.write_keyword("VARCHAR");
22828                    }
22829                    Some(DialectType::Spark)
22830                    | Some(DialectType::Hive)
22831                    | Some(DialectType::Databricks) => {
22832                        // Spark/Hive use BINARY instead of VARBINARY
22833                        self.write_keyword("BINARY");
22834                        if let Some(n) = length {
22835                            self.write(&format!("({})", n));
22836                        }
22837                    }
22838                    Some(DialectType::ClickHouse) => {
22839                        // ClickHouse maps VARBINARY to String (wrapped in Nullable unless map key)
22840                        self.write_clickhouse_type("String");
22841                    }
22842                    _ => {
22843                        self.write_keyword("VARBINARY");
22844                        if let Some(n) = length {
22845                            self.write(&format!("({})", n));
22846                        }
22847                    }
22848                }
22849            }
22850            DataType::Blob => {
22851                // Dialect-specific blob type mappings
22852                match self.config.dialect {
22853                    Some(DialectType::PostgreSQL) => self.write_keyword("BYTEA"),
22854                    Some(DialectType::Redshift) => self.write_keyword("VARBYTE"),
22855                    Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
22856                        self.write_keyword("VARBINARY")
22857                    }
22858                    Some(DialectType::BigQuery) => self.write_keyword("BYTES"),
22859                    Some(DialectType::Exasol) => self.write_keyword("VARCHAR"),
22860                    Some(DialectType::Presto)
22861                    | Some(DialectType::Trino)
22862                    | Some(DialectType::Athena) => self.write_keyword("VARBINARY"),
22863                    Some(DialectType::DuckDB) => {
22864                        // Python sqlglot: BLOB -> VARBINARY for DuckDB (base TYPE_MAPPING)
22865                        // DuckDB identity works via: BLOB -> transform VarBinary -> generator BLOB
22866                        self.write_keyword("VARBINARY");
22867                    }
22868                    Some(DialectType::Spark)
22869                    | Some(DialectType::Databricks)
22870                    | Some(DialectType::Hive) => self.write_keyword("BINARY"),
22871                    Some(DialectType::ClickHouse) => {
22872                        // BLOB maps to Nullable(String) in ClickHouse, even in column defs
22873                        // where we normally suppress Nullable wrapping (clickhouse_nullable_depth = -1).
22874                        // This matches Python sqlglot behavior.
22875                        self.write("Nullable(String)");
22876                    }
22877                    _ => self.write_keyword("BLOB"),
22878                }
22879            }
22880            DataType::Bit { length } => {
22881                // Dialect-specific bit type mappings
22882                match self.config.dialect {
22883                    Some(DialectType::Dremio)
22884                    | Some(DialectType::Spark)
22885                    | Some(DialectType::Databricks)
22886                    | Some(DialectType::Hive)
22887                    | Some(DialectType::Snowflake)
22888                    | Some(DialectType::BigQuery)
22889                    | Some(DialectType::Presto)
22890                    | Some(DialectType::Trino)
22891                    | Some(DialectType::ClickHouse)
22892                    | Some(DialectType::Redshift) => {
22893                        // These dialects don't support BIT type, use BOOLEAN
22894                        self.write_keyword("BOOLEAN");
22895                    }
22896                    _ => {
22897                        self.write_keyword("BIT");
22898                        if let Some(n) = length {
22899                            self.write(&format!("({})", n));
22900                        }
22901                    }
22902                }
22903            }
22904            DataType::VarBit { length } => {
22905                self.write_keyword("VARBIT");
22906                if let Some(n) = length {
22907                    self.write(&format!("({})", n));
22908                }
22909            }
22910            DataType::Date => self.write_keyword("DATE"),
22911            DataType::Time {
22912                precision,
22913                timezone,
22914            } => {
22915                if *timezone {
22916                    // Dialect-specific TIME WITH TIME ZONE output
22917                    match self.config.dialect {
22918                        Some(DialectType::DuckDB) => {
22919                            // DuckDB: TIMETZ (drops precision)
22920                            self.write_keyword("TIMETZ");
22921                        }
22922                        Some(DialectType::PostgreSQL) => {
22923                            // PostgreSQL: TIMETZ or TIMETZ(p)
22924                            self.write_keyword("TIMETZ");
22925                            if let Some(p) = precision {
22926                                self.write(&format!("({})", p));
22927                            }
22928                        }
22929                        _ => {
22930                            // Presto/Trino/Redshift/others: TIME(p) WITH TIME ZONE
22931                            self.write_keyword("TIME");
22932                            if let Some(p) = precision {
22933                                self.write(&format!("({})", p));
22934                            }
22935                            self.write_keyword(" WITH TIME ZONE");
22936                        }
22937                    }
22938                } else {
22939                    // Spark/Hive/Databricks: TIME -> TIMESTAMP (TIME not supported)
22940                    if matches!(
22941                        self.config.dialect,
22942                        Some(DialectType::Spark)
22943                            | Some(DialectType::Databricks)
22944                            | Some(DialectType::Hive)
22945                    ) {
22946                        self.write_keyword("TIMESTAMP");
22947                    } else {
22948                        self.write_keyword("TIME");
22949                        if let Some(p) = precision {
22950                            self.write(&format!("({})", p));
22951                        }
22952                    }
22953                }
22954            }
22955            DataType::Timestamp {
22956                precision,
22957                timezone,
22958            } => {
22959                // Dialect-specific timestamp type mappings
22960                match self.config.dialect {
22961                    Some(DialectType::ClickHouse) => {
22962                        self.write("DateTime");
22963                        if let Some(p) = precision {
22964                            self.write(&format!("({})", p));
22965                        }
22966                    }
22967                    Some(DialectType::TSQL) => {
22968                        if *timezone {
22969                            self.write_keyword("DATETIMEOFFSET");
22970                        } else {
22971                            self.write_keyword("DATETIME2");
22972                        }
22973                        if let Some(p) = precision {
22974                            self.write(&format!("({})", p));
22975                        }
22976                    }
22977                    Some(DialectType::MySQL) => {
22978                        // MySQL: TIMESTAMP stays as TIMESTAMP in DDL; CAST mapping handled separately
22979                        self.write_keyword("TIMESTAMP");
22980                        if let Some(p) = precision {
22981                            self.write(&format!("({})", p));
22982                        }
22983                    }
22984                    Some(DialectType::Doris) | Some(DialectType::StarRocks) => {
22985                        // Doris/StarRocks: TIMESTAMP -> DATETIME
22986                        self.write_keyword("DATETIME");
22987                        if let Some(p) = precision {
22988                            self.write(&format!("({})", p));
22989                        }
22990                    }
22991                    Some(DialectType::BigQuery) => {
22992                        // BigQuery: TIMESTAMP is always UTC, DATETIME is timezone-naive
22993                        if *timezone {
22994                            self.write_keyword("TIMESTAMP");
22995                        } else {
22996                            self.write_keyword("DATETIME");
22997                        }
22998                    }
22999                    Some(DialectType::DuckDB) => {
23000                        // DuckDB: TIMESTAMPTZ shorthand
23001                        if *timezone {
23002                            self.write_keyword("TIMESTAMPTZ");
23003                        } else {
23004                            self.write_keyword("TIMESTAMP");
23005                            if let Some(p) = precision {
23006                                self.write(&format!("({})", p));
23007                            }
23008                        }
23009                    }
23010                    _ => {
23011                        if *timezone && !self.config.tz_to_with_time_zone {
23012                            // Use TIMESTAMPTZ shorthand when dialect doesn't prefer WITH TIME ZONE
23013                            self.write_keyword("TIMESTAMPTZ");
23014                            if let Some(p) = precision {
23015                                self.write(&format!("({})", p));
23016                            }
23017                        } else {
23018                            self.write_keyword("TIMESTAMP");
23019                            if let Some(p) = precision {
23020                                self.write(&format!("({})", p));
23021                            }
23022                            if *timezone {
23023                                self.write_space();
23024                                self.write_keyword("WITH TIME ZONE");
23025                            }
23026                        }
23027                    }
23028                }
23029            }
23030            DataType::Interval { unit, to } => {
23031                self.write_keyword("INTERVAL");
23032                if let Some(u) = unit {
23033                    self.write_space();
23034                    self.write_keyword(u);
23035                }
23036                // Handle range intervals like DAY TO HOUR
23037                if let Some(t) = to {
23038                    self.write_space();
23039                    self.write_keyword("TO");
23040                    self.write_space();
23041                    self.write_keyword(t);
23042                }
23043            }
23044            DataType::Json => {
23045                // Dialect-specific JSON type mappings
23046                match self.config.dialect {
23047                    Some(DialectType::Oracle) => self.write_keyword("JSON"), // Oracle 21c+
23048                    Some(DialectType::TSQL) => self.write_keyword("NVARCHAR(MAX)"), // No native JSON type
23049                    Some(DialectType::MySQL) => self.write_keyword("JSON"),
23050                    Some(DialectType::Snowflake) => self.write_keyword("VARIANT"),
23051                    _ => self.write_keyword("JSON"),
23052                }
23053            }
23054            DataType::JsonB => {
23055                // JSONB is PostgreSQL specific, but Doris also supports it
23056                match self.config.dialect {
23057                    Some(DialectType::PostgreSQL) => self.write_keyword("JSONB"),
23058                    Some(DialectType::Doris) => self.write_keyword("JSONB"),
23059                    Some(DialectType::Snowflake) => self.write_keyword("VARIANT"),
23060                    Some(DialectType::TSQL) => self.write_keyword("NVARCHAR(MAX)"),
23061                    Some(DialectType::DuckDB) => self.write_keyword("JSON"), // DuckDB maps JSONB to JSON
23062                    _ => self.write_keyword("JSON"), // Fall back to JSON for other dialects
23063                }
23064            }
23065            DataType::Uuid => {
23066                // Dialect-specific UUID type mappings
23067                match self.config.dialect {
23068                    Some(DialectType::TSQL) => self.write_keyword("UNIQUEIDENTIFIER"),
23069                    Some(DialectType::MySQL) => self.write_keyword("CHAR(36)"),
23070                    Some(DialectType::Oracle) => self.write_keyword("RAW(16)"),
23071                    Some(DialectType::BigQuery)
23072                    | Some(DialectType::Spark)
23073                    | Some(DialectType::Databricks) => self.write_keyword("STRING"),
23074                    _ => self.write_keyword("UUID"),
23075                }
23076            }
23077            DataType::Array {
23078                element_type,
23079                dimension,
23080            } => {
23081                // Dialect-specific array syntax
23082                match self.config.dialect {
23083                    Some(DialectType::PostgreSQL)
23084                    | Some(DialectType::Redshift)
23085                    | Some(DialectType::DuckDB) => {
23086                        // PostgreSQL uses TYPE[] or TYPE[N] syntax
23087                        self.generate_data_type(element_type)?;
23088                        if let Some(dim) = dimension {
23089                            self.write(&format!("[{}]", dim));
23090                        } else {
23091                            self.write("[]");
23092                        }
23093                    }
23094                    Some(DialectType::BigQuery) => {
23095                        self.write_keyword("ARRAY<");
23096                        self.generate_data_type(element_type)?;
23097                        self.write(">");
23098                    }
23099                    Some(DialectType::Snowflake)
23100                    | Some(DialectType::Presto)
23101                    | Some(DialectType::Trino)
23102                    | Some(DialectType::ClickHouse) => {
23103                        // These dialects use Array(TYPE) parentheses syntax
23104                        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
23105                            self.write("Array(");
23106                        } else {
23107                            self.write_keyword("ARRAY(");
23108                        }
23109                        self.generate_data_type(element_type)?;
23110                        self.write(")");
23111                    }
23112                    Some(DialectType::TSQL)
23113                    | Some(DialectType::MySQL)
23114                    | Some(DialectType::Oracle) => {
23115                        // These dialects don't have native array types
23116                        // Fall back to JSON or use native workarounds
23117                        match self.config.dialect {
23118                            Some(DialectType::MySQL) => self.write_keyword("JSON"),
23119                            Some(DialectType::TSQL) => self.write_keyword("NVARCHAR(MAX)"),
23120                            _ => self.write_keyword("JSON"),
23121                        }
23122                    }
23123                    _ => {
23124                        // Default: use angle bracket syntax (ARRAY<T>)
23125                        self.write_keyword("ARRAY<");
23126                        self.generate_data_type(element_type)?;
23127                        self.write(">");
23128                    }
23129                }
23130            }
23131            DataType::List { element_type } => {
23132                // Materialize: element_type LIST (postfix syntax)
23133                self.generate_data_type(element_type)?;
23134                self.write_keyword(" LIST");
23135            }
23136            DataType::Map {
23137                key_type,
23138                value_type,
23139            } => {
23140                // Use parentheses for Snowflake and RisingWave, bracket syntax for Materialize, angle brackets for others
23141                match self.config.dialect {
23142                    Some(DialectType::Materialize) => {
23143                        // Materialize: MAP[key_type => value_type]
23144                        self.write_keyword("MAP[");
23145                        self.generate_data_type(key_type)?;
23146                        self.write(" => ");
23147                        self.generate_data_type(value_type)?;
23148                        self.write("]");
23149                    }
23150                    Some(DialectType::Snowflake)
23151                    | Some(DialectType::RisingWave)
23152                    | Some(DialectType::DuckDB)
23153                    | Some(DialectType::Presto)
23154                    | Some(DialectType::Trino)
23155                    | Some(DialectType::Athena) => {
23156                        self.write_keyword("MAP(");
23157                        self.generate_data_type(key_type)?;
23158                        self.write(", ");
23159                        self.generate_data_type(value_type)?;
23160                        self.write(")");
23161                    }
23162                    Some(DialectType::ClickHouse) => {
23163                        // ClickHouse: Map(key_type, value_type) with parenthesized syntax
23164                        // Key types must NOT be wrapped in Nullable
23165                        self.write("Map(");
23166                        self.clickhouse_nullable_depth = -1; // suppress Nullable for key
23167                        self.generate_data_type(key_type)?;
23168                        self.clickhouse_nullable_depth = 0;
23169                        self.write(", ");
23170                        self.generate_data_type(value_type)?;
23171                        self.write(")");
23172                    }
23173                    _ => {
23174                        self.write_keyword("MAP<");
23175                        self.generate_data_type(key_type)?;
23176                        self.write(", ");
23177                        self.generate_data_type(value_type)?;
23178                        self.write(">");
23179                    }
23180                }
23181            }
23182            DataType::Vector {
23183                element_type,
23184                dimension,
23185            } => {
23186                if matches!(self.config.dialect, Some(DialectType::SingleStore)) {
23187                    // SingleStore format: VECTOR(dimension, type_alias)
23188                    self.write_keyword("VECTOR(");
23189                    if let Some(dim) = dimension {
23190                        self.write(&dim.to_string());
23191                    }
23192                    // Map type back to SingleStore alias
23193                    let type_alias = element_type.as_ref().and_then(|et| match et.as_ref() {
23194                        DataType::TinyInt { .. } => Some("I8"),
23195                        DataType::SmallInt { .. } => Some("I16"),
23196                        DataType::Int { .. } => Some("I32"),
23197                        DataType::BigInt { .. } => Some("I64"),
23198                        DataType::Float { .. } => Some("F32"),
23199                        DataType::Double { .. } => Some("F64"),
23200                        _ => None,
23201                    });
23202                    if let Some(alias) = type_alias {
23203                        if dimension.is_some() {
23204                            self.write(", ");
23205                        }
23206                        self.write(alias);
23207                    }
23208                    self.write(")");
23209                } else {
23210                    // Snowflake format: VECTOR(type, dimension)
23211                    self.write_keyword("VECTOR(");
23212                    if let Some(ref et) = element_type {
23213                        self.generate_data_type(et)?;
23214                        if dimension.is_some() {
23215                            self.write(", ");
23216                        }
23217                    }
23218                    if let Some(dim) = dimension {
23219                        self.write(&dim.to_string());
23220                    }
23221                    self.write(")");
23222                }
23223            }
23224            DataType::Object { fields, modifier } => {
23225                self.write_keyword("OBJECT(");
23226                for (i, (name, dt, not_null)) in fields.iter().enumerate() {
23227                    if i > 0 {
23228                        self.write(", ");
23229                    }
23230                    self.write(name);
23231                    self.write(" ");
23232                    self.generate_data_type(dt)?;
23233                    if *not_null {
23234                        self.write_keyword(" NOT NULL");
23235                    }
23236                }
23237                self.write(")");
23238                if let Some(mod_str) = modifier {
23239                    self.write(" ");
23240                    self.write_keyword(mod_str);
23241                }
23242            }
23243            DataType::Struct { fields, nested } => {
23244                // Dialect-specific struct type mappings
23245                match self.config.dialect {
23246                    Some(DialectType::Snowflake) => {
23247                        // Snowflake maps STRUCT to OBJECT
23248                        self.write_keyword("OBJECT(");
23249                        for (i, field) in fields.iter().enumerate() {
23250                            if i > 0 {
23251                                self.write(", ");
23252                            }
23253                            if !field.name.is_empty() {
23254                                self.write(&field.name);
23255                                self.write(" ");
23256                            }
23257                            self.generate_data_type(&field.data_type)?;
23258                        }
23259                        self.write(")");
23260                    }
23261                    Some(DialectType::Presto) | Some(DialectType::Trino) => {
23262                        // Presto/Trino use ROW(name TYPE, ...) syntax
23263                        self.write_keyword("ROW(");
23264                        for (i, field) in fields.iter().enumerate() {
23265                            if i > 0 {
23266                                self.write(", ");
23267                            }
23268                            if !field.name.is_empty() {
23269                                self.write(&field.name);
23270                                self.write(" ");
23271                            }
23272                            self.generate_data_type(&field.data_type)?;
23273                        }
23274                        self.write(")");
23275                    }
23276                    Some(DialectType::DuckDB) => {
23277                        // DuckDB uses parenthesized syntax: STRUCT(name TYPE, ...)
23278                        self.write_keyword("STRUCT(");
23279                        for (i, field) in fields.iter().enumerate() {
23280                            if i > 0 {
23281                                self.write(", ");
23282                            }
23283                            if !field.name.is_empty() {
23284                                self.write(&field.name);
23285                                self.write(" ");
23286                            }
23287                            self.generate_data_type(&field.data_type)?;
23288                        }
23289                        self.write(")");
23290                    }
23291                    Some(DialectType::ClickHouse) => {
23292                        // ClickHouse uses Tuple(name TYPE, ...) for struct types
23293                        self.write("Tuple(");
23294                        for (i, field) in fields.iter().enumerate() {
23295                            if i > 0 {
23296                                self.write(", ");
23297                            }
23298                            if !field.name.is_empty() {
23299                                self.write(&field.name);
23300                                self.write(" ");
23301                            }
23302                            self.generate_data_type(&field.data_type)?;
23303                        }
23304                        self.write(")");
23305                    }
23306                    Some(DialectType::SingleStore) => {
23307                        // SingleStore uses RECORD(name TYPE, ...) for struct types
23308                        self.write_keyword("RECORD(");
23309                        for (i, field) in fields.iter().enumerate() {
23310                            if i > 0 {
23311                                self.write(", ");
23312                            }
23313                            if !field.name.is_empty() {
23314                                self.write(&field.name);
23315                                self.write(" ");
23316                            }
23317                            self.generate_data_type(&field.data_type)?;
23318                        }
23319                        self.write(")");
23320                    }
23321                    _ => {
23322                        // Hive/Spark always use angle bracket syntax: STRUCT<name: TYPE>
23323                        let force_angle_brackets = matches!(
23324                            self.config.dialect,
23325                            Some(DialectType::Hive)
23326                                | Some(DialectType::Spark)
23327                                | Some(DialectType::Databricks)
23328                        );
23329                        if *nested && !force_angle_brackets {
23330                            self.write_keyword("STRUCT(");
23331                            for (i, field) in fields.iter().enumerate() {
23332                                if i > 0 {
23333                                    self.write(", ");
23334                                }
23335                                if !field.name.is_empty() {
23336                                    self.write(&field.name);
23337                                    self.write(" ");
23338                                }
23339                                self.generate_data_type(&field.data_type)?;
23340                            }
23341                            self.write(")");
23342                        } else {
23343                            self.write_keyword("STRUCT<");
23344                            for (i, field) in fields.iter().enumerate() {
23345                                if i > 0 {
23346                                    self.write(", ");
23347                                }
23348                                if !field.name.is_empty() {
23349                                    // Named field: name TYPE (with configurable separator for Hive)
23350                                    self.write(&field.name);
23351                                    self.write(self.config.struct_field_sep);
23352                                }
23353                                // For anonymous fields, just output the type
23354                                self.generate_data_type(&field.data_type)?;
23355                                // Spark/Databricks: Output COMMENT clause if present
23356                                if let Some(comment) = &field.comment {
23357                                    self.write(" COMMENT '");
23358                                    self.write(comment);
23359                                    self.write("'");
23360                                }
23361                                // BigQuery: Output OPTIONS clause if present
23362                                if !field.options.is_empty() {
23363                                    self.write(" ");
23364                                    self.generate_options_clause(&field.options)?;
23365                                }
23366                            }
23367                            self.write(">");
23368                        }
23369                    }
23370                }
23371            }
23372            DataType::Enum {
23373                values,
23374                assignments,
23375            } => {
23376                // DuckDB ENUM type: ENUM('RED', 'GREEN', 'BLUE')
23377                // ClickHouse: Enum('hello' = 1, 'world' = 2)
23378                if self.config.dialect == Some(DialectType::ClickHouse) {
23379                    self.write("Enum(");
23380                } else {
23381                    self.write_keyword("ENUM(");
23382                }
23383                for (i, val) in values.iter().enumerate() {
23384                    if i > 0 {
23385                        self.write(", ");
23386                    }
23387                    self.write("'");
23388                    self.write(val);
23389                    self.write("'");
23390                    if let Some(Some(assignment)) = assignments.get(i) {
23391                        self.write(" = ");
23392                        self.write(assignment);
23393                    }
23394                }
23395                self.write(")");
23396            }
23397            DataType::Set { values } => {
23398                // MySQL SET type: SET('a', 'b', 'c')
23399                self.write_keyword("SET(");
23400                for (i, val) in values.iter().enumerate() {
23401                    if i > 0 {
23402                        self.write(", ");
23403                    }
23404                    self.write("'");
23405                    self.write(val);
23406                    self.write("'");
23407                }
23408                self.write(")");
23409            }
23410            DataType::Union { fields } => {
23411                // DuckDB UNION type: UNION(num INT, str TEXT)
23412                self.write_keyword("UNION(");
23413                for (i, (name, dt)) in fields.iter().enumerate() {
23414                    if i > 0 {
23415                        self.write(", ");
23416                    }
23417                    if !name.is_empty() {
23418                        self.write(name);
23419                        self.write(" ");
23420                    }
23421                    self.generate_data_type(dt)?;
23422                }
23423                self.write(")");
23424            }
23425            DataType::Nullable { inner } => {
23426                // ClickHouse: Nullable(T), other dialects: just the inner type
23427                if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
23428                    self.write("Nullable(");
23429                    // Suppress inner Nullable wrapping to prevent Nullable(Nullable(...))
23430                    let saved_depth = self.clickhouse_nullable_depth;
23431                    self.clickhouse_nullable_depth = -1;
23432                    self.generate_data_type(inner)?;
23433                    self.clickhouse_nullable_depth = saved_depth;
23434                    self.write(")");
23435                } else {
23436                    // Map ClickHouse-specific custom type names to standard types
23437                    match inner.as_ref() {
23438                        DataType::Custom { name } if name.eq_ignore_ascii_case("DATETIME") => {
23439                            self.generate_data_type(&DataType::Timestamp {
23440                                precision: None,
23441                                timezone: false,
23442                            })?;
23443                        }
23444                        _ => {
23445                            self.generate_data_type(inner)?;
23446                        }
23447                    }
23448                }
23449            }
23450            DataType::Custom { name } => {
23451                // Handle dialect-specific type transformations
23452                let name_upper = name.to_ascii_uppercase();
23453                match self.config.dialect {
23454                    Some(DialectType::ClickHouse) => {
23455                        let (base_upper, suffix) = if let Some(idx) = name.find('(') {
23456                            (name_upper[..idx].to_string(), &name[idx..])
23457                        } else {
23458                            (name_upper.clone(), "")
23459                        };
23460                        let mapped = match base_upper.as_str() {
23461                            "DATETIME" | "TIMESTAMPTZ" | "TIMESTAMP" | "TIMESTAMPNTZ"
23462                            | "SMALLDATETIME" | "DATETIME2" => "DateTime",
23463                            "DATETIME64" => "DateTime64",
23464                            "DATE32" => "Date32",
23465                            "INT" => "Int32",
23466                            "MEDIUMINT" => "Int32",
23467                            "INT8" => "Int8",
23468                            "INT16" => "Int16",
23469                            "INT32" => "Int32",
23470                            "INT64" => "Int64",
23471                            "INT128" => "Int128",
23472                            "INT256" => "Int256",
23473                            "UINT8" => "UInt8",
23474                            "UINT16" => "UInt16",
23475                            "UINT32" => "UInt32",
23476                            "UINT64" => "UInt64",
23477                            "UINT128" => "UInt128",
23478                            "UINT256" => "UInt256",
23479                            "FLOAT32" => "Float32",
23480                            "FLOAT64" => "Float64",
23481                            "DECIMAL32" => "Decimal32",
23482                            "DECIMAL64" => "Decimal64",
23483                            "DECIMAL128" => "Decimal128",
23484                            "DECIMAL256" => "Decimal256",
23485                            "ENUM" => "Enum",
23486                            "ENUM8" => "Enum8",
23487                            "ENUM16" => "Enum16",
23488                            "FIXEDSTRING" => "FixedString",
23489                            "NESTED" => "Nested",
23490                            "LOWCARDINALITY" => "LowCardinality",
23491                            "NULLABLE" => "Nullable",
23492                            "IPV4" => "IPv4",
23493                            "IPV6" => "IPv6",
23494                            "POINT" => "Point",
23495                            "RING" => "Ring",
23496                            "LINESTRING" => "LineString",
23497                            "MULTILINESTRING" => "MultiLineString",
23498                            "POLYGON" => "Polygon",
23499                            "MULTIPOLYGON" => "MultiPolygon",
23500                            "AGGREGATEFUNCTION" => "AggregateFunction",
23501                            "SIMPLEAGGREGATEFUNCTION" => "SimpleAggregateFunction",
23502                            "DYNAMIC" => "Dynamic",
23503                            _ => "",
23504                        };
23505                        if mapped.is_empty() {
23506                            self.write(name);
23507                        } else {
23508                            self.write(mapped);
23509                            self.write(suffix);
23510                        }
23511                    }
23512                    Some(DialectType::MySQL)
23513                        if name_upper == "TIMESTAMPTZ" || name_upper == "TIMESTAMPLTZ" =>
23514                    {
23515                        // MySQL doesn't support TIMESTAMPTZ/TIMESTAMPLTZ, use TIMESTAMP
23516                        self.write_keyword("TIMESTAMP");
23517                    }
23518                    Some(DialectType::TSQL) if name_upper == "VARIANT" => {
23519                        self.write_keyword("SQL_VARIANT");
23520                    }
23521                    Some(DialectType::DuckDB) if name_upper == "DECFLOAT" => {
23522                        self.write_keyword("DECIMAL(38, 5)");
23523                    }
23524                    Some(DialectType::Exasol) => {
23525                        // Exasol type mappings for custom types
23526                        match name_upper.as_str() {
23527                            // Binary types → VARCHAR
23528                            "LONGBLOB" | "MEDIUMBLOB" | "TINYBLOB" => self.write_keyword("VARCHAR"),
23529                            // Text types → VARCHAR (TEXT → LONG VARCHAR is handled by DataType::Text)
23530                            "LONGTEXT" | "MEDIUMTEXT" | "TINYTEXT" => self.write_keyword("VARCHAR"),
23531                            // Integer types
23532                            "MEDIUMINT" => self.write_keyword("INT"),
23533                            // Decimal types → DECIMAL
23534                            "DECIMAL32" | "DECIMAL64" | "DECIMAL128" | "DECIMAL256" => {
23535                                self.write_keyword("DECIMAL")
23536                            }
23537                            // Timestamp types
23538                            "DATETIME" => self.write_keyword("TIMESTAMP"),
23539                            "TIMESTAMPLTZ" => self.write_keyword("TIMESTAMP WITH LOCAL TIME ZONE"),
23540                            _ => self.write(name),
23541                        }
23542                    }
23543                    Some(DialectType::Dremio) => {
23544                        // Dremio type mappings for custom types
23545                        match name_upper.as_str() {
23546                            "TIMESTAMPNTZ" | "DATETIME" => self.write_keyword("TIMESTAMP"),
23547                            "ARRAY" => self.write_keyword("LIST"),
23548                            "NCHAR" => self.write_keyword("VARCHAR"),
23549                            _ => self.write(name),
23550                        }
23551                    }
23552                    // Map dialect-specific custom types to standard SQL types for other dialects
23553                    _ => {
23554                        // Extract base name and args for types with parenthesized args (e.g., DATETIME2(3))
23555                        let (base_upper, _args_str) = if let Some(idx) = name_upper.find('(') {
23556                            (name_upper[..idx].to_string(), Some(&name[idx..]))
23557                        } else {
23558                            (name_upper.clone(), None)
23559                        };
23560
23561                        match base_upper.as_str() {
23562                            "INT64"
23563                                if !matches!(self.config.dialect, Some(DialectType::BigQuery)) =>
23564                            {
23565                                self.write_keyword("BIGINT");
23566                            }
23567                            "FLOAT64"
23568                                if !matches!(self.config.dialect, Some(DialectType::BigQuery)) =>
23569                            {
23570                                self.write_keyword("DOUBLE");
23571                            }
23572                            "BOOL"
23573                                if !matches!(self.config.dialect, Some(DialectType::BigQuery)) =>
23574                            {
23575                                self.write_keyword("BOOLEAN");
23576                            }
23577                            "BYTES"
23578                                if matches!(
23579                                    self.config.dialect,
23580                                    Some(DialectType::Spark)
23581                                        | Some(DialectType::Hive)
23582                                        | Some(DialectType::Databricks)
23583                                ) =>
23584                            {
23585                                self.write_keyword("BINARY");
23586                            }
23587                            "BYTES"
23588                                if !matches!(self.config.dialect, Some(DialectType::BigQuery)) =>
23589                            {
23590                                self.write_keyword("VARBINARY");
23591                            }
23592                            // TSQL DATETIME2/SMALLDATETIME -> TIMESTAMP
23593                            "DATETIME2" | "SMALLDATETIME"
23594                                if !matches!(
23595                                    self.config.dialect,
23596                                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
23597                                ) =>
23598                            {
23599                                // PostgreSQL preserves precision, others don't
23600                                if matches!(
23601                                    self.config.dialect,
23602                                    Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
23603                                ) {
23604                                    self.write_keyword("TIMESTAMP");
23605                                    if let Some(args) = _args_str {
23606                                        self.write(args);
23607                                    }
23608                                } else {
23609                                    self.write_keyword("TIMESTAMP");
23610                                }
23611                            }
23612                            // TSQL DATETIMEOFFSET -> TIMESTAMPTZ
23613                            "DATETIMEOFFSET"
23614                                if !matches!(
23615                                    self.config.dialect,
23616                                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
23617                                ) =>
23618                            {
23619                                if matches!(
23620                                    self.config.dialect,
23621                                    Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
23622                                ) {
23623                                    self.write_keyword("TIMESTAMPTZ");
23624                                    if let Some(args) = _args_str {
23625                                        self.write(args);
23626                                    }
23627                                } else {
23628                                    self.write_keyword("TIMESTAMPTZ");
23629                                }
23630                            }
23631                            // TSQL UNIQUEIDENTIFIER -> UUID or STRING
23632                            "UNIQUEIDENTIFIER"
23633                                if !matches!(
23634                                    self.config.dialect,
23635                                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
23636                                ) =>
23637                            {
23638                                match self.config.dialect {
23639                                    Some(DialectType::Spark)
23640                                    | Some(DialectType::Databricks)
23641                                    | Some(DialectType::Hive) => self.write_keyword("STRING"),
23642                                    _ => self.write_keyword("UUID"),
23643                                }
23644                            }
23645                            // TSQL BIT -> BOOLEAN for most dialects
23646                            "BIT"
23647                                if !matches!(
23648                                    self.config.dialect,
23649                                    Some(DialectType::TSQL)
23650                                        | Some(DialectType::Fabric)
23651                                        | Some(DialectType::PostgreSQL)
23652                                        | Some(DialectType::MySQL)
23653                                        | Some(DialectType::DuckDB)
23654                                ) =>
23655                            {
23656                                self.write_keyword("BOOLEAN");
23657                            }
23658                            // TSQL NVARCHAR -> VARCHAR (with default size 30 for some dialects)
23659                            "NVARCHAR"
23660                                if !matches!(
23661                                    self.config.dialect,
23662                                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
23663                                ) =>
23664                            {
23665                                match self.config.dialect {
23666                                    Some(DialectType::Oracle) => {
23667                                        // Oracle: NVARCHAR -> NVARCHAR2
23668                                        self.write_keyword("NVARCHAR2");
23669                                        if let Some(args) = _args_str {
23670                                            self.write(args);
23671                                        }
23672                                    }
23673                                    Some(DialectType::BigQuery) => {
23674                                        // BigQuery: NVARCHAR -> STRING
23675                                        self.write_keyword("STRING");
23676                                    }
23677                                    Some(DialectType::SQLite) | Some(DialectType::DuckDB) => {
23678                                        self.write_keyword("TEXT");
23679                                        if let Some(args) = _args_str {
23680                                            self.write(args);
23681                                        }
23682                                    }
23683                                    Some(DialectType::Hive) => {
23684                                        // Hive: NVARCHAR -> STRING
23685                                        self.write_keyword("STRING");
23686                                    }
23687                                    Some(DialectType::Spark) | Some(DialectType::Databricks) => {
23688                                        if _args_str.is_some() {
23689                                            self.write_keyword("VARCHAR");
23690                                            self.write(_args_str.unwrap());
23691                                        } else {
23692                                            self.write_keyword("STRING");
23693                                        }
23694                                    }
23695                                    _ => {
23696                                        self.write_keyword("VARCHAR");
23697                                        if let Some(args) = _args_str {
23698                                            self.write(args);
23699                                        }
23700                                    }
23701                                }
23702                            }
23703                            // NCHAR -> CHAR (NCHAR for Oracle/TSQL, STRING for BigQuery/Hive)
23704                            "NCHAR"
23705                                if !matches!(
23706                                    self.config.dialect,
23707                                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
23708                                ) =>
23709                            {
23710                                match self.config.dialect {
23711                                    Some(DialectType::Oracle) => {
23712                                        // Oracle natively supports NCHAR
23713                                        self.write_keyword("NCHAR");
23714                                        if let Some(args) = _args_str {
23715                                            self.write(args);
23716                                        }
23717                                    }
23718                                    Some(DialectType::BigQuery) => {
23719                                        // BigQuery: NCHAR -> STRING
23720                                        self.write_keyword("STRING");
23721                                    }
23722                                    Some(DialectType::Hive) => {
23723                                        // Hive: NCHAR -> STRING
23724                                        self.write_keyword("STRING");
23725                                    }
23726                                    Some(DialectType::SQLite) | Some(DialectType::DuckDB) => {
23727                                        self.write_keyword("TEXT");
23728                                        if let Some(args) = _args_str {
23729                                            self.write(args);
23730                                        }
23731                                    }
23732                                    Some(DialectType::Spark) | Some(DialectType::Databricks) => {
23733                                        if _args_str.is_some() {
23734                                            self.write_keyword("CHAR");
23735                                            self.write(_args_str.unwrap());
23736                                        } else {
23737                                            self.write_keyword("STRING");
23738                                        }
23739                                    }
23740                                    _ => {
23741                                        self.write_keyword("CHAR");
23742                                        if let Some(args) = _args_str {
23743                                            self.write(args);
23744                                        }
23745                                    }
23746                                }
23747                            }
23748                            // MySQL text variant types -> map to appropriate target type
23749                            // For MySQL/SingleStore: keep original name (column definitions), CAST handling is in generate_cast
23750                            "LONGTEXT" | "MEDIUMTEXT" | "TINYTEXT" => match self.config.dialect {
23751                                Some(DialectType::MySQL)
23752                                | Some(DialectType::SingleStore)
23753                                | Some(DialectType::TiDB) => self.write_keyword(&base_upper),
23754                                Some(DialectType::Spark)
23755                                | Some(DialectType::Databricks)
23756                                | Some(DialectType::Hive) => self.write_keyword("TEXT"),
23757                                Some(DialectType::BigQuery) => self.write_keyword("STRING"),
23758                                Some(DialectType::Presto)
23759                                | Some(DialectType::Trino)
23760                                | Some(DialectType::Athena) => self.write_keyword("VARCHAR"),
23761                                Some(DialectType::Snowflake)
23762                                | Some(DialectType::Redshift)
23763                                | Some(DialectType::Dremio) => self.write_keyword("VARCHAR"),
23764                                _ => self.write_keyword("TEXT"),
23765                            },
23766                            // MySQL blob variant types -> map to appropriate target type
23767                            // For MySQL/SingleStore: keep original name (column definitions), CAST handling is in generate_cast
23768                            "LONGBLOB" | "MEDIUMBLOB" | "TINYBLOB" => match self.config.dialect {
23769                                Some(DialectType::MySQL)
23770                                | Some(DialectType::SingleStore)
23771                                | Some(DialectType::TiDB) => self.write_keyword(&base_upper),
23772                                Some(DialectType::Spark)
23773                                | Some(DialectType::Databricks)
23774                                | Some(DialectType::Hive) => self.write_keyword("BLOB"),
23775                                Some(DialectType::DuckDB) => self.write_keyword("VARBINARY"),
23776                                Some(DialectType::BigQuery) => self.write_keyword("BYTES"),
23777                                Some(DialectType::Presto)
23778                                | Some(DialectType::Trino)
23779                                | Some(DialectType::Athena) => self.write_keyword("VARBINARY"),
23780                                Some(DialectType::Snowflake)
23781                                | Some(DialectType::Redshift)
23782                                | Some(DialectType::Dremio) => self.write_keyword("VARBINARY"),
23783                                _ => self.write_keyword("BLOB"),
23784                            },
23785                            // LONGVARCHAR -> TEXT for SQLite, VARCHAR for others
23786                            "LONGVARCHAR" => match self.config.dialect {
23787                                Some(DialectType::SQLite) => self.write_keyword("TEXT"),
23788                                _ => self.write_keyword("VARCHAR"),
23789                            },
23790                            // DATETIME -> TIMESTAMP for most, DATETIME for MySQL/Doris/StarRocks/Snowflake
23791                            "DATETIME" => {
23792                                match self.config.dialect {
23793                                    Some(DialectType::MySQL)
23794                                    | Some(DialectType::Doris)
23795                                    | Some(DialectType::StarRocks)
23796                                    | Some(DialectType::TSQL)
23797                                    | Some(DialectType::Fabric)
23798                                    | Some(DialectType::BigQuery)
23799                                    | Some(DialectType::SQLite)
23800                                    | Some(DialectType::Snowflake) => {
23801                                        self.write_keyword("DATETIME");
23802                                        if let Some(args) = _args_str {
23803                                            self.write(args);
23804                                        }
23805                                    }
23806                                    Some(_) => {
23807                                        // Only map to TIMESTAMP when we have a specific target dialect
23808                                        self.write_keyword("TIMESTAMP");
23809                                        if let Some(args) = _args_str {
23810                                            self.write(args);
23811                                        }
23812                                    }
23813                                    None => {
23814                                        // No dialect - preserve original
23815                                        self.write(name);
23816                                    }
23817                                }
23818                            }
23819                            // VARCHAR2/NVARCHAR2 (Oracle) -> VARCHAR for non-Oracle targets
23820                            "VARCHAR2"
23821                                if !matches!(self.config.dialect, Some(DialectType::Oracle)) =>
23822                            {
23823                                match self.config.dialect {
23824                                    Some(DialectType::DuckDB) | Some(DialectType::SQLite) => {
23825                                        self.write_keyword("TEXT");
23826                                    }
23827                                    Some(DialectType::Hive)
23828                                    | Some(DialectType::Spark)
23829                                    | Some(DialectType::Databricks)
23830                                    | Some(DialectType::BigQuery)
23831                                    | Some(DialectType::ClickHouse)
23832                                    | Some(DialectType::StarRocks)
23833                                    | Some(DialectType::Doris) => {
23834                                        self.write_keyword("STRING");
23835                                    }
23836                                    _ => {
23837                                        self.write_keyword("VARCHAR");
23838                                        if let Some(args) = _args_str {
23839                                            self.write(args);
23840                                        }
23841                                    }
23842                                }
23843                            }
23844                            "NVARCHAR2"
23845                                if !matches!(self.config.dialect, Some(DialectType::Oracle)) =>
23846                            {
23847                                match self.config.dialect {
23848                                    Some(DialectType::DuckDB) | Some(DialectType::SQLite) => {
23849                                        self.write_keyword("TEXT");
23850                                    }
23851                                    Some(DialectType::Hive)
23852                                    | Some(DialectType::Spark)
23853                                    | Some(DialectType::Databricks)
23854                                    | Some(DialectType::BigQuery)
23855                                    | Some(DialectType::ClickHouse)
23856                                    | Some(DialectType::StarRocks)
23857                                    | Some(DialectType::Doris) => {
23858                                        self.write_keyword("STRING");
23859                                    }
23860                                    _ => {
23861                                        self.write_keyword("VARCHAR");
23862                                        if let Some(args) = _args_str {
23863                                            self.write(args);
23864                                        }
23865                                    }
23866                                }
23867                            }
23868                            _ => self.write(name),
23869                        }
23870                    }
23871                }
23872            }
23873            DataType::Geometry { subtype, srid } => {
23874                // Dialect-specific geometry type mappings
23875                match self.config.dialect {
23876                    Some(DialectType::MySQL) => {
23877                        // MySQL uses POINT SRID 4326 syntax for specific types
23878                        if let Some(sub) = subtype {
23879                            self.write_keyword(sub);
23880                            if let Some(s) = srid {
23881                                self.write(" SRID ");
23882                                self.write(&s.to_string());
23883                            }
23884                        } else {
23885                            self.write_keyword("GEOMETRY");
23886                        }
23887                    }
23888                    Some(DialectType::BigQuery) => {
23889                        // BigQuery only supports GEOGRAPHY, not GEOMETRY
23890                        self.write_keyword("GEOGRAPHY");
23891                    }
23892                    Some(DialectType::Teradata) => {
23893                        // Teradata uses ST_GEOMETRY
23894                        self.write_keyword("ST_GEOMETRY");
23895                        if subtype.is_some() || srid.is_some() {
23896                            self.write("(");
23897                            if let Some(sub) = subtype {
23898                                self.write_keyword(sub);
23899                            }
23900                            if let Some(s) = srid {
23901                                if subtype.is_some() {
23902                                    self.write(", ");
23903                                }
23904                                self.write(&s.to_string());
23905                            }
23906                            self.write(")");
23907                        }
23908                    }
23909                    _ => {
23910                        // PostgreSQL, Snowflake, DuckDB use GEOMETRY(subtype, srid) syntax
23911                        self.write_keyword("GEOMETRY");
23912                        if subtype.is_some() || srid.is_some() {
23913                            self.write("(");
23914                            if let Some(sub) = subtype {
23915                                self.write_keyword(sub);
23916                            }
23917                            if let Some(s) = srid {
23918                                if subtype.is_some() {
23919                                    self.write(", ");
23920                                }
23921                                self.write(&s.to_string());
23922                            }
23923                            self.write(")");
23924                        }
23925                    }
23926                }
23927            }
23928            DataType::Geography { subtype, srid } => {
23929                // Dialect-specific geography type mappings
23930                match self.config.dialect {
23931                    Some(DialectType::MySQL) => {
23932                        // MySQL doesn't have native GEOGRAPHY, use GEOMETRY with SRID 4326
23933                        if let Some(sub) = subtype {
23934                            self.write_keyword(sub);
23935                        } else {
23936                            self.write_keyword("GEOMETRY");
23937                        }
23938                        // Geography implies SRID 4326 (WGS84)
23939                        let effective_srid = srid.unwrap_or(4326);
23940                        self.write(" SRID ");
23941                        self.write(&effective_srid.to_string());
23942                    }
23943                    Some(DialectType::BigQuery) => {
23944                        // BigQuery uses simple GEOGRAPHY without parameters
23945                        self.write_keyword("GEOGRAPHY");
23946                    }
23947                    Some(DialectType::Snowflake) => {
23948                        // Snowflake uses GEOGRAPHY without parameters
23949                        self.write_keyword("GEOGRAPHY");
23950                    }
23951                    _ => {
23952                        // PostgreSQL uses GEOGRAPHY(subtype, srid) syntax
23953                        self.write_keyword("GEOGRAPHY");
23954                        if subtype.is_some() || srid.is_some() {
23955                            self.write("(");
23956                            if let Some(sub) = subtype {
23957                                self.write_keyword(sub);
23958                            }
23959                            if let Some(s) = srid {
23960                                if subtype.is_some() {
23961                                    self.write(", ");
23962                                }
23963                                self.write(&s.to_string());
23964                            }
23965                            self.write(")");
23966                        }
23967                    }
23968                }
23969            }
23970            DataType::CharacterSet { name } => {
23971                // For MySQL CONVERT USING - output as CHAR CHARACTER SET name
23972                self.write_keyword("CHAR CHARACTER SET ");
23973                self.write(name);
23974            }
23975            _ => self.write("UNKNOWN"),
23976        }
23977        Ok(())
23978    }
23979
23980    // === Helper methods ===
23981
23982    #[inline]
23983    fn write(&mut self, s: &str) {
23984        self.output.push_str(s);
23985    }
23986
23987    #[inline]
23988    fn write_space(&mut self) {
23989        self.output.push(' ');
23990    }
23991
23992    #[inline]
23993    fn write_keyword(&mut self, keyword: &str) {
23994        if self.config.uppercase_keywords {
23995            self.output.push_str(keyword);
23996        } else {
23997            for b in keyword.bytes() {
23998                self.output.push(b.to_ascii_lowercase() as char);
23999            }
24000        }
24001    }
24002
24003    /// Write a function name respecting the normalize_functions config setting
24004    fn write_func_name(&mut self, name: &str) {
24005        let normalized = self.normalize_func_name(name);
24006        self.output.push_str(normalized.as_ref());
24007    }
24008
24009    /// Convert strptime format string to Exasol format string
24010    /// Exasol TIME_MAPPING (reverse of Python sqlglot):
24011    /// %Y -> YYYY, %y -> YY, %m -> MM, %d -> DD, %H -> HH, %M -> MI, %S -> SS, %a -> DY
24012    fn convert_strptime_to_exasol_format(format: &str) -> String {
24013        let mut result = String::new();
24014        let chars: Vec<char> = format.chars().collect();
24015        let mut i = 0;
24016        while i < chars.len() {
24017            if chars[i] == '%' && i + 1 < chars.len() {
24018                let spec = chars[i + 1];
24019                let exasol_spec = match spec {
24020                    'Y' => "YYYY",
24021                    'y' => "YY",
24022                    'm' => "MM",
24023                    'd' => "DD",
24024                    'H' => "HH",
24025                    'M' => "MI",
24026                    'S' => "SS",
24027                    'a' => "DY",    // abbreviated weekday name
24028                    'A' => "DAY",   // full weekday name
24029                    'b' => "MON",   // abbreviated month name
24030                    'B' => "MONTH", // full month name
24031                    'I' => "H12",   // 12-hour format
24032                    'u' => "ID",    // ISO weekday (1-7)
24033                    'V' => "IW",    // ISO week number
24034                    'G' => "IYYY",  // ISO year
24035                    'W' => "UW",    // Week number (Monday as first day)
24036                    'U' => "UW",    // Week number (Sunday as first day)
24037                    'z' => "Z",     // timezone offset
24038                    _ => {
24039                        // Unknown specifier, keep as-is
24040                        result.push('%');
24041                        result.push(spec);
24042                        i += 2;
24043                        continue;
24044                    }
24045                };
24046                result.push_str(exasol_spec);
24047                i += 2;
24048            } else {
24049                result.push(chars[i]);
24050                i += 1;
24051            }
24052        }
24053        result
24054    }
24055
24056    /// Convert strptime format string to PostgreSQL/Redshift format string
24057    /// PostgreSQL INVERSE_TIME_MAPPING from Python sqlglot:
24058    /// %Y -> YYYY, %y -> YY, %m -> MM, %d -> DD, %H -> HH24, %M -> MI, %S -> SS, %f -> US, etc.
24059    fn convert_strptime_to_postgres_format(format: &str) -> String {
24060        let mut result = String::new();
24061        let chars: Vec<char> = format.chars().collect();
24062        let mut i = 0;
24063        while i < chars.len() {
24064            if chars[i] == '%' && i + 1 < chars.len() {
24065                // Check for %-d, %-m, etc. (non-padded, 3-char sequence)
24066                if chars[i + 1] == '-' && i + 2 < chars.len() {
24067                    let spec = chars[i + 2];
24068                    let pg_spec = match spec {
24069                        'd' => "FMDD",
24070                        'm' => "FMMM",
24071                        'H' => "FMHH24",
24072                        'M' => "FMMI",
24073                        'S' => "FMSS",
24074                        _ => {
24075                            result.push('%');
24076                            result.push('-');
24077                            result.push(spec);
24078                            i += 3;
24079                            continue;
24080                        }
24081                    };
24082                    result.push_str(pg_spec);
24083                    i += 3;
24084                    continue;
24085                }
24086                let spec = chars[i + 1];
24087                let pg_spec = match spec {
24088                    'Y' => "YYYY",
24089                    'y' => "YY",
24090                    'm' => "MM",
24091                    'd' => "DD",
24092                    'H' => "HH24",
24093                    'I' => "HH12",
24094                    'M' => "MI",
24095                    'S' => "SS",
24096                    'f' => "US",      // microseconds
24097                    'u' => "D",       // day of week (1=Monday)
24098                    'j' => "DDD",     // day of year
24099                    'z' => "OF",      // UTC offset
24100                    'Z' => "TZ",      // timezone name
24101                    'A' => "TMDay",   // full weekday name
24102                    'a' => "TMDy",    // abbreviated weekday name
24103                    'b' => "TMMon",   // abbreviated month name
24104                    'B' => "TMMonth", // full month name
24105                    'U' => "WW",      // week number
24106                    _ => {
24107                        // Unknown specifier, keep as-is
24108                        result.push('%');
24109                        result.push(spec);
24110                        i += 2;
24111                        continue;
24112                    }
24113                };
24114                result.push_str(pg_spec);
24115                i += 2;
24116            } else {
24117                result.push(chars[i]);
24118                i += 1;
24119            }
24120        }
24121        result
24122    }
24123
24124    /// Write a LIMIT expression value, evaluating constant expressions if limit_only_literals is set
24125    fn write_limit_expr(&mut self, expr: &Expression) -> Result<()> {
24126        if self.config.limit_only_literals {
24127            if let Some(value) = Self::try_evaluate_constant(expr) {
24128                self.write(&value.to_string());
24129                return Ok(());
24130            }
24131        }
24132        self.generate_expression(expr)
24133    }
24134
24135    /// Format a comment with proper spacing.
24136    /// Converts `/*text*/` to `/* text */` (adding internal spaces if not present).
24137    /// Python SQLGlot normalizes comment format to have spaces inside block comments.
24138    fn write_formatted_comment(&mut self, comment: &str) {
24139        // Normalize all comments to block comment format /* ... */
24140        // This matches Python sqlglot behavior which always outputs block comments
24141        let content = if comment.starts_with("/*") && comment.ends_with("*/") {
24142            // Already block comment - extract inner content
24143            // Preserve internal whitespace, but ensure at least one space padding
24144            &comment[2..comment.len() - 2]
24145        } else if comment.starts_with("--") {
24146            // Line comment - extract content after --
24147            // Preserve internal whitespace (e.g., "--       x" -> "/*       x */")
24148            &comment[2..]
24149        } else {
24150            // Raw content (no delimiters)
24151            comment
24152        };
24153        // Skip empty comments (e.g., bare "--" with no content)
24154        if content.trim().is_empty() {
24155            return;
24156        }
24157        // Ensure at least one space after /* and before */
24158        self.output.push_str("/*");
24159        if !content.starts_with(' ') {
24160            self.output.push(' ');
24161        }
24162        self.output.push_str(content);
24163        if !content.ends_with(' ') {
24164            self.output.push(' ');
24165        }
24166        self.output.push_str("*/");
24167    }
24168
24169    /// Escape a raw block content (from dollar-quoted string) for single-quoted output.
24170    /// Escapes single quotes with backslash, and for Snowflake also escapes backslashes.
24171    fn escape_block_for_single_quote(&self, block: &str) -> String {
24172        let escape_backslash = matches!(
24173            self.config.dialect,
24174            Some(crate::dialects::DialectType::Snowflake)
24175        );
24176        let mut escaped = String::with_capacity(block.len() + 4);
24177        for ch in block.chars() {
24178            if ch == '\'' {
24179                escaped.push('\\');
24180                escaped.push('\'');
24181            } else if escape_backslash && ch == '\\' {
24182                escaped.push('\\');
24183                escaped.push('\\');
24184            } else {
24185                escaped.push(ch);
24186            }
24187        }
24188        escaped
24189    }
24190
24191    fn write_newline(&mut self) {
24192        self.output.push('\n');
24193    }
24194
24195    fn write_indent(&mut self) {
24196        for _ in 0..self.indent_level {
24197            self.output.push_str(self.config.indent);
24198        }
24199    }
24200
24201    // === SQLGlot-style pretty printing helpers ===
24202
24203    /// Returns the separator string for pretty printing.
24204    /// Check if the total length of arguments exceeds max_text_width.
24205    /// Used for dynamic line breaking in expressions() formatting.
24206    fn too_wide(&self, args: &[String]) -> bool {
24207        args.iter().map(|s| s.len()).sum::<usize>() > self.config.max_text_width
24208    }
24209
24210    /// Generate an expression to a string using a temporary non-pretty generator.
24211    /// Useful for width calculations before deciding on formatting.
24212    fn generate_to_string(&self, expr: &Expression) -> Result<String> {
24213        let config = GeneratorConfig {
24214            pretty: false,
24215            dialect: self.config.dialect,
24216            ..Default::default()
24217        };
24218        let mut gen = Generator::with_config(config);
24219        gen.generate_expression(expr)?;
24220        Ok(gen.output)
24221    }
24222
24223    /// Writes a clause with a single condition (WHERE, HAVING, QUALIFY).
24224    /// In pretty mode: newline + indented keyword + newline + indented condition
24225    fn write_clause_condition(&mut self, keyword: &str, condition: &Expression) -> Result<()> {
24226        if self.config.pretty {
24227            self.write_newline();
24228            self.write_indent();
24229            self.write_keyword(keyword);
24230            self.write_newline();
24231            self.indent_level += 1;
24232            self.write_indent();
24233            self.generate_expression(condition)?;
24234            self.indent_level -= 1;
24235        } else {
24236            self.write_space();
24237            self.write_keyword(keyword);
24238            self.write_space();
24239            self.generate_expression(condition)?;
24240        }
24241        Ok(())
24242    }
24243
24244    /// Writes a clause with a list of expressions (GROUP BY, DISTRIBUTE BY, CLUSTER BY).
24245    /// In pretty mode: each expression on new line with indentation
24246    fn write_clause_expressions(&mut self, keyword: &str, exprs: &[Expression]) -> Result<()> {
24247        if exprs.is_empty() {
24248            return Ok(());
24249        }
24250
24251        if self.config.pretty {
24252            self.write_newline();
24253            self.write_indent();
24254            self.write_keyword(keyword);
24255            self.write_newline();
24256            self.indent_level += 1;
24257            for (i, expr) in exprs.iter().enumerate() {
24258                if i > 0 {
24259                    self.write(",");
24260                    self.write_newline();
24261                }
24262                self.write_indent();
24263                self.generate_expression(expr)?;
24264            }
24265            self.indent_level -= 1;
24266        } else {
24267            self.write_space();
24268            self.write_keyword(keyword);
24269            self.write_space();
24270            for (i, expr) in exprs.iter().enumerate() {
24271                if i > 0 {
24272                    self.write(", ");
24273                }
24274                self.generate_expression(expr)?;
24275            }
24276        }
24277        Ok(())
24278    }
24279
24280    /// Writes ORDER BY / SORT BY clause with Ordered expressions
24281    fn write_order_clause(&mut self, keyword: &str, orderings: &[Ordered]) -> Result<()> {
24282        if orderings.is_empty() {
24283            return Ok(());
24284        }
24285
24286        if self.config.pretty {
24287            self.write_newline();
24288            self.write_indent();
24289            self.write_keyword(keyword);
24290            self.write_newline();
24291            self.indent_level += 1;
24292            for (i, ordered) in orderings.iter().enumerate() {
24293                if i > 0 {
24294                    self.write(",");
24295                    self.write_newline();
24296                }
24297                self.write_indent();
24298                self.generate_ordered(ordered)?;
24299            }
24300            self.indent_level -= 1;
24301        } else {
24302            self.write_space();
24303            self.write_keyword(keyword);
24304            self.write_space();
24305            for (i, ordered) in orderings.iter().enumerate() {
24306                if i > 0 {
24307                    self.write(", ");
24308                }
24309                self.generate_ordered(ordered)?;
24310            }
24311        }
24312        Ok(())
24313    }
24314
24315    /// Writes WINDOW clause with named window definitions
24316    fn write_window_clause(&mut self, windows: &[NamedWindow]) -> Result<()> {
24317        if windows.is_empty() {
24318            return Ok(());
24319        }
24320
24321        if self.config.pretty {
24322            self.write_newline();
24323            self.write_indent();
24324            self.write_keyword("WINDOW");
24325            self.write_newline();
24326            self.indent_level += 1;
24327            for (i, named_window) in windows.iter().enumerate() {
24328                if i > 0 {
24329                    self.write(",");
24330                    self.write_newline();
24331                }
24332                self.write_indent();
24333                self.generate_identifier(&named_window.name)?;
24334                self.write_space();
24335                self.write_keyword("AS");
24336                self.write(" (");
24337                self.generate_over(&named_window.spec)?;
24338                self.write(")");
24339            }
24340            self.indent_level -= 1;
24341        } else {
24342            self.write_space();
24343            self.write_keyword("WINDOW");
24344            self.write_space();
24345            for (i, named_window) in windows.iter().enumerate() {
24346                if i > 0 {
24347                    self.write(", ");
24348                }
24349                self.generate_identifier(&named_window.name)?;
24350                self.write_space();
24351                self.write_keyword("AS");
24352                self.write(" (");
24353                self.generate_over(&named_window.spec)?;
24354                self.write(")");
24355            }
24356        }
24357        Ok(())
24358    }
24359
24360    // === BATCH-GENERATED STUB METHODS (481 variants) ===
24361    fn generate_ai_agg(&mut self, e: &AIAgg) -> Result<()> {
24362        // AI_AGG(this, expression)
24363        self.write_keyword("AI_AGG");
24364        self.write("(");
24365        self.generate_expression(&e.this)?;
24366        self.write(", ");
24367        self.generate_expression(&e.expression)?;
24368        self.write(")");
24369        Ok(())
24370    }
24371
24372    fn generate_ai_classify(&mut self, e: &AIClassify) -> Result<()> {
24373        // AI_CLASSIFY(input, [categories], [config])
24374        self.write_keyword("AI_CLASSIFY");
24375        self.write("(");
24376        self.generate_expression(&e.this)?;
24377        if let Some(categories) = &e.categories {
24378            self.write(", ");
24379            self.generate_expression(categories)?;
24380        }
24381        if let Some(config) = &e.config {
24382            self.write(", ");
24383            self.generate_expression(config)?;
24384        }
24385        self.write(")");
24386        Ok(())
24387    }
24388
24389    fn generate_add_partition(&mut self, e: &AddPartition) -> Result<()> {
24390        // Python: return f"ADD {exists}{self.sql(expression.this)}{location}"
24391        self.write_keyword("ADD");
24392        self.write_space();
24393        if e.exists {
24394            self.write_keyword("IF NOT EXISTS");
24395            self.write_space();
24396        }
24397        self.generate_expression(&e.this)?;
24398        if let Some(location) = &e.location {
24399            self.write_space();
24400            self.generate_expression(location)?;
24401        }
24402        Ok(())
24403    }
24404
24405    fn generate_algorithm_property(&mut self, e: &AlgorithmProperty) -> Result<()> {
24406        // Python: return f"ALGORITHM={self.sql(expression, 'this')}"
24407        self.write_keyword("ALGORITHM");
24408        self.write("=");
24409        self.generate_expression(&e.this)?;
24410        Ok(())
24411    }
24412
24413    fn generate_aliases(&mut self, e: &Aliases) -> Result<()> {
24414        // Python: return f"{self.sql(expression, 'this')} AS ({self.expressions(expression, flat=True)})"
24415        self.generate_expression(&e.this)?;
24416        self.write_space();
24417        self.write_keyword("AS");
24418        self.write(" (");
24419        for (i, expr) in e.expressions.iter().enumerate() {
24420            if i > 0 {
24421                self.write(", ");
24422            }
24423            self.generate_expression(expr)?;
24424        }
24425        self.write(")");
24426        Ok(())
24427    }
24428
24429    fn generate_allowed_values_property(&mut self, e: &AllowedValuesProperty) -> Result<()> {
24430        // Python: return f"ALLOWED_VALUES {self.expressions(e, flat=True)}"
24431        self.write_keyword("ALLOWED_VALUES");
24432        self.write_space();
24433        for (i, expr) in e.expressions.iter().enumerate() {
24434            if i > 0 {
24435                self.write(", ");
24436            }
24437            self.generate_expression(expr)?;
24438        }
24439        Ok(())
24440    }
24441
24442    fn generate_alter_column(&mut self, e: &AlterColumn) -> Result<()> {
24443        // Python: complex logic based on dtype, default, comment, visible, etc.
24444        self.write_keyword("ALTER COLUMN");
24445        self.write_space();
24446        self.generate_expression(&e.this)?;
24447
24448        if let Some(dtype) = &e.dtype {
24449            self.write_space();
24450            self.write_keyword("SET DATA TYPE");
24451            self.write_space();
24452            self.generate_expression(dtype)?;
24453            if let Some(collate) = &e.collate {
24454                self.write_space();
24455                self.write_keyword("COLLATE");
24456                self.write_space();
24457                self.generate_expression(collate)?;
24458            }
24459            if let Some(using) = &e.using {
24460                self.write_space();
24461                self.write_keyword("USING");
24462                self.write_space();
24463                self.generate_expression(using)?;
24464            }
24465        } else if let Some(default) = &e.default {
24466            self.write_space();
24467            self.write_keyword("SET DEFAULT");
24468            self.write_space();
24469            self.generate_expression(default)?;
24470        } else if let Some(comment) = &e.comment {
24471            self.write_space();
24472            self.write_keyword("COMMENT");
24473            self.write_space();
24474            self.generate_expression(comment)?;
24475        } else if let Some(drop) = &e.drop {
24476            self.write_space();
24477            self.write_keyword("DROP");
24478            self.write_space();
24479            self.generate_expression(drop)?;
24480        } else if let Some(visible) = &e.visible {
24481            self.write_space();
24482            self.generate_expression(visible)?;
24483        } else if let Some(rename_to) = &e.rename_to {
24484            self.write_space();
24485            self.write_keyword("RENAME TO");
24486            self.write_space();
24487            self.generate_expression(rename_to)?;
24488        } else if let Some(allow_null) = &e.allow_null {
24489            self.write_space();
24490            self.generate_expression(allow_null)?;
24491        }
24492        Ok(())
24493    }
24494
24495    fn generate_alter_session(&mut self, e: &AlterSession) -> Result<()> {
24496        // Python: keyword = "UNSET" if expression.args.get("unset") else "SET"; return f"{keyword} {items_sql}"
24497        self.write_keyword("ALTER SESSION");
24498        self.write_space();
24499        if e.unset.is_some() {
24500            self.write_keyword("UNSET");
24501        } else {
24502            self.write_keyword("SET");
24503        }
24504        self.write_space();
24505        for (i, expr) in e.expressions.iter().enumerate() {
24506            if i > 0 {
24507                self.write(", ");
24508            }
24509            self.generate_expression(expr)?;
24510        }
24511        Ok(())
24512    }
24513
24514    fn generate_alter_set(&mut self, e: &AlterSet) -> Result<()> {
24515        // Python (Snowflake): return f"SET{exprs}{file_format}{copy_options}{tag}"
24516        self.write_keyword("SET");
24517
24518        // Generate option (e.g., AUTHORIZATION, LOGGED, UNLOGGED, etc.)
24519        if let Some(opt) = &e.option {
24520            self.write_space();
24521            self.generate_expression(opt)?;
24522        }
24523
24524        // Generate PROPERTIES (for Trino SET PROPERTIES x = y, ...)
24525        // Check if expressions look like property assignments
24526        if !e.expressions.is_empty() {
24527            // Check if this looks like property assignments (for SET PROPERTIES)
24528            let is_properties = e
24529                .expressions
24530                .iter()
24531                .any(|expr| matches!(expr, Expression::Eq(_)));
24532            if is_properties && e.option.is_none() {
24533                self.write_space();
24534                self.write_keyword("PROPERTIES");
24535            }
24536            self.write_space();
24537            for (i, expr) in e.expressions.iter().enumerate() {
24538                if i > 0 {
24539                    self.write(", ");
24540                }
24541                self.generate_expression(expr)?;
24542            }
24543        }
24544
24545        // Generate STAGE_FILE_FORMAT = (...) with space-separated properties
24546        if let Some(file_format) = &e.file_format {
24547            self.write(" ");
24548            self.write_keyword("STAGE_FILE_FORMAT");
24549            self.write(" = (");
24550            self.generate_space_separated_properties(file_format)?;
24551            self.write(")");
24552        }
24553
24554        // Generate STAGE_COPY_OPTIONS = (...) with space-separated properties
24555        if let Some(copy_options) = &e.copy_options {
24556            self.write(" ");
24557            self.write_keyword("STAGE_COPY_OPTIONS");
24558            self.write(" = (");
24559            self.generate_space_separated_properties(copy_options)?;
24560            self.write(")");
24561        }
24562
24563        // Generate TAG ...
24564        if let Some(tag) = &e.tag {
24565            self.write(" ");
24566            self.write_keyword("TAG");
24567            self.write(" ");
24568            self.generate_expression(tag)?;
24569        }
24570
24571        Ok(())
24572    }
24573
24574    /// Generate space-separated properties (for Snowflake STAGE_FILE_FORMAT, etc.)
24575    fn generate_space_separated_properties(&mut self, expr: &Expression) -> Result<()> {
24576        match expr {
24577            Expression::Tuple(t) => {
24578                for (i, prop) in t.expressions.iter().enumerate() {
24579                    if i > 0 {
24580                        self.write(" ");
24581                    }
24582                    self.generate_expression(prop)?;
24583                }
24584            }
24585            _ => {
24586                self.generate_expression(expr)?;
24587            }
24588        }
24589        Ok(())
24590    }
24591
24592    fn generate_alter_sort_key(&mut self, e: &AlterSortKey) -> Result<()> {
24593        // Python: return f"ALTER{compound} SORTKEY {this or expressions}"
24594        self.write_keyword("ALTER");
24595        if e.compound.is_some() {
24596            self.write_space();
24597            self.write_keyword("COMPOUND");
24598        }
24599        self.write_space();
24600        self.write_keyword("SORTKEY");
24601        self.write_space();
24602        if let Some(this) = &e.this {
24603            self.generate_expression(this)?;
24604        } else if !e.expressions.is_empty() {
24605            self.write("(");
24606            for (i, expr) in e.expressions.iter().enumerate() {
24607                if i > 0 {
24608                    self.write(", ");
24609                }
24610                self.generate_expression(expr)?;
24611            }
24612            self.write(")");
24613        }
24614        Ok(())
24615    }
24616
24617    fn generate_analyze(&mut self, e: &Analyze) -> Result<()> {
24618        // Python: return f"ANALYZE{options}{kind}{this}{partition}{mode}{inner_expression}{properties}"
24619        self.write_keyword("ANALYZE");
24620        if !e.options.is_empty() {
24621            self.write_space();
24622            for (i, opt) in e.options.iter().enumerate() {
24623                if i > 0 {
24624                    self.write_space();
24625                }
24626                // Write options as keywords (not identifiers) to avoid quoting reserved words like FULL
24627                if let Expression::Identifier(id) = opt {
24628                    self.write_keyword(&id.name);
24629                } else {
24630                    self.generate_expression(opt)?;
24631                }
24632            }
24633        }
24634        if let Some(kind) = &e.kind {
24635            self.write_space();
24636            self.write_keyword(kind);
24637        }
24638        if let Some(this) = &e.this {
24639            self.write_space();
24640            self.generate_expression(this)?;
24641        }
24642        // Column list: ANALYZE tbl(col1, col2) (PostgreSQL)
24643        if !e.columns.is_empty() {
24644            self.write("(");
24645            for (i, col) in e.columns.iter().enumerate() {
24646                if i > 0 {
24647                    self.write(", ");
24648                }
24649                self.write(col);
24650            }
24651            self.write(")");
24652        }
24653        if let Some(partition) = &e.partition {
24654            self.write_space();
24655            self.generate_expression(partition)?;
24656        }
24657        if let Some(mode) = &e.mode {
24658            self.write_space();
24659            self.generate_expression(mode)?;
24660        }
24661        if let Some(expression) = &e.expression {
24662            self.write_space();
24663            self.generate_expression(expression)?;
24664        }
24665        if !e.properties.is_empty() {
24666            self.write_space();
24667            self.write_keyword(self.config.with_properties_prefix);
24668            self.write(" (");
24669            for (i, prop) in e.properties.iter().enumerate() {
24670                if i > 0 {
24671                    self.write(", ");
24672                }
24673                self.generate_expression(prop)?;
24674            }
24675            self.write(")");
24676        }
24677        Ok(())
24678    }
24679
24680    fn generate_analyze_delete(&mut self, e: &AnalyzeDelete) -> Result<()> {
24681        // Python: return f"DELETE{kind} STATISTICS"
24682        self.write_keyword("DELETE");
24683        if let Some(kind) = &e.kind {
24684            self.write_space();
24685            self.write_keyword(kind);
24686        }
24687        self.write_space();
24688        self.write_keyword("STATISTICS");
24689        Ok(())
24690    }
24691
24692    fn generate_analyze_histogram(&mut self, e: &AnalyzeHistogram) -> Result<()> {
24693        // Python: return f"{this} HISTOGRAM ON {columns}{inner_expression}{update_options}"
24694        // Write `this` (UPDATE or DROP) as keyword to avoid quoting reserved words
24695        if let Expression::Identifier(id) = e.this.as_ref() {
24696            self.write_keyword(&id.name);
24697        } else {
24698            self.generate_expression(&e.this)?;
24699        }
24700        self.write_space();
24701        self.write_keyword("HISTOGRAM ON");
24702        self.write_space();
24703        for (i, expr) in e.expressions.iter().enumerate() {
24704            if i > 0 {
24705                self.write(", ");
24706            }
24707            self.generate_expression(expr)?;
24708        }
24709        if let Some(expression) = &e.expression {
24710            self.write_space();
24711            self.generate_expression(expression)?;
24712        }
24713        if let Some(update_options) = &e.update_options {
24714            self.write_space();
24715            self.generate_expression(update_options)?;
24716            self.write_space();
24717            self.write_keyword("UPDATE");
24718        }
24719        Ok(())
24720    }
24721
24722    fn generate_analyze_list_chained_rows(&mut self, e: &AnalyzeListChainedRows) -> Result<()> {
24723        // Python: return f"LIST CHAINED ROWS{inner_expression}"
24724        self.write_keyword("LIST CHAINED ROWS");
24725        if let Some(expression) = &e.expression {
24726            self.write_space();
24727            self.write_keyword("INTO");
24728            self.write_space();
24729            self.generate_expression(expression)?;
24730        }
24731        Ok(())
24732    }
24733
24734    fn generate_analyze_sample(&mut self, e: &AnalyzeSample) -> Result<()> {
24735        // Python: return f"SAMPLE {sample} {kind}"
24736        self.write_keyword("SAMPLE");
24737        self.write_space();
24738        if let Some(sample) = &e.sample {
24739            self.generate_expression(sample)?;
24740            self.write_space();
24741        }
24742        self.write_keyword(&e.kind);
24743        Ok(())
24744    }
24745
24746    fn generate_analyze_statistics(&mut self, e: &AnalyzeStatistics) -> Result<()> {
24747        // Python: return f"{kind}{option} STATISTICS{this}{columns}"
24748        self.write_keyword(&e.kind);
24749        if let Some(option) = &e.option {
24750            self.write_space();
24751            self.generate_expression(option)?;
24752        }
24753        self.write_space();
24754        self.write_keyword("STATISTICS");
24755        if let Some(this) = &e.this {
24756            self.write_space();
24757            self.generate_expression(this)?;
24758        }
24759        if !e.expressions.is_empty() {
24760            self.write_space();
24761            for (i, expr) in e.expressions.iter().enumerate() {
24762                if i > 0 {
24763                    self.write(", ");
24764                }
24765                self.generate_expression(expr)?;
24766            }
24767        }
24768        Ok(())
24769    }
24770
24771    fn generate_analyze_validate(&mut self, e: &AnalyzeValidate) -> Result<()> {
24772        // Python: return f"VALIDATE {kind}{this}{inner_expression}"
24773        self.write_keyword("VALIDATE");
24774        self.write_space();
24775        self.write_keyword(&e.kind);
24776        if let Some(this) = &e.this {
24777            self.write_space();
24778            // this is a keyword string like "UPDATE", "CASCADE FAST", etc. - write as keywords
24779            if let Expression::Identifier(id) = this.as_ref() {
24780                self.write_keyword(&id.name);
24781            } else {
24782                self.generate_expression(this)?;
24783            }
24784        }
24785        if let Some(expression) = &e.expression {
24786            self.write_space();
24787            self.write_keyword("INTO");
24788            self.write_space();
24789            self.generate_expression(expression)?;
24790        }
24791        Ok(())
24792    }
24793
24794    fn generate_analyze_with(&mut self, e: &AnalyzeWith) -> Result<()> {
24795        // Python: return f"WITH {expressions}"
24796        self.write_keyword("WITH");
24797        self.write_space();
24798        for (i, expr) in e.expressions.iter().enumerate() {
24799            if i > 0 {
24800                self.write(", ");
24801            }
24802            self.generate_expression(expr)?;
24803        }
24804        Ok(())
24805    }
24806
24807    fn generate_anonymous(&mut self, e: &Anonymous) -> Result<()> {
24808        // Anonymous represents a generic function call: FUNC_NAME(args...)
24809        // Python: return self.func(self.sql(expression, "this"), *expression.expressions)
24810        self.generate_expression(&e.this)?;
24811        self.write("(");
24812        for (i, arg) in e.expressions.iter().enumerate() {
24813            if i > 0 {
24814                self.write(", ");
24815            }
24816            self.generate_expression(arg)?;
24817        }
24818        self.write(")");
24819        Ok(())
24820    }
24821
24822    fn generate_anonymous_agg_func(&mut self, e: &AnonymousAggFunc) -> Result<()> {
24823        // Same as Anonymous but for aggregate functions
24824        self.generate_expression(&e.this)?;
24825        self.write("(");
24826        for (i, arg) in e.expressions.iter().enumerate() {
24827            if i > 0 {
24828                self.write(", ");
24829            }
24830            self.generate_expression(arg)?;
24831        }
24832        self.write(")");
24833        Ok(())
24834    }
24835
24836    fn generate_apply(&mut self, e: &Apply) -> Result<()> {
24837        // Python: return f"{this} APPLY({expr})"
24838        self.generate_expression(&e.this)?;
24839        self.write_space();
24840        self.write_keyword("APPLY");
24841        self.write("(");
24842        self.generate_expression(&e.expression)?;
24843        self.write(")");
24844        Ok(())
24845    }
24846
24847    fn generate_approx_percentile_estimate(&mut self, e: &ApproxPercentileEstimate) -> Result<()> {
24848        // APPROX_PERCENTILE_ESTIMATE(this, percentile)
24849        self.write_keyword("APPROX_PERCENTILE_ESTIMATE");
24850        self.write("(");
24851        self.generate_expression(&e.this)?;
24852        if let Some(percentile) = &e.percentile {
24853            self.write(", ");
24854            self.generate_expression(percentile)?;
24855        }
24856        self.write(")");
24857        Ok(())
24858    }
24859
24860    fn generate_approx_quantile(&mut self, e: &ApproxQuantile) -> Result<()> {
24861        // APPROX_QUANTILE(this, quantile[, accuracy][, weight])
24862        self.write_keyword("APPROX_QUANTILE");
24863        self.write("(");
24864        self.generate_expression(&e.this)?;
24865        if let Some(quantile) = &e.quantile {
24866            self.write(", ");
24867            self.generate_expression(quantile)?;
24868        }
24869        if let Some(accuracy) = &e.accuracy {
24870            self.write(", ");
24871            self.generate_expression(accuracy)?;
24872        }
24873        if let Some(weight) = &e.weight {
24874            self.write(", ");
24875            self.generate_expression(weight)?;
24876        }
24877        self.write(")");
24878        Ok(())
24879    }
24880
24881    fn generate_approx_quantiles(&mut self, e: &ApproxQuantiles) -> Result<()> {
24882        // APPROX_QUANTILES(this, expression)
24883        self.write_keyword("APPROX_QUANTILES");
24884        self.write("(");
24885        self.generate_expression(&e.this)?;
24886        if let Some(expression) = &e.expression {
24887            self.write(", ");
24888            self.generate_expression(expression)?;
24889        }
24890        self.write(")");
24891        Ok(())
24892    }
24893
24894    fn generate_approx_top_k(&mut self, e: &ApproxTopK) -> Result<()> {
24895        // APPROX_TOP_K(this[, expression][, counters])
24896        self.write_keyword("APPROX_TOP_K");
24897        self.write("(");
24898        self.generate_expression(&e.this)?;
24899        if let Some(expression) = &e.expression {
24900            self.write(", ");
24901            self.generate_expression(expression)?;
24902        }
24903        if let Some(counters) = &e.counters {
24904            self.write(", ");
24905            self.generate_expression(counters)?;
24906        }
24907        self.write(")");
24908        Ok(())
24909    }
24910
24911    fn generate_approx_top_k_accumulate(&mut self, e: &ApproxTopKAccumulate) -> Result<()> {
24912        // APPROX_TOP_K_ACCUMULATE(this[, expression])
24913        self.write_keyword("APPROX_TOP_K_ACCUMULATE");
24914        self.write("(");
24915        self.generate_expression(&e.this)?;
24916        if let Some(expression) = &e.expression {
24917            self.write(", ");
24918            self.generate_expression(expression)?;
24919        }
24920        self.write(")");
24921        Ok(())
24922    }
24923
24924    fn generate_approx_top_k_combine(&mut self, e: &ApproxTopKCombine) -> Result<()> {
24925        // APPROX_TOP_K_COMBINE(this[, expression])
24926        self.write_keyword("APPROX_TOP_K_COMBINE");
24927        self.write("(");
24928        self.generate_expression(&e.this)?;
24929        if let Some(expression) = &e.expression {
24930            self.write(", ");
24931            self.generate_expression(expression)?;
24932        }
24933        self.write(")");
24934        Ok(())
24935    }
24936
24937    fn generate_approx_top_k_estimate(&mut self, e: &ApproxTopKEstimate) -> Result<()> {
24938        // APPROX_TOP_K_ESTIMATE(this[, expression])
24939        self.write_keyword("APPROX_TOP_K_ESTIMATE");
24940        self.write("(");
24941        self.generate_expression(&e.this)?;
24942        if let Some(expression) = &e.expression {
24943            self.write(", ");
24944            self.generate_expression(expression)?;
24945        }
24946        self.write(")");
24947        Ok(())
24948    }
24949
24950    fn generate_approx_top_sum(&mut self, e: &ApproxTopSum) -> Result<()> {
24951        // APPROX_TOP_SUM(this, expression[, count])
24952        self.write_keyword("APPROX_TOP_SUM");
24953        self.write("(");
24954        self.generate_expression(&e.this)?;
24955        self.write(", ");
24956        self.generate_expression(&e.expression)?;
24957        if let Some(count) = &e.count {
24958            self.write(", ");
24959            self.generate_expression(count)?;
24960        }
24961        self.write(")");
24962        Ok(())
24963    }
24964
24965    fn generate_arg_max(&mut self, e: &ArgMax) -> Result<()> {
24966        // ARG_MAX(this, expression[, count])
24967        self.write_keyword("ARG_MAX");
24968        self.write("(");
24969        self.generate_expression(&e.this)?;
24970        self.write(", ");
24971        self.generate_expression(&e.expression)?;
24972        if let Some(count) = &e.count {
24973            self.write(", ");
24974            self.generate_expression(count)?;
24975        }
24976        self.write(")");
24977        Ok(())
24978    }
24979
24980    fn generate_arg_min(&mut self, e: &ArgMin) -> Result<()> {
24981        // ARG_MIN(this, expression[, count])
24982        self.write_keyword("ARG_MIN");
24983        self.write("(");
24984        self.generate_expression(&e.this)?;
24985        self.write(", ");
24986        self.generate_expression(&e.expression)?;
24987        if let Some(count) = &e.count {
24988            self.write(", ");
24989            self.generate_expression(count)?;
24990        }
24991        self.write(")");
24992        Ok(())
24993    }
24994
24995    fn generate_array_all(&mut self, e: &ArrayAll) -> Result<()> {
24996        // ARRAY_ALL(this, expression)
24997        self.write_keyword("ARRAY_ALL");
24998        self.write("(");
24999        self.generate_expression(&e.this)?;
25000        self.write(", ");
25001        self.generate_expression(&e.expression)?;
25002        self.write(")");
25003        Ok(())
25004    }
25005
25006    fn generate_array_any(&mut self, e: &ArrayAny) -> Result<()> {
25007        // ARRAY_ANY(this, expression) - fallback implementation
25008        self.write_keyword("ARRAY_ANY");
25009        self.write("(");
25010        self.generate_expression(&e.this)?;
25011        self.write(", ");
25012        self.generate_expression(&e.expression)?;
25013        self.write(")");
25014        Ok(())
25015    }
25016
25017    fn generate_array_construct_compact(&mut self, e: &ArrayConstructCompact) -> Result<()> {
25018        // ARRAY_CONSTRUCT_COMPACT(expressions...)
25019        self.write_keyword("ARRAY_CONSTRUCT_COMPACT");
25020        self.write("(");
25021        for (i, expr) in e.expressions.iter().enumerate() {
25022            if i > 0 {
25023                self.write(", ");
25024            }
25025            self.generate_expression(expr)?;
25026        }
25027        self.write(")");
25028        Ok(())
25029    }
25030
25031    fn generate_array_sum(&mut self, e: &ArraySum) -> Result<()> {
25032        // ARRAY_SUM(this[, expression])
25033        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
25034            self.write("arraySum");
25035        } else {
25036            self.write_keyword("ARRAY_SUM");
25037        }
25038        self.write("(");
25039        self.generate_expression(&e.this)?;
25040        if let Some(expression) = &e.expression {
25041            self.write(", ");
25042            self.generate_expression(expression)?;
25043        }
25044        self.write(")");
25045        Ok(())
25046    }
25047
25048    fn generate_at_index(&mut self, e: &AtIndex) -> Result<()> {
25049        // Python: return f"{this} AT {index}"
25050        self.generate_expression(&e.this)?;
25051        self.write_space();
25052        self.write_keyword("AT");
25053        self.write_space();
25054        self.generate_expression(&e.expression)?;
25055        Ok(())
25056    }
25057
25058    fn generate_attach(&mut self, e: &Attach) -> Result<()> {
25059        // Python: return f"ATTACH{exists_sql} {this}{expressions}"
25060        self.write_keyword("ATTACH");
25061        if e.exists {
25062            self.write_space();
25063            self.write_keyword("IF NOT EXISTS");
25064        }
25065        self.write_space();
25066        self.generate_expression(&e.this)?;
25067        if !e.expressions.is_empty() {
25068            self.write(" (");
25069            for (i, expr) in e.expressions.iter().enumerate() {
25070                if i > 0 {
25071                    self.write(", ");
25072                }
25073                self.generate_expression(expr)?;
25074            }
25075            self.write(")");
25076        }
25077        Ok(())
25078    }
25079
25080    fn generate_attach_option(&mut self, e: &AttachOption) -> Result<()> {
25081        // AttachOption: this [expression]
25082        // Python sqlglot: no equals sign, just space-separated
25083        self.generate_expression(&e.this)?;
25084        if let Some(expression) = &e.expression {
25085            self.write_space();
25086            self.generate_expression(expression)?;
25087        }
25088        Ok(())
25089    }
25090
25091    /// Generate the auto_increment keyword and options for a column definition.
25092    /// Different dialects use different syntax: IDENTITY, AUTOINCREMENT, AUTO_INCREMENT,
25093    /// GENERATED AS IDENTITY, etc.
25094    fn generate_auto_increment_keyword(
25095        &mut self,
25096        col: &crate::expressions::ColumnDef,
25097    ) -> Result<()> {
25098        use crate::dialects::DialectType;
25099        if matches!(self.config.dialect, Some(DialectType::Redshift)) {
25100            self.write_keyword("IDENTITY");
25101            if col.auto_increment_start.is_some() || col.auto_increment_increment.is_some() {
25102                self.write("(");
25103                if let Some(ref start) = col.auto_increment_start {
25104                    self.generate_expression(start)?;
25105                } else {
25106                    self.write("0");
25107                }
25108                self.write(", ");
25109                if let Some(ref inc) = col.auto_increment_increment {
25110                    self.generate_expression(inc)?;
25111                } else {
25112                    self.write("1");
25113                }
25114                self.write(")");
25115            }
25116        } else if matches!(
25117            self.config.dialect,
25118            Some(DialectType::Snowflake) | Some(DialectType::SQLite)
25119        ) {
25120            self.write_keyword("AUTOINCREMENT");
25121            if let Some(ref start) = col.auto_increment_start {
25122                self.write_space();
25123                self.write_keyword("START");
25124                self.write_space();
25125                self.generate_expression(start)?;
25126            }
25127            if let Some(ref inc) = col.auto_increment_increment {
25128                self.write_space();
25129                self.write_keyword("INCREMENT");
25130                self.write_space();
25131                self.generate_expression(inc)?;
25132            }
25133            if let Some(order) = col.auto_increment_order {
25134                self.write_space();
25135                if order {
25136                    self.write_keyword("ORDER");
25137                } else {
25138                    self.write_keyword("NOORDER");
25139                }
25140            }
25141        } else if matches!(self.config.dialect, Some(DialectType::PostgreSQL)) {
25142            self.write_keyword("GENERATED BY DEFAULT AS IDENTITY");
25143            if col.auto_increment_start.is_some() || col.auto_increment_increment.is_some() {
25144                self.write(" (");
25145                let mut first = true;
25146                if let Some(ref start) = col.auto_increment_start {
25147                    self.write_keyword("START WITH");
25148                    self.write_space();
25149                    self.generate_expression(start)?;
25150                    first = false;
25151                }
25152                if let Some(ref inc) = col.auto_increment_increment {
25153                    if !first {
25154                        self.write_space();
25155                    }
25156                    self.write_keyword("INCREMENT BY");
25157                    self.write_space();
25158                    self.generate_expression(inc)?;
25159                }
25160                self.write(")");
25161            }
25162        } else if matches!(self.config.dialect, Some(DialectType::Databricks)) {
25163            // IDENTITY(start, increment) -> GENERATED BY DEFAULT AS IDENTITY
25164            // Plain IDENTITY/AUTO_INCREMENT -> GENERATED ALWAYS AS IDENTITY
25165            if col.auto_increment_start.is_some() || col.auto_increment_increment.is_some() {
25166                self.write_keyword("GENERATED BY DEFAULT AS IDENTITY");
25167            } else {
25168                self.write_keyword("GENERATED ALWAYS AS IDENTITY");
25169            }
25170            if col.auto_increment_start.is_some() || col.auto_increment_increment.is_some() {
25171                self.write(" (");
25172                let mut first = true;
25173                if let Some(ref start) = col.auto_increment_start {
25174                    self.write_keyword("START WITH");
25175                    self.write_space();
25176                    self.generate_expression(start)?;
25177                    first = false;
25178                }
25179                if let Some(ref inc) = col.auto_increment_increment {
25180                    if !first {
25181                        self.write_space();
25182                    }
25183                    self.write_keyword("INCREMENT BY");
25184                    self.write_space();
25185                    self.generate_expression(inc)?;
25186                }
25187                self.write(")");
25188            }
25189        } else if matches!(
25190            self.config.dialect,
25191            Some(DialectType::TSQL) | Some(DialectType::Fabric)
25192        ) {
25193            self.write_keyword("IDENTITY");
25194            if col.auto_increment_start.is_some() || col.auto_increment_increment.is_some() {
25195                self.write("(");
25196                if let Some(ref start) = col.auto_increment_start {
25197                    self.generate_expression(start)?;
25198                } else {
25199                    self.write("0");
25200                }
25201                self.write(", ");
25202                if let Some(ref inc) = col.auto_increment_increment {
25203                    self.generate_expression(inc)?;
25204                } else {
25205                    self.write("1");
25206                }
25207                self.write(")");
25208            }
25209        } else {
25210            self.write_keyword("AUTO_INCREMENT");
25211            if let Some(ref start) = col.auto_increment_start {
25212                self.write_space();
25213                self.write_keyword("START");
25214                self.write_space();
25215                self.generate_expression(start)?;
25216            }
25217            if let Some(ref inc) = col.auto_increment_increment {
25218                self.write_space();
25219                self.write_keyword("INCREMENT");
25220                self.write_space();
25221                self.generate_expression(inc)?;
25222            }
25223            if let Some(order) = col.auto_increment_order {
25224                self.write_space();
25225                if order {
25226                    self.write_keyword("ORDER");
25227                } else {
25228                    self.write_keyword("NOORDER");
25229                }
25230            }
25231        }
25232        Ok(())
25233    }
25234
25235    fn generate_auto_increment_property(&mut self, e: &AutoIncrementProperty) -> Result<()> {
25236        // AUTO_INCREMENT=value
25237        self.write_keyword("AUTO_INCREMENT");
25238        self.write("=");
25239        self.generate_expression(&e.this)?;
25240        Ok(())
25241    }
25242
25243    fn generate_auto_refresh_property(&mut self, e: &AutoRefreshProperty) -> Result<()> {
25244        // AUTO_REFRESH=value
25245        self.write_keyword("AUTO_REFRESH");
25246        self.write("=");
25247        self.generate_expression(&e.this)?;
25248        Ok(())
25249    }
25250
25251    fn generate_backup_property(&mut self, e: &BackupProperty) -> Result<()> {
25252        // BACKUP YES|NO (Redshift syntax uses space, not equals)
25253        self.write_keyword("BACKUP");
25254        self.write_space();
25255        self.generate_expression(&e.this)?;
25256        Ok(())
25257    }
25258
25259    fn generate_base64_decode_binary(&mut self, e: &Base64DecodeBinary) -> Result<()> {
25260        // BASE64_DECODE_BINARY(this[, alphabet])
25261        self.write_keyword("BASE64_DECODE_BINARY");
25262        self.write("(");
25263        self.generate_expression(&e.this)?;
25264        if let Some(alphabet) = &e.alphabet {
25265            self.write(", ");
25266            self.generate_expression(alphabet)?;
25267        }
25268        self.write(")");
25269        Ok(())
25270    }
25271
25272    fn generate_base64_decode_string(&mut self, e: &Base64DecodeString) -> Result<()> {
25273        // BASE64_DECODE_STRING(this[, alphabet])
25274        self.write_keyword("BASE64_DECODE_STRING");
25275        self.write("(");
25276        self.generate_expression(&e.this)?;
25277        if let Some(alphabet) = &e.alphabet {
25278            self.write(", ");
25279            self.generate_expression(alphabet)?;
25280        }
25281        self.write(")");
25282        Ok(())
25283    }
25284
25285    fn generate_base64_encode(&mut self, e: &Base64Encode) -> Result<()> {
25286        // BASE64_ENCODE(this[, max_line_length][, alphabet])
25287        self.write_keyword("BASE64_ENCODE");
25288        self.write("(");
25289        self.generate_expression(&e.this)?;
25290        if let Some(max_line_length) = &e.max_line_length {
25291            self.write(", ");
25292            self.generate_expression(max_line_length)?;
25293        }
25294        if let Some(alphabet) = &e.alphabet {
25295            self.write(", ");
25296            self.generate_expression(alphabet)?;
25297        }
25298        self.write(")");
25299        Ok(())
25300    }
25301
25302    fn generate_block_compression_property(&mut self, e: &BlockCompressionProperty) -> Result<()> {
25303        // BLOCKCOMPRESSION=... (complex Teradata property)
25304        self.write_keyword("BLOCKCOMPRESSION");
25305        self.write("=");
25306        if let Some(autotemp) = &e.autotemp {
25307            self.write_keyword("AUTOTEMP");
25308            self.write("(");
25309            self.generate_expression(autotemp)?;
25310            self.write(")");
25311        }
25312        if let Some(always) = &e.always {
25313            self.generate_expression(always)?;
25314        }
25315        if let Some(default) = &e.default {
25316            self.generate_expression(default)?;
25317        }
25318        if let Some(manual) = &e.manual {
25319            self.generate_expression(manual)?;
25320        }
25321        if let Some(never) = &e.never {
25322            self.generate_expression(never)?;
25323        }
25324        Ok(())
25325    }
25326
25327    fn generate_booland(&mut self, e: &Booland) -> Result<()> {
25328        // Python: return f"(({self.sql(expression, 'this')}) AND ({self.sql(expression, 'expression')}))"
25329        self.write("((");
25330        self.generate_expression(&e.this)?;
25331        self.write(") ");
25332        self.write_keyword("AND");
25333        self.write(" (");
25334        self.generate_expression(&e.expression)?;
25335        self.write("))");
25336        Ok(())
25337    }
25338
25339    fn generate_boolor(&mut self, e: &Boolor) -> Result<()> {
25340        // Python: return f"(({self.sql(expression, 'this')}) OR ({self.sql(expression, 'expression')}))"
25341        self.write("((");
25342        self.generate_expression(&e.this)?;
25343        self.write(") ");
25344        self.write_keyword("OR");
25345        self.write(" (");
25346        self.generate_expression(&e.expression)?;
25347        self.write("))");
25348        Ok(())
25349    }
25350
25351    fn generate_build_property(&mut self, e: &BuildProperty) -> Result<()> {
25352        // BUILD value (e.g., BUILD IMMEDIATE, BUILD DEFERRED)
25353        self.write_keyword("BUILD");
25354        self.write_space();
25355        self.generate_expression(&e.this)?;
25356        Ok(())
25357    }
25358
25359    fn generate_byte_string(&mut self, e: &ByteString) -> Result<()> {
25360        // Byte string literal like B'...' or X'...'
25361        self.generate_expression(&e.this)?;
25362        Ok(())
25363    }
25364
25365    fn generate_case_specific_column_constraint(
25366        &mut self,
25367        e: &CaseSpecificColumnConstraint,
25368    ) -> Result<()> {
25369        // CASESPECIFIC or NOT CASESPECIFIC (Teradata)
25370        if e.not_.is_some() {
25371            self.write_keyword("NOT");
25372            self.write_space();
25373        }
25374        self.write_keyword("CASESPECIFIC");
25375        Ok(())
25376    }
25377
25378    fn generate_cast_to_str_type(&mut self, e: &CastToStrType) -> Result<()> {
25379        // Cast to string type (dialect-specific)
25380        self.write_keyword("CAST");
25381        self.write("(");
25382        self.generate_expression(&e.this)?;
25383        if self.config.dialect == Some(DialectType::ClickHouse) {
25384            // ClickHouse: CAST(expr, 'type_string')
25385            self.write(", ");
25386        } else {
25387            self.write_space();
25388            self.write_keyword("AS");
25389            self.write_space();
25390        }
25391        if let Some(to) = &e.to {
25392            self.generate_expression(to)?;
25393        }
25394        self.write(")");
25395        Ok(())
25396    }
25397
25398    fn generate_changes(&mut self, e: &Changes) -> Result<()> {
25399        // CHANGES (INFORMATION => value) AT|BEFORE (...) END (...)
25400        // Python: f"CHANGES ({information}){at_before}{end}"
25401        self.write_keyword("CHANGES");
25402        self.write(" (");
25403        if let Some(information) = &e.information {
25404            self.write_keyword("INFORMATION");
25405            self.write(" => ");
25406            self.generate_expression(information)?;
25407        }
25408        self.write(")");
25409        // at_before and end are HistoricalData expressions that generate their own keywords
25410        if let Some(at_before) = &e.at_before {
25411            self.write(" ");
25412            self.generate_expression(at_before)?;
25413        }
25414        if let Some(end) = &e.end {
25415            self.write(" ");
25416            self.generate_expression(end)?;
25417        }
25418        Ok(())
25419    }
25420
25421    fn generate_character_set_column_constraint(
25422        &mut self,
25423        e: &CharacterSetColumnConstraint,
25424    ) -> Result<()> {
25425        // CHARACTER SET charset_name
25426        self.write_keyword("CHARACTER SET");
25427        self.write_space();
25428        self.generate_expression(&e.this)?;
25429        Ok(())
25430    }
25431
25432    fn generate_character_set_property(&mut self, e: &CharacterSetProperty) -> Result<()> {
25433        // [DEFAULT] CHARACTER SET=value
25434        if e.default.is_some() {
25435            self.write_keyword("DEFAULT");
25436            self.write_space();
25437        }
25438        self.write_keyword("CHARACTER SET");
25439        self.write("=");
25440        self.generate_expression(&e.this)?;
25441        Ok(())
25442    }
25443
25444    fn generate_check_column_constraint(&mut self, e: &CheckColumnConstraint) -> Result<()> {
25445        // Python: return f"CHECK ({self.sql(expression, 'this')}){enforced}"
25446        self.write_keyword("CHECK");
25447        self.write(" (");
25448        self.generate_expression(&e.this)?;
25449        self.write(")");
25450        if e.enforced.is_some() {
25451            self.write_space();
25452            self.write_keyword("ENFORCED");
25453        }
25454        Ok(())
25455    }
25456
25457    fn generate_assume_column_constraint(&mut self, e: &AssumeColumnConstraint) -> Result<()> {
25458        // Python: return f"ASSUME ({self.sql(e, 'this')})"
25459        self.write_keyword("ASSUME");
25460        self.write(" (");
25461        self.generate_expression(&e.this)?;
25462        self.write(")");
25463        Ok(())
25464    }
25465
25466    fn generate_check_json(&mut self, e: &CheckJson) -> Result<()> {
25467        // CHECK_JSON(this)
25468        self.write_keyword("CHECK_JSON");
25469        self.write("(");
25470        self.generate_expression(&e.this)?;
25471        self.write(")");
25472        Ok(())
25473    }
25474
25475    fn generate_check_xml(&mut self, e: &CheckXml) -> Result<()> {
25476        // CHECK_XML(this)
25477        self.write_keyword("CHECK_XML");
25478        self.write("(");
25479        self.generate_expression(&e.this)?;
25480        self.write(")");
25481        Ok(())
25482    }
25483
25484    fn generate_checksum_property(&mut self, e: &ChecksumProperty) -> Result<()> {
25485        // CHECKSUM=[ON|OFF|DEFAULT]
25486        self.write_keyword("CHECKSUM");
25487        self.write("=");
25488        if e.on.is_some() {
25489            self.write_keyword("ON");
25490        } else if e.default.is_some() {
25491            self.write_keyword("DEFAULT");
25492        } else {
25493            self.write_keyword("OFF");
25494        }
25495        Ok(())
25496    }
25497
25498    fn generate_clone(&mut self, e: &Clone) -> Result<()> {
25499        // Python: return f"{shallow}{keyword} {this}"
25500        if e.shallow.is_some() {
25501            self.write_keyword("SHALLOW");
25502            self.write_space();
25503        }
25504        if e.copy.is_some() {
25505            self.write_keyword("COPY");
25506        } else {
25507            self.write_keyword("CLONE");
25508        }
25509        self.write_space();
25510        self.generate_expression(&e.this)?;
25511        Ok(())
25512    }
25513
25514    fn generate_cluster_by(&mut self, e: &ClusterBy) -> Result<()> {
25515        // CLUSTER BY (expressions)
25516        self.write_keyword("CLUSTER BY");
25517        self.write(" (");
25518        for (i, ord) in e.expressions.iter().enumerate() {
25519            if i > 0 {
25520                self.write(", ");
25521            }
25522            self.generate_ordered(ord)?;
25523        }
25524        self.write(")");
25525        Ok(())
25526    }
25527
25528    fn generate_cluster_by_columns_property(&mut self, e: &ClusterByColumnsProperty) -> Result<()> {
25529        // BigQuery table property: CLUSTER BY col1, col2
25530        self.write_keyword("CLUSTER BY");
25531        self.write_space();
25532        for (i, col) in e.columns.iter().enumerate() {
25533            if i > 0 {
25534                self.write(", ");
25535            }
25536            self.generate_identifier(col)?;
25537        }
25538        Ok(())
25539    }
25540
25541    fn generate_clustered_by_property(&mut self, e: &ClusteredByProperty) -> Result<()> {
25542        // Python: return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS"
25543        self.write_keyword("CLUSTERED BY");
25544        self.write(" (");
25545        for (i, expr) in e.expressions.iter().enumerate() {
25546            if i > 0 {
25547                self.write(", ");
25548            }
25549            self.generate_expression(expr)?;
25550        }
25551        self.write(")");
25552        if let Some(sorted_by) = &e.sorted_by {
25553            self.write_space();
25554            self.write_keyword("SORTED BY");
25555            self.write(" (");
25556            // Unwrap Tuple to avoid double parentheses
25557            if let Expression::Tuple(t) = sorted_by.as_ref() {
25558                for (i, expr) in t.expressions.iter().enumerate() {
25559                    if i > 0 {
25560                        self.write(", ");
25561                    }
25562                    self.generate_expression(expr)?;
25563                }
25564            } else {
25565                self.generate_expression(sorted_by)?;
25566            }
25567            self.write(")");
25568        }
25569        if let Some(buckets) = &e.buckets {
25570            self.write_space();
25571            self.write_keyword("INTO");
25572            self.write_space();
25573            self.generate_expression(buckets)?;
25574            self.write_space();
25575            self.write_keyword("BUCKETS");
25576        }
25577        Ok(())
25578    }
25579
25580    fn generate_collate_property(&mut self, e: &CollateProperty) -> Result<()> {
25581        // [DEFAULT] COLLATE [=] value
25582        // BigQuery uses space: DEFAULT COLLATE 'en'
25583        // Others use equals: COLLATE='en'
25584        if e.default.is_some() {
25585            self.write_keyword("DEFAULT");
25586            self.write_space();
25587        }
25588        self.write_keyword("COLLATE");
25589        // BigQuery uses space between COLLATE and value
25590        match self.config.dialect {
25591            Some(DialectType::BigQuery) => self.write_space(),
25592            _ => self.write("="),
25593        }
25594        self.generate_expression(&e.this)?;
25595        Ok(())
25596    }
25597
25598    fn generate_column_constraint(&mut self, e: &ColumnConstraint) -> Result<()> {
25599        // ColumnConstraint is an enum
25600        match e {
25601            ColumnConstraint::NotNull => {
25602                self.write_keyword("NOT NULL");
25603            }
25604            ColumnConstraint::Null => {
25605                self.write_keyword("NULL");
25606            }
25607            ColumnConstraint::Unique => {
25608                self.write_keyword("UNIQUE");
25609            }
25610            ColumnConstraint::PrimaryKey => {
25611                self.write_keyword("PRIMARY KEY");
25612            }
25613            ColumnConstraint::Default(expr) => {
25614                self.write_keyword("DEFAULT");
25615                self.write_space();
25616                self.generate_expression(expr)?;
25617            }
25618            ColumnConstraint::Check(expr) => {
25619                self.write_keyword("CHECK");
25620                self.write(" (");
25621                self.generate_expression(expr)?;
25622                self.write(")");
25623            }
25624            ColumnConstraint::References(fk_ref) => {
25625                if fk_ref.has_foreign_key_keywords {
25626                    self.write_keyword("FOREIGN KEY");
25627                    self.write_space();
25628                }
25629                self.write_keyword("REFERENCES");
25630                self.write_space();
25631                self.generate_table(&fk_ref.table)?;
25632                if !fk_ref.columns.is_empty() {
25633                    self.write(" (");
25634                    for (i, col) in fk_ref.columns.iter().enumerate() {
25635                        if i > 0 {
25636                            self.write(", ");
25637                        }
25638                        self.generate_identifier(col)?;
25639                    }
25640                    self.write(")");
25641                }
25642            }
25643            ColumnConstraint::GeneratedAsIdentity(gen) => {
25644                self.write_keyword("GENERATED");
25645                self.write_space();
25646                if gen.always {
25647                    self.write_keyword("ALWAYS");
25648                } else {
25649                    self.write_keyword("BY DEFAULT");
25650                    if gen.on_null {
25651                        self.write_space();
25652                        self.write_keyword("ON NULL");
25653                    }
25654                }
25655                self.write_space();
25656                self.write_keyword("AS IDENTITY");
25657            }
25658            ColumnConstraint::Collate(collation) => {
25659                self.write_keyword("COLLATE");
25660                self.write_space();
25661                self.generate_identifier(collation)?;
25662            }
25663            ColumnConstraint::Comment(comment) => {
25664                self.write_keyword("COMMENT");
25665                self.write(" '");
25666                self.write(comment);
25667                self.write("'");
25668            }
25669            ColumnConstraint::ComputedColumn(cc) => {
25670                self.generate_computed_column_inline(cc)?;
25671            }
25672            ColumnConstraint::GeneratedAsRow(gar) => {
25673                self.generate_generated_as_row_inline(gar)?;
25674            }
25675            ColumnConstraint::Tags(tags) => {
25676                self.write_keyword("TAG");
25677                self.write(" (");
25678                for (i, expr) in tags.expressions.iter().enumerate() {
25679                    if i > 0 {
25680                        self.write(", ");
25681                    }
25682                    self.generate_expression(expr)?;
25683                }
25684                self.write(")");
25685            }
25686            ColumnConstraint::Path(path_expr) => {
25687                self.write_keyword("PATH");
25688                self.write_space();
25689                self.generate_expression(path_expr)?;
25690            }
25691        }
25692        Ok(())
25693    }
25694
25695    fn generate_column_position(&mut self, e: &ColumnPosition) -> Result<()> {
25696        // ColumnPosition is an enum
25697        match e {
25698            ColumnPosition::First => {
25699                self.write_keyword("FIRST");
25700            }
25701            ColumnPosition::After(ident) => {
25702                self.write_keyword("AFTER");
25703                self.write_space();
25704                self.generate_identifier(ident)?;
25705            }
25706        }
25707        Ok(())
25708    }
25709
25710    fn generate_column_prefix(&mut self, e: &ColumnPrefix) -> Result<()> {
25711        // column(prefix)
25712        self.generate_expression(&e.this)?;
25713        self.write("(");
25714        self.generate_expression(&e.expression)?;
25715        self.write(")");
25716        Ok(())
25717    }
25718
25719    fn generate_columns(&mut self, e: &Columns) -> Result<()> {
25720        // If unpack is true, this came from * COLUMNS(pattern)
25721        // DuckDB syntax: * COLUMNS(c ILIKE '%suffix') or COLUMNS(pattern)
25722        if let Some(ref unpack) = e.unpack {
25723            if let Expression::Boolean(b) = unpack.as_ref() {
25724                if b.value {
25725                    self.write("*");
25726                }
25727            }
25728        }
25729        self.write_keyword("COLUMNS");
25730        self.write("(");
25731        self.generate_expression(&e.this)?;
25732        self.write(")");
25733        Ok(())
25734    }
25735
25736    fn generate_combined_agg_func(&mut self, e: &CombinedAggFunc) -> Result<()> {
25737        // Combined aggregate: FUNC(args) combined
25738        self.generate_expression(&e.this)?;
25739        self.write("(");
25740        for (i, expr) in e.expressions.iter().enumerate() {
25741            if i > 0 {
25742                self.write(", ");
25743            }
25744            self.generate_expression(expr)?;
25745        }
25746        self.write(")");
25747        Ok(())
25748    }
25749
25750    fn generate_combined_parameterized_agg(&mut self, e: &CombinedParameterizedAgg) -> Result<()> {
25751        // Combined parameterized aggregate: FUNC(params)(expressions)
25752        self.generate_expression(&e.this)?;
25753        self.write("(");
25754        for (i, param) in e.params.iter().enumerate() {
25755            if i > 0 {
25756                self.write(", ");
25757            }
25758            self.generate_expression(param)?;
25759        }
25760        self.write(")(");
25761        for (i, expr) in e.expressions.iter().enumerate() {
25762            if i > 0 {
25763                self.write(", ");
25764            }
25765            self.generate_expression(expr)?;
25766        }
25767        self.write(")");
25768        Ok(())
25769    }
25770
25771    fn generate_commit(&mut self, e: &Commit) -> Result<()> {
25772        // COMMIT [TRANSACTION [transaction_name]] [WITH (DELAYED_DURABILITY = ON|OFF)] [AND [NO] CHAIN]
25773        self.write_keyword("COMMIT");
25774
25775        // TSQL always uses COMMIT TRANSACTION
25776        if e.this.is_none()
25777            && matches!(
25778                self.config.dialect,
25779                Some(DialectType::TSQL) | Some(DialectType::Fabric)
25780            )
25781        {
25782            self.write_space();
25783            self.write_keyword("TRANSACTION");
25784        }
25785
25786        // Check if this has TRANSACTION keyword or transaction name
25787        if let Some(this) = &e.this {
25788            // Check if it's just the "TRANSACTION" marker or an actual transaction name
25789            let is_transaction_marker = matches!(
25790                this.as_ref(),
25791                Expression::Identifier(id) if id.name == "TRANSACTION"
25792            );
25793
25794            self.write_space();
25795            self.write_keyword("TRANSACTION");
25796
25797            // If it's a real transaction name, output it
25798            if !is_transaction_marker {
25799                self.write_space();
25800                self.generate_expression(this)?;
25801            }
25802        }
25803
25804        // Output WITH (DELAYED_DURABILITY = ON|OFF) for TSQL
25805        if let Some(durability) = &e.durability {
25806            self.write_space();
25807            self.write_keyword("WITH");
25808            self.write(" (");
25809            self.write_keyword("DELAYED_DURABILITY");
25810            self.write(" = ");
25811            if let Expression::Boolean(BooleanLiteral { value: true }) = durability.as_ref() {
25812                self.write_keyword("ON");
25813            } else {
25814                self.write_keyword("OFF");
25815            }
25816            self.write(")");
25817        }
25818
25819        // Output AND [NO] CHAIN
25820        if let Some(chain) = &e.chain {
25821            self.write_space();
25822            if let Expression::Boolean(BooleanLiteral { value: false }) = chain.as_ref() {
25823                self.write_keyword("AND NO CHAIN");
25824            } else {
25825                self.write_keyword("AND CHAIN");
25826            }
25827        }
25828        Ok(())
25829    }
25830
25831    fn generate_comprehension(&mut self, e: &Comprehension) -> Result<()> {
25832        // Python-style comprehension: [expr FOR var[, pos] IN iterator IF condition]
25833        self.write("[");
25834        self.generate_expression(&e.this)?;
25835        self.write_space();
25836        self.write_keyword("FOR");
25837        self.write_space();
25838        self.generate_expression(&e.expression)?;
25839        // Handle optional position variable (for enumerate-like syntax)
25840        if let Some(pos) = &e.position {
25841            self.write(", ");
25842            self.generate_expression(pos)?;
25843        }
25844        if let Some(iterator) = &e.iterator {
25845            self.write_space();
25846            self.write_keyword("IN");
25847            self.write_space();
25848            self.generate_expression(iterator)?;
25849        }
25850        if let Some(condition) = &e.condition {
25851            self.write_space();
25852            self.write_keyword("IF");
25853            self.write_space();
25854            self.generate_expression(condition)?;
25855        }
25856        self.write("]");
25857        Ok(())
25858    }
25859
25860    fn generate_compress(&mut self, e: &Compress) -> Result<()> {
25861        // COMPRESS(this[, method])
25862        self.write_keyword("COMPRESS");
25863        self.write("(");
25864        self.generate_expression(&e.this)?;
25865        if let Some(method) = &e.method {
25866            self.write(", '");
25867            self.write(method);
25868            self.write("'");
25869        }
25870        self.write(")");
25871        Ok(())
25872    }
25873
25874    fn generate_compress_column_constraint(&mut self, e: &CompressColumnConstraint) -> Result<()> {
25875        // Python: return f"COMPRESS {this}"
25876        self.write_keyword("COMPRESS");
25877        if let Some(this) = &e.this {
25878            self.write_space();
25879            self.generate_expression(this)?;
25880        }
25881        Ok(())
25882    }
25883
25884    fn generate_computed_column_constraint(&mut self, e: &ComputedColumnConstraint) -> Result<()> {
25885        // Python: return f"AS {this}{persisted}"
25886        self.write_keyword("AS");
25887        self.write_space();
25888        self.generate_expression(&e.this)?;
25889        if e.not_null.is_some() {
25890            self.write_space();
25891            self.write_keyword("PERSISTED NOT NULL");
25892        } else if e.persisted.is_some() {
25893            self.write_space();
25894            self.write_keyword("PERSISTED");
25895        }
25896        Ok(())
25897    }
25898
25899    /// Generate a ComputedColumn constraint inline within a column definition.
25900    /// Handles MySQL/PostgreSQL: GENERATED ALWAYS AS (expr) STORED|VIRTUAL
25901    /// Handles TSQL: AS (expr) [PERSISTED] [NOT NULL]
25902    fn generate_computed_column_inline(&mut self, cc: &ComputedColumn) -> Result<()> {
25903        let computed_expr = if matches!(
25904            self.config.dialect,
25905            Some(DialectType::TSQL) | Some(DialectType::Fabric)
25906        ) {
25907            match &*cc.expression {
25908                Expression::Year(y) if !matches!(&y.this, Expression::Cast(c) if matches!(c.to, DataType::Date)) =>
25909                {
25910                    let wrapped = Expression::Cast(Box::new(Cast {
25911                        this: y.this.clone(),
25912                        to: DataType::Date,
25913                        trailing_comments: Vec::new(),
25914                        double_colon_syntax: false,
25915                        format: None,
25916                        default: None,
25917                        inferred_type: None,
25918                    }));
25919                    Expression::Year(Box::new(UnaryFunc::new(wrapped)))
25920                }
25921                Expression::Function(f)
25922                    if f.name.eq_ignore_ascii_case("YEAR")
25923                        && f.args.len() == 1
25924                        && !matches!(&f.args[0], Expression::Cast(c) if matches!(c.to, DataType::Date)) =>
25925                {
25926                    let wrapped = Expression::Cast(Box::new(Cast {
25927                        this: f.args[0].clone(),
25928                        to: DataType::Date,
25929                        trailing_comments: Vec::new(),
25930                        double_colon_syntax: false,
25931                        format: None,
25932                        default: None,
25933                        inferred_type: None,
25934                    }));
25935                    Expression::Function(Box::new(Function::new("YEAR".to_string(), vec![wrapped])))
25936                }
25937                _ => *cc.expression.clone(),
25938            }
25939        } else {
25940            *cc.expression.clone()
25941        };
25942
25943        match cc.persistence_kind.as_deref() {
25944            Some("STORED") | Some("VIRTUAL") => {
25945                // MySQL/PostgreSQL: GENERATED ALWAYS AS (expr) STORED|VIRTUAL
25946                self.write_keyword("GENERATED ALWAYS AS");
25947                self.write(" (");
25948                self.generate_expression(&computed_expr)?;
25949                self.write(")");
25950                self.write_space();
25951                if cc.persisted {
25952                    self.write_keyword("STORED");
25953                } else {
25954                    self.write_keyword("VIRTUAL");
25955                }
25956            }
25957            Some("PERSISTED") => {
25958                // TSQL/SingleStore: AS (expr) PERSISTED [TYPE] [NOT NULL]
25959                self.write_keyword("AS");
25960                self.write(" (");
25961                self.generate_expression(&computed_expr)?;
25962                self.write(")");
25963                self.write_space();
25964                self.write_keyword("PERSISTED");
25965                // Output data type if present (SingleStore: PERSISTED TYPE NOT NULL)
25966                if let Some(ref dt) = cc.data_type {
25967                    self.write_space();
25968                    self.generate_data_type(dt)?;
25969                }
25970                if cc.not_null {
25971                    self.write_space();
25972                    self.write_keyword("NOT NULL");
25973                }
25974            }
25975            _ => {
25976                // Spark/Databricks/Hive: GENERATED ALWAYS AS (expr)
25977                // TSQL computed column without PERSISTED: AS (expr)
25978                if matches!(
25979                    self.config.dialect,
25980                    Some(DialectType::Spark)
25981                        | Some(DialectType::Databricks)
25982                        | Some(DialectType::Hive)
25983                ) {
25984                    self.write_keyword("GENERATED ALWAYS AS");
25985                    self.write(" (");
25986                    self.generate_expression(&computed_expr)?;
25987                    self.write(")");
25988                } else if matches!(
25989                    self.config.dialect,
25990                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
25991                ) {
25992                    self.write_keyword("AS");
25993                    let omit_parens = matches!(computed_expr, Expression::Year(_))
25994                        || matches!(&computed_expr, Expression::Function(f) if f.name.eq_ignore_ascii_case("YEAR"));
25995                    if omit_parens {
25996                        self.write_space();
25997                        self.generate_expression(&computed_expr)?;
25998                    } else {
25999                        self.write(" (");
26000                        self.generate_expression(&computed_expr)?;
26001                        self.write(")");
26002                    }
26003                } else {
26004                    self.write_keyword("AS");
26005                    self.write(" (");
26006                    self.generate_expression(&computed_expr)?;
26007                    self.write(")");
26008                }
26009            }
26010        }
26011        Ok(())
26012    }
26013
26014    /// Generate a GeneratedAsRow constraint inline within a column definition.
26015    /// TSQL temporal: GENERATED ALWAYS AS ROW START|END [HIDDEN]
26016    fn generate_generated_as_row_inline(&mut self, gar: &GeneratedAsRow) -> Result<()> {
26017        self.write_keyword("GENERATED ALWAYS AS ROW ");
26018        if gar.start {
26019            self.write_keyword("START");
26020        } else {
26021            self.write_keyword("END");
26022        }
26023        if gar.hidden {
26024            self.write_space();
26025            self.write_keyword("HIDDEN");
26026        }
26027        Ok(())
26028    }
26029
26030    /// Generate just the SYSTEM_VERSIONING=ON(...) content without WITH() wrapper.
26031    fn generate_system_versioning_content(
26032        &mut self,
26033        e: &WithSystemVersioningProperty,
26034    ) -> Result<()> {
26035        let mut parts = Vec::new();
26036
26037        if let Some(this) = &e.this {
26038            let mut s = String::from("HISTORY_TABLE=");
26039            let mut gen = Generator::with_arc_config(self.config.clone());
26040            gen.generate_expression(this)?;
26041            s.push_str(&gen.output);
26042            parts.push(s);
26043        }
26044
26045        if let Some(data_consistency) = &e.data_consistency {
26046            let mut s = String::from("DATA_CONSISTENCY_CHECK=");
26047            let mut gen = Generator::with_arc_config(self.config.clone());
26048            gen.generate_expression(data_consistency)?;
26049            s.push_str(&gen.output);
26050            parts.push(s);
26051        }
26052
26053        if let Some(retention_period) = &e.retention_period {
26054            let mut s = String::from("HISTORY_RETENTION_PERIOD=");
26055            let mut gen = Generator::with_arc_config(self.config.clone());
26056            gen.generate_expression(retention_period)?;
26057            s.push_str(&gen.output);
26058            parts.push(s);
26059        }
26060
26061        self.write_keyword("SYSTEM_VERSIONING");
26062        self.write("=");
26063
26064        if !parts.is_empty() {
26065            self.write_keyword("ON");
26066            self.write("(");
26067            self.write(&parts.join(", "));
26068            self.write(")");
26069        } else if e.on.is_some() {
26070            self.write_keyword("ON");
26071        } else {
26072            self.write_keyword("OFF");
26073        }
26074
26075        Ok(())
26076    }
26077
26078    fn generate_conditional_insert(&mut self, e: &ConditionalInsert) -> Result<()> {
26079        // Conditional INSERT for multi-table inserts
26080        // Output: [WHEN cond THEN | ELSE] INTO table [(cols)] [VALUES (...)]
26081        if e.else_.is_some() {
26082            self.write_keyword("ELSE");
26083            self.write_space();
26084        } else if let Some(expression) = &e.expression {
26085            self.write_keyword("WHEN");
26086            self.write_space();
26087            self.generate_expression(expression)?;
26088            self.write_space();
26089            self.write_keyword("THEN");
26090            self.write_space();
26091        }
26092
26093        // Handle Insert expression specially - output "INTO table (cols) VALUES (...)"
26094        // without the "INSERT " prefix
26095        if let Expression::Insert(insert) = e.this.as_ref() {
26096            self.write_keyword("INTO");
26097            self.write_space();
26098            self.generate_table(&insert.table)?;
26099
26100            // Optional column list
26101            if !insert.columns.is_empty() {
26102                self.write(" (");
26103                for (i, col) in insert.columns.iter().enumerate() {
26104                    if i > 0 {
26105                        self.write(", ");
26106                    }
26107                    self.generate_identifier(col)?;
26108                }
26109                self.write(")");
26110            }
26111
26112            // Optional VALUES clause
26113            if !insert.values.is_empty() {
26114                self.write_space();
26115                self.write_keyword("VALUES");
26116                for (row_idx, row) in insert.values.iter().enumerate() {
26117                    if row_idx > 0 {
26118                        self.write(", ");
26119                    }
26120                    self.write(" (");
26121                    for (i, val) in row.iter().enumerate() {
26122                        if i > 0 {
26123                            self.write(", ");
26124                        }
26125                        self.generate_expression(val)?;
26126                    }
26127                    self.write(")");
26128                }
26129            }
26130        } else {
26131            // Fallback for non-Insert expressions
26132            self.generate_expression(&e.this)?;
26133        }
26134        Ok(())
26135    }
26136
26137    fn generate_constraint(&mut self, e: &Constraint) -> Result<()> {
26138        // Python: return f"CONSTRAINT {this} {expressions}"
26139        self.write_keyword("CONSTRAINT");
26140        self.write_space();
26141        self.generate_expression(&e.this)?;
26142        if !e.expressions.is_empty() {
26143            self.write_space();
26144            for (i, expr) in e.expressions.iter().enumerate() {
26145                if i > 0 {
26146                    self.write_space();
26147                }
26148                self.generate_expression(expr)?;
26149            }
26150        }
26151        Ok(())
26152    }
26153
26154    fn generate_convert_timezone(&mut self, e: &ConvertTimezone) -> Result<()> {
26155        // CONVERT_TIMEZONE([source_tz,] target_tz, timestamp)
26156        self.write_keyword("CONVERT_TIMEZONE");
26157        self.write("(");
26158        let mut first = true;
26159        if let Some(source_tz) = &e.source_tz {
26160            self.generate_expression(source_tz)?;
26161            first = false;
26162        }
26163        if let Some(target_tz) = &e.target_tz {
26164            if !first {
26165                self.write(", ");
26166            }
26167            self.generate_expression(target_tz)?;
26168            first = false;
26169        }
26170        if let Some(timestamp) = &e.timestamp {
26171            if !first {
26172                self.write(", ");
26173            }
26174            self.generate_expression(timestamp)?;
26175        }
26176        self.write(")");
26177        Ok(())
26178    }
26179
26180    fn generate_convert_to_charset(&mut self, e: &ConvertToCharset) -> Result<()> {
26181        // CONVERT(this USING dest)
26182        self.write_keyword("CONVERT");
26183        self.write("(");
26184        self.generate_expression(&e.this)?;
26185        if let Some(dest) = &e.dest {
26186            self.write_space();
26187            self.write_keyword("USING");
26188            self.write_space();
26189            self.generate_expression(dest)?;
26190        }
26191        self.write(")");
26192        Ok(())
26193    }
26194
26195    fn generate_copy(&mut self, e: &CopyStmt) -> Result<()> {
26196        self.write_keyword("COPY");
26197        if e.is_into {
26198            self.write_space();
26199            self.write_keyword("INTO");
26200        }
26201        self.write_space();
26202
26203        // Generate target table or query (or stage for COPY INTO @stage)
26204        if let Expression::Literal(lit) = &e.this {
26205            if let Literal::String(s) = lit.as_ref() {
26206            if s.starts_with('@') {
26207                self.write(s);
26208            } else {
26209                self.generate_expression(&e.this)?;
26210            }
26211        }
26212        } else {
26213            self.generate_expression(&e.this)?;
26214        }
26215
26216        // FROM or TO based on kind
26217        if e.kind {
26218            // kind=true means FROM (loading into table)
26219            if self.config.pretty {
26220                self.write_newline();
26221            } else {
26222                self.write_space();
26223            }
26224            self.write_keyword("FROM");
26225            self.write_space();
26226        } else if !e.files.is_empty() {
26227            // kind=false means TO (exporting)
26228            if self.config.pretty {
26229                self.write_newline();
26230            } else {
26231                self.write_space();
26232            }
26233            self.write_keyword("TO");
26234            self.write_space();
26235        }
26236
26237        // Generate source/destination files
26238        for (i, file) in e.files.iter().enumerate() {
26239            if i > 0 {
26240                self.write_space();
26241            }
26242            // For stage references (strings starting with @), output without quotes
26243            if let Expression::Literal(lit) = file {
26244                if let Literal::String(s) = lit.as_ref() {
26245                if s.starts_with('@') {
26246                    self.write(s);
26247                } else {
26248                    self.generate_expression(file)?;
26249                }
26250            }
26251            } else if let Expression::Identifier(id) = file {
26252                // Backtick-quoted file path (Databricks style: `s3://link`)
26253                if id.quoted {
26254                    self.write("`");
26255                    self.write(&id.name);
26256                    self.write("`");
26257                } else {
26258                    self.generate_expression(file)?;
26259                }
26260            } else {
26261                self.generate_expression(file)?;
26262            }
26263        }
26264
26265        // Generate credentials if present (Snowflake style - not wrapped in WITH)
26266        if !e.with_wrapped {
26267            if let Some(ref creds) = e.credentials {
26268                if let Some(ref storage) = creds.storage {
26269                    if self.config.pretty {
26270                        self.write_newline();
26271                    } else {
26272                        self.write_space();
26273                    }
26274                    self.write_keyword("STORAGE_INTEGRATION");
26275                    self.write(" = ");
26276                    self.write(storage);
26277                }
26278                if creds.credentials.is_empty() {
26279                    // Empty credentials: CREDENTIALS = ()
26280                    if self.config.pretty {
26281                        self.write_newline();
26282                    } else {
26283                        self.write_space();
26284                    }
26285                    self.write_keyword("CREDENTIALS");
26286                    self.write(" = ()");
26287                } else {
26288                    if self.config.pretty {
26289                        self.write_newline();
26290                    } else {
26291                        self.write_space();
26292                    }
26293                    self.write_keyword("CREDENTIALS");
26294                    // Check if this is Redshift-style (single value with empty key)
26295                    // vs Snowflake-style (multiple key=value pairs)
26296                    if creds.credentials.len() == 1 && creds.credentials[0].0.is_empty() {
26297                        // Redshift style: CREDENTIALS 'value'
26298                        self.write(" '");
26299                        self.write(&creds.credentials[0].1);
26300                        self.write("'");
26301                    } else {
26302                        // Snowflake style: CREDENTIALS = (KEY='value' ...)
26303                        self.write(" = (");
26304                        for (i, (k, v)) in creds.credentials.iter().enumerate() {
26305                            if i > 0 {
26306                                self.write_space();
26307                            }
26308                            self.write(k);
26309                            self.write("='");
26310                            self.write(v);
26311                            self.write("'");
26312                        }
26313                        self.write(")");
26314                    }
26315                }
26316                if let Some(ref encryption) = creds.encryption {
26317                    self.write_space();
26318                    self.write_keyword("ENCRYPTION");
26319                    self.write(" = ");
26320                    self.write(encryption);
26321                }
26322            }
26323        }
26324
26325        // Generate parameters
26326        if !e.params.is_empty() {
26327            if e.with_wrapped {
26328                // DuckDB/PostgreSQL/TSQL WITH (...) format
26329                self.write_space();
26330                self.write_keyword("WITH");
26331                self.write(" (");
26332                for (i, param) in e.params.iter().enumerate() {
26333                    if i > 0 {
26334                        self.write(", ");
26335                    }
26336                    self.generate_copy_param_with_format(param)?;
26337                }
26338                self.write(")");
26339            } else {
26340                // Snowflake/Redshift format: KEY = VALUE or KEY VALUE (space separated, no WITH wrapper)
26341                // For Redshift: IAM_ROLE value, CREDENTIALS 'value', REGION 'value', FORMAT type
26342                // For Snowflake: KEY = VALUE
26343                for param in &e.params {
26344                    if self.config.pretty {
26345                        self.write_newline();
26346                    } else {
26347                        self.write_space();
26348                    }
26349                    // Preserve original case of parameter name (important for Redshift COPY options)
26350                    self.write(&param.name);
26351                    if let Some(ref value) = param.value {
26352                        // Use = only if it was present in the original (param.eq)
26353                        if param.eq {
26354                            self.write(" = ");
26355                        } else {
26356                            self.write(" ");
26357                        }
26358                        if !param.values.is_empty() {
26359                            self.write("(");
26360                            for (i, v) in param.values.iter().enumerate() {
26361                                if i > 0 {
26362                                    self.write_space();
26363                                }
26364                                self.generate_copy_nested_param(v)?;
26365                            }
26366                            self.write(")");
26367                        } else {
26368                            // For COPY parameter values, output identifiers without quoting
26369                            self.generate_copy_param_value(value)?;
26370                        }
26371                    } else if !param.values.is_empty() {
26372                        // For varlen options like FORMAT_OPTIONS, COPY_OPTIONS - no = before (
26373                        if param.eq {
26374                            self.write(" = (");
26375                        } else {
26376                            self.write(" (");
26377                        }
26378                        // Determine separator for values inside parentheses:
26379                        // - Snowflake FILE_FORMAT = (TYPE=CSV FIELD_DELIMITER='|') → space-separated (has = before parens)
26380                        // - Databricks FORMAT_OPTIONS ('opt1'='true', 'opt2'='test') → comma-separated (no = before parens)
26381                        // - Simple value lists like FILES = ('file1', 'file2') → comma-separated
26382                        let is_key_value_pairs = param
26383                            .values
26384                            .first()
26385                            .map_or(false, |v| matches!(v, Expression::Eq(_)));
26386                        let sep = if is_key_value_pairs && param.eq {
26387                            " "
26388                        } else {
26389                            ", "
26390                        };
26391                        for (i, v) in param.values.iter().enumerate() {
26392                            if i > 0 {
26393                                self.write(sep);
26394                            }
26395                            self.generate_copy_nested_param(v)?;
26396                        }
26397                        self.write(")");
26398                    }
26399                }
26400            }
26401        }
26402
26403        Ok(())
26404    }
26405
26406    /// Generate a COPY parameter in WITH (...) format
26407    /// Handles both KEY = VALUE (TSQL) and KEY VALUE (DuckDB/PostgreSQL) formats
26408    fn generate_copy_param_with_format(&mut self, param: &CopyParameter) -> Result<()> {
26409        self.write_keyword(&param.name);
26410        if !param.values.is_empty() {
26411            // Nested values: CREDENTIAL = (IDENTITY='...', SECRET='...')
26412            self.write(" = (");
26413            for (i, v) in param.values.iter().enumerate() {
26414                if i > 0 {
26415                    self.write(", ");
26416                }
26417                self.generate_copy_nested_param(v)?;
26418            }
26419            self.write(")");
26420        } else if let Some(ref value) = param.value {
26421            if param.eq {
26422                self.write(" = ");
26423            } else {
26424                self.write(" ");
26425            }
26426            self.generate_expression(value)?;
26427        }
26428        Ok(())
26429    }
26430
26431    /// Generate nested parameter for COPY statements (KEY=VALUE without spaces)
26432    fn generate_copy_nested_param(&mut self, expr: &Expression) -> Result<()> {
26433        match expr {
26434            Expression::Eq(eq) => {
26435                // Generate key
26436                match &eq.left {
26437                    Expression::Column(c) => self.write(&c.name.name),
26438                    _ => self.generate_expression(&eq.left)?,
26439                }
26440                self.write("=");
26441                // Generate value
26442                match &eq.right {
26443                    Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
26444                        let Literal::String(s) = lit.as_ref() else { unreachable!() };
26445                        self.write("'");
26446                        self.write(s);
26447                        self.write("'");
26448                    }
26449                    Expression::Tuple(t) => {
26450                        // For lists like NULL_IF=('', 'str1')
26451                        self.write("(");
26452                        if self.config.pretty {
26453                            self.write_newline();
26454                            self.indent_level += 1;
26455                            for (i, item) in t.expressions.iter().enumerate() {
26456                                if i > 0 {
26457                                    self.write(", ");
26458                                }
26459                                self.write_indent();
26460                                self.generate_expression(item)?;
26461                            }
26462                            self.write_newline();
26463                            self.indent_level -= 1;
26464                        } else {
26465                            for (i, item) in t.expressions.iter().enumerate() {
26466                                if i > 0 {
26467                                    self.write(", ");
26468                                }
26469                                self.generate_expression(item)?;
26470                            }
26471                        }
26472                        self.write(")");
26473                    }
26474                    _ => self.generate_expression(&eq.right)?,
26475                }
26476                Ok(())
26477            }
26478            Expression::Column(c) => {
26479                // Standalone keyword like COMPRESSION
26480                self.write(&c.name.name);
26481                Ok(())
26482            }
26483            _ => self.generate_expression(expr),
26484        }
26485    }
26486
26487    /// Generate a COPY parameter value, outputting identifiers/columns without quoting
26488    /// This is needed for Redshift-style COPY params like: IAM_ROLE default, FORMAT orc
26489    fn generate_copy_param_value(&mut self, expr: &Expression) -> Result<()> {
26490        match expr {
26491            Expression::Column(c) => {
26492                // Output identifier, preserving quotes if originally quoted
26493                if c.name.quoted {
26494                    self.write("\"");
26495                    self.write(&c.name.name);
26496                    self.write("\"");
26497                } else {
26498                    self.write(&c.name.name);
26499                }
26500                Ok(())
26501            }
26502            Expression::Identifier(id) => {
26503                // Output identifier, preserving quotes if originally quoted
26504                if id.quoted {
26505                    self.write("\"");
26506                    self.write(&id.name);
26507                    self.write("\"");
26508                } else {
26509                    self.write(&id.name);
26510                }
26511                Ok(())
26512            }
26513            Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
26514                let Literal::String(s) = lit.as_ref() else { unreachable!() };
26515                // Output string with quotes
26516                self.write("'");
26517                self.write(s);
26518                self.write("'");
26519                Ok(())
26520            }
26521            _ => self.generate_expression(expr),
26522        }
26523    }
26524
26525    fn generate_copy_parameter(&mut self, e: &CopyParameter) -> Result<()> {
26526        self.write_keyword(&e.name);
26527        if let Some(ref value) = e.value {
26528            if e.eq {
26529                self.write(" = ");
26530            } else {
26531                self.write(" ");
26532            }
26533            self.generate_expression(value)?;
26534        }
26535        if !e.values.is_empty() {
26536            if e.eq {
26537                self.write(" = ");
26538            } else {
26539                self.write(" ");
26540            }
26541            self.write("(");
26542            for (i, v) in e.values.iter().enumerate() {
26543                if i > 0 {
26544                    self.write(", ");
26545                }
26546                self.generate_expression(v)?;
26547            }
26548            self.write(")");
26549        }
26550        Ok(())
26551    }
26552
26553    fn generate_corr(&mut self, e: &Corr) -> Result<()> {
26554        // CORR(this, expression)
26555        self.write_keyword("CORR");
26556        self.write("(");
26557        self.generate_expression(&e.this)?;
26558        self.write(", ");
26559        self.generate_expression(&e.expression)?;
26560        self.write(")");
26561        Ok(())
26562    }
26563
26564    fn generate_cosine_distance(&mut self, e: &CosineDistance) -> Result<()> {
26565        // COSINE_DISTANCE(this, expression)
26566        self.write_keyword("COSINE_DISTANCE");
26567        self.write("(");
26568        self.generate_expression(&e.this)?;
26569        self.write(", ");
26570        self.generate_expression(&e.expression)?;
26571        self.write(")");
26572        Ok(())
26573    }
26574
26575    fn generate_covar_pop(&mut self, e: &CovarPop) -> Result<()> {
26576        // COVAR_POP(this, expression)
26577        self.write_keyword("COVAR_POP");
26578        self.write("(");
26579        self.generate_expression(&e.this)?;
26580        self.write(", ");
26581        self.generate_expression(&e.expression)?;
26582        self.write(")");
26583        Ok(())
26584    }
26585
26586    fn generate_covar_samp(&mut self, e: &CovarSamp) -> Result<()> {
26587        // COVAR_SAMP(this, expression)
26588        self.write_keyword("COVAR_SAMP");
26589        self.write("(");
26590        self.generate_expression(&e.this)?;
26591        self.write(", ");
26592        self.generate_expression(&e.expression)?;
26593        self.write(")");
26594        Ok(())
26595    }
26596
26597    fn generate_credentials(&mut self, e: &Credentials) -> Result<()> {
26598        // CREDENTIALS (key1='value1', key2='value2')
26599        self.write_keyword("CREDENTIALS");
26600        self.write(" (");
26601        for (i, (key, value)) in e.credentials.iter().enumerate() {
26602            if i > 0 {
26603                self.write(", ");
26604            }
26605            self.write(key);
26606            self.write("='");
26607            self.write(value);
26608            self.write("'");
26609        }
26610        self.write(")");
26611        Ok(())
26612    }
26613
26614    fn generate_credentials_property(&mut self, e: &CredentialsProperty) -> Result<()> {
26615        // CREDENTIALS=(expressions)
26616        self.write_keyword("CREDENTIALS");
26617        self.write("=(");
26618        for (i, expr) in e.expressions.iter().enumerate() {
26619            if i > 0 {
26620                self.write(", ");
26621            }
26622            self.generate_expression(expr)?;
26623        }
26624        self.write(")");
26625        Ok(())
26626    }
26627
26628    fn generate_cte(&mut self, e: &Cte) -> Result<()> {
26629        use crate::dialects::DialectType;
26630
26631        // Python: return f"{alias_sql}{key_expressions} AS {materialized or ''}{self.wrap(expression)}"
26632        // Output: alias [(col1, col2, ...)] AS [MATERIALIZED|NOT MATERIALIZED] (subquery)
26633        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) && !e.alias_first {
26634            self.generate_expression(&e.this)?;
26635            self.write_space();
26636            self.write_keyword("AS");
26637            self.write_space();
26638            self.generate_identifier(&e.alias)?;
26639            return Ok(());
26640        }
26641        self.write(&e.alias.name);
26642
26643        // BigQuery doesn't support column aliases in CTE definitions
26644        let skip_cte_columns = matches!(self.config.dialect, Some(DialectType::BigQuery));
26645
26646        if !e.columns.is_empty() && !skip_cte_columns {
26647            self.write("(");
26648            for (i, col) in e.columns.iter().enumerate() {
26649                if i > 0 {
26650                    self.write(", ");
26651                }
26652                self.write(&col.name);
26653            }
26654            self.write(")");
26655        }
26656        // USING KEY (columns) for DuckDB recursive CTEs
26657        if !e.key_expressions.is_empty() {
26658            self.write_space();
26659            self.write_keyword("USING KEY");
26660            self.write(" (");
26661            for (i, key) in e.key_expressions.iter().enumerate() {
26662                if i > 0 {
26663                    self.write(", ");
26664                }
26665                self.write(&key.name);
26666            }
26667            self.write(")");
26668        }
26669        self.write_space();
26670        self.write_keyword("AS");
26671        self.write_space();
26672        if let Some(materialized) = e.materialized {
26673            if materialized {
26674                self.write_keyword("MATERIALIZED");
26675            } else {
26676                self.write_keyword("NOT MATERIALIZED");
26677            }
26678            self.write_space();
26679        }
26680        self.write("(");
26681        self.generate_expression(&e.this)?;
26682        self.write(")");
26683        Ok(())
26684    }
26685
26686    fn generate_cube(&mut self, e: &Cube) -> Result<()> {
26687        // Python: return f"CUBE {self.wrap(expressions)}" if expressions else "WITH CUBE"
26688        if e.expressions.is_empty() {
26689            self.write_keyword("WITH CUBE");
26690        } else {
26691            self.write_keyword("CUBE");
26692            self.write("(");
26693            for (i, expr) in e.expressions.iter().enumerate() {
26694                if i > 0 {
26695                    self.write(", ");
26696                }
26697                self.generate_expression(expr)?;
26698            }
26699            self.write(")");
26700        }
26701        Ok(())
26702    }
26703
26704    fn generate_current_datetime(&mut self, e: &CurrentDatetime) -> Result<()> {
26705        // CURRENT_DATETIME or CURRENT_DATETIME(timezone)
26706        self.write_keyword("CURRENT_DATETIME");
26707        if let Some(this) = &e.this {
26708            self.write("(");
26709            self.generate_expression(this)?;
26710            self.write(")");
26711        }
26712        Ok(())
26713    }
26714
26715    fn generate_current_schema(&mut self, _e: &CurrentSchema) -> Result<()> {
26716        // CURRENT_SCHEMA - no arguments
26717        self.write_keyword("CURRENT_SCHEMA");
26718        Ok(())
26719    }
26720
26721    fn generate_current_schemas(&mut self, e: &CurrentSchemas) -> Result<()> {
26722        // CURRENT_SCHEMAS(include_implicit)
26723        self.write_keyword("CURRENT_SCHEMAS");
26724        self.write("(");
26725        // Snowflake: drop the argument (CURRENT_SCHEMAS() takes no args)
26726        if !matches!(
26727            self.config.dialect,
26728            Some(crate::dialects::DialectType::Snowflake)
26729        ) {
26730            if let Some(this) = &e.this {
26731                self.generate_expression(this)?;
26732            }
26733        }
26734        self.write(")");
26735        Ok(())
26736    }
26737
26738    fn generate_current_user(&mut self, e: &CurrentUser) -> Result<()> {
26739        // CURRENT_USER or CURRENT_USER()
26740        self.write_keyword("CURRENT_USER");
26741        // Some dialects always need parens: Snowflake, Spark, Hive, DuckDB, BigQuery, MySQL, Databricks
26742        let needs_parens = e.this.is_some()
26743            || matches!(
26744                self.config.dialect,
26745                Some(DialectType::Snowflake)
26746                    | Some(DialectType::Spark)
26747                    | Some(DialectType::Hive)
26748                    | Some(DialectType::DuckDB)
26749                    | Some(DialectType::BigQuery)
26750                    | Some(DialectType::MySQL)
26751                    | Some(DialectType::Databricks)
26752            );
26753        if needs_parens {
26754            self.write("()");
26755        }
26756        Ok(())
26757    }
26758
26759    fn generate_d_pipe(&mut self, e: &DPipe) -> Result<()> {
26760        // In Solr, || is OR, not string concatenation (DPIPE_IS_STRING_CONCAT = False)
26761        if self.config.dialect == Some(DialectType::Solr) {
26762            self.generate_expression(&e.this)?;
26763            self.write(" ");
26764            self.write_keyword("OR");
26765            self.write(" ");
26766            self.generate_expression(&e.expression)?;
26767        } else if self.config.dialect == Some(DialectType::MySQL) {
26768            self.generate_mysql_concat_from_dpipe(e)?;
26769        } else {
26770            // String concatenation: this || expression
26771            self.generate_expression(&e.this)?;
26772            self.write(" || ");
26773            self.generate_expression(&e.expression)?;
26774        }
26775        Ok(())
26776    }
26777
26778    fn generate_data_blocksize_property(&mut self, e: &DataBlocksizeProperty) -> Result<()> {
26779        // DATABLOCKSIZE=... (Teradata)
26780        self.write_keyword("DATABLOCKSIZE");
26781        self.write("=");
26782        if let Some(size) = e.size {
26783            self.write(&size.to_string());
26784            if let Some(units) = &e.units {
26785                self.write_space();
26786                self.generate_expression(units)?;
26787            }
26788        } else if e.minimum.is_some() {
26789            self.write_keyword("MINIMUM");
26790        } else if e.maximum.is_some() {
26791            self.write_keyword("MAXIMUM");
26792        } else if e.default.is_some() {
26793            self.write_keyword("DEFAULT");
26794        }
26795        Ok(())
26796    }
26797
26798    fn generate_data_deletion_property(&mut self, e: &DataDeletionProperty) -> Result<()> {
26799        // DATA_DELETION=ON or DATA_DELETION=OFF or DATA_DELETION=ON(FILTER_COLUMN=col, RETENTION_PERIOD=...)
26800        self.write_keyword("DATA_DELETION");
26801        self.write("=");
26802
26803        let is_on = matches!(&*e.on, Expression::Boolean(BooleanLiteral { value: true }));
26804        let has_options = e.filter_column.is_some() || e.retention_period.is_some();
26805
26806        if is_on {
26807            self.write_keyword("ON");
26808            if has_options {
26809                self.write("(");
26810                let mut first = true;
26811                if let Some(filter_column) = &e.filter_column {
26812                    self.write_keyword("FILTER_COLUMN");
26813                    self.write("=");
26814                    self.generate_expression(filter_column)?;
26815                    first = false;
26816                }
26817                if let Some(retention_period) = &e.retention_period {
26818                    if !first {
26819                        self.write(", ");
26820                    }
26821                    self.write_keyword("RETENTION_PERIOD");
26822                    self.write("=");
26823                    self.generate_expression(retention_period)?;
26824                }
26825                self.write(")");
26826            }
26827        } else {
26828            self.write_keyword("OFF");
26829        }
26830        Ok(())
26831    }
26832
26833    /// Generate a Date function expression
26834    /// For Exasol: {d'value'} -> TO_DATE('value')
26835    /// For other dialects: DATE('value')
26836    fn generate_date_func(&mut self, e: &UnaryFunc) -> Result<()> {
26837        use crate::dialects::DialectType;
26838        use crate::expressions::Literal;
26839
26840        match self.config.dialect {
26841            // Exasol uses TO_DATE for Date expressions
26842            Some(DialectType::Exasol) => {
26843                self.write_keyword("TO_DATE");
26844                self.write("(");
26845                // Extract the string value from the expression if it's a string literal
26846                match &e.this {
26847                    Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
26848                        let Literal::String(s) = lit.as_ref() else { unreachable!() };
26849                        self.write("'");
26850                        self.write(s);
26851                        self.write("'");
26852                    }
26853                    _ => {
26854                        self.generate_expression(&e.this)?;
26855                    }
26856                }
26857                self.write(")");
26858            }
26859            // Standard: DATE(value)
26860            _ => {
26861                self.write_keyword("DATE");
26862                self.write("(");
26863                self.generate_expression(&e.this)?;
26864                self.write(")");
26865            }
26866        }
26867        Ok(())
26868    }
26869
26870    fn generate_date_bin(&mut self, e: &DateBin) -> Result<()> {
26871        // DATE_BIN(interval, timestamp[, origin])
26872        self.write_keyword("DATE_BIN");
26873        self.write("(");
26874        self.generate_expression(&e.this)?;
26875        self.write(", ");
26876        self.generate_expression(&e.expression)?;
26877        if let Some(origin) = &e.origin {
26878            self.write(", ");
26879            self.generate_expression(origin)?;
26880        }
26881        self.write(")");
26882        Ok(())
26883    }
26884
26885    fn generate_date_format_column_constraint(
26886        &mut self,
26887        e: &DateFormatColumnConstraint,
26888    ) -> Result<()> {
26889        // FORMAT 'format_string' (Teradata)
26890        self.write_keyword("FORMAT");
26891        self.write_space();
26892        self.generate_expression(&e.this)?;
26893        Ok(())
26894    }
26895
26896    fn generate_date_from_parts(&mut self, e: &DateFromParts) -> Result<()> {
26897        // DATE_FROM_PARTS(year, month, day) or DATEFROMPARTS(year, month, day)
26898        self.write_keyword("DATE_FROM_PARTS");
26899        self.write("(");
26900        let mut first = true;
26901        if let Some(year) = &e.year {
26902            self.generate_expression(year)?;
26903            first = false;
26904        }
26905        if let Some(month) = &e.month {
26906            if !first {
26907                self.write(", ");
26908            }
26909            self.generate_expression(month)?;
26910            first = false;
26911        }
26912        if let Some(day) = &e.day {
26913            if !first {
26914                self.write(", ");
26915            }
26916            self.generate_expression(day)?;
26917        }
26918        self.write(")");
26919        Ok(())
26920    }
26921
26922    fn generate_datetime(&mut self, e: &Datetime) -> Result<()> {
26923        // DATETIME(this) or DATETIME(this, expression)
26924        self.write_keyword("DATETIME");
26925        self.write("(");
26926        self.generate_expression(&e.this)?;
26927        if let Some(expr) = &e.expression {
26928            self.write(", ");
26929            self.generate_expression(expr)?;
26930        }
26931        self.write(")");
26932        Ok(())
26933    }
26934
26935    fn generate_datetime_add(&mut self, e: &DatetimeAdd) -> Result<()> {
26936        // DATETIME_ADD(this, expression, unit)
26937        self.write_keyword("DATETIME_ADD");
26938        self.write("(");
26939        self.generate_expression(&e.this)?;
26940        self.write(", ");
26941        self.generate_expression(&e.expression)?;
26942        if let Some(unit) = &e.unit {
26943            self.write(", ");
26944            self.write_keyword(unit);
26945        }
26946        self.write(")");
26947        Ok(())
26948    }
26949
26950    fn generate_datetime_diff(&mut self, e: &DatetimeDiff) -> Result<()> {
26951        // DATETIME_DIFF(this, expression, unit)
26952        self.write_keyword("DATETIME_DIFF");
26953        self.write("(");
26954        self.generate_expression(&e.this)?;
26955        self.write(", ");
26956        self.generate_expression(&e.expression)?;
26957        if let Some(unit) = &e.unit {
26958            self.write(", ");
26959            self.write_keyword(unit);
26960        }
26961        self.write(")");
26962        Ok(())
26963    }
26964
26965    fn generate_datetime_sub(&mut self, e: &DatetimeSub) -> Result<()> {
26966        // DATETIME_SUB(this, expression, unit)
26967        self.write_keyword("DATETIME_SUB");
26968        self.write("(");
26969        self.generate_expression(&e.this)?;
26970        self.write(", ");
26971        self.generate_expression(&e.expression)?;
26972        if let Some(unit) = &e.unit {
26973            self.write(", ");
26974            self.write_keyword(unit);
26975        }
26976        self.write(")");
26977        Ok(())
26978    }
26979
26980    fn generate_datetime_trunc(&mut self, e: &DatetimeTrunc) -> Result<()> {
26981        // DATETIME_TRUNC(this, unit, zone)
26982        self.write_keyword("DATETIME_TRUNC");
26983        self.write("(");
26984        self.generate_expression(&e.this)?;
26985        self.write(", ");
26986        self.write_keyword(&e.unit);
26987        if let Some(zone) = &e.zone {
26988            self.write(", ");
26989            self.generate_expression(zone)?;
26990        }
26991        self.write(")");
26992        Ok(())
26993    }
26994
26995    fn generate_dayname(&mut self, e: &Dayname) -> Result<()> {
26996        // DAYNAME(this)
26997        self.write_keyword("DAYNAME");
26998        self.write("(");
26999        self.generate_expression(&e.this)?;
27000        self.write(")");
27001        Ok(())
27002    }
27003
27004    fn generate_declare(&mut self, e: &Declare) -> Result<()> {
27005        // DECLARE [OR REPLACE] var1 AS type1, var2 AS type2, ...
27006        self.write_keyword("DECLARE");
27007        self.write_space();
27008        if e.replace {
27009            self.write_keyword("OR");
27010            self.write_space();
27011            self.write_keyword("REPLACE");
27012            self.write_space();
27013        }
27014        for (i, expr) in e.expressions.iter().enumerate() {
27015            if i > 0 {
27016                self.write(", ");
27017            }
27018            self.generate_expression(expr)?;
27019        }
27020        Ok(())
27021    }
27022
27023    fn generate_declare_item(&mut self, e: &DeclareItem) -> Result<()> {
27024        use crate::dialects::DialectType;
27025
27026        // variable TYPE [DEFAULT default]
27027        self.generate_expression(&e.this)?;
27028        // BigQuery multi-variable: DECLARE X, Y, Z INT64
27029        for name in &e.additional_names {
27030            self.write(", ");
27031            self.generate_expression(name)?;
27032        }
27033        if let Some(kind) = &e.kind {
27034            self.write_space();
27035            // BigQuery uses: DECLARE x INT64 DEFAULT value (no AS)
27036            // TSQL: Always includes AS (normalization)
27037            // Others: Include AS if present in original
27038            match self.config.dialect {
27039                Some(DialectType::BigQuery) => {
27040                    self.write(kind);
27041                }
27042                Some(DialectType::TSQL) => {
27043                    // TSQL DECLARE: no AS keyword (sqlglot convention)
27044                    // Normalize INT to INTEGER for simple declarations
27045                    // Complex TABLE declarations (with CLUSTERED/INDEX) are preserved as-is
27046                    let is_complex_table = kind.starts_with("TABLE")
27047                        && (kind.contains("CLUSTERED") || kind.contains("INDEX"));
27048                    if is_complex_table {
27049                        self.write(kind);
27050                    } else if kind == "INT" {
27051                        self.write("INTEGER");
27052                    } else if kind.starts_with("TABLE") {
27053                        // Normalize INT to INTEGER inside simple TABLE column definitions
27054                        let normalized = kind
27055                            .replace(" INT ", " INTEGER ")
27056                            .replace(" INT,", " INTEGER,")
27057                            .replace(" INT)", " INTEGER)")
27058                            .replace("(INT ", "(INTEGER ");
27059                        self.write(&normalized);
27060                    } else {
27061                        self.write(kind);
27062                    }
27063                }
27064                _ => {
27065                    if e.has_as {
27066                        self.write_keyword("AS");
27067                        self.write_space();
27068                    }
27069                    self.write(kind);
27070                }
27071            }
27072        }
27073        if let Some(default) = &e.default {
27074            // BigQuery uses DEFAULT, others use =
27075            match self.config.dialect {
27076                Some(DialectType::BigQuery) => {
27077                    self.write_space();
27078                    self.write_keyword("DEFAULT");
27079                    self.write_space();
27080                }
27081                _ => {
27082                    self.write(" = ");
27083                }
27084            }
27085            self.generate_expression(default)?;
27086        }
27087        Ok(())
27088    }
27089
27090    fn generate_decode_case(&mut self, e: &DecodeCase) -> Result<()> {
27091        // DECODE(expr, search1, result1, search2, result2, ..., default)
27092        self.write_keyword("DECODE");
27093        self.write("(");
27094        for (i, expr) in e.expressions.iter().enumerate() {
27095            if i > 0 {
27096                self.write(", ");
27097            }
27098            self.generate_expression(expr)?;
27099        }
27100        self.write(")");
27101        Ok(())
27102    }
27103
27104    fn generate_decompress_binary(&mut self, e: &DecompressBinary) -> Result<()> {
27105        // DECOMPRESS(expr, 'method')
27106        self.write_keyword("DECOMPRESS");
27107        self.write("(");
27108        self.generate_expression(&e.this)?;
27109        self.write(", '");
27110        self.write(&e.method);
27111        self.write("')");
27112        Ok(())
27113    }
27114
27115    fn generate_decompress_string(&mut self, e: &DecompressString) -> Result<()> {
27116        // DECOMPRESS(expr, 'method')
27117        self.write_keyword("DECOMPRESS");
27118        self.write("(");
27119        self.generate_expression(&e.this)?;
27120        self.write(", '");
27121        self.write(&e.method);
27122        self.write("')");
27123        Ok(())
27124    }
27125
27126    fn generate_decrypt(&mut self, e: &Decrypt) -> Result<()> {
27127        // DECRYPT(value, passphrase [, aad [, algorithm]])
27128        self.write_keyword("DECRYPT");
27129        self.write("(");
27130        self.generate_expression(&e.this)?;
27131        if let Some(passphrase) = &e.passphrase {
27132            self.write(", ");
27133            self.generate_expression(passphrase)?;
27134        }
27135        if let Some(aad) = &e.aad {
27136            self.write(", ");
27137            self.generate_expression(aad)?;
27138        }
27139        if let Some(method) = &e.encryption_method {
27140            self.write(", ");
27141            self.generate_expression(method)?;
27142        }
27143        self.write(")");
27144        Ok(())
27145    }
27146
27147    fn generate_decrypt_raw(&mut self, e: &DecryptRaw) -> Result<()> {
27148        // DECRYPT_RAW(value, key [, iv [, aad [, algorithm]]])
27149        self.write_keyword("DECRYPT_RAW");
27150        self.write("(");
27151        self.generate_expression(&e.this)?;
27152        if let Some(key) = &e.key {
27153            self.write(", ");
27154            self.generate_expression(key)?;
27155        }
27156        if let Some(iv) = &e.iv {
27157            self.write(", ");
27158            self.generate_expression(iv)?;
27159        }
27160        if let Some(aad) = &e.aad {
27161            self.write(", ");
27162            self.generate_expression(aad)?;
27163        }
27164        if let Some(method) = &e.encryption_method {
27165            self.write(", ");
27166            self.generate_expression(method)?;
27167        }
27168        self.write(")");
27169        Ok(())
27170    }
27171
27172    fn generate_definer_property(&mut self, e: &DefinerProperty) -> Result<()> {
27173        // DEFINER = user
27174        self.write_keyword("DEFINER");
27175        self.write(" = ");
27176        self.generate_expression(&e.this)?;
27177        Ok(())
27178    }
27179
27180    fn generate_detach(&mut self, e: &Detach) -> Result<()> {
27181        // Python: DETACH[DATABASE IF EXISTS] this
27182        self.write_keyword("DETACH");
27183        if e.exists {
27184            self.write_keyword(" DATABASE IF EXISTS");
27185        }
27186        self.write_space();
27187        self.generate_expression(&e.this)?;
27188        Ok(())
27189    }
27190
27191    fn generate_dict_property(&mut self, e: &DictProperty) -> Result<()> {
27192        let property_name = match e.this.as_ref() {
27193            Expression::Identifier(id) => id.name.as_str(),
27194            Expression::Var(v) => v.this.as_str(),
27195            _ => "DICTIONARY",
27196        };
27197        self.write_keyword(property_name);
27198        self.write("(");
27199        self.write(&e.kind);
27200        if let Some(settings) = &e.settings {
27201            self.write("(");
27202            if let Expression::Tuple(t) = settings.as_ref() {
27203                if self.config.pretty && !t.expressions.is_empty() {
27204                    self.write_newline();
27205                    self.indent_level += 1;
27206                    for (i, pair) in t.expressions.iter().enumerate() {
27207                        if i > 0 {
27208                            self.write(",");
27209                            self.write_newline();
27210                        }
27211                        self.write_indent();
27212                        if let Expression::Tuple(pair_tuple) = pair {
27213                            if let Some(k) = pair_tuple.expressions.first() {
27214                                self.generate_expression(k)?;
27215                            }
27216                            if let Some(v) = pair_tuple.expressions.get(1) {
27217                                self.write(" ");
27218                                self.generate_expression(v)?;
27219                            }
27220                        } else {
27221                            self.generate_expression(pair)?;
27222                        }
27223                    }
27224                    self.indent_level -= 1;
27225                    self.write_newline();
27226                    self.write_indent();
27227                } else {
27228                    for (i, pair) in t.expressions.iter().enumerate() {
27229                        if i > 0 {
27230                            // ClickHouse dict properties are space-separated, not comma-separated
27231                            self.write(" ");
27232                        }
27233                        if let Expression::Tuple(pair_tuple) = pair {
27234                            if let Some(k) = pair_tuple.expressions.first() {
27235                                self.generate_expression(k)?;
27236                            }
27237                            if let Some(v) = pair_tuple.expressions.get(1) {
27238                                self.write(" ");
27239                                self.generate_expression(v)?;
27240                            }
27241                        } else {
27242                            self.generate_expression(pair)?;
27243                        }
27244                    }
27245                }
27246            } else {
27247                self.generate_expression(settings)?;
27248            }
27249            self.write(")");
27250        } else {
27251            // No settings but kind had parens (e.g., SOURCE(NULL()), LAYOUT(FLAT()))
27252            self.write("()");
27253        }
27254        self.write(")");
27255        Ok(())
27256    }
27257
27258    fn generate_dict_range(&mut self, e: &DictRange) -> Result<()> {
27259        let property_name = match e.this.as_ref() {
27260            Expression::Identifier(id) => id.name.as_str(),
27261            Expression::Var(v) => v.this.as_str(),
27262            _ => "RANGE",
27263        };
27264        self.write_keyword(property_name);
27265        self.write("(");
27266        if let Some(min) = &e.min {
27267            self.write_keyword("MIN");
27268            self.write_space();
27269            self.generate_expression(min)?;
27270        }
27271        if let Some(max) = &e.max {
27272            self.write_space();
27273            self.write_keyword("MAX");
27274            self.write_space();
27275            self.generate_expression(max)?;
27276        }
27277        self.write(")");
27278        Ok(())
27279    }
27280
27281    fn generate_directory(&mut self, e: &Directory) -> Result<()> {
27282        // Python: {local}DIRECTORY {this}{row_format}
27283        if e.local.is_some() {
27284            self.write_keyword("LOCAL ");
27285        }
27286        self.write_keyword("DIRECTORY");
27287        self.write_space();
27288        self.generate_expression(&e.this)?;
27289        if let Some(row_format) = &e.row_format {
27290            self.write_space();
27291            self.generate_expression(row_format)?;
27292        }
27293        Ok(())
27294    }
27295
27296    fn generate_dist_key_property(&mut self, e: &DistKeyProperty) -> Result<()> {
27297        // Redshift: DISTKEY(column)
27298        self.write_keyword("DISTKEY");
27299        self.write("(");
27300        self.generate_expression(&e.this)?;
27301        self.write(")");
27302        Ok(())
27303    }
27304
27305    fn generate_dist_style_property(&mut self, e: &DistStyleProperty) -> Result<()> {
27306        // Redshift: DISTSTYLE KEY|ALL|EVEN|AUTO
27307        self.write_keyword("DISTSTYLE");
27308        self.write_space();
27309        self.generate_expression(&e.this)?;
27310        Ok(())
27311    }
27312
27313    fn generate_distribute_by(&mut self, e: &DistributeBy) -> Result<()> {
27314        // Python: "DISTRIBUTE BY" expressions
27315        self.write_keyword("DISTRIBUTE BY");
27316        self.write_space();
27317        for (i, expr) in e.expressions.iter().enumerate() {
27318            if i > 0 {
27319                self.write(", ");
27320            }
27321            self.generate_expression(expr)?;
27322        }
27323        Ok(())
27324    }
27325
27326    fn generate_distributed_by_property(&mut self, e: &DistributedByProperty) -> Result<()> {
27327        // Python: DISTRIBUTED BY kind (expressions) BUCKETS buckets order
27328        self.write_keyword("DISTRIBUTED BY");
27329        self.write_space();
27330        self.write(&e.kind);
27331        if !e.expressions.is_empty() {
27332            self.write(" (");
27333            for (i, expr) in e.expressions.iter().enumerate() {
27334                if i > 0 {
27335                    self.write(", ");
27336                }
27337                self.generate_expression(expr)?;
27338            }
27339            self.write(")");
27340        }
27341        if let Some(buckets) = &e.buckets {
27342            self.write_space();
27343            self.write_keyword("BUCKETS");
27344            self.write_space();
27345            self.generate_expression(buckets)?;
27346        }
27347        if let Some(order) = &e.order {
27348            self.write_space();
27349            self.generate_expression(order)?;
27350        }
27351        Ok(())
27352    }
27353
27354    fn generate_dot_product(&mut self, e: &DotProduct) -> Result<()> {
27355        // DOT_PRODUCT(vector1, vector2)
27356        self.write_keyword("DOT_PRODUCT");
27357        self.write("(");
27358        self.generate_expression(&e.this)?;
27359        self.write(", ");
27360        self.generate_expression(&e.expression)?;
27361        self.write(")");
27362        Ok(())
27363    }
27364
27365    fn generate_drop_partition(&mut self, e: &DropPartition) -> Result<()> {
27366        // Python: DROP{IF EXISTS }expressions
27367        self.write_keyword("DROP");
27368        if e.exists {
27369            self.write_keyword(" IF EXISTS ");
27370        } else {
27371            self.write_space();
27372        }
27373        for (i, expr) in e.expressions.iter().enumerate() {
27374            if i > 0 {
27375                self.write(", ");
27376            }
27377            self.generate_expression(expr)?;
27378        }
27379        Ok(())
27380    }
27381
27382    fn generate_duplicate_key_property(&mut self, e: &DuplicateKeyProperty) -> Result<()> {
27383        // Python: DUPLICATE KEY (expressions)
27384        self.write_keyword("DUPLICATE KEY");
27385        self.write(" (");
27386        for (i, expr) in e.expressions.iter().enumerate() {
27387            if i > 0 {
27388                self.write(", ");
27389            }
27390            self.generate_expression(expr)?;
27391        }
27392        self.write(")");
27393        Ok(())
27394    }
27395
27396    fn generate_elt(&mut self, e: &Elt) -> Result<()> {
27397        // ELT(index, str1, str2, ...)
27398        self.write_keyword("ELT");
27399        self.write("(");
27400        self.generate_expression(&e.this)?;
27401        for expr in &e.expressions {
27402            self.write(", ");
27403            self.generate_expression(expr)?;
27404        }
27405        self.write(")");
27406        Ok(())
27407    }
27408
27409    fn generate_encode(&mut self, e: &Encode) -> Result<()> {
27410        // ENCODE(string, charset)
27411        self.write_keyword("ENCODE");
27412        self.write("(");
27413        self.generate_expression(&e.this)?;
27414        if let Some(charset) = &e.charset {
27415            self.write(", ");
27416            self.generate_expression(charset)?;
27417        }
27418        self.write(")");
27419        Ok(())
27420    }
27421
27422    fn generate_encode_property(&mut self, e: &EncodeProperty) -> Result<()> {
27423        // Python: [KEY ]ENCODE this [properties]
27424        if e.key.is_some() {
27425            self.write_keyword("KEY ");
27426        }
27427        self.write_keyword("ENCODE");
27428        self.write_space();
27429        self.generate_expression(&e.this)?;
27430        if !e.properties.is_empty() {
27431            self.write(" (");
27432            for (i, prop) in e.properties.iter().enumerate() {
27433                if i > 0 {
27434                    self.write(", ");
27435                }
27436                self.generate_expression(prop)?;
27437            }
27438            self.write(")");
27439        }
27440        Ok(())
27441    }
27442
27443    fn generate_encrypt(&mut self, e: &Encrypt) -> Result<()> {
27444        // ENCRYPT(value, passphrase [, aad [, algorithm]])
27445        self.write_keyword("ENCRYPT");
27446        self.write("(");
27447        self.generate_expression(&e.this)?;
27448        if let Some(passphrase) = &e.passphrase {
27449            self.write(", ");
27450            self.generate_expression(passphrase)?;
27451        }
27452        if let Some(aad) = &e.aad {
27453            self.write(", ");
27454            self.generate_expression(aad)?;
27455        }
27456        if let Some(method) = &e.encryption_method {
27457            self.write(", ");
27458            self.generate_expression(method)?;
27459        }
27460        self.write(")");
27461        Ok(())
27462    }
27463
27464    fn generate_encrypt_raw(&mut self, e: &EncryptRaw) -> Result<()> {
27465        // ENCRYPT_RAW(value, key [, iv [, aad [, algorithm]]])
27466        self.write_keyword("ENCRYPT_RAW");
27467        self.write("(");
27468        self.generate_expression(&e.this)?;
27469        if let Some(key) = &e.key {
27470            self.write(", ");
27471            self.generate_expression(key)?;
27472        }
27473        if let Some(iv) = &e.iv {
27474            self.write(", ");
27475            self.generate_expression(iv)?;
27476        }
27477        if let Some(aad) = &e.aad {
27478            self.write(", ");
27479            self.generate_expression(aad)?;
27480        }
27481        if let Some(method) = &e.encryption_method {
27482            self.write(", ");
27483            self.generate_expression(method)?;
27484        }
27485        self.write(")");
27486        Ok(())
27487    }
27488
27489    fn generate_engine_property(&mut self, e: &EngineProperty) -> Result<()> {
27490        // MySQL: ENGINE = InnoDB
27491        self.write_keyword("ENGINE");
27492        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
27493            self.write("=");
27494        } else {
27495            self.write(" = ");
27496        }
27497        self.generate_expression(&e.this)?;
27498        Ok(())
27499    }
27500
27501    fn generate_enviroment_property(&mut self, e: &EnviromentProperty) -> Result<()> {
27502        // ENVIRONMENT (expressions)
27503        self.write_keyword("ENVIRONMENT");
27504        self.write(" (");
27505        for (i, expr) in e.expressions.iter().enumerate() {
27506            if i > 0 {
27507                self.write(", ");
27508            }
27509            self.generate_expression(expr)?;
27510        }
27511        self.write(")");
27512        Ok(())
27513    }
27514
27515    fn generate_ephemeral_column_constraint(
27516        &mut self,
27517        e: &EphemeralColumnConstraint,
27518    ) -> Result<()> {
27519        // MySQL: EPHEMERAL [expr]
27520        self.write_keyword("EPHEMERAL");
27521        if let Some(this) = &e.this {
27522            self.write_space();
27523            self.generate_expression(this)?;
27524        }
27525        Ok(())
27526    }
27527
27528    fn generate_equal_null(&mut self, e: &EqualNull) -> Result<()> {
27529        // Snowflake: EQUAL_NULL(a, b)
27530        self.write_keyword("EQUAL_NULL");
27531        self.write("(");
27532        self.generate_expression(&e.this)?;
27533        self.write(", ");
27534        self.generate_expression(&e.expression)?;
27535        self.write(")");
27536        Ok(())
27537    }
27538
27539    fn generate_euclidean_distance(&mut self, e: &EuclideanDistance) -> Result<()> {
27540        use crate::dialects::DialectType;
27541
27542        // PostgreSQL uses <-> operator syntax
27543        match self.config.dialect {
27544            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift) => {
27545                self.generate_expression(&e.this)?;
27546                self.write(" <-> ");
27547                self.generate_expression(&e.expression)?;
27548            }
27549            _ => {
27550                // Other dialects use EUCLIDEAN_DISTANCE function
27551                self.write_keyword("EUCLIDEAN_DISTANCE");
27552                self.write("(");
27553                self.generate_expression(&e.this)?;
27554                self.write(", ");
27555                self.generate_expression(&e.expression)?;
27556                self.write(")");
27557            }
27558        }
27559        Ok(())
27560    }
27561
27562    fn generate_execute_as_property(&mut self, e: &ExecuteAsProperty) -> Result<()> {
27563        // EXECUTE AS CALLER|OWNER|user
27564        self.write_keyword("EXECUTE AS");
27565        self.write_space();
27566        self.generate_expression(&e.this)?;
27567        Ok(())
27568    }
27569
27570    fn generate_export(&mut self, e: &Export) -> Result<()> {
27571        // BigQuery: EXPORT DATA [WITH CONNECTION connection] OPTIONS (...) AS query
27572        self.write_keyword("EXPORT DATA");
27573        if let Some(connection) = &e.connection {
27574            self.write_space();
27575            self.write_keyword("WITH CONNECTION");
27576            self.write_space();
27577            self.generate_expression(connection)?;
27578        }
27579        if !e.options.is_empty() {
27580            self.write_space();
27581            self.generate_options_clause(&e.options)?;
27582        }
27583        self.write_space();
27584        self.write_keyword("AS");
27585        self.write_space();
27586        self.generate_expression(&e.this)?;
27587        Ok(())
27588    }
27589
27590    fn generate_external_property(&mut self, e: &ExternalProperty) -> Result<()> {
27591        // EXTERNAL [this]
27592        self.write_keyword("EXTERNAL");
27593        if let Some(this) = &e.this {
27594            self.write_space();
27595            self.generate_expression(this)?;
27596        }
27597        Ok(())
27598    }
27599
27600    fn generate_fallback_property(&mut self, e: &FallbackProperty) -> Result<()> {
27601        // Python: {no}FALLBACK{protection}
27602        if e.no.is_some() {
27603            self.write_keyword("NO ");
27604        }
27605        self.write_keyword("FALLBACK");
27606        if e.protection.is_some() {
27607            self.write_keyword(" PROTECTION");
27608        }
27609        Ok(())
27610    }
27611
27612    fn generate_farm_fingerprint(&mut self, e: &FarmFingerprint) -> Result<()> {
27613        // BigQuery: FARM_FINGERPRINT(value)
27614        self.write_keyword("FARM_FINGERPRINT");
27615        self.write("(");
27616        for (i, expr) in e.expressions.iter().enumerate() {
27617            if i > 0 {
27618                self.write(", ");
27619            }
27620            self.generate_expression(expr)?;
27621        }
27622        self.write(")");
27623        Ok(())
27624    }
27625
27626    fn generate_features_at_time(&mut self, e: &FeaturesAtTime) -> Result<()> {
27627        // BigQuery ML: FEATURES_AT_TIME(feature_view, time, [num_rows], [ignore_feature_nulls])
27628        self.write_keyword("FEATURES_AT_TIME");
27629        self.write("(");
27630        self.generate_expression(&e.this)?;
27631        if let Some(time) = &e.time {
27632            self.write(", ");
27633            self.generate_expression(time)?;
27634        }
27635        if let Some(num_rows) = &e.num_rows {
27636            self.write(", ");
27637            self.generate_expression(num_rows)?;
27638        }
27639        if let Some(ignore_nulls) = &e.ignore_feature_nulls {
27640            self.write(", ");
27641            self.generate_expression(ignore_nulls)?;
27642        }
27643        self.write(")");
27644        Ok(())
27645    }
27646
27647    fn generate_fetch(&mut self, e: &Fetch) -> Result<()> {
27648        // For dialects that prefer LIMIT, convert simple FETCH to LIMIT
27649        let use_limit = !e.percent
27650            && !e.with_ties
27651            && e.count.is_some()
27652            && matches!(
27653                self.config.dialect,
27654                Some(DialectType::Spark)
27655                    | Some(DialectType::Hive)
27656                    | Some(DialectType::DuckDB)
27657                    | Some(DialectType::SQLite)
27658                    | Some(DialectType::MySQL)
27659                    | Some(DialectType::BigQuery)
27660                    | Some(DialectType::Databricks)
27661                    | Some(DialectType::StarRocks)
27662                    | Some(DialectType::Doris)
27663                    | Some(DialectType::Athena)
27664                    | Some(DialectType::ClickHouse)
27665            );
27666
27667        if use_limit {
27668            self.write_keyword("LIMIT");
27669            self.write_space();
27670            self.generate_expression(e.count.as_ref().unwrap())?;
27671            return Ok(());
27672        }
27673
27674        // Python: FETCH direction count limit_options
27675        self.write_keyword("FETCH");
27676        if !e.direction.is_empty() {
27677            self.write_space();
27678            self.write_keyword(&e.direction);
27679        }
27680        if let Some(count) = &e.count {
27681            self.write_space();
27682            self.generate_expression(count)?;
27683        }
27684        // Generate PERCENT, ROWS, WITH TIES/ONLY
27685        if e.percent {
27686            self.write_keyword(" PERCENT");
27687        }
27688        if e.rows {
27689            self.write_keyword(" ROWS");
27690        }
27691        if e.with_ties {
27692            self.write_keyword(" WITH TIES");
27693        } else if e.rows {
27694            self.write_keyword(" ONLY");
27695        } else {
27696            self.write_keyword(" ROWS ONLY");
27697        }
27698        Ok(())
27699    }
27700
27701    fn generate_file_format_property(&mut self, e: &FileFormatProperty) -> Result<()> {
27702        // For Hive format: STORED AS this or STORED AS INPUTFORMAT x OUTPUTFORMAT y
27703        // For Spark/Databricks without hive_format: USING this
27704        // For Snowflake/others: FILE_FORMAT = this or FILE_FORMAT = (expressions)
27705        if e.hive_format.is_some() {
27706            // Hive format: STORED AS ...
27707            self.write_keyword("STORED AS");
27708            self.write_space();
27709            if let Some(this) = &e.this {
27710                // Uppercase the format name (e.g., parquet -> PARQUET)
27711                if let Expression::Identifier(id) = this.as_ref() {
27712                    self.write_keyword(&id.name.to_ascii_uppercase());
27713                } else {
27714                    self.generate_expression(this)?;
27715                }
27716            }
27717        } else if matches!(self.config.dialect, Some(DialectType::Hive)) {
27718            // Hive: STORED AS format
27719            self.write_keyword("STORED AS");
27720            self.write_space();
27721            if let Some(this) = &e.this {
27722                if let Expression::Identifier(id) = this.as_ref() {
27723                    self.write_keyword(&id.name.to_ascii_uppercase());
27724                } else {
27725                    self.generate_expression(this)?;
27726                }
27727            }
27728        } else if matches!(
27729            self.config.dialect,
27730            Some(DialectType::Spark) | Some(DialectType::Databricks)
27731        ) {
27732            // Spark/Databricks: USING format (e.g., USING DELTA)
27733            self.write_keyword("USING");
27734            self.write_space();
27735            if let Some(this) = &e.this {
27736                self.generate_expression(this)?;
27737            }
27738        } else {
27739            // Snowflake/standard format
27740            self.write_keyword("FILE_FORMAT");
27741            self.write(" = ");
27742            if let Some(this) = &e.this {
27743                self.generate_expression(this)?;
27744            } else if !e.expressions.is_empty() {
27745                self.write("(");
27746                for (i, expr) in e.expressions.iter().enumerate() {
27747                    if i > 0 {
27748                        self.write(", ");
27749                    }
27750                    self.generate_expression(expr)?;
27751                }
27752                self.write(")");
27753            }
27754        }
27755        Ok(())
27756    }
27757
27758    fn generate_filter(&mut self, e: &Filter) -> Result<()> {
27759        // agg_func FILTER(WHERE condition)
27760        self.generate_expression(&e.this)?;
27761        self.write_space();
27762        self.write_keyword("FILTER");
27763        self.write("(");
27764        self.write_keyword("WHERE");
27765        self.write_space();
27766        self.generate_expression(&e.expression)?;
27767        self.write(")");
27768        Ok(())
27769    }
27770
27771    fn generate_float64(&mut self, e: &Float64) -> Result<()> {
27772        // FLOAT64(this) or FLOAT64(this, expression)
27773        self.write_keyword("FLOAT64");
27774        self.write("(");
27775        self.generate_expression(&e.this)?;
27776        if let Some(expr) = &e.expression {
27777            self.write(", ");
27778            self.generate_expression(expr)?;
27779        }
27780        self.write(")");
27781        Ok(())
27782    }
27783
27784    fn generate_for_in(&mut self, e: &ForIn) -> Result<()> {
27785        // FOR this DO expression
27786        self.write_keyword("FOR");
27787        self.write_space();
27788        self.generate_expression(&e.this)?;
27789        self.write_space();
27790        self.write_keyword("DO");
27791        self.write_space();
27792        self.generate_expression(&e.expression)?;
27793        Ok(())
27794    }
27795
27796    fn generate_foreign_key(&mut self, e: &ForeignKey) -> Result<()> {
27797        // FOREIGN KEY (cols) REFERENCES table(cols) ON DELETE action ON UPDATE action
27798        self.write_keyword("FOREIGN KEY");
27799        if !e.expressions.is_empty() {
27800            self.write(" (");
27801            for (i, expr) in e.expressions.iter().enumerate() {
27802                if i > 0 {
27803                    self.write(", ");
27804                }
27805                self.generate_expression(expr)?;
27806            }
27807            self.write(")");
27808        }
27809        if let Some(reference) = &e.reference {
27810            self.write_space();
27811            self.generate_expression(reference)?;
27812        }
27813        if let Some(delete) = &e.delete {
27814            self.write_space();
27815            self.write_keyword("ON DELETE");
27816            self.write_space();
27817            self.generate_expression(delete)?;
27818        }
27819        if let Some(update) = &e.update {
27820            self.write_space();
27821            self.write_keyword("ON UPDATE");
27822            self.write_space();
27823            self.generate_expression(update)?;
27824        }
27825        if !e.options.is_empty() {
27826            self.write_space();
27827            for (i, opt) in e.options.iter().enumerate() {
27828                if i > 0 {
27829                    self.write_space();
27830                }
27831                self.generate_expression(opt)?;
27832            }
27833        }
27834        Ok(())
27835    }
27836
27837    fn generate_format(&mut self, e: &Format) -> Result<()> {
27838        // FORMAT(this, expressions...)
27839        self.write_keyword("FORMAT");
27840        self.write("(");
27841        self.generate_expression(&e.this)?;
27842        for expr in &e.expressions {
27843            self.write(", ");
27844            self.generate_expression(expr)?;
27845        }
27846        self.write(")");
27847        Ok(())
27848    }
27849
27850    fn generate_format_phrase(&mut self, e: &FormatPhrase) -> Result<()> {
27851        // Teradata: column (FORMAT 'format_string')
27852        self.generate_expression(&e.this)?;
27853        self.write(" (");
27854        self.write_keyword("FORMAT");
27855        self.write(" '");
27856        self.write(&e.format);
27857        self.write("')");
27858        Ok(())
27859    }
27860
27861    fn generate_freespace_property(&mut self, e: &FreespaceProperty) -> Result<()> {
27862        // Python: FREESPACE=this[PERCENT]
27863        self.write_keyword("FREESPACE");
27864        self.write("=");
27865        self.generate_expression(&e.this)?;
27866        if e.percent.is_some() {
27867            self.write_keyword(" PERCENT");
27868        }
27869        Ok(())
27870    }
27871
27872    fn generate_from(&mut self, e: &From) -> Result<()> {
27873        // Python: return f"{self.seg('FROM')} {self.sql(expression, 'this')}"
27874        self.write_keyword("FROM");
27875        self.write_space();
27876
27877        // BigQuery, Hive, Spark, Databricks, SQLite, and ClickHouse prefer explicit CROSS JOIN over comma syntax
27878        // But keep commas when TABLESAMPLE is present
27879        // Also keep commas when the source dialect is Generic/None and target is one of these dialects
27880        use crate::dialects::DialectType;
27881        let has_tablesample = e
27882            .expressions
27883            .iter()
27884            .any(|expr| matches!(expr, Expression::TableSample(_)));
27885        let is_cross_join_dialect = matches!(
27886            self.config.dialect,
27887            Some(DialectType::BigQuery)
27888                | Some(DialectType::Hive)
27889                | Some(DialectType::Spark)
27890                | Some(DialectType::Databricks)
27891                | Some(DialectType::SQLite)
27892                | Some(DialectType::ClickHouse)
27893        );
27894        let source_is_same_as_target2 = self.config.source_dialect.is_some()
27895            && self.config.source_dialect == self.config.dialect;
27896        let source_is_cross_join_dialect2 = matches!(
27897            self.config.source_dialect,
27898            Some(DialectType::BigQuery)
27899                | Some(DialectType::Hive)
27900                | Some(DialectType::Spark)
27901                | Some(DialectType::Databricks)
27902                | Some(DialectType::SQLite)
27903                | Some(DialectType::ClickHouse)
27904        );
27905        let use_cross_join = !has_tablesample
27906            && is_cross_join_dialect
27907            && (source_is_same_as_target2
27908                || source_is_cross_join_dialect2
27909                || self.config.source_dialect.is_none());
27910
27911        // Snowflake wraps standalone VALUES in FROM clause with parentheses
27912        let wrap_values_in_parens = matches!(self.config.dialect, Some(DialectType::Snowflake));
27913
27914        for (i, expr) in e.expressions.iter().enumerate() {
27915            if i > 0 {
27916                if use_cross_join {
27917                    self.write(" CROSS JOIN ");
27918                } else {
27919                    self.write(", ");
27920                }
27921            }
27922            if wrap_values_in_parens && matches!(expr, Expression::Values(_)) {
27923                self.write("(");
27924                self.generate_expression(expr)?;
27925                self.write(")");
27926            } else {
27927                self.generate_expression(expr)?;
27928            }
27929        }
27930        Ok(())
27931    }
27932
27933    fn generate_from_base(&mut self, e: &FromBase) -> Result<()> {
27934        // FROM_BASE(this, expression) - convert from base N
27935        self.write_keyword("FROM_BASE");
27936        self.write("(");
27937        self.generate_expression(&e.this)?;
27938        self.write(", ");
27939        self.generate_expression(&e.expression)?;
27940        self.write(")");
27941        Ok(())
27942    }
27943
27944    fn generate_from_time_zone(&mut self, e: &FromTimeZone) -> Result<()> {
27945        // this AT TIME ZONE zone AT TIME ZONE 'UTC'
27946        self.generate_expression(&e.this)?;
27947        if let Some(zone) = &e.zone {
27948            self.write_space();
27949            self.write_keyword("AT TIME ZONE");
27950            self.write_space();
27951            self.generate_expression(zone)?;
27952            self.write_space();
27953            self.write_keyword("AT TIME ZONE");
27954            self.write(" 'UTC'");
27955        }
27956        Ok(())
27957    }
27958
27959    fn generate_gap_fill(&mut self, e: &GapFill) -> Result<()> {
27960        // GAP_FILL(this, ts_column, bucket_width, ...)
27961        self.write_keyword("GAP_FILL");
27962        self.write("(");
27963        self.generate_expression(&e.this)?;
27964        if let Some(ts_column) = &e.ts_column {
27965            self.write(", ");
27966            self.generate_expression(ts_column)?;
27967        }
27968        if let Some(bucket_width) = &e.bucket_width {
27969            self.write(", ");
27970            self.generate_expression(bucket_width)?;
27971        }
27972        if let Some(partitioning_columns) = &e.partitioning_columns {
27973            self.write(", ");
27974            self.generate_expression(partitioning_columns)?;
27975        }
27976        if let Some(value_columns) = &e.value_columns {
27977            self.write(", ");
27978            self.generate_expression(value_columns)?;
27979        }
27980        self.write(")");
27981        Ok(())
27982    }
27983
27984    fn generate_generate_date_array(&mut self, e: &GenerateDateArray) -> Result<()> {
27985        // GENERATE_DATE_ARRAY(start, end, step)
27986        self.write_keyword("GENERATE_DATE_ARRAY");
27987        self.write("(");
27988        let mut first = true;
27989        if let Some(start) = &e.start {
27990            self.generate_expression(start)?;
27991            first = false;
27992        }
27993        if let Some(end) = &e.end {
27994            if !first {
27995                self.write(", ");
27996            }
27997            self.generate_expression(end)?;
27998            first = false;
27999        }
28000        if let Some(step) = &e.step {
28001            if !first {
28002                self.write(", ");
28003            }
28004            self.generate_expression(step)?;
28005        }
28006        self.write(")");
28007        Ok(())
28008    }
28009
28010    fn generate_generate_embedding(&mut self, e: &GenerateEmbedding) -> Result<()> {
28011        // ML.GENERATE_EMBEDDING(model, content, params)
28012        self.write_keyword("ML.GENERATE_EMBEDDING");
28013        self.write("(");
28014        self.generate_expression(&e.this)?;
28015        self.write(", ");
28016        self.generate_expression(&e.expression)?;
28017        if let Some(params) = &e.params_struct {
28018            self.write(", ");
28019            self.generate_expression(params)?;
28020        }
28021        self.write(")");
28022        Ok(())
28023    }
28024
28025    fn generate_generate_series(&mut self, e: &GenerateSeries) -> Result<()> {
28026        // Dialect-specific function name
28027        let fn_name = match self.config.dialect {
28028            Some(DialectType::Presto)
28029            | Some(DialectType::Trino)
28030            | Some(DialectType::Athena)
28031            | Some(DialectType::Spark)
28032            | Some(DialectType::Databricks)
28033            | Some(DialectType::Hive) => "SEQUENCE",
28034            _ => "GENERATE_SERIES",
28035        };
28036        self.write_keyword(fn_name);
28037        self.write("(");
28038        let mut first = true;
28039        if let Some(start) = &e.start {
28040            self.generate_expression(start)?;
28041            first = false;
28042        }
28043        if let Some(end) = &e.end {
28044            if !first {
28045                self.write(", ");
28046            }
28047            self.generate_expression(end)?;
28048            first = false;
28049        }
28050        if let Some(step) = &e.step {
28051            if !first {
28052                self.write(", ");
28053            }
28054            // For Presto/Trino: convert WEEK intervals to DAY multiples
28055            // e.g., INTERVAL '1' WEEK -> (1 * INTERVAL '7' DAY)
28056            if matches!(
28057                self.config.dialect,
28058                Some(DialectType::Presto) | Some(DialectType::Trino) | Some(DialectType::Athena)
28059            ) {
28060                if let Some(converted) = self.convert_week_interval_to_day(step) {
28061                    self.generate_expression(&converted)?;
28062                } else {
28063                    self.generate_expression(step)?;
28064                }
28065            } else {
28066                self.generate_expression(step)?;
28067            }
28068        }
28069        self.write(")");
28070        Ok(())
28071    }
28072
28073    /// Convert a WEEK interval to a DAY-based multiplication expression for Presto/Trino.
28074    /// INTERVAL N WEEK -> (N * INTERVAL '7' DAY)
28075    fn convert_week_interval_to_day(&self, expr: &Expression) -> Option<Expression> {
28076        use crate::expressions::*;
28077        if let Expression::Interval(ref iv) = expr {
28078            // Check for structured WEEK unit
28079            let (is_week, count_str) = if let Some(IntervalUnitSpec::Simple {
28080                unit: IntervalUnit::Week,
28081                ..
28082            }) = &iv.unit
28083            {
28084                // Value is in iv.this
28085                let count = match &iv.this {
28086                    Some(Expression::Literal(lit)) => match lit.as_ref() {
28087                        Literal::String(s) | Literal::Number(s) => s.clone(),
28088                        _ => return None,
28089                    },
28090                    _ => return None,
28091                };
28092                (true, count)
28093            } else if iv.unit.is_none() {
28094                // Check for string-encoded interval like "1 WEEK"
28095                if let Some(Expression::Literal(lit)) = &iv.this {
28096                    if let Literal::String(s) = lit.as_ref() {
28097                    let parts: Vec<&str> = s.trim().splitn(2, char::is_whitespace).collect();
28098                    if parts.len() == 2 && parts[1].eq_ignore_ascii_case("WEEK") {
28099                        (true, parts[0].to_string())
28100                    } else {
28101                        (false, String::new())
28102                    }
28103                } else { (false, String::new()) }
28104                } else {
28105                    (false, String::new())
28106                }
28107            } else {
28108                (false, String::new())
28109            };
28110
28111            if is_week {
28112                // Build: (N * INTERVAL '7' DAY)
28113                let count_expr = Expression::Literal(Box::new(Literal::Number(count_str)));
28114                let day_interval = Expression::Interval(Box::new(Interval {
28115                    this: Some(Expression::Literal(Box::new(Literal::String("7".to_string())))),
28116                    unit: Some(IntervalUnitSpec::Simple {
28117                        unit: IntervalUnit::Day,
28118                        use_plural: false,
28119                    }),
28120                }));
28121                let mul = Expression::Mul(Box::new(BinaryOp {
28122                    left: count_expr,
28123                    right: day_interval,
28124                    left_comments: vec![],
28125                    operator_comments: vec![],
28126                    trailing_comments: vec![],
28127                    inferred_type: None,
28128                }));
28129                return Some(Expression::Paren(Box::new(Paren {
28130                    this: mul,
28131                    trailing_comments: vec![],
28132                })));
28133            }
28134        }
28135        None
28136    }
28137
28138    fn generate_generate_timestamp_array(&mut self, e: &GenerateTimestampArray) -> Result<()> {
28139        // GENERATE_TIMESTAMP_ARRAY(start, end, step)
28140        self.write_keyword("GENERATE_TIMESTAMP_ARRAY");
28141        self.write("(");
28142        let mut first = true;
28143        if let Some(start) = &e.start {
28144            self.generate_expression(start)?;
28145            first = false;
28146        }
28147        if let Some(end) = &e.end {
28148            if !first {
28149                self.write(", ");
28150            }
28151            self.generate_expression(end)?;
28152            first = false;
28153        }
28154        if let Some(step) = &e.step {
28155            if !first {
28156                self.write(", ");
28157            }
28158            self.generate_expression(step)?;
28159        }
28160        self.write(")");
28161        Ok(())
28162    }
28163
28164    fn generate_generated_as_identity_column_constraint(
28165        &mut self,
28166        e: &GeneratedAsIdentityColumnConstraint,
28167    ) -> Result<()> {
28168        use crate::dialects::DialectType;
28169
28170        // For Snowflake, use AUTOINCREMENT START x INCREMENT y syntax
28171        if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
28172            self.write_keyword("AUTOINCREMENT");
28173            if let Some(start) = &e.start {
28174                self.write_keyword(" START ");
28175                self.generate_expression(start)?;
28176            }
28177            if let Some(increment) = &e.increment {
28178                self.write_keyword(" INCREMENT ");
28179                self.generate_expression(increment)?;
28180            }
28181            return Ok(());
28182        }
28183
28184        // Python: GENERATED [ALWAYS|BY DEFAULT [ON NULL]] AS IDENTITY [(start, increment, ...)]
28185        self.write_keyword("GENERATED");
28186        if let Some(this) = &e.this {
28187            // Check if it's a truthy boolean expression
28188            if let Expression::Boolean(b) = this.as_ref() {
28189                if b.value {
28190                    self.write_keyword(" ALWAYS");
28191                } else {
28192                    self.write_keyword(" BY DEFAULT");
28193                    if e.on_null.is_some() {
28194                        self.write_keyword(" ON NULL");
28195                    }
28196                }
28197            } else {
28198                self.write_keyword(" ALWAYS");
28199            }
28200        }
28201        self.write_keyword(" AS IDENTITY");
28202        // Add sequence options if any
28203        let has_options = e.start.is_some()
28204            || e.increment.is_some()
28205            || e.minvalue.is_some()
28206            || e.maxvalue.is_some();
28207        if has_options {
28208            self.write(" (");
28209            let mut first = true;
28210            if let Some(start) = &e.start {
28211                self.write_keyword("START WITH ");
28212                self.generate_expression(start)?;
28213                first = false;
28214            }
28215            if let Some(increment) = &e.increment {
28216                if !first {
28217                    self.write(" ");
28218                }
28219                self.write_keyword("INCREMENT BY ");
28220                self.generate_expression(increment)?;
28221                first = false;
28222            }
28223            if let Some(minvalue) = &e.minvalue {
28224                if !first {
28225                    self.write(" ");
28226                }
28227                self.write_keyword("MINVALUE ");
28228                self.generate_expression(minvalue)?;
28229                first = false;
28230            }
28231            if let Some(maxvalue) = &e.maxvalue {
28232                if !first {
28233                    self.write(" ");
28234                }
28235                self.write_keyword("MAXVALUE ");
28236                self.generate_expression(maxvalue)?;
28237            }
28238            self.write(")");
28239        }
28240        Ok(())
28241    }
28242
28243    fn generate_generated_as_row_column_constraint(
28244        &mut self,
28245        e: &GeneratedAsRowColumnConstraint,
28246    ) -> Result<()> {
28247        // Python: GENERATED ALWAYS AS ROW START|END [HIDDEN]
28248        self.write_keyword("GENERATED ALWAYS AS ROW ");
28249        if e.start.is_some() {
28250            self.write_keyword("START");
28251        } else {
28252            self.write_keyword("END");
28253        }
28254        if e.hidden.is_some() {
28255            self.write_keyword(" HIDDEN");
28256        }
28257        Ok(())
28258    }
28259
28260    fn generate_get(&mut self, e: &Get) -> Result<()> {
28261        // GET this target properties
28262        self.write_keyword("GET");
28263        self.write_space();
28264        self.generate_expression(&e.this)?;
28265        if let Some(target) = &e.target {
28266            self.write_space();
28267            self.generate_expression(target)?;
28268        }
28269        for prop in &e.properties {
28270            self.write_space();
28271            self.generate_expression(prop)?;
28272        }
28273        Ok(())
28274    }
28275
28276    fn generate_get_extract(&mut self, e: &GetExtract) -> Result<()> {
28277        // GetExtract generates bracket access: this[expression]
28278        self.generate_expression(&e.this)?;
28279        self.write("[");
28280        self.generate_expression(&e.expression)?;
28281        self.write("]");
28282        Ok(())
28283    }
28284
28285    fn generate_getbit(&mut self, e: &Getbit) -> Result<()> {
28286        // GETBIT(this, expression) or GET_BIT(this, expression)
28287        self.write_keyword("GETBIT");
28288        self.write("(");
28289        self.generate_expression(&e.this)?;
28290        self.write(", ");
28291        self.generate_expression(&e.expression)?;
28292        self.write(")");
28293        Ok(())
28294    }
28295
28296    fn generate_grant_principal(&mut self, e: &GrantPrincipal) -> Result<()> {
28297        // [ROLE|GROUP] name (e.g., "ROLE admin", "GROUP qa_users", or just "user1")
28298        if e.is_role {
28299            self.write_keyword("ROLE");
28300            self.write_space();
28301        } else if e.is_group {
28302            self.write_keyword("GROUP");
28303            self.write_space();
28304        }
28305        self.write(&e.name.name);
28306        Ok(())
28307    }
28308
28309    fn generate_grant_privilege(&mut self, e: &GrantPrivilege) -> Result<()> {
28310        // privilege(columns) or just privilege
28311        self.generate_expression(&e.this)?;
28312        if !e.expressions.is_empty() {
28313            self.write("(");
28314            for (i, expr) in e.expressions.iter().enumerate() {
28315                if i > 0 {
28316                    self.write(", ");
28317                }
28318                self.generate_expression(expr)?;
28319            }
28320            self.write(")");
28321        }
28322        Ok(())
28323    }
28324
28325    fn generate_group(&mut self, e: &Group) -> Result<()> {
28326        // Python handles GROUP BY ALL/DISTINCT modifiers and grouping expressions
28327        self.write_keyword("GROUP BY");
28328        // Handle ALL/DISTINCT modifier: Some(true) = ALL, Some(false) = DISTINCT
28329        match e.all {
28330            Some(true) => {
28331                self.write_space();
28332                self.write_keyword("ALL");
28333            }
28334            Some(false) => {
28335                self.write_space();
28336                self.write_keyword("DISTINCT");
28337            }
28338            None => {}
28339        }
28340        if !e.expressions.is_empty() {
28341            self.write_space();
28342            for (i, expr) in e.expressions.iter().enumerate() {
28343                if i > 0 {
28344                    self.write(", ");
28345                }
28346                self.generate_expression(expr)?;
28347            }
28348        }
28349        // Handle CUBE, ROLLUP, GROUPING SETS
28350        if let Some(cube) = &e.cube {
28351            if !e.expressions.is_empty() {
28352                self.write(", ");
28353            } else {
28354                self.write_space();
28355            }
28356            self.generate_expression(cube)?;
28357        }
28358        if let Some(rollup) = &e.rollup {
28359            if !e.expressions.is_empty() || e.cube.is_some() {
28360                self.write(", ");
28361            } else {
28362                self.write_space();
28363            }
28364            self.generate_expression(rollup)?;
28365        }
28366        if let Some(grouping_sets) = &e.grouping_sets {
28367            if !e.expressions.is_empty() || e.cube.is_some() || e.rollup.is_some() {
28368                self.write(", ");
28369            } else {
28370                self.write_space();
28371            }
28372            self.generate_expression(grouping_sets)?;
28373        }
28374        if let Some(totals) = &e.totals {
28375            self.write_space();
28376            self.write_keyword("WITH TOTALS");
28377            self.generate_expression(totals)?;
28378        }
28379        Ok(())
28380    }
28381
28382    fn generate_group_by(&mut self, e: &GroupBy) -> Result<()> {
28383        // GROUP BY expressions
28384        self.write_keyword("GROUP BY");
28385        // Handle ALL/DISTINCT modifier: Some(true) = ALL, Some(false) = DISTINCT
28386        match e.all {
28387            Some(true) => {
28388                self.write_space();
28389                self.write_keyword("ALL");
28390            }
28391            Some(false) => {
28392                self.write_space();
28393                self.write_keyword("DISTINCT");
28394            }
28395            None => {}
28396        }
28397
28398        // Check for trailing WITH CUBE or WITH ROLLUP (Hive/MySQL syntax)
28399        // These are represented as Cube/Rollup expressions with empty expressions at the end
28400        let mut trailing_cube = false;
28401        let mut trailing_rollup = false;
28402        let mut regular_expressions: Vec<&Expression> = Vec::new();
28403
28404        for expr in &e.expressions {
28405            match expr {
28406                Expression::Cube(c) if c.expressions.is_empty() => {
28407                    trailing_cube = true;
28408                }
28409                Expression::Rollup(r) if r.expressions.is_empty() => {
28410                    trailing_rollup = true;
28411                }
28412                _ => {
28413                    regular_expressions.push(expr);
28414                }
28415            }
28416        }
28417
28418        // In pretty mode, put columns on separate lines
28419        if self.config.pretty {
28420            self.write_newline();
28421            self.indent_level += 1;
28422            for (i, expr) in regular_expressions.iter().enumerate() {
28423                if i > 0 {
28424                    self.write(",");
28425                    self.write_newline();
28426                }
28427                self.write_indent();
28428                self.generate_expression(expr)?;
28429            }
28430            self.indent_level -= 1;
28431        } else {
28432            self.write_space();
28433            for (i, expr) in regular_expressions.iter().enumerate() {
28434                if i > 0 {
28435                    self.write(", ");
28436                }
28437                self.generate_expression(expr)?;
28438            }
28439        }
28440
28441        // Output trailing WITH CUBE or WITH ROLLUP
28442        if trailing_cube {
28443            self.write_space();
28444            self.write_keyword("WITH CUBE");
28445        } else if trailing_rollup {
28446            self.write_space();
28447            self.write_keyword("WITH ROLLUP");
28448        }
28449
28450        // ClickHouse: WITH TOTALS
28451        if e.totals {
28452            self.write_space();
28453            self.write_keyword("WITH TOTALS");
28454        }
28455
28456        Ok(())
28457    }
28458
28459    fn generate_grouping(&mut self, e: &Grouping) -> Result<()> {
28460        // GROUPING(col1, col2, ...)
28461        self.write_keyword("GROUPING");
28462        self.write("(");
28463        for (i, expr) in e.expressions.iter().enumerate() {
28464            if i > 0 {
28465                self.write(", ");
28466            }
28467            self.generate_expression(expr)?;
28468        }
28469        self.write(")");
28470        Ok(())
28471    }
28472
28473    fn generate_grouping_id(&mut self, e: &GroupingId) -> Result<()> {
28474        // GROUPING_ID(col1, col2, ...)
28475        self.write_keyword("GROUPING_ID");
28476        self.write("(");
28477        for (i, expr) in e.expressions.iter().enumerate() {
28478            if i > 0 {
28479                self.write(", ");
28480            }
28481            self.generate_expression(expr)?;
28482        }
28483        self.write(")");
28484        Ok(())
28485    }
28486
28487    fn generate_grouping_sets(&mut self, e: &GroupingSets) -> Result<()> {
28488        // Python: return f"GROUPING SETS {self.wrap(grouping_sets)}"
28489        self.write_keyword("GROUPING SETS");
28490        self.write(" (");
28491        for (i, expr) in e.expressions.iter().enumerate() {
28492            if i > 0 {
28493                self.write(", ");
28494            }
28495            self.generate_expression(expr)?;
28496        }
28497        self.write(")");
28498        Ok(())
28499    }
28500
28501    fn generate_hash_agg(&mut self, e: &HashAgg) -> Result<()> {
28502        // HASH_AGG(this, expressions...)
28503        self.write_keyword("HASH_AGG");
28504        self.write("(");
28505        self.generate_expression(&e.this)?;
28506        for expr in &e.expressions {
28507            self.write(", ");
28508            self.generate_expression(expr)?;
28509        }
28510        self.write(")");
28511        Ok(())
28512    }
28513
28514    fn generate_having(&mut self, e: &Having) -> Result<()> {
28515        // Python: return f"{self.seg('HAVING')}{self.sep()}{this}"
28516        self.write_keyword("HAVING");
28517        self.write_space();
28518        self.generate_expression(&e.this)?;
28519        Ok(())
28520    }
28521
28522    fn generate_having_max(&mut self, e: &HavingMax) -> Result<()> {
28523        // Python: this HAVING MAX|MIN expression
28524        self.generate_expression(&e.this)?;
28525        self.write_space();
28526        self.write_keyword("HAVING");
28527        self.write_space();
28528        if e.max.is_some() {
28529            self.write_keyword("MAX");
28530        } else {
28531            self.write_keyword("MIN");
28532        }
28533        self.write_space();
28534        self.generate_expression(&e.expression)?;
28535        Ok(())
28536    }
28537
28538    fn generate_heredoc(&mut self, e: &Heredoc) -> Result<()> {
28539        use crate::dialects::DialectType;
28540        // DuckDB: convert dollar-tagged strings to single-quoted
28541        if matches!(self.config.dialect, Some(DialectType::DuckDB)) {
28542            // Extract the string content and output as single-quoted
28543            if let Expression::Literal(ref lit) = *e.this {
28544                if let Literal::String(ref s) = lit.as_ref() {
28545                return self.generate_string_literal(s);
28546            }
28547            }
28548        }
28549        // PostgreSQL: preserve dollar-quoting
28550        if matches!(
28551            self.config.dialect,
28552            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
28553        ) {
28554            self.write("$");
28555            if let Some(tag) = &e.tag {
28556                self.generate_expression(tag)?;
28557            }
28558            self.write("$");
28559            self.generate_expression(&e.this)?;
28560            self.write("$");
28561            if let Some(tag) = &e.tag {
28562                self.generate_expression(tag)?;
28563            }
28564            self.write("$");
28565            return Ok(());
28566        }
28567        // Default: output as dollar-tagged
28568        self.write("$");
28569        if let Some(tag) = &e.tag {
28570            self.generate_expression(tag)?;
28571        }
28572        self.write("$");
28573        self.generate_expression(&e.this)?;
28574        self.write("$");
28575        if let Some(tag) = &e.tag {
28576            self.generate_expression(tag)?;
28577        }
28578        self.write("$");
28579        Ok(())
28580    }
28581
28582    fn generate_hex_encode(&mut self, e: &HexEncode) -> Result<()> {
28583        // HEX_ENCODE(this)
28584        self.write_keyword("HEX_ENCODE");
28585        self.write("(");
28586        self.generate_expression(&e.this)?;
28587        self.write(")");
28588        Ok(())
28589    }
28590
28591    fn generate_historical_data(&mut self, e: &HistoricalData) -> Result<()> {
28592        // Python: this (kind => expression)
28593        // Write the keyword (AT/BEFORE/END) directly to avoid quoting it as a reserved word
28594        match e.this.as_ref() {
28595            Expression::Identifier(id) => self.write(&id.name),
28596            other => self.generate_expression(other)?,
28597        }
28598        self.write(" (");
28599        self.write(&e.kind);
28600        self.write(" => ");
28601        self.generate_expression(&e.expression)?;
28602        self.write(")");
28603        Ok(())
28604    }
28605
28606    fn generate_hll(&mut self, e: &Hll) -> Result<()> {
28607        // HLL(this, expressions...)
28608        self.write_keyword("HLL");
28609        self.write("(");
28610        self.generate_expression(&e.this)?;
28611        for expr in &e.expressions {
28612            self.write(", ");
28613            self.generate_expression(expr)?;
28614        }
28615        self.write(")");
28616        Ok(())
28617    }
28618
28619    fn generate_in_out_column_constraint(&mut self, e: &InOutColumnConstraint) -> Result<()> {
28620        // Python: IN|OUT|IN OUT
28621        if e.input_.is_some() && e.output.is_some() {
28622            self.write_keyword("IN OUT");
28623        } else if e.input_.is_some() {
28624            self.write_keyword("IN");
28625        } else if e.output.is_some() {
28626            self.write_keyword("OUT");
28627        }
28628        Ok(())
28629    }
28630
28631    fn generate_include_property(&mut self, e: &IncludeProperty) -> Result<()> {
28632        // Python: INCLUDE this [column_def] [AS alias]
28633        self.write_keyword("INCLUDE");
28634        self.write_space();
28635        self.generate_expression(&e.this)?;
28636        if let Some(column_def) = &e.column_def {
28637            self.write_space();
28638            self.generate_expression(column_def)?;
28639        }
28640        if let Some(alias) = &e.alias {
28641            self.write_space();
28642            self.write_keyword("AS");
28643            self.write_space();
28644            self.write(alias);
28645        }
28646        Ok(())
28647    }
28648
28649    fn generate_index(&mut self, e: &Index) -> Result<()> {
28650        // [UNIQUE] [PRIMARY] [AMP] INDEX [name] [ON table] (params)
28651        if e.unique {
28652            self.write_keyword("UNIQUE");
28653            self.write_space();
28654        }
28655        if e.primary.is_some() {
28656            self.write_keyword("PRIMARY");
28657            self.write_space();
28658        }
28659        if e.amp.is_some() {
28660            self.write_keyword("AMP");
28661            self.write_space();
28662        }
28663        if e.table.is_none() {
28664            self.write_keyword("INDEX");
28665            self.write_space();
28666        }
28667        if let Some(name) = &e.this {
28668            self.generate_expression(name)?;
28669            self.write_space();
28670        }
28671        if let Some(table) = &e.table {
28672            self.write_keyword("ON");
28673            self.write_space();
28674            self.generate_expression(table)?;
28675        }
28676        if !e.params.is_empty() {
28677            self.write("(");
28678            for (i, param) in e.params.iter().enumerate() {
28679                if i > 0 {
28680                    self.write(", ");
28681                }
28682                self.generate_expression(param)?;
28683            }
28684            self.write(")");
28685        }
28686        Ok(())
28687    }
28688
28689    fn generate_index_column_constraint(&mut self, e: &IndexColumnConstraint) -> Result<()> {
28690        // Python: kind INDEX [this] [USING index_type] (expressions) [options]
28691        if let Some(kind) = &e.kind {
28692            self.write(kind);
28693            self.write_space();
28694        }
28695        self.write_keyword("INDEX");
28696        if let Some(this) = &e.this {
28697            self.write_space();
28698            self.generate_expression(this)?;
28699        }
28700        if let Some(index_type) = &e.index_type {
28701            self.write_space();
28702            self.write_keyword("USING");
28703            self.write_space();
28704            self.generate_expression(index_type)?;
28705        }
28706        if !e.expressions.is_empty() {
28707            self.write(" (");
28708            for (i, expr) in e.expressions.iter().enumerate() {
28709                if i > 0 {
28710                    self.write(", ");
28711                }
28712                self.generate_expression(expr)?;
28713            }
28714            self.write(")");
28715        }
28716        for opt in &e.options {
28717            self.write_space();
28718            self.generate_expression(opt)?;
28719        }
28720        Ok(())
28721    }
28722
28723    fn generate_index_constraint_option(&mut self, e: &IndexConstraintOption) -> Result<()> {
28724        // Python: KEY_BLOCK_SIZE = x | USING x | WITH PARSER x | COMMENT x | visible | engine_attr | secondary_engine_attr
28725        if let Some(key_block_size) = &e.key_block_size {
28726            self.write_keyword("KEY_BLOCK_SIZE");
28727            self.write(" = ");
28728            self.generate_expression(key_block_size)?;
28729        } else if let Some(using) = &e.using {
28730            self.write_keyword("USING");
28731            self.write_space();
28732            self.generate_expression(using)?;
28733        } else if let Some(parser) = &e.parser {
28734            self.write_keyword("WITH PARSER");
28735            self.write_space();
28736            self.generate_expression(parser)?;
28737        } else if let Some(comment) = &e.comment {
28738            self.write_keyword("COMMENT");
28739            self.write_space();
28740            self.generate_expression(comment)?;
28741        } else if let Some(visible) = &e.visible {
28742            self.generate_expression(visible)?;
28743        } else if let Some(engine_attr) = &e.engine_attr {
28744            self.write_keyword("ENGINE_ATTRIBUTE");
28745            self.write(" = ");
28746            self.generate_expression(engine_attr)?;
28747        } else if let Some(secondary_engine_attr) = &e.secondary_engine_attr {
28748            self.write_keyword("SECONDARY_ENGINE_ATTRIBUTE");
28749            self.write(" = ");
28750            self.generate_expression(secondary_engine_attr)?;
28751        }
28752        Ok(())
28753    }
28754
28755    fn generate_index_parameters(&mut self, e: &IndexParameters) -> Result<()> {
28756        // Python: [USING using] (columns) [PARTITION BY partition_by] [where] [INCLUDE (include)] [WITH (with_storage)] [USING INDEX TABLESPACE tablespace]
28757        if let Some(using) = &e.using {
28758            self.write_keyword("USING");
28759            self.write_space();
28760            self.generate_expression(using)?;
28761        }
28762        if !e.columns.is_empty() {
28763            self.write("(");
28764            for (i, col) in e.columns.iter().enumerate() {
28765                if i > 0 {
28766                    self.write(", ");
28767                }
28768                self.generate_expression(col)?;
28769            }
28770            self.write(")");
28771        }
28772        if let Some(partition_by) = &e.partition_by {
28773            self.write_space();
28774            self.write_keyword("PARTITION BY");
28775            self.write_space();
28776            self.generate_expression(partition_by)?;
28777        }
28778        if let Some(where_) = &e.where_ {
28779            self.write_space();
28780            self.generate_expression(where_)?;
28781        }
28782        if let Some(include) = &e.include {
28783            self.write_space();
28784            self.write_keyword("INCLUDE");
28785            self.write(" (");
28786            self.generate_expression(include)?;
28787            self.write(")");
28788        }
28789        if let Some(with_storage) = &e.with_storage {
28790            self.write_space();
28791            self.write_keyword("WITH");
28792            self.write(" (");
28793            self.generate_expression(with_storage)?;
28794            self.write(")");
28795        }
28796        if let Some(tablespace) = &e.tablespace {
28797            self.write_space();
28798            self.write_keyword("USING INDEX TABLESPACE");
28799            self.write_space();
28800            self.generate_expression(tablespace)?;
28801        }
28802        Ok(())
28803    }
28804
28805    fn generate_index_table_hint(&mut self, e: &IndexTableHint) -> Result<()> {
28806        // Python: this INDEX [FOR target] (expressions)
28807        // Write hint type (USE/IGNORE/FORCE) as keyword, not through generate_expression
28808        // to avoid quoting reserved keywords like IGNORE, FORCE, JOIN
28809        if let Expression::Identifier(id) = &*e.this {
28810            self.write_keyword(&id.name);
28811        } else {
28812            self.generate_expression(&e.this)?;
28813        }
28814        self.write_space();
28815        self.write_keyword("INDEX");
28816        if let Some(target) = &e.target {
28817            self.write_space();
28818            self.write_keyword("FOR");
28819            self.write_space();
28820            if let Expression::Identifier(id) = &**target {
28821                self.write_keyword(&id.name);
28822            } else {
28823                self.generate_expression(target)?;
28824            }
28825        }
28826        // Always output parentheses (even if empty, e.g. USE INDEX ())
28827        self.write(" (");
28828        for (i, expr) in e.expressions.iter().enumerate() {
28829            if i > 0 {
28830                self.write(", ");
28831            }
28832            self.generate_expression(expr)?;
28833        }
28834        self.write(")");
28835        Ok(())
28836    }
28837
28838    fn generate_inherits_property(&mut self, e: &InheritsProperty) -> Result<()> {
28839        // INHERITS (table1, table2, ...)
28840        self.write_keyword("INHERITS");
28841        self.write(" (");
28842        for (i, expr) in e.expressions.iter().enumerate() {
28843            if i > 0 {
28844                self.write(", ");
28845            }
28846            self.generate_expression(expr)?;
28847        }
28848        self.write(")");
28849        Ok(())
28850    }
28851
28852    fn generate_input_model_property(&mut self, e: &InputModelProperty) -> Result<()> {
28853        // INPUT(model)
28854        self.write_keyword("INPUT");
28855        self.write("(");
28856        self.generate_expression(&e.this)?;
28857        self.write(")");
28858        Ok(())
28859    }
28860
28861    fn generate_input_output_format(&mut self, e: &InputOutputFormat) -> Result<()> {
28862        // Python: INPUTFORMAT input_format OUTPUTFORMAT output_format
28863        if let Some(input_format) = &e.input_format {
28864            self.write_keyword("INPUTFORMAT");
28865            self.write_space();
28866            self.generate_expression(input_format)?;
28867        }
28868        if let Some(output_format) = &e.output_format {
28869            if e.input_format.is_some() {
28870                self.write(" ");
28871            }
28872            self.write_keyword("OUTPUTFORMAT");
28873            self.write_space();
28874            self.generate_expression(output_format)?;
28875        }
28876        Ok(())
28877    }
28878
28879    fn generate_install(&mut self, e: &Install) -> Result<()> {
28880        // [FORCE] INSTALL extension [FROM source]
28881        if e.force.is_some() {
28882            self.write_keyword("FORCE");
28883            self.write_space();
28884        }
28885        self.write_keyword("INSTALL");
28886        self.write_space();
28887        self.generate_expression(&e.this)?;
28888        if let Some(from) = &e.from_ {
28889            self.write_space();
28890            self.write_keyword("FROM");
28891            self.write_space();
28892            self.generate_expression(from)?;
28893        }
28894        Ok(())
28895    }
28896
28897    fn generate_interval_op(&mut self, e: &IntervalOp) -> Result<()> {
28898        // INTERVAL 'expression' unit
28899        self.write_keyword("INTERVAL");
28900        self.write_space();
28901        // When a unit is specified and the expression is a number,
28902        self.generate_expression(&e.expression)?;
28903        if let Some(unit) = &e.unit {
28904            self.write_space();
28905            self.write(unit);
28906        }
28907        Ok(())
28908    }
28909
28910    fn generate_interval_span(&mut self, e: &IntervalSpan) -> Result<()> {
28911        // unit TO unit (e.g., HOUR TO SECOND)
28912        self.write(&format!("{:?}", e.this).to_ascii_uppercase());
28913        self.write_space();
28914        self.write_keyword("TO");
28915        self.write_space();
28916        self.write(&format!("{:?}", e.expression).to_ascii_uppercase());
28917        Ok(())
28918    }
28919
28920    fn generate_into_clause(&mut self, e: &IntoClause) -> Result<()> {
28921        // INTO [TEMPORARY|UNLOGGED] table
28922        self.write_keyword("INTO");
28923        if e.temporary {
28924            self.write_keyword(" TEMPORARY");
28925        }
28926        if e.unlogged.is_some() {
28927            self.write_keyword(" UNLOGGED");
28928        }
28929        if let Some(this) = &e.this {
28930            self.write_space();
28931            self.generate_expression(this)?;
28932        }
28933        if !e.expressions.is_empty() {
28934            self.write(" (");
28935            for (i, expr) in e.expressions.iter().enumerate() {
28936                if i > 0 {
28937                    self.write(", ");
28938                }
28939                self.generate_expression(expr)?;
28940            }
28941            self.write(")");
28942        }
28943        Ok(())
28944    }
28945
28946    fn generate_introducer(&mut self, e: &Introducer) -> Result<()> {
28947        // Python: this expression (e.g., _utf8 'string')
28948        self.generate_expression(&e.this)?;
28949        self.write_space();
28950        self.generate_expression(&e.expression)?;
28951        Ok(())
28952    }
28953
28954    fn generate_isolated_loading_property(&mut self, e: &IsolatedLoadingProperty) -> Result<()> {
28955        // Python: WITH [NO] [CONCURRENT] ISOLATED LOADING [target]
28956        self.write_keyword("WITH");
28957        if e.no.is_some() {
28958            self.write_keyword(" NO");
28959        }
28960        if e.concurrent.is_some() {
28961            self.write_keyword(" CONCURRENT");
28962        }
28963        self.write_keyword(" ISOLATED LOADING");
28964        if let Some(target) = &e.target {
28965            self.write_space();
28966            self.generate_expression(target)?;
28967        }
28968        Ok(())
28969    }
28970
28971    fn generate_json(&mut self, e: &JSON) -> Result<()> {
28972        // Python: JSON [this] [WITHOUT|WITH] [UNIQUE KEYS]
28973        self.write_keyword("JSON");
28974        if let Some(this) = &e.this {
28975            self.write_space();
28976            self.generate_expression(this)?;
28977        }
28978        if let Some(with_) = &e.with_ {
28979            // Check if it's a truthy boolean
28980            if let Expression::Boolean(b) = with_.as_ref() {
28981                if b.value {
28982                    self.write_keyword(" WITH");
28983                } else {
28984                    self.write_keyword(" WITHOUT");
28985                }
28986            }
28987        }
28988        if e.unique {
28989            self.write_keyword(" UNIQUE KEYS");
28990        }
28991        Ok(())
28992    }
28993
28994    fn generate_json_array(&mut self, e: &JSONArray) -> Result<()> {
28995        // Python: return self.func("JSON_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})")
28996        self.write_keyword("JSON_ARRAY");
28997        self.write("(");
28998        for (i, expr) in e.expressions.iter().enumerate() {
28999            if i > 0 {
29000                self.write(", ");
29001            }
29002            self.generate_expression(expr)?;
29003        }
29004        if let Some(null_handling) = &e.null_handling {
29005            self.write_space();
29006            self.generate_expression(null_handling)?;
29007        }
29008        if let Some(return_type) = &e.return_type {
29009            self.write_space();
29010            self.write_keyword("RETURNING");
29011            self.write_space();
29012            self.generate_expression(return_type)?;
29013        }
29014        if e.strict.is_some() {
29015            self.write_space();
29016            self.write_keyword("STRICT");
29017        }
29018        self.write(")");
29019        Ok(())
29020    }
29021
29022    fn generate_json_array_agg_struct(&mut self, e: &JSONArrayAgg) -> Result<()> {
29023        // JSON_ARRAYAGG(this [ORDER BY ...] [NULL ON NULL | ABSENT ON NULL] [RETURNING type] [STRICT])
29024        self.write_keyword("JSON_ARRAYAGG");
29025        self.write("(");
29026        self.generate_expression(&e.this)?;
29027        if let Some(order) = &e.order {
29028            self.write_space();
29029            // Order is stored as an OrderBy expression
29030            if let Expression::OrderBy(ob) = order.as_ref() {
29031                self.write_keyword("ORDER BY");
29032                self.write_space();
29033                for (i, ord) in ob.expressions.iter().enumerate() {
29034                    if i > 0 {
29035                        self.write(", ");
29036                    }
29037                    self.generate_ordered(ord)?;
29038                }
29039            } else {
29040                // Fallback: generate the expression directly
29041                self.generate_expression(order)?;
29042            }
29043        }
29044        if let Some(null_handling) = &e.null_handling {
29045            self.write_space();
29046            self.generate_expression(null_handling)?;
29047        }
29048        if let Some(return_type) = &e.return_type {
29049            self.write_space();
29050            self.write_keyword("RETURNING");
29051            self.write_space();
29052            self.generate_expression(return_type)?;
29053        }
29054        if e.strict.is_some() {
29055            self.write_space();
29056            self.write_keyword("STRICT");
29057        }
29058        self.write(")");
29059        Ok(())
29060    }
29061
29062    fn generate_json_object_agg_struct(&mut self, e: &JSONObjectAgg) -> Result<()> {
29063        // JSON_OBJECTAGG(key: value [NULL ON NULL | ABSENT ON NULL] [WITH UNIQUE KEYS] [RETURNING type])
29064        self.write_keyword("JSON_OBJECTAGG");
29065        self.write("(");
29066        for (i, expr) in e.expressions.iter().enumerate() {
29067            if i > 0 {
29068                self.write(", ");
29069            }
29070            self.generate_expression(expr)?;
29071        }
29072        if let Some(null_handling) = &e.null_handling {
29073            self.write_space();
29074            self.generate_expression(null_handling)?;
29075        }
29076        if let Some(unique_keys) = &e.unique_keys {
29077            self.write_space();
29078            if let Expression::Boolean(b) = unique_keys.as_ref() {
29079                if b.value {
29080                    self.write_keyword("WITH UNIQUE KEYS");
29081                } else {
29082                    self.write_keyword("WITHOUT UNIQUE KEYS");
29083                }
29084            }
29085        }
29086        if let Some(return_type) = &e.return_type {
29087            self.write_space();
29088            self.write_keyword("RETURNING");
29089            self.write_space();
29090            self.generate_expression(return_type)?;
29091        }
29092        self.write(")");
29093        Ok(())
29094    }
29095
29096    fn generate_json_array_append(&mut self, e: &JSONArrayAppend) -> Result<()> {
29097        // JSON_ARRAY_APPEND(this, path, value, ...)
29098        self.write_keyword("JSON_ARRAY_APPEND");
29099        self.write("(");
29100        self.generate_expression(&e.this)?;
29101        for expr in &e.expressions {
29102            self.write(", ");
29103            self.generate_expression(expr)?;
29104        }
29105        self.write(")");
29106        Ok(())
29107    }
29108
29109    fn generate_json_array_contains(&mut self, e: &JSONArrayContains) -> Result<()> {
29110        // JSON_ARRAY_CONTAINS(this, expression)
29111        self.write_keyword("JSON_ARRAY_CONTAINS");
29112        self.write("(");
29113        self.generate_expression(&e.this)?;
29114        self.write(", ");
29115        self.generate_expression(&e.expression)?;
29116        self.write(")");
29117        Ok(())
29118    }
29119
29120    fn generate_json_array_insert(&mut self, e: &JSONArrayInsert) -> Result<()> {
29121        // JSON_ARRAY_INSERT(this, path, value, ...)
29122        self.write_keyword("JSON_ARRAY_INSERT");
29123        self.write("(");
29124        self.generate_expression(&e.this)?;
29125        for expr in &e.expressions {
29126            self.write(", ");
29127            self.generate_expression(expr)?;
29128        }
29129        self.write(")");
29130        Ok(())
29131    }
29132
29133    fn generate_jsonb_exists(&mut self, e: &JSONBExists) -> Result<()> {
29134        // JSONB_EXISTS(this, path)
29135        self.write_keyword("JSONB_EXISTS");
29136        self.write("(");
29137        self.generate_expression(&e.this)?;
29138        if let Some(path) = &e.path {
29139            self.write(", ");
29140            self.generate_expression(path)?;
29141        }
29142        self.write(")");
29143        Ok(())
29144    }
29145
29146    fn generate_jsonb_extract_scalar(&mut self, e: &JSONBExtractScalar) -> Result<()> {
29147        // JSONB_EXTRACT_SCALAR(this, expression)
29148        self.write_keyword("JSONB_EXTRACT_SCALAR");
29149        self.write("(");
29150        self.generate_expression(&e.this)?;
29151        self.write(", ");
29152        self.generate_expression(&e.expression)?;
29153        self.write(")");
29154        Ok(())
29155    }
29156
29157    fn generate_jsonb_object_agg(&mut self, e: &JSONBObjectAgg) -> Result<()> {
29158        // JSONB_OBJECT_AGG(this, expression)
29159        self.write_keyword("JSONB_OBJECT_AGG");
29160        self.write("(");
29161        self.generate_expression(&e.this)?;
29162        self.write(", ");
29163        self.generate_expression(&e.expression)?;
29164        self.write(")");
29165        Ok(())
29166    }
29167
29168    fn generate_json_column_def(&mut self, e: &JSONColumnDef) -> Result<()> {
29169        // Python: NESTED PATH path schema | this kind PATH path [FOR ORDINALITY]
29170        if let Some(nested_schema) = &e.nested_schema {
29171            self.write_keyword("NESTED");
29172            if let Some(path) = &e.path {
29173                self.write_space();
29174                self.write_keyword("PATH");
29175                self.write_space();
29176                self.generate_expression(path)?;
29177            }
29178            self.write_space();
29179            self.generate_expression(nested_schema)?;
29180        } else {
29181            if let Some(this) = &e.this {
29182                self.generate_expression(this)?;
29183            }
29184            if let Some(kind) = &e.kind {
29185                self.write_space();
29186                self.write(kind);
29187            }
29188            if let Some(path) = &e.path {
29189                self.write_space();
29190                self.write_keyword("PATH");
29191                self.write_space();
29192                self.generate_expression(path)?;
29193            }
29194            if e.ordinality.is_some() {
29195                self.write_keyword(" FOR ORDINALITY");
29196            }
29197        }
29198        Ok(())
29199    }
29200
29201    fn generate_json_exists(&mut self, e: &JSONExists) -> Result<()> {
29202        // JSON_EXISTS(this, path PASSING vars ON ERROR/EMPTY condition)
29203        self.write_keyword("JSON_EXISTS");
29204        self.write("(");
29205        self.generate_expression(&e.this)?;
29206        if let Some(path) = &e.path {
29207            self.write(", ");
29208            self.generate_expression(path)?;
29209        }
29210        if let Some(passing) = &e.passing {
29211            self.write_space();
29212            self.write_keyword("PASSING");
29213            self.write_space();
29214            self.generate_expression(passing)?;
29215        }
29216        if let Some(on_condition) = &e.on_condition {
29217            self.write_space();
29218            self.generate_expression(on_condition)?;
29219        }
29220        self.write(")");
29221        Ok(())
29222    }
29223
29224    fn generate_json_cast(&mut self, e: &JSONCast) -> Result<()> {
29225        self.generate_expression(&e.this)?;
29226        self.write(".:");
29227        // If the data type has nested type parameters (like Array(JSON), Map(String, Int)),
29228        // wrap the entire type string in double quotes.
29229        // This matches Python sqlglot's ClickHouse _json_cast_sql behavior.
29230        if Self::data_type_has_nested_expressions(&e.to) {
29231            // Generate the data type to a temporary string buffer, then wrap in quotes
29232            let saved = std::mem::take(&mut self.output);
29233            self.generate_data_type(&e.to)?;
29234            let type_sql = std::mem::replace(&mut self.output, saved);
29235            self.write("\"");
29236            self.write(&type_sql);
29237            self.write("\"");
29238        } else {
29239            self.generate_data_type(&e.to)?;
29240        }
29241        Ok(())
29242    }
29243
29244    /// Check if a DataType has nested type expressions (sub-types).
29245    /// This corresponds to Python sqlglot's `to.expressions` being non-empty.
29246    fn data_type_has_nested_expressions(dt: &DataType) -> bool {
29247        matches!(
29248            dt,
29249            DataType::Array { .. }
29250                | DataType::Map { .. }
29251                | DataType::Struct { .. }
29252        )
29253    }
29254
29255    fn generate_json_extract_array(&mut self, e: &JSONExtractArray) -> Result<()> {
29256        // JSON_EXTRACT_ARRAY(this, expression)
29257        self.write_keyword("JSON_EXTRACT_ARRAY");
29258        self.write("(");
29259        self.generate_expression(&e.this)?;
29260        if let Some(expr) = &e.expression {
29261            self.write(", ");
29262            self.generate_expression(expr)?;
29263        }
29264        self.write(")");
29265        Ok(())
29266    }
29267
29268    fn generate_json_extract_quote(&mut self, e: &JSONExtractQuote) -> Result<()> {
29269        // Snowflake: KEEP [OMIT] QUOTES [SCALAR_ONLY] for JSON extraction
29270        if let Some(option) = &e.option {
29271            self.generate_expression(option)?;
29272            self.write_space();
29273        }
29274        self.write_keyword("QUOTES");
29275        if e.scalar.is_some() {
29276            self.write_keyword(" SCALAR_ONLY");
29277        }
29278        Ok(())
29279    }
29280
29281    fn generate_json_extract_scalar(&mut self, e: &JSONExtractScalar) -> Result<()> {
29282        // JSON_EXTRACT_SCALAR(this, expression)
29283        self.write_keyword("JSON_EXTRACT_SCALAR");
29284        self.write("(");
29285        self.generate_expression(&e.this)?;
29286        self.write(", ");
29287        self.generate_expression(&e.expression)?;
29288        self.write(")");
29289        Ok(())
29290    }
29291
29292    fn generate_json_extract_path(&mut self, e: &JSONExtract) -> Result<()> {
29293        // For variant_extract (Snowflake/Databricks colon syntax like a:field)
29294        // Databricks uses col:path syntax, Snowflake uses GET_PATH(col, 'path')
29295        // Otherwise output JSON_EXTRACT(this, expression)
29296        if e.variant_extract.is_some() {
29297            use crate::dialects::DialectType;
29298            if matches!(self.config.dialect, Some(DialectType::Databricks)) {
29299                // Databricks: output col:path syntax (e.g., c1:price, c1:price.foo, c1:price.bar[1])
29300                // Keys that are not safe identifiers (contain hyphens, spaces, etc.) must use
29301                // bracket notation: c:["x-y"] instead of c:x-y
29302                self.generate_expression(&e.this)?;
29303                self.write(":");
29304                match e.expression.as_ref() {
29305                    Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
29306                        let Literal::String(s) = lit.as_ref() else { unreachable!() };
29307                        self.write_databricks_json_path(s);
29308                    }
29309                    _ => {
29310                        // Fallback: generate as-is (shouldn't happen in typical cases)
29311                        self.generate_expression(&e.expression)?;
29312                    }
29313                }
29314            } else {
29315                // Snowflake and others: use GET_PATH(col, 'path')
29316                self.write_keyword("GET_PATH");
29317                self.write("(");
29318                self.generate_expression(&e.this)?;
29319                self.write(", ");
29320                self.generate_expression(&e.expression)?;
29321                self.write(")");
29322            }
29323        } else {
29324            self.write_keyword("JSON_EXTRACT");
29325            self.write("(");
29326            self.generate_expression(&e.this)?;
29327            self.write(", ");
29328            self.generate_expression(&e.expression)?;
29329            for expr in &e.expressions {
29330                self.write(", ");
29331                self.generate_expression(expr)?;
29332            }
29333            self.write(")");
29334        }
29335        Ok(())
29336    }
29337
29338    /// Write a Databricks JSON colon-path, using bracket notation for keys
29339    /// that are not safe identifiers (e.g., contain hyphens, spaces, etc.)
29340    /// Safe identifier regex: ^[_a-zA-Z]\w*$
29341    fn write_databricks_json_path(&mut self, path: &str) {
29342        // If the path already starts with bracket notation (e.g., '["fr\'uit"]'),
29343        // it was already formatted by the parser - output as-is
29344        if path.starts_with("[\"") || path.starts_with("['") {
29345            self.write(path);
29346            return;
29347        }
29348        // Split the path into segments at '.' boundaries, but preserve bracket subscripts
29349        // e.g., "price.items[0].name" -> ["price", "items[0]", "name"]
29350        // e.g., "x-y" -> ["x-y"]
29351        let mut first = true;
29352        for segment in path.split('.') {
29353            if !first {
29354                self.write(".");
29355            }
29356            first = false;
29357            // Check if there's a bracket subscript in this segment: "items[0]"
29358            if let Some(bracket_pos) = segment.find('[') {
29359                let key = &segment[..bracket_pos];
29360                let subscript = &segment[bracket_pos..];
29361                if key.is_empty() {
29362                    // Bracket notation at start of segment (e.g., already formatted)
29363                    self.write(segment);
29364                } else if Self::is_safe_json_path_key(key) {
29365                    self.write(key);
29366                    self.write(subscript);
29367                } else {
29368                    self.write("[\"");
29369                    self.write(key);
29370                    self.write("\"]");
29371                    self.write(subscript);
29372                }
29373            } else if Self::is_safe_json_path_key(segment) {
29374                self.write(segment);
29375            } else {
29376                self.write("[\"");
29377                self.write(segment);
29378                self.write("\"]");
29379            }
29380        }
29381    }
29382
29383    /// Check if a JSON path key is a safe identifier that doesn't need bracket quoting.
29384    /// Matches Python sqlglot's SAFE_IDENTIFIER_RE: ^[_a-zA-Z]\w*$
29385    fn is_safe_json_path_key(key: &str) -> bool {
29386        if key.is_empty() {
29387            return false;
29388        }
29389        let mut chars = key.chars();
29390        let first = chars.next().unwrap();
29391        if first != '_' && !first.is_ascii_alphabetic() {
29392            return false;
29393        }
29394        chars.all(|c| c == '_' || c.is_ascii_alphanumeric())
29395    }
29396
29397    fn generate_json_format(&mut self, e: &JSONFormat) -> Result<()> {
29398        // Output: {expr} FORMAT JSON
29399        // This wraps an expression with FORMAT JSON suffix (Oracle JSON function syntax)
29400        if let Some(this) = &e.this {
29401            self.generate_expression(this)?;
29402            self.write_space();
29403        }
29404        self.write_keyword("FORMAT JSON");
29405        Ok(())
29406    }
29407
29408    fn generate_json_key_value(&mut self, e: &JSONKeyValue) -> Result<()> {
29409        // key: value (for JSON objects)
29410        self.generate_expression(&e.this)?;
29411        self.write(": ");
29412        self.generate_expression(&e.expression)?;
29413        Ok(())
29414    }
29415
29416    fn generate_json_keys(&mut self, e: &JSONKeys) -> Result<()> {
29417        // JSON_KEYS(this, expression, expressions...)
29418        self.write_keyword("JSON_KEYS");
29419        self.write("(");
29420        self.generate_expression(&e.this)?;
29421        if let Some(expr) = &e.expression {
29422            self.write(", ");
29423            self.generate_expression(expr)?;
29424        }
29425        for expr in &e.expressions {
29426            self.write(", ");
29427            self.generate_expression(expr)?;
29428        }
29429        self.write(")");
29430        Ok(())
29431    }
29432
29433    fn generate_json_keys_at_depth(&mut self, e: &JSONKeysAtDepth) -> Result<()> {
29434        // JSON_KEYS(this, expression)
29435        self.write_keyword("JSON_KEYS");
29436        self.write("(");
29437        self.generate_expression(&e.this)?;
29438        if let Some(expr) = &e.expression {
29439            self.write(", ");
29440            self.generate_expression(expr)?;
29441        }
29442        self.write(")");
29443        Ok(())
29444    }
29445
29446    fn generate_json_path_expr(&mut self, e: &JSONPath) -> Result<()> {
29447        // JSONPath expression: generates a quoted path like '$.foo' or '$[0]'
29448        // The path components are concatenated without spaces
29449        let mut path_str = String::new();
29450        for expr in &e.expressions {
29451            match expr {
29452                Expression::JSONPathRoot(_) => {
29453                    path_str.push('$');
29454                }
29455                Expression::JSONPathKey(k) => {
29456                    // .key or ."key" (quote if key has special characters)
29457                    if let Expression::Literal(lit) =
29458                        k.this.as_ref()
29459                    {
29460                        if let crate::expressions::Literal::String(s) = lit.as_ref() {
29461                        path_str.push('.');
29462                        // Quote the key if it contains non-alphanumeric characters (hyphens, spaces, etc.)
29463                        let needs_quoting = s.chars().any(|c| !c.is_alphanumeric() && c != '_');
29464                        if needs_quoting {
29465                            path_str.push('"');
29466                            path_str.push_str(s);
29467                            path_str.push('"');
29468                        } else {
29469                            path_str.push_str(s);
29470                        }
29471                    }
29472                    }
29473                }
29474                Expression::JSONPathSubscript(s) => {
29475                    // [index]
29476                    if let Expression::Literal(lit) =
29477                        s.this.as_ref()
29478                    {
29479                        if let crate::expressions::Literal::Number(n) = lit.as_ref() {
29480                        path_str.push('[');
29481                        path_str.push_str(n);
29482                        path_str.push(']');
29483                    }
29484                    }
29485                }
29486                _ => {
29487                    // For other path parts, try to generate them
29488                    let mut temp_gen = Self::with_arc_config(self.config.clone());
29489                    temp_gen.generate_expression(expr)?;
29490                    path_str.push_str(&temp_gen.output);
29491                }
29492            }
29493        }
29494        // Output as quoted string
29495        self.write("'");
29496        self.write(&path_str);
29497        self.write("'");
29498        Ok(())
29499    }
29500
29501    fn generate_json_path_filter(&mut self, e: &JSONPathFilter) -> Result<()> {
29502        // JSON path filter: ?(predicate)
29503        self.write("?(");
29504        self.generate_expression(&e.this)?;
29505        self.write(")");
29506        Ok(())
29507    }
29508
29509    fn generate_json_path_key(&mut self, e: &JSONPathKey) -> Result<()> {
29510        // JSON path key: .key or ["key"]
29511        self.write(".");
29512        self.generate_expression(&e.this)?;
29513        Ok(())
29514    }
29515
29516    fn generate_json_path_recursive(&mut self, e: &JSONPathRecursive) -> Result<()> {
29517        // JSON path recursive descent: ..
29518        self.write("..");
29519        if let Some(this) = &e.this {
29520            self.generate_expression(this)?;
29521        }
29522        Ok(())
29523    }
29524
29525    fn generate_json_path_root(&mut self) -> Result<()> {
29526        // JSON path root: $
29527        self.write("$");
29528        Ok(())
29529    }
29530
29531    fn generate_json_path_script(&mut self, e: &JSONPathScript) -> Result<()> {
29532        // JSON path script: (expression)
29533        self.write("(");
29534        self.generate_expression(&e.this)?;
29535        self.write(")");
29536        Ok(())
29537    }
29538
29539    fn generate_json_path_selector(&mut self, e: &JSONPathSelector) -> Result<()> {
29540        // JSON path selector: *
29541        self.generate_expression(&e.this)?;
29542        Ok(())
29543    }
29544
29545    fn generate_json_path_slice(&mut self, e: &JSONPathSlice) -> Result<()> {
29546        // JSON path slice: [start:end:step]
29547        self.write("[");
29548        if let Some(start) = &e.start {
29549            self.generate_expression(start)?;
29550        }
29551        self.write(":");
29552        if let Some(end) = &e.end {
29553            self.generate_expression(end)?;
29554        }
29555        if let Some(step) = &e.step {
29556            self.write(":");
29557            self.generate_expression(step)?;
29558        }
29559        self.write("]");
29560        Ok(())
29561    }
29562
29563    fn generate_json_path_subscript(&mut self, e: &JSONPathSubscript) -> Result<()> {
29564        // JSON path subscript: [index] or [*]
29565        self.write("[");
29566        self.generate_expression(&e.this)?;
29567        self.write("]");
29568        Ok(())
29569    }
29570
29571    fn generate_json_path_union(&mut self, e: &JSONPathUnion) -> Result<()> {
29572        // JSON path union: [key1, key2, ...]
29573        self.write("[");
29574        for (i, expr) in e.expressions.iter().enumerate() {
29575            if i > 0 {
29576                self.write(", ");
29577            }
29578            self.generate_expression(expr)?;
29579        }
29580        self.write("]");
29581        Ok(())
29582    }
29583
29584    fn generate_json_remove(&mut self, e: &JSONRemove) -> Result<()> {
29585        // JSON_REMOVE(this, path1, path2, ...)
29586        self.write_keyword("JSON_REMOVE");
29587        self.write("(");
29588        self.generate_expression(&e.this)?;
29589        for expr in &e.expressions {
29590            self.write(", ");
29591            self.generate_expression(expr)?;
29592        }
29593        self.write(")");
29594        Ok(())
29595    }
29596
29597    fn generate_json_schema(&mut self, e: &JSONSchema) -> Result<()> {
29598        // COLUMNS(col1 type, col2 type, ...)
29599        // When pretty printing and content is too wide, format with each column on a separate line
29600        self.write_keyword("COLUMNS");
29601        self.write("(");
29602
29603        if self.config.pretty && !e.expressions.is_empty() {
29604            // First, generate all expressions into strings to check width
29605            let mut expr_strings: Vec<String> = Vec::with_capacity(e.expressions.len());
29606            for expr in &e.expressions {
29607                let mut temp_gen = Generator::with_arc_config(self.config.clone());
29608                temp_gen.generate_expression(expr)?;
29609                expr_strings.push(temp_gen.output);
29610            }
29611
29612            // Check if total width exceeds max_text_width
29613            if self.too_wide(&expr_strings) {
29614                // Pretty print: each column on its own line
29615                self.write_newline();
29616                self.indent_level += 1;
29617                for (i, expr_str) in expr_strings.iter().enumerate() {
29618                    if i > 0 {
29619                        self.write(",");
29620                        self.write_newline();
29621                    }
29622                    self.write_indent();
29623                    self.write(expr_str);
29624                }
29625                self.write_newline();
29626                self.indent_level -= 1;
29627                self.write_indent();
29628            } else {
29629                // Compact: all on one line
29630                for (i, expr_str) in expr_strings.iter().enumerate() {
29631                    if i > 0 {
29632                        self.write(", ");
29633                    }
29634                    self.write(expr_str);
29635                }
29636            }
29637        } else {
29638            // Non-pretty mode: compact format
29639            for (i, expr) in e.expressions.iter().enumerate() {
29640                if i > 0 {
29641                    self.write(", ");
29642                }
29643                self.generate_expression(expr)?;
29644            }
29645        }
29646        self.write(")");
29647        Ok(())
29648    }
29649
29650    fn generate_json_set(&mut self, e: &JSONSet) -> Result<()> {
29651        // JSON_SET(this, path, value, ...)
29652        self.write_keyword("JSON_SET");
29653        self.write("(");
29654        self.generate_expression(&e.this)?;
29655        for expr in &e.expressions {
29656            self.write(", ");
29657            self.generate_expression(expr)?;
29658        }
29659        self.write(")");
29660        Ok(())
29661    }
29662
29663    fn generate_json_strip_nulls(&mut self, e: &JSONStripNulls) -> Result<()> {
29664        // JSON_STRIP_NULLS(this, expression)
29665        self.write_keyword("JSON_STRIP_NULLS");
29666        self.write("(");
29667        self.generate_expression(&e.this)?;
29668        if let Some(expr) = &e.expression {
29669            self.write(", ");
29670            self.generate_expression(expr)?;
29671        }
29672        self.write(")");
29673        Ok(())
29674    }
29675
29676    fn generate_json_table(&mut self, e: &JSONTable) -> Result<()> {
29677        // JSON_TABLE(this, path [error_handling] [empty_handling] schema)
29678        self.write_keyword("JSON_TABLE");
29679        self.write("(");
29680        self.generate_expression(&e.this)?;
29681        if let Some(path) = &e.path {
29682            self.write(", ");
29683            self.generate_expression(path)?;
29684        }
29685        if let Some(error_handling) = &e.error_handling {
29686            self.write_space();
29687            self.generate_expression(error_handling)?;
29688        }
29689        if let Some(empty_handling) = &e.empty_handling {
29690            self.write_space();
29691            self.generate_expression(empty_handling)?;
29692        }
29693        if let Some(schema) = &e.schema {
29694            self.write_space();
29695            self.generate_expression(schema)?;
29696        }
29697        self.write(")");
29698        Ok(())
29699    }
29700
29701    fn generate_json_type(&mut self, e: &JSONType) -> Result<()> {
29702        // JSON_TYPE(this)
29703        self.write_keyword("JSON_TYPE");
29704        self.write("(");
29705        self.generate_expression(&e.this)?;
29706        self.write(")");
29707        Ok(())
29708    }
29709
29710    fn generate_json_value(&mut self, e: &JSONValue) -> Result<()> {
29711        // JSON_VALUE(this, path RETURNING type ON condition)
29712        self.write_keyword("JSON_VALUE");
29713        self.write("(");
29714        self.generate_expression(&e.this)?;
29715        if let Some(path) = &e.path {
29716            self.write(", ");
29717            self.generate_expression(path)?;
29718        }
29719        if let Some(returning) = &e.returning {
29720            self.write_space();
29721            self.write_keyword("RETURNING");
29722            self.write_space();
29723            self.generate_expression(returning)?;
29724        }
29725        if let Some(on_condition) = &e.on_condition {
29726            self.write_space();
29727            self.generate_expression(on_condition)?;
29728        }
29729        self.write(")");
29730        Ok(())
29731    }
29732
29733    fn generate_json_value_array(&mut self, e: &JSONValueArray) -> Result<()> {
29734        // JSON_VALUE_ARRAY(this)
29735        self.write_keyword("JSON_VALUE_ARRAY");
29736        self.write("(");
29737        self.generate_expression(&e.this)?;
29738        self.write(")");
29739        Ok(())
29740    }
29741
29742    fn generate_jarowinkler_similarity(&mut self, e: &JarowinklerSimilarity) -> Result<()> {
29743        // JAROWINKLER_SIMILARITY(str1, str2)
29744        self.write_keyword("JAROWINKLER_SIMILARITY");
29745        self.write("(");
29746        self.generate_expression(&e.this)?;
29747        self.write(", ");
29748        self.generate_expression(&e.expression)?;
29749        self.write(")");
29750        Ok(())
29751    }
29752
29753    fn generate_join_hint(&mut self, e: &JoinHint) -> Result<()> {
29754        // Python: this(expressions)
29755        self.generate_expression(&e.this)?;
29756        self.write("(");
29757        for (i, expr) in e.expressions.iter().enumerate() {
29758            if i > 0 {
29759                self.write(", ");
29760            }
29761            self.generate_expression(expr)?;
29762        }
29763        self.write(")");
29764        Ok(())
29765    }
29766
29767    fn generate_journal_property(&mut self, e: &JournalProperty) -> Result<()> {
29768        // Python: {no}{local}{dual}{before}{after}JOURNAL
29769        if e.no.is_some() {
29770            self.write_keyword("NO ");
29771        }
29772        if let Some(local) = &e.local {
29773            self.generate_expression(local)?;
29774            self.write_space();
29775        }
29776        if e.dual.is_some() {
29777            self.write_keyword("DUAL ");
29778        }
29779        if e.before.is_some() {
29780            self.write_keyword("BEFORE ");
29781        }
29782        if e.after.is_some() {
29783            self.write_keyword("AFTER ");
29784        }
29785        self.write_keyword("JOURNAL");
29786        Ok(())
29787    }
29788
29789    fn generate_language_property(&mut self, e: &LanguageProperty) -> Result<()> {
29790        // LANGUAGE language_name
29791        self.write_keyword("LANGUAGE");
29792        self.write_space();
29793        self.generate_expression(&e.this)?;
29794        Ok(())
29795    }
29796
29797    fn generate_lateral(&mut self, e: &Lateral) -> Result<()> {
29798        // Python: handles LATERAL VIEW (Hive/Spark) and regular LATERAL
29799        if e.view.is_some() {
29800            // LATERAL VIEW [OUTER] expression [alias] [AS columns]
29801            self.write_keyword("LATERAL VIEW");
29802            if e.outer.is_some() {
29803                self.write_space();
29804                self.write_keyword("OUTER");
29805            }
29806            self.write_space();
29807            self.generate_expression(&e.this)?;
29808            if let Some(alias) = &e.alias {
29809                self.write_space();
29810                self.write(alias);
29811            }
29812        } else {
29813            // LATERAL subquery/function [WITH ORDINALITY] [AS alias(columns)]
29814            self.write_keyword("LATERAL");
29815            self.write_space();
29816            self.generate_expression(&e.this)?;
29817            if e.ordinality.is_some() {
29818                self.write_space();
29819                self.write_keyword("WITH ORDINALITY");
29820            }
29821            if let Some(alias) = &e.alias {
29822                self.write_space();
29823                self.write_keyword("AS");
29824                self.write_space();
29825                self.write(alias);
29826                if !e.column_aliases.is_empty() {
29827                    self.write("(");
29828                    for (i, col) in e.column_aliases.iter().enumerate() {
29829                        if i > 0 {
29830                            self.write(", ");
29831                        }
29832                        self.write(col);
29833                    }
29834                    self.write(")");
29835                }
29836            }
29837        }
29838        Ok(())
29839    }
29840
29841    fn generate_like_property(&mut self, e: &LikeProperty) -> Result<()> {
29842        // Python: LIKE this [options]
29843        self.write_keyword("LIKE");
29844        self.write_space();
29845        self.generate_expression(&e.this)?;
29846        for expr in &e.expressions {
29847            self.write_space();
29848            self.generate_expression(expr)?;
29849        }
29850        Ok(())
29851    }
29852
29853    fn generate_limit(&mut self, e: &Limit) -> Result<()> {
29854        self.write_keyword("LIMIT");
29855        self.write_space();
29856        self.write_limit_expr(&e.this)?;
29857        if e.percent {
29858            self.write_space();
29859            self.write_keyword("PERCENT");
29860        }
29861        // Emit any comments that were captured from before the LIMIT keyword
29862        for comment in &e.comments {
29863            self.write(" ");
29864            self.write_formatted_comment(comment);
29865        }
29866        Ok(())
29867    }
29868
29869    fn generate_limit_options(&mut self, e: &LimitOptions) -> Result<()> {
29870        // Python: [PERCENT][ROWS][WITH TIES|ONLY]
29871        if e.percent.is_some() {
29872            self.write_keyword(" PERCENT");
29873        }
29874        if e.rows.is_some() {
29875            self.write_keyword(" ROWS");
29876        }
29877        if e.with_ties.is_some() {
29878            self.write_keyword(" WITH TIES");
29879        } else if e.rows.is_some() {
29880            self.write_keyword(" ONLY");
29881        }
29882        Ok(())
29883    }
29884
29885    fn generate_list(&mut self, e: &List) -> Result<()> {
29886        use crate::dialects::DialectType;
29887        let is_materialize = matches!(self.config.dialect, Some(DialectType::Materialize));
29888
29889        // Check if this is a subquery-based list (LIST(SELECT ...))
29890        if e.expressions.len() == 1 {
29891            if let Expression::Select(_) = &e.expressions[0] {
29892                self.write_keyword("LIST");
29893                self.write("(");
29894                self.generate_expression(&e.expressions[0])?;
29895                self.write(")");
29896                return Ok(());
29897            }
29898        }
29899
29900        // For Materialize, output as LIST[expr, expr, ...]
29901        if is_materialize {
29902            self.write_keyword("LIST");
29903            self.write("[");
29904            for (i, expr) in e.expressions.iter().enumerate() {
29905                if i > 0 {
29906                    self.write(", ");
29907                }
29908                self.generate_expression(expr)?;
29909            }
29910            self.write("]");
29911        } else {
29912            // For other dialects, output as LIST(expr, expr, ...)
29913            self.write_keyword("LIST");
29914            self.write("(");
29915            for (i, expr) in e.expressions.iter().enumerate() {
29916                if i > 0 {
29917                    self.write(", ");
29918                }
29919                self.generate_expression(expr)?;
29920            }
29921            self.write(")");
29922        }
29923        Ok(())
29924    }
29925
29926    fn generate_tomap(&mut self, e: &ToMap) -> Result<()> {
29927        // Check if this is a subquery-based map (MAP(SELECT ...))
29928        if let Expression::Select(_) = &*e.this {
29929            self.write_keyword("MAP");
29930            self.write("(");
29931            self.generate_expression(&e.this)?;
29932            self.write(")");
29933            return Ok(());
29934        }
29935
29936        let is_duckdb = matches!(self.config.dialect, Some(DialectType::DuckDB));
29937
29938        // For Struct-based map: DuckDB uses MAP {'key': value}, Materialize uses MAP['key' => value]
29939        self.write_keyword("MAP");
29940        if is_duckdb {
29941            self.write(" {");
29942        } else {
29943            self.write("[");
29944        }
29945        if let Expression::Struct(s) = &*e.this {
29946            for (i, (_, expr)) in s.fields.iter().enumerate() {
29947                if i > 0 {
29948                    self.write(", ");
29949                }
29950                if let Expression::PropertyEQ(op) = expr {
29951                    self.generate_expression(&op.left)?;
29952                    if is_duckdb {
29953                        self.write(": ");
29954                    } else {
29955                        self.write(" => ");
29956                    }
29957                    self.generate_expression(&op.right)?;
29958                } else {
29959                    self.generate_expression(expr)?;
29960                }
29961            }
29962        }
29963        if is_duckdb {
29964            self.write("}");
29965        } else {
29966            self.write("]");
29967        }
29968        Ok(())
29969    }
29970
29971    fn generate_localtime(&mut self, e: &Localtime) -> Result<()> {
29972        // Python: LOCALTIME or LOCALTIME(precision)
29973        self.write_keyword("LOCALTIME");
29974        if let Some(precision) = &e.this {
29975            self.write("(");
29976            self.generate_expression(precision)?;
29977            self.write(")");
29978        }
29979        Ok(())
29980    }
29981
29982    fn generate_localtimestamp(&mut self, e: &Localtimestamp) -> Result<()> {
29983        // Python: LOCALTIMESTAMP or LOCALTIMESTAMP(precision)
29984        self.write_keyword("LOCALTIMESTAMP");
29985        if let Some(precision) = &e.this {
29986            self.write("(");
29987            self.generate_expression(precision)?;
29988            self.write(")");
29989        }
29990        Ok(())
29991    }
29992
29993    fn generate_location_property(&mut self, e: &LocationProperty) -> Result<()> {
29994        // LOCATION 'path'
29995        self.write_keyword("LOCATION");
29996        self.write_space();
29997        self.generate_expression(&e.this)?;
29998        Ok(())
29999    }
30000
30001    fn generate_lock(&mut self, e: &Lock) -> Result<()> {
30002        // Python: FOR UPDATE|FOR SHARE [OF tables] [NOWAIT|WAIT n]
30003        if e.update.is_some() {
30004            if e.key.is_some() {
30005                self.write_keyword("FOR NO KEY UPDATE");
30006            } else {
30007                self.write_keyword("FOR UPDATE");
30008            }
30009        } else {
30010            if e.key.is_some() {
30011                self.write_keyword("FOR KEY SHARE");
30012            } else {
30013                self.write_keyword("FOR SHARE");
30014            }
30015        }
30016        if !e.expressions.is_empty() {
30017            self.write_keyword(" OF ");
30018            for (i, expr) in e.expressions.iter().enumerate() {
30019                if i > 0 {
30020                    self.write(", ");
30021                }
30022                self.generate_expression(expr)?;
30023            }
30024        }
30025        // Handle wait option following Python sqlglot convention:
30026        // - Boolean(true) -> NOWAIT
30027        // - Boolean(false) -> SKIP LOCKED
30028        // - Literal (number) -> WAIT n
30029        if let Some(wait) = &e.wait {
30030            match wait.as_ref() {
30031                Expression::Boolean(b) => {
30032                    if b.value {
30033                        self.write_keyword(" NOWAIT");
30034                    } else {
30035                        self.write_keyword(" SKIP LOCKED");
30036                    }
30037                }
30038                _ => {
30039                    // It's a literal (number), output WAIT n
30040                    self.write_keyword(" WAIT ");
30041                    self.generate_expression(wait)?;
30042                }
30043            }
30044        }
30045        Ok(())
30046    }
30047
30048    fn generate_lock_property(&mut self, e: &LockProperty) -> Result<()> {
30049        // LOCK property
30050        self.write_keyword("LOCK");
30051        self.write_space();
30052        self.generate_expression(&e.this)?;
30053        Ok(())
30054    }
30055
30056    fn generate_locking_property(&mut self, e: &LockingProperty) -> Result<()> {
30057        // Python: LOCKING kind [this] [for_or_in] lock_type [OVERRIDE]
30058        self.write_keyword("LOCKING");
30059        self.write_space();
30060        self.write(&e.kind);
30061        if let Some(this) = &e.this {
30062            self.write_space();
30063            self.generate_expression(this)?;
30064        }
30065        if let Some(for_or_in) = &e.for_or_in {
30066            self.write_space();
30067            self.generate_expression(for_or_in)?;
30068        }
30069        if let Some(lock_type) = &e.lock_type {
30070            self.write_space();
30071            self.generate_expression(lock_type)?;
30072        }
30073        if e.override_.is_some() {
30074            self.write_keyword(" OVERRIDE");
30075        }
30076        Ok(())
30077    }
30078
30079    fn generate_locking_statement(&mut self, e: &LockingStatement) -> Result<()> {
30080        // this expression
30081        self.generate_expression(&e.this)?;
30082        self.write_space();
30083        self.generate_expression(&e.expression)?;
30084        Ok(())
30085    }
30086
30087    fn generate_log_property(&mut self, e: &LogProperty) -> Result<()> {
30088        // [NO] LOG
30089        if e.no.is_some() {
30090            self.write_keyword("NO ");
30091        }
30092        self.write_keyword("LOG");
30093        Ok(())
30094    }
30095
30096    fn generate_md5_digest(&mut self, e: &MD5Digest) -> Result<()> {
30097        // MD5(this, expressions...)
30098        self.write_keyword("MD5");
30099        self.write("(");
30100        self.generate_expression(&e.this)?;
30101        for expr in &e.expressions {
30102            self.write(", ");
30103            self.generate_expression(expr)?;
30104        }
30105        self.write(")");
30106        Ok(())
30107    }
30108
30109    fn generate_ml_forecast(&mut self, e: &MLForecast) -> Result<()> {
30110        // ML.FORECAST(model, [params])
30111        self.write_keyword("ML.FORECAST");
30112        self.write("(");
30113        self.generate_expression(&e.this)?;
30114        if let Some(expression) = &e.expression {
30115            self.write(", ");
30116            self.generate_expression(expression)?;
30117        }
30118        if let Some(params) = &e.params_struct {
30119            self.write(", ");
30120            self.generate_expression(params)?;
30121        }
30122        self.write(")");
30123        Ok(())
30124    }
30125
30126    fn generate_ml_translate(&mut self, e: &MLTranslate) -> Result<()> {
30127        // ML.TRANSLATE(model, input, [params])
30128        self.write_keyword("ML.TRANSLATE");
30129        self.write("(");
30130        self.generate_expression(&e.this)?;
30131        self.write(", ");
30132        self.generate_expression(&e.expression)?;
30133        if let Some(params) = &e.params_struct {
30134            self.write(", ");
30135            self.generate_expression(params)?;
30136        }
30137        self.write(")");
30138        Ok(())
30139    }
30140
30141    fn generate_make_interval(&mut self, e: &MakeInterval) -> Result<()> {
30142        // MAKE_INTERVAL(years => x, months => y, ...)
30143        self.write_keyword("MAKE_INTERVAL");
30144        self.write("(");
30145        let mut first = true;
30146        if let Some(year) = &e.year {
30147            self.write("years => ");
30148            self.generate_expression(year)?;
30149            first = false;
30150        }
30151        if let Some(month) = &e.month {
30152            if !first {
30153                self.write(", ");
30154            }
30155            self.write("months => ");
30156            self.generate_expression(month)?;
30157            first = false;
30158        }
30159        if let Some(week) = &e.week {
30160            if !first {
30161                self.write(", ");
30162            }
30163            self.write("weeks => ");
30164            self.generate_expression(week)?;
30165            first = false;
30166        }
30167        if let Some(day) = &e.day {
30168            if !first {
30169                self.write(", ");
30170            }
30171            self.write("days => ");
30172            self.generate_expression(day)?;
30173            first = false;
30174        }
30175        if let Some(hour) = &e.hour {
30176            if !first {
30177                self.write(", ");
30178            }
30179            self.write("hours => ");
30180            self.generate_expression(hour)?;
30181            first = false;
30182        }
30183        if let Some(minute) = &e.minute {
30184            if !first {
30185                self.write(", ");
30186            }
30187            self.write("mins => ");
30188            self.generate_expression(minute)?;
30189            first = false;
30190        }
30191        if let Some(second) = &e.second {
30192            if !first {
30193                self.write(", ");
30194            }
30195            self.write("secs => ");
30196            self.generate_expression(second)?;
30197        }
30198        self.write(")");
30199        Ok(())
30200    }
30201
30202    fn generate_manhattan_distance(&mut self, e: &ManhattanDistance) -> Result<()> {
30203        // MANHATTAN_DISTANCE(vector1, vector2)
30204        self.write_keyword("MANHATTAN_DISTANCE");
30205        self.write("(");
30206        self.generate_expression(&e.this)?;
30207        self.write(", ");
30208        self.generate_expression(&e.expression)?;
30209        self.write(")");
30210        Ok(())
30211    }
30212
30213    fn generate_map(&mut self, e: &Map) -> Result<()> {
30214        // MAP(key1, value1, key2, value2, ...)
30215        self.write_keyword("MAP");
30216        self.write("(");
30217        for (i, (key, value)) in e.keys.iter().zip(e.values.iter()).enumerate() {
30218            if i > 0 {
30219                self.write(", ");
30220            }
30221            self.generate_expression(key)?;
30222            self.write(", ");
30223            self.generate_expression(value)?;
30224        }
30225        self.write(")");
30226        Ok(())
30227    }
30228
30229    fn generate_map_cat(&mut self, e: &MapCat) -> Result<()> {
30230        // MAP_CAT(map1, map2)
30231        self.write_keyword("MAP_CAT");
30232        self.write("(");
30233        self.generate_expression(&e.this)?;
30234        self.write(", ");
30235        self.generate_expression(&e.expression)?;
30236        self.write(")");
30237        Ok(())
30238    }
30239
30240    fn generate_map_delete(&mut self, e: &MapDelete) -> Result<()> {
30241        // MAP_DELETE(map, key1, key2, ...)
30242        self.write_keyword("MAP_DELETE");
30243        self.write("(");
30244        self.generate_expression(&e.this)?;
30245        for expr in &e.expressions {
30246            self.write(", ");
30247            self.generate_expression(expr)?;
30248        }
30249        self.write(")");
30250        Ok(())
30251    }
30252
30253    fn generate_map_insert(&mut self, e: &MapInsert) -> Result<()> {
30254        // MAP_INSERT(map, key, value, [update_flag])
30255        self.write_keyword("MAP_INSERT");
30256        self.write("(");
30257        self.generate_expression(&e.this)?;
30258        if let Some(key) = &e.key {
30259            self.write(", ");
30260            self.generate_expression(key)?;
30261        }
30262        if let Some(value) = &e.value {
30263            self.write(", ");
30264            self.generate_expression(value)?;
30265        }
30266        if let Some(update_flag) = &e.update_flag {
30267            self.write(", ");
30268            self.generate_expression(update_flag)?;
30269        }
30270        self.write(")");
30271        Ok(())
30272    }
30273
30274    fn generate_map_pick(&mut self, e: &MapPick) -> Result<()> {
30275        // MAP_PICK(map, key1, key2, ...)
30276        self.write_keyword("MAP_PICK");
30277        self.write("(");
30278        self.generate_expression(&e.this)?;
30279        for expr in &e.expressions {
30280            self.write(", ");
30281            self.generate_expression(expr)?;
30282        }
30283        self.write(")");
30284        Ok(())
30285    }
30286
30287    fn generate_masking_policy_column_constraint(
30288        &mut self,
30289        e: &MaskingPolicyColumnConstraint,
30290    ) -> Result<()> {
30291        // Python: MASKING POLICY name [USING (cols)]
30292        self.write_keyword("MASKING POLICY");
30293        self.write_space();
30294        self.generate_expression(&e.this)?;
30295        if !e.expressions.is_empty() {
30296            self.write_keyword(" USING");
30297            self.write(" (");
30298            for (i, expr) in e.expressions.iter().enumerate() {
30299                if i > 0 {
30300                    self.write(", ");
30301                }
30302                self.generate_expression(expr)?;
30303            }
30304            self.write(")");
30305        }
30306        Ok(())
30307    }
30308
30309    fn generate_match_against(&mut self, e: &MatchAgainst) -> Result<()> {
30310        if matches!(
30311            self.config.dialect,
30312            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
30313        ) {
30314            if e.expressions.len() > 1 {
30315                self.write("(");
30316            }
30317            for (i, expr) in e.expressions.iter().enumerate() {
30318                if i > 0 {
30319                    self.write_keyword(" OR ");
30320                }
30321                self.generate_expression(expr)?;
30322                self.write_space();
30323                self.write("@@");
30324                self.write_space();
30325                self.generate_expression(&e.this)?;
30326            }
30327            if e.expressions.len() > 1 {
30328                self.write(")");
30329            }
30330            return Ok(());
30331        }
30332
30333        // MATCH(columns) AGAINST(expr [modifier])
30334        self.write_keyword("MATCH");
30335        self.write("(");
30336        for (i, expr) in e.expressions.iter().enumerate() {
30337            if i > 0 {
30338                self.write(", ");
30339            }
30340            self.generate_expression(expr)?;
30341        }
30342        self.write(")");
30343        self.write_keyword(" AGAINST");
30344        self.write("(");
30345        self.generate_expression(&e.this)?;
30346        if let Some(modifier) = &e.modifier {
30347            self.write_space();
30348            self.generate_expression(modifier)?;
30349        }
30350        self.write(")");
30351        Ok(())
30352    }
30353
30354    fn generate_match_recognize_measure(&mut self, e: &MatchRecognizeMeasure) -> Result<()> {
30355        // Python: [window_frame] this
30356        if let Some(window_frame) = &e.window_frame {
30357            self.write(&format!("{:?}", window_frame).to_ascii_uppercase());
30358            self.write_space();
30359        }
30360        self.generate_expression(&e.this)?;
30361        Ok(())
30362    }
30363
30364    fn generate_materialized_property(&mut self, e: &MaterializedProperty) -> Result<()> {
30365        // MATERIALIZED [this]
30366        self.write_keyword("MATERIALIZED");
30367        if let Some(this) = &e.this {
30368            self.write_space();
30369            self.generate_expression(this)?;
30370        }
30371        Ok(())
30372    }
30373
30374    fn generate_merge(&mut self, e: &Merge) -> Result<()> {
30375        // MERGE INTO target USING source ON condition WHEN ...
30376        // DuckDB variant: MERGE INTO target USING source USING (key_columns) WHEN ...
30377        if let Some(with_) = &e.with_ {
30378            self.generate_expression(with_)?;
30379            self.write_space();
30380        }
30381        self.write_keyword("MERGE INTO");
30382        self.write_space();
30383        self.generate_expression(&e.this)?;
30384
30385        // USING clause - newline before in pretty mode
30386        if self.config.pretty {
30387            self.write_newline();
30388            self.write_indent();
30389        } else {
30390            self.write_space();
30391        }
30392        self.write_keyword("USING");
30393        self.write_space();
30394        self.generate_expression(&e.using)?;
30395
30396        // ON clause - newline before in pretty mode
30397        if let Some(on) = &e.on {
30398            if self.config.pretty {
30399                self.write_newline();
30400                self.write_indent();
30401            } else {
30402                self.write_space();
30403            }
30404            self.write_keyword("ON");
30405            self.write_space();
30406            self.generate_expression(on)?;
30407        }
30408        // DuckDB USING (key_columns) clause
30409        if let Some(using_cond) = &e.using_cond {
30410            self.write_space();
30411            self.write_keyword("USING");
30412            self.write_space();
30413            self.write("(");
30414            // using_cond is a Tuple containing the column identifiers
30415            if let Expression::Tuple(tuple) = using_cond.as_ref() {
30416                for (i, col) in tuple.expressions.iter().enumerate() {
30417                    if i > 0 {
30418                        self.write(", ");
30419                    }
30420                    self.generate_expression(col)?;
30421                }
30422            } else {
30423                self.generate_expression(using_cond)?;
30424            }
30425            self.write(")");
30426        }
30427        // For PostgreSQL dialect, extract target table name/alias to strip from UPDATE SET
30428        let saved_merge_strip = std::mem::take(&mut self.merge_strip_qualifiers);
30429        if matches!(
30430            self.config.dialect,
30431            Some(crate::DialectType::PostgreSQL)
30432                | Some(crate::DialectType::Redshift)
30433                | Some(crate::DialectType::Trino)
30434                | Some(crate::DialectType::Presto)
30435                | Some(crate::DialectType::Athena)
30436        ) {
30437            let mut names = Vec::new();
30438            match e.this.as_ref() {
30439                Expression::Alias(a) => {
30440                    // e.g., "x AS z" -> strip both "x" and "z"
30441                    if let Expression::Table(t) = &a.this {
30442                        names.push(t.name.name.clone());
30443                    } else if let Expression::Identifier(id) = &a.this {
30444                        names.push(id.name.clone());
30445                    }
30446                    names.push(a.alias.name.clone());
30447                }
30448                Expression::Table(t) => {
30449                    names.push(t.name.name.clone());
30450                }
30451                Expression::Identifier(id) => {
30452                    names.push(id.name.clone());
30453                }
30454                _ => {}
30455            }
30456            self.merge_strip_qualifiers = names;
30457        }
30458
30459        // WHEN clauses - newline before each in pretty mode
30460        if let Some(whens) = &e.whens {
30461            if self.config.pretty {
30462                self.write_newline();
30463                self.write_indent();
30464            } else {
30465                self.write_space();
30466            }
30467            self.generate_expression(whens)?;
30468        }
30469
30470        // Restore merge_strip_qualifiers
30471        self.merge_strip_qualifiers = saved_merge_strip;
30472
30473        // OUTPUT/RETURNING clause - newline before in pretty mode
30474        if let Some(returning) = &e.returning {
30475            if self.config.pretty {
30476                self.write_newline();
30477                self.write_indent();
30478            } else {
30479                self.write_space();
30480            }
30481            self.generate_expression(returning)?;
30482        }
30483        Ok(())
30484    }
30485
30486    fn generate_merge_block_ratio_property(&mut self, e: &MergeBlockRatioProperty) -> Result<()> {
30487        // Python: NO MERGEBLOCKRATIO | DEFAULT MERGEBLOCKRATIO | MERGEBLOCKRATIO=this [PERCENT]
30488        if e.no.is_some() {
30489            self.write_keyword("NO MERGEBLOCKRATIO");
30490        } else if e.default.is_some() {
30491            self.write_keyword("DEFAULT MERGEBLOCKRATIO");
30492        } else {
30493            self.write_keyword("MERGEBLOCKRATIO");
30494            self.write("=");
30495            if let Some(this) = &e.this {
30496                self.generate_expression(this)?;
30497            }
30498            if e.percent.is_some() {
30499                self.write_keyword(" PERCENT");
30500            }
30501        }
30502        Ok(())
30503    }
30504
30505    fn generate_merge_tree_ttl(&mut self, e: &MergeTreeTTL) -> Result<()> {
30506        // TTL expressions [WHERE where] [GROUP BY group] [SET aggregates]
30507        self.write_keyword("TTL");
30508        let pretty_clickhouse = self.config.pretty
30509            && matches!(
30510                self.config.dialect,
30511                Some(crate::dialects::DialectType::ClickHouse)
30512            );
30513
30514        if pretty_clickhouse {
30515            self.write_newline();
30516            self.indent_level += 1;
30517            for (i, expr) in e.expressions.iter().enumerate() {
30518                if i > 0 {
30519                    self.write(",");
30520                    self.write_newline();
30521                }
30522                self.write_indent();
30523                self.generate_expression(expr)?;
30524            }
30525            self.indent_level -= 1;
30526        } else {
30527            self.write_space();
30528            for (i, expr) in e.expressions.iter().enumerate() {
30529                if i > 0 {
30530                    self.write(", ");
30531                }
30532                self.generate_expression(expr)?;
30533            }
30534        }
30535
30536        if let Some(where_) = &e.where_ {
30537            if pretty_clickhouse {
30538                self.write_newline();
30539                if let Expression::Where(w) = where_.as_ref() {
30540                    self.write_indent();
30541                    self.write_keyword("WHERE");
30542                    self.write_newline();
30543                    self.indent_level += 1;
30544                    self.write_indent();
30545                    self.generate_expression(&w.this)?;
30546                    self.indent_level -= 1;
30547                } else {
30548                    self.write_indent();
30549                    self.generate_expression(where_)?;
30550                }
30551            } else {
30552                self.write_space();
30553                self.generate_expression(where_)?;
30554            }
30555        }
30556        if let Some(group) = &e.group {
30557            if pretty_clickhouse {
30558                self.write_newline();
30559                if let Expression::Group(g) = group.as_ref() {
30560                    self.write_indent();
30561                    self.write_keyword("GROUP BY");
30562                    self.write_newline();
30563                    self.indent_level += 1;
30564                    for (i, expr) in g.expressions.iter().enumerate() {
30565                        if i > 0 {
30566                            self.write(",");
30567                            self.write_newline();
30568                        }
30569                        self.write_indent();
30570                        self.generate_expression(expr)?;
30571                    }
30572                    self.indent_level -= 1;
30573                } else {
30574                    self.write_indent();
30575                    self.generate_expression(group)?;
30576                }
30577            } else {
30578                self.write_space();
30579                self.generate_expression(group)?;
30580            }
30581        }
30582        if let Some(aggregates) = &e.aggregates {
30583            if pretty_clickhouse {
30584                self.write_newline();
30585                self.write_indent();
30586                self.write_keyword("SET");
30587                self.write_newline();
30588                self.indent_level += 1;
30589                if let Expression::Tuple(t) = aggregates.as_ref() {
30590                    for (i, agg) in t.expressions.iter().enumerate() {
30591                        if i > 0 {
30592                            self.write(",");
30593                            self.write_newline();
30594                        }
30595                        self.write_indent();
30596                        self.generate_expression(agg)?;
30597                    }
30598                } else {
30599                    self.write_indent();
30600                    self.generate_expression(aggregates)?;
30601                }
30602                self.indent_level -= 1;
30603            } else {
30604                self.write_space();
30605                self.write_keyword("SET");
30606                self.write_space();
30607                self.generate_expression(aggregates)?;
30608            }
30609        }
30610        Ok(())
30611    }
30612
30613    fn generate_merge_tree_ttl_action(&mut self, e: &MergeTreeTTLAction) -> Result<()> {
30614        // Python: this [DELETE] [RECOMPRESS codec] [TO DISK disk] [TO VOLUME volume]
30615        self.generate_expression(&e.this)?;
30616        if e.delete.is_some() {
30617            self.write_keyword(" DELETE");
30618        }
30619        if let Some(recompress) = &e.recompress {
30620            self.write_keyword(" RECOMPRESS ");
30621            self.generate_expression(recompress)?;
30622        }
30623        if let Some(to_disk) = &e.to_disk {
30624            self.write_keyword(" TO DISK ");
30625            self.generate_expression(to_disk)?;
30626        }
30627        if let Some(to_volume) = &e.to_volume {
30628            self.write_keyword(" TO VOLUME ");
30629            self.generate_expression(to_volume)?;
30630        }
30631        Ok(())
30632    }
30633
30634    fn generate_minhash(&mut self, e: &Minhash) -> Result<()> {
30635        // MINHASH(this, expressions...)
30636        self.write_keyword("MINHASH");
30637        self.write("(");
30638        self.generate_expression(&e.this)?;
30639        for expr in &e.expressions {
30640            self.write(", ");
30641            self.generate_expression(expr)?;
30642        }
30643        self.write(")");
30644        Ok(())
30645    }
30646
30647    fn generate_model_attribute(&mut self, e: &ModelAttribute) -> Result<()> {
30648        // model!attribute - Snowflake syntax
30649        self.generate_expression(&e.this)?;
30650        self.write("!");
30651        self.generate_expression(&e.expression)?;
30652        Ok(())
30653    }
30654
30655    fn generate_monthname(&mut self, e: &Monthname) -> Result<()> {
30656        // MONTHNAME(this)
30657        self.write_keyword("MONTHNAME");
30658        self.write("(");
30659        self.generate_expression(&e.this)?;
30660        self.write(")");
30661        Ok(())
30662    }
30663
30664    fn generate_multitable_inserts(&mut self, e: &MultitableInserts) -> Result<()> {
30665        // Output leading comments
30666        for comment in &e.leading_comments {
30667            self.write_formatted_comment(comment);
30668            if self.config.pretty {
30669                self.write_newline();
30670                self.write_indent();
30671            } else {
30672                self.write_space();
30673            }
30674        }
30675        // Python: INSERT kind expressions source
30676        self.write_keyword("INSERT");
30677        self.write_space();
30678        self.write(&e.kind);
30679        if self.config.pretty {
30680            self.indent_level += 1;
30681            for expr in &e.expressions {
30682                self.write_newline();
30683                self.write_indent();
30684                self.generate_expression(expr)?;
30685            }
30686            self.indent_level -= 1;
30687        } else {
30688            for expr in &e.expressions {
30689                self.write_space();
30690                self.generate_expression(expr)?;
30691            }
30692        }
30693        if let Some(source) = &e.source {
30694            if self.config.pretty {
30695                self.write_newline();
30696                self.write_indent();
30697            } else {
30698                self.write_space();
30699            }
30700            self.generate_expression(source)?;
30701        }
30702        Ok(())
30703    }
30704
30705    fn generate_next_value_for(&mut self, e: &NextValueFor) -> Result<()> {
30706        // Python: NEXT VALUE FOR this [OVER (order)]
30707        self.write_keyword("NEXT VALUE FOR");
30708        self.write_space();
30709        self.generate_expression(&e.this)?;
30710        if let Some(order) = &e.order {
30711            self.write_space();
30712            self.write_keyword("OVER");
30713            self.write(" (");
30714            self.generate_expression(order)?;
30715            self.write(")");
30716        }
30717        Ok(())
30718    }
30719
30720    fn generate_normal(&mut self, e: &Normal) -> Result<()> {
30721        // NORMAL(mean, stddev, gen)
30722        self.write_keyword("NORMAL");
30723        self.write("(");
30724        self.generate_expression(&e.this)?;
30725        if let Some(stddev) = &e.stddev {
30726            self.write(", ");
30727            self.generate_expression(stddev)?;
30728        }
30729        if let Some(gen) = &e.gen {
30730            self.write(", ");
30731            self.generate_expression(gen)?;
30732        }
30733        self.write(")");
30734        Ok(())
30735    }
30736
30737    fn generate_normalize(&mut self, e: &Normalize) -> Result<()> {
30738        // NORMALIZE(this, form) or CASEFOLD version
30739        if e.is_casefold.is_some() {
30740            self.write_keyword("NORMALIZE_AND_CASEFOLD");
30741        } else {
30742            self.write_keyword("NORMALIZE");
30743        }
30744        self.write("(");
30745        self.generate_expression(&e.this)?;
30746        if let Some(form) = &e.form {
30747            self.write(", ");
30748            self.generate_expression(form)?;
30749        }
30750        self.write(")");
30751        Ok(())
30752    }
30753
30754    fn generate_not_null_column_constraint(&mut self, e: &NotNullColumnConstraint) -> Result<()> {
30755        // Python: [NOT ]NULL
30756        if e.allow_null.is_none() {
30757            self.write_keyword("NOT ");
30758        }
30759        self.write_keyword("NULL");
30760        Ok(())
30761    }
30762
30763    fn generate_nullif(&mut self, e: &Nullif) -> Result<()> {
30764        // NULLIF(this, expression)
30765        self.write_keyword("NULLIF");
30766        self.write("(");
30767        self.generate_expression(&e.this)?;
30768        self.write(", ");
30769        self.generate_expression(&e.expression)?;
30770        self.write(")");
30771        Ok(())
30772    }
30773
30774    fn generate_number_to_str(&mut self, e: &NumberToStr) -> Result<()> {
30775        // FORMAT(this, format, culture)
30776        self.write_keyword("FORMAT");
30777        self.write("(");
30778        self.generate_expression(&e.this)?;
30779        self.write(", '");
30780        self.write(&e.format);
30781        self.write("'");
30782        if let Some(culture) = &e.culture {
30783            self.write(", ");
30784            self.generate_expression(culture)?;
30785        }
30786        self.write(")");
30787        Ok(())
30788    }
30789
30790    fn generate_object_agg(&mut self, e: &ObjectAgg) -> Result<()> {
30791        // OBJECT_AGG(key, value)
30792        self.write_keyword("OBJECT_AGG");
30793        self.write("(");
30794        self.generate_expression(&e.this)?;
30795        self.write(", ");
30796        self.generate_expression(&e.expression)?;
30797        self.write(")");
30798        Ok(())
30799    }
30800
30801    fn generate_object_identifier(&mut self, e: &ObjectIdentifier) -> Result<()> {
30802        // Python: Just returns the name
30803        self.generate_expression(&e.this)?;
30804        Ok(())
30805    }
30806
30807    fn generate_object_insert(&mut self, e: &ObjectInsert) -> Result<()> {
30808        // OBJECT_INSERT(obj, key, value, [update_flag])
30809        self.write_keyword("OBJECT_INSERT");
30810        self.write("(");
30811        self.generate_expression(&e.this)?;
30812        if let Some(key) = &e.key {
30813            self.write(", ");
30814            self.generate_expression(key)?;
30815        }
30816        if let Some(value) = &e.value {
30817            self.write(", ");
30818            self.generate_expression(value)?;
30819        }
30820        if let Some(update_flag) = &e.update_flag {
30821            self.write(", ");
30822            self.generate_expression(update_flag)?;
30823        }
30824        self.write(")");
30825        Ok(())
30826    }
30827
30828    fn generate_offset(&mut self, e: &Offset) -> Result<()> {
30829        // OFFSET value [ROW|ROWS]
30830        self.write_keyword("OFFSET");
30831        self.write_space();
30832        self.generate_expression(&e.this)?;
30833        // Output ROWS keyword only for TSQL/Oracle targets
30834        if e.rows == Some(true)
30835            && matches!(
30836                self.config.dialect,
30837                Some(crate::dialects::DialectType::TSQL)
30838                    | Some(crate::dialects::DialectType::Oracle)
30839            )
30840        {
30841            self.write_space();
30842            self.write_keyword("ROWS");
30843        }
30844        Ok(())
30845    }
30846
30847    fn generate_qualify(&mut self, e: &Qualify) -> Result<()> {
30848        // QUALIFY condition (Snowflake/BigQuery)
30849        self.write_keyword("QUALIFY");
30850        self.write_space();
30851        self.generate_expression(&e.this)?;
30852        Ok(())
30853    }
30854
30855    fn generate_on_cluster(&mut self, e: &OnCluster) -> Result<()> {
30856        // ON CLUSTER cluster_name
30857        self.write_keyword("ON CLUSTER");
30858        self.write_space();
30859        self.generate_expression(&e.this)?;
30860        Ok(())
30861    }
30862
30863    fn generate_on_commit_property(&mut self, e: &OnCommitProperty) -> Result<()> {
30864        // ON COMMIT [DELETE ROWS | PRESERVE ROWS]
30865        self.write_keyword("ON COMMIT");
30866        if e.delete.is_some() {
30867            self.write_keyword(" DELETE ROWS");
30868        } else {
30869            self.write_keyword(" PRESERVE ROWS");
30870        }
30871        Ok(())
30872    }
30873
30874    fn generate_on_condition(&mut self, e: &OnCondition) -> Result<()> {
30875        // Python: error/empty/null handling
30876        if let Some(empty) = &e.empty {
30877            self.generate_expression(empty)?;
30878            self.write_keyword(" ON EMPTY");
30879        }
30880        if let Some(error) = &e.error {
30881            if e.empty.is_some() {
30882                self.write_space();
30883            }
30884            self.generate_expression(error)?;
30885            self.write_keyword(" ON ERROR");
30886        }
30887        if let Some(null) = &e.null {
30888            if e.empty.is_some() || e.error.is_some() {
30889                self.write_space();
30890            }
30891            self.generate_expression(null)?;
30892            self.write_keyword(" ON NULL");
30893        }
30894        Ok(())
30895    }
30896
30897    fn generate_on_conflict(&mut self, e: &OnConflict) -> Result<()> {
30898        // Materialize doesn't support ON CONFLICT - skip entirely
30899        if matches!(self.config.dialect, Some(DialectType::Materialize)) {
30900            return Ok(());
30901        }
30902        // Python: ON CONFLICT|ON DUPLICATE KEY [ON CONSTRAINT constraint] [conflict_keys] action
30903        if e.duplicate.is_some() {
30904            // MySQL: ON DUPLICATE KEY UPDATE col = val, ...
30905            self.write_keyword("ON DUPLICATE KEY UPDATE");
30906            for (i, expr) in e.expressions.iter().enumerate() {
30907                if i > 0 {
30908                    self.write(",");
30909                }
30910                self.write_space();
30911                self.generate_expression(expr)?;
30912            }
30913            return Ok(());
30914        } else {
30915            self.write_keyword("ON CONFLICT");
30916        }
30917        if let Some(constraint) = &e.constraint {
30918            self.write_keyword(" ON CONSTRAINT ");
30919            self.generate_expression(constraint)?;
30920        }
30921        if let Some(conflict_keys) = &e.conflict_keys {
30922            // conflict_keys can be a Tuple containing expressions
30923            if let Expression::Tuple(t) = conflict_keys.as_ref() {
30924                self.write("(");
30925                for (i, expr) in t.expressions.iter().enumerate() {
30926                    if i > 0 {
30927                        self.write(", ");
30928                    }
30929                    self.generate_expression(expr)?;
30930                }
30931                self.write(")");
30932            } else {
30933                self.write("(");
30934                self.generate_expression(conflict_keys)?;
30935                self.write(")");
30936            }
30937        }
30938        if let Some(index_predicate) = &e.index_predicate {
30939            self.write_keyword(" WHERE ");
30940            self.generate_expression(index_predicate)?;
30941        }
30942        if let Some(action) = &e.action {
30943            // Check if action is "NOTHING" or an UPDATE set
30944            if let Expression::Identifier(id) = action.as_ref() {
30945                if id.name.eq_ignore_ascii_case("NOTHING") {
30946                    self.write_keyword(" DO NOTHING");
30947                } else {
30948                    self.write_keyword(" DO ");
30949                    self.generate_expression(action)?;
30950                }
30951            } else if let Expression::Tuple(t) = action.as_ref() {
30952                // DO UPDATE SET col1 = val1, col2 = val2
30953                self.write_keyword(" DO UPDATE SET ");
30954                for (i, expr) in t.expressions.iter().enumerate() {
30955                    if i > 0 {
30956                        self.write(", ");
30957                    }
30958                    self.generate_expression(expr)?;
30959                }
30960            } else {
30961                self.write_keyword(" DO ");
30962                self.generate_expression(action)?;
30963            }
30964        }
30965        // WHERE clause for the UPDATE action
30966        if let Some(where_) = &e.where_ {
30967            self.write_keyword(" WHERE ");
30968            self.generate_expression(where_)?;
30969        }
30970        Ok(())
30971    }
30972
30973    fn generate_on_property(&mut self, e: &OnProperty) -> Result<()> {
30974        // ON property_value
30975        self.write_keyword("ON");
30976        self.write_space();
30977        self.generate_expression(&e.this)?;
30978        Ok(())
30979    }
30980
30981    fn generate_opclass(&mut self, e: &Opclass) -> Result<()> {
30982        // Python: this expression (e.g., column opclass)
30983        self.generate_expression(&e.this)?;
30984        self.write_space();
30985        self.generate_expression(&e.expression)?;
30986        Ok(())
30987    }
30988
30989    fn generate_open_json(&mut self, e: &OpenJSON) -> Result<()> {
30990        // Python: OPENJSON(this[, path]) [WITH (columns)]
30991        self.write_keyword("OPENJSON");
30992        self.write("(");
30993        self.generate_expression(&e.this)?;
30994        if let Some(path) = &e.path {
30995            self.write(", ");
30996            self.generate_expression(path)?;
30997        }
30998        self.write(")");
30999        if !e.expressions.is_empty() {
31000            self.write_keyword(" WITH");
31001            if self.config.pretty {
31002                self.write(" (\n");
31003                self.indent_level += 2;
31004                for (i, expr) in e.expressions.iter().enumerate() {
31005                    if i > 0 {
31006                        self.write(",\n");
31007                    }
31008                    self.write_indent();
31009                    self.generate_expression(expr)?;
31010                }
31011                self.write("\n");
31012                self.indent_level -= 2;
31013                self.write(")");
31014            } else {
31015                self.write(" (");
31016                for (i, expr) in e.expressions.iter().enumerate() {
31017                    if i > 0 {
31018                        self.write(", ");
31019                    }
31020                    self.generate_expression(expr)?;
31021                }
31022                self.write(")");
31023            }
31024        }
31025        Ok(())
31026    }
31027
31028    fn generate_open_json_column_def(&mut self, e: &OpenJSONColumnDef) -> Result<()> {
31029        // Python: this kind [path] [AS JSON]
31030        self.generate_expression(&e.this)?;
31031        self.write_space();
31032        // Use parsed data_type if available, otherwise fall back to kind string
31033        if let Some(ref dt) = e.data_type {
31034            self.generate_data_type(dt)?;
31035        } else if !e.kind.is_empty() {
31036            self.write(&e.kind);
31037        }
31038        if let Some(path) = &e.path {
31039            self.write_space();
31040            self.generate_expression(path)?;
31041        }
31042        if e.as_json.is_some() {
31043            self.write_keyword(" AS JSON");
31044        }
31045        Ok(())
31046    }
31047
31048    fn generate_operator(&mut self, e: &Operator) -> Result<()> {
31049        // this OPERATOR(op) expression
31050        self.generate_expression(&e.this)?;
31051        self.write_space();
31052        if let Some(op) = &e.operator {
31053            self.write_keyword("OPERATOR");
31054            self.write("(");
31055            self.generate_expression(op)?;
31056            self.write(")");
31057        }
31058        // Emit inline comments between OPERATOR() and the RHS
31059        for comment in &e.comments {
31060            self.write_space();
31061            self.write_formatted_comment(comment);
31062        }
31063        self.write_space();
31064        self.generate_expression(&e.expression)?;
31065        Ok(())
31066    }
31067
31068    fn generate_order_by(&mut self, e: &OrderBy) -> Result<()> {
31069        // ORDER BY expr1 [ASC|DESC] [NULLS FIRST|LAST], expr2 ...
31070        self.write_keyword("ORDER BY");
31071        let pretty_clickhouse_single_paren = self.config.pretty
31072            && matches!(self.config.dialect, Some(DialectType::ClickHouse))
31073            && e.expressions.len() == 1
31074            && matches!(e.expressions[0].this, Expression::Paren(ref p) if !matches!(p.this, Expression::Tuple(_)));
31075        let clickhouse_single_tuple = matches!(self.config.dialect, Some(DialectType::ClickHouse))
31076            && e.expressions.len() == 1
31077            && matches!(e.expressions[0].this, Expression::Tuple(_))
31078            && !e.expressions[0].desc
31079            && e.expressions[0].nulls_first.is_none();
31080
31081        if pretty_clickhouse_single_paren {
31082            self.write_space();
31083            if let Expression::Paren(p) = &e.expressions[0].this {
31084                self.write("(");
31085                self.write_newline();
31086                self.indent_level += 1;
31087                self.write_indent();
31088                self.generate_expression(&p.this)?;
31089                self.indent_level -= 1;
31090                self.write_newline();
31091                self.write(")");
31092            }
31093            return Ok(());
31094        }
31095
31096        if clickhouse_single_tuple {
31097            self.write_space();
31098            if let Expression::Tuple(t) = &e.expressions[0].this {
31099                self.write("(");
31100                for (i, expr) in t.expressions.iter().enumerate() {
31101                    if i > 0 {
31102                        self.write(", ");
31103                    }
31104                    self.generate_expression(expr)?;
31105                }
31106                self.write(")");
31107            }
31108            return Ok(());
31109        }
31110
31111        self.write_space();
31112        for (i, ordered) in e.expressions.iter().enumerate() {
31113            if i > 0 {
31114                self.write(", ");
31115            }
31116            self.generate_expression(&ordered.this)?;
31117            if ordered.desc {
31118                self.write_space();
31119                self.write_keyword("DESC");
31120            } else if ordered.explicit_asc {
31121                self.write_space();
31122                self.write_keyword("ASC");
31123            }
31124            if let Some(nulls_first) = ordered.nulls_first {
31125                // In Dremio, NULLS LAST is the default, so skip generating it
31126                let skip_nulls_last =
31127                    !nulls_first && matches!(self.config.dialect, Some(DialectType::Dremio));
31128                if !skip_nulls_last {
31129                    self.write_space();
31130                    self.write_keyword("NULLS");
31131                    self.write_space();
31132                    if nulls_first {
31133                        self.write_keyword("FIRST");
31134                    } else {
31135                        self.write_keyword("LAST");
31136                    }
31137                }
31138            }
31139        }
31140        Ok(())
31141    }
31142
31143    fn generate_output_model_property(&mut self, e: &OutputModelProperty) -> Result<()> {
31144        // OUTPUT(model)
31145        self.write_keyword("OUTPUT");
31146        self.write("(");
31147        if self.config.pretty {
31148            self.indent_level += 1;
31149            self.write_newline();
31150            self.write_indent();
31151            self.generate_expression(&e.this)?;
31152            self.indent_level -= 1;
31153            self.write_newline();
31154        } else {
31155            self.generate_expression(&e.this)?;
31156        }
31157        self.write(")");
31158        Ok(())
31159    }
31160
31161    fn generate_overflow_truncate_behavior(&mut self, e: &OverflowTruncateBehavior) -> Result<()> {
31162        // Python: TRUNCATE [filler] WITH|WITHOUT COUNT
31163        self.write_keyword("TRUNCATE");
31164        if let Some(this) = &e.this {
31165            self.write_space();
31166            self.generate_expression(this)?;
31167        }
31168        if e.with_count.is_some() {
31169            self.write_keyword(" WITH COUNT");
31170        } else {
31171            self.write_keyword(" WITHOUT COUNT");
31172        }
31173        Ok(())
31174    }
31175
31176    fn generate_parameterized_agg(&mut self, e: &ParameterizedAgg) -> Result<()> {
31177        // Python: name(expressions)(params)
31178        self.generate_expression(&e.this)?;
31179        self.write("(");
31180        for (i, expr) in e.expressions.iter().enumerate() {
31181            if i > 0 {
31182                self.write(", ");
31183            }
31184            self.generate_expression(expr)?;
31185        }
31186        self.write(")(");
31187        for (i, param) in e.params.iter().enumerate() {
31188            if i > 0 {
31189                self.write(", ");
31190            }
31191            self.generate_expression(param)?;
31192        }
31193        self.write(")");
31194        Ok(())
31195    }
31196
31197    fn generate_parse_datetime(&mut self, e: &ParseDatetime) -> Result<()> {
31198        // PARSE_DATETIME(format, this) or similar
31199        self.write_keyword("PARSE_DATETIME");
31200        self.write("(");
31201        if let Some(format) = &e.format {
31202            self.write("'");
31203            self.write(format);
31204            self.write("', ");
31205        }
31206        self.generate_expression(&e.this)?;
31207        if let Some(zone) = &e.zone {
31208            self.write(", ");
31209            self.generate_expression(zone)?;
31210        }
31211        self.write(")");
31212        Ok(())
31213    }
31214
31215    fn generate_parse_ip(&mut self, e: &ParseIp) -> Result<()> {
31216        // PARSE_IP(this, type, permissive)
31217        self.write_keyword("PARSE_IP");
31218        self.write("(");
31219        self.generate_expression(&e.this)?;
31220        if let Some(type_) = &e.type_ {
31221            self.write(", ");
31222            self.generate_expression(type_)?;
31223        }
31224        if let Some(permissive) = &e.permissive {
31225            self.write(", ");
31226            self.generate_expression(permissive)?;
31227        }
31228        self.write(")");
31229        Ok(())
31230    }
31231
31232    fn generate_parse_json(&mut self, e: &ParseJSON) -> Result<()> {
31233        // PARSE_JSON(this, [expression])
31234        self.write_keyword("PARSE_JSON");
31235        self.write("(");
31236        self.generate_expression(&e.this)?;
31237        if let Some(expression) = &e.expression {
31238            self.write(", ");
31239            self.generate_expression(expression)?;
31240        }
31241        self.write(")");
31242        Ok(())
31243    }
31244
31245    fn generate_parse_time(&mut self, e: &ParseTime) -> Result<()> {
31246        // PARSE_TIME(format, this) or STR_TO_TIME(this, format)
31247        self.write_keyword("PARSE_TIME");
31248        self.write("(");
31249        self.write(&format!("'{}'", e.format));
31250        self.write(", ");
31251        self.generate_expression(&e.this)?;
31252        self.write(")");
31253        Ok(())
31254    }
31255
31256    fn generate_parse_url(&mut self, e: &ParseUrl) -> Result<()> {
31257        // PARSE_URL(this, [part_to_extract], [key], [permissive])
31258        self.write_keyword("PARSE_URL");
31259        self.write("(");
31260        self.generate_expression(&e.this)?;
31261        if let Some(part) = &e.part_to_extract {
31262            self.write(", ");
31263            self.generate_expression(part)?;
31264        }
31265        if let Some(key) = &e.key {
31266            self.write(", ");
31267            self.generate_expression(key)?;
31268        }
31269        if let Some(permissive) = &e.permissive {
31270            self.write(", ");
31271            self.generate_expression(permissive)?;
31272        }
31273        self.write(")");
31274        Ok(())
31275    }
31276
31277    fn generate_partition_expr(&mut self, e: &Partition) -> Result<()> {
31278        // PARTITION(expr1, expr2, ...) or SUBPARTITION(expr1, expr2, ...)
31279        if e.subpartition {
31280            self.write_keyword("SUBPARTITION");
31281        } else {
31282            self.write_keyword("PARTITION");
31283        }
31284        self.write("(");
31285        for (i, expr) in e.expressions.iter().enumerate() {
31286            if i > 0 {
31287                self.write(", ");
31288            }
31289            self.generate_expression(expr)?;
31290        }
31291        self.write(")");
31292        Ok(())
31293    }
31294
31295    fn generate_partition_bound_spec(&mut self, e: &PartitionBoundSpec) -> Result<()> {
31296        // IN (values) or WITH (MODULUS this, REMAINDER expression) or FROM (from) TO (to)
31297        if let Some(this) = &e.this {
31298            if let Some(expression) = &e.expression {
31299                // WITH (MODULUS this, REMAINDER expression)
31300                self.write_keyword("WITH");
31301                self.write(" (");
31302                self.write_keyword("MODULUS");
31303                self.write_space();
31304                self.generate_expression(this)?;
31305                self.write(", ");
31306                self.write_keyword("REMAINDER");
31307                self.write_space();
31308                self.generate_expression(expression)?;
31309                self.write(")");
31310            } else {
31311                // IN (this) - this could be a list
31312                self.write_keyword("IN");
31313                self.write(" (");
31314                self.generate_partition_bound_values(this)?;
31315                self.write(")");
31316            }
31317        } else if let (Some(from), Some(to)) = (&e.from_expressions, &e.to_expressions) {
31318            // FROM (from_expressions) TO (to_expressions)
31319            self.write_keyword("FROM");
31320            self.write(" (");
31321            self.generate_partition_bound_values(from)?;
31322            self.write(") ");
31323            self.write_keyword("TO");
31324            self.write(" (");
31325            self.generate_partition_bound_values(to)?;
31326            self.write(")");
31327        }
31328        Ok(())
31329    }
31330
31331    /// Generate partition bound values - handles Tuple expressions by outputting
31332    /// contents without wrapping parens (since caller provides the parens)
31333    fn generate_partition_bound_values(&mut self, expr: &Expression) -> Result<()> {
31334        if let Expression::Tuple(t) = expr {
31335            for (i, e) in t.expressions.iter().enumerate() {
31336                if i > 0 {
31337                    self.write(", ");
31338                }
31339                self.generate_expression(e)?;
31340            }
31341            Ok(())
31342        } else {
31343            self.generate_expression(expr)
31344        }
31345    }
31346
31347    fn generate_partition_by_list_property(&mut self, e: &PartitionByListProperty) -> Result<()> {
31348        // PARTITION BY LIST (partition_expressions) (create_expressions)
31349        self.write_keyword("PARTITION BY LIST");
31350        if let Some(partition_exprs) = &e.partition_expressions {
31351            self.write(" (");
31352            // Unwrap Tuple for partition columns (don't generate outer parens from Tuple)
31353            self.generate_doris_partition_expressions(partition_exprs)?;
31354            self.write(")");
31355        }
31356        if let Some(create_exprs) = &e.create_expressions {
31357            self.write(" (");
31358            // Unwrap Tuple for partition definitions
31359            self.generate_doris_partition_definitions(create_exprs)?;
31360            self.write(")");
31361        }
31362        Ok(())
31363    }
31364
31365    fn generate_partition_by_range_property(&mut self, e: &PartitionByRangeProperty) -> Result<()> {
31366        // PARTITION BY RANGE (partition_expressions) (create_expressions)
31367        self.write_keyword("PARTITION BY RANGE");
31368        if let Some(partition_exprs) = &e.partition_expressions {
31369            self.write(" (");
31370            // Unwrap Tuple for partition columns (don't generate outer parens from Tuple)
31371            self.generate_doris_partition_expressions(partition_exprs)?;
31372            self.write(")");
31373        }
31374        if let Some(create_exprs) = &e.create_expressions {
31375            self.write(" (");
31376            // Check for dynamic partition (PartitionByRangePropertyDynamic) or static (Tuple of Partition)
31377            self.generate_doris_partition_definitions(create_exprs)?;
31378            self.write(")");
31379        }
31380        Ok(())
31381    }
31382
31383    /// Generate Doris partition column expressions (unwrap Tuple)
31384    fn generate_doris_partition_expressions(&mut self, expr: &Expression) -> Result<()> {
31385        if let Expression::Tuple(t) = expr {
31386            for (i, e) in t.expressions.iter().enumerate() {
31387                if i > 0 {
31388                    self.write(", ");
31389                }
31390                self.generate_expression(e)?;
31391            }
31392        } else {
31393            self.generate_expression(expr)?;
31394        }
31395        Ok(())
31396    }
31397
31398    /// Generate Doris partition definitions (comma-separated Partition expressions)
31399    fn generate_doris_partition_definitions(&mut self, expr: &Expression) -> Result<()> {
31400        match expr {
31401            Expression::Tuple(t) => {
31402                // Multiple partitions, comma-separated
31403                for (i, part) in t.expressions.iter().enumerate() {
31404                    if i > 0 {
31405                        self.write(", ");
31406                    }
31407                    // For Partition expressions, generate the inner PartitionRange/PartitionList directly
31408                    if let Expression::Partition(p) = part {
31409                        for (j, inner) in p.expressions.iter().enumerate() {
31410                            if j > 0 {
31411                                self.write(", ");
31412                            }
31413                            self.generate_expression(inner)?;
31414                        }
31415                    } else {
31416                        self.generate_expression(part)?;
31417                    }
31418                }
31419            }
31420            Expression::PartitionByRangePropertyDynamic(_) => {
31421                // Dynamic partition - FROM/TO/INTERVAL
31422                self.generate_expression(expr)?;
31423            }
31424            _ => {
31425                self.generate_expression(expr)?;
31426            }
31427        }
31428        Ok(())
31429    }
31430
31431    fn generate_partition_by_range_property_dynamic(
31432        &mut self,
31433        e: &PartitionByRangePropertyDynamic,
31434    ) -> Result<()> {
31435        if e.use_start_end {
31436            // StarRocks: START ('val') END ('val') EVERY (expr)
31437            if let Some(start) = &e.start {
31438                self.write_keyword("START");
31439                self.write(" (");
31440                self.generate_expression(start)?;
31441                self.write(")");
31442            }
31443            if let Some(end) = &e.end {
31444                self.write_space();
31445                self.write_keyword("END");
31446                self.write(" (");
31447                self.generate_expression(end)?;
31448                self.write(")");
31449            }
31450            if let Some(every) = &e.every {
31451                self.write_space();
31452                self.write_keyword("EVERY");
31453                self.write(" (");
31454                // Use unquoted interval format for StarRocks
31455                self.generate_doris_interval(every)?;
31456                self.write(")");
31457            }
31458        } else {
31459            // Doris: FROM (start) TO (end) INTERVAL n UNIT
31460            if let Some(start) = &e.start {
31461                self.write_keyword("FROM");
31462                self.write(" (");
31463                self.generate_expression(start)?;
31464                self.write(")");
31465            }
31466            if let Some(end) = &e.end {
31467                self.write_space();
31468                self.write_keyword("TO");
31469                self.write(" (");
31470                self.generate_expression(end)?;
31471                self.write(")");
31472            }
31473            if let Some(every) = &e.every {
31474                self.write_space();
31475                // Generate INTERVAL n UNIT (not quoted, for Doris dynamic partition)
31476                self.generate_doris_interval(every)?;
31477            }
31478        }
31479        Ok(())
31480    }
31481
31482    /// Generate Doris-style interval without quoting numbers: INTERVAL n UNIT
31483    fn generate_doris_interval(&mut self, expr: &Expression) -> Result<()> {
31484        if let Expression::Interval(interval) = expr {
31485            self.write_keyword("INTERVAL");
31486            if let Some(ref value) = interval.this {
31487                self.write_space();
31488                // If the value is a string literal that looks like a number,
31489                // output it without quotes (matching Python sqlglot's
31490                // partitionbyrangepropertydynamic_sql which converts back to number)
31491                match value {
31492                    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()) =>
31493                    {
31494                        if let Literal::String(s) = lit.as_ref() {
31495                            self.write(s);
31496                        }
31497                    }
31498                    _ => {
31499                        self.generate_expression(value)?;
31500                    }
31501                }
31502            }
31503            if let Some(ref unit_spec) = interval.unit {
31504                self.write_space();
31505                self.write_interval_unit_spec(unit_spec)?;
31506            }
31507            Ok(())
31508        } else {
31509            self.generate_expression(expr)
31510        }
31511    }
31512
31513    fn generate_partition_by_truncate(&mut self, e: &PartitionByTruncate) -> Result<()> {
31514        // TRUNCATE(expression, this)
31515        self.write_keyword("TRUNCATE");
31516        self.write("(");
31517        self.generate_expression(&e.expression)?;
31518        self.write(", ");
31519        self.generate_expression(&e.this)?;
31520        self.write(")");
31521        Ok(())
31522    }
31523
31524    fn generate_partition_list(&mut self, e: &PartitionList) -> Result<()> {
31525        // Doris: PARTITION name VALUES IN (val1, val2)
31526        self.write_keyword("PARTITION");
31527        self.write_space();
31528        self.generate_expression(&e.this)?;
31529        self.write_space();
31530        self.write_keyword("VALUES IN");
31531        self.write(" (");
31532        for (i, expr) in e.expressions.iter().enumerate() {
31533            if i > 0 {
31534                self.write(", ");
31535            }
31536            self.generate_expression(expr)?;
31537        }
31538        self.write(")");
31539        Ok(())
31540    }
31541
31542    fn generate_partition_range(&mut self, e: &PartitionRange) -> Result<()> {
31543        // Check if this is a TSQL-style simple range (e.g., "2 TO 5")
31544        // TSQL ranges have no expressions and just use `this TO expression`
31545        if e.expressions.is_empty() && e.expression.is_some() {
31546            // TSQL: simple range like "2 TO 5" - no PARTITION keyword
31547            self.generate_expression(&e.this)?;
31548            self.write_space();
31549            self.write_keyword("TO");
31550            self.write_space();
31551            self.generate_expression(e.expression.as_ref().unwrap())?;
31552            return Ok(());
31553        }
31554
31555        // Doris: PARTITION name VALUES LESS THAN (val) or PARTITION name VALUES [(val1), (val2))
31556        self.write_keyword("PARTITION");
31557        self.write_space();
31558        self.generate_expression(&e.this)?;
31559        self.write_space();
31560
31561        // Check if expressions contain Tuple (bracket notation) or single values (LESS THAN)
31562        if e.expressions.len() == 1 {
31563            // Single value: VALUES LESS THAN (val)
31564            self.write_keyword("VALUES LESS THAN");
31565            self.write(" (");
31566            self.generate_expression(&e.expressions[0])?;
31567            self.write(")");
31568        } else if !e.expressions.is_empty() {
31569            // Multiple values with Tuple: VALUES [(val1), (val2))
31570            self.write_keyword("VALUES");
31571            self.write(" [");
31572            for (i, expr) in e.expressions.iter().enumerate() {
31573                if i > 0 {
31574                    self.write(", ");
31575                }
31576                // If the expr is a Tuple, generate its contents wrapped in parens
31577                if let Expression::Tuple(t) = expr {
31578                    self.write("(");
31579                    for (j, inner) in t.expressions.iter().enumerate() {
31580                        if j > 0 {
31581                            self.write(", ");
31582                        }
31583                        self.generate_expression(inner)?;
31584                    }
31585                    self.write(")");
31586                } else {
31587                    self.write("(");
31588                    self.generate_expression(expr)?;
31589                    self.write(")");
31590                }
31591            }
31592            self.write(")");
31593        }
31594        Ok(())
31595    }
31596
31597    fn generate_partitioned_by_bucket(&mut self, e: &PartitionedByBucket) -> Result<()> {
31598        // BUCKET(this, expression)
31599        self.write_keyword("BUCKET");
31600        self.write("(");
31601        self.generate_expression(&e.this)?;
31602        self.write(", ");
31603        self.generate_expression(&e.expression)?;
31604        self.write(")");
31605        Ok(())
31606    }
31607
31608    fn generate_partition_by_property(&mut self, e: &PartitionByProperty) -> Result<()> {
31609        // BigQuery table property: PARTITION BY expression [, expression ...]
31610        self.write_keyword("PARTITION BY");
31611        self.write_space();
31612        for (i, expr) in e.expressions.iter().enumerate() {
31613            if i > 0 {
31614                self.write(", ");
31615            }
31616            self.generate_expression(expr)?;
31617        }
31618        Ok(())
31619    }
31620
31621    fn generate_partitioned_by_property(&mut self, e: &PartitionedByProperty) -> Result<()> {
31622        // PARTITIONED BY this (Teradata/ClickHouse use PARTITION BY)
31623        if matches!(
31624            self.config.dialect,
31625            Some(crate::dialects::DialectType::Teradata)
31626                | Some(crate::dialects::DialectType::ClickHouse)
31627        ) {
31628            self.write_keyword("PARTITION BY");
31629        } else {
31630            self.write_keyword("PARTITIONED BY");
31631        }
31632        self.write_space();
31633        // In pretty mode, always use multiline tuple format for PARTITIONED BY
31634        if self.config.pretty {
31635            if let Expression::Tuple(ref tuple) = *e.this {
31636                self.write("(");
31637                self.write_newline();
31638                self.indent_level += 1;
31639                for (i, expr) in tuple.expressions.iter().enumerate() {
31640                    if i > 0 {
31641                        self.write(",");
31642                        self.write_newline();
31643                    }
31644                    self.write_indent();
31645                    self.generate_expression(expr)?;
31646                }
31647                self.indent_level -= 1;
31648                self.write_newline();
31649                self.write(")");
31650            } else {
31651                self.generate_expression(&e.this)?;
31652            }
31653        } else {
31654            self.generate_expression(&e.this)?;
31655        }
31656        Ok(())
31657    }
31658
31659    fn generate_partitioned_of_property(&mut self, e: &PartitionedOfProperty) -> Result<()> {
31660        // PARTITION OF this FOR VALUES expression or PARTITION OF this DEFAULT
31661        self.write_keyword("PARTITION OF");
31662        self.write_space();
31663        self.generate_expression(&e.this)?;
31664        // Check if expression is a PartitionBoundSpec
31665        if let Expression::PartitionBoundSpec(_) = e.expression.as_ref() {
31666            self.write_space();
31667            self.write_keyword("FOR VALUES");
31668            self.write_space();
31669            self.generate_expression(&e.expression)?;
31670        } else {
31671            self.write_space();
31672            self.write_keyword("DEFAULT");
31673        }
31674        Ok(())
31675    }
31676
31677    fn generate_period_for_system_time_constraint(
31678        &mut self,
31679        e: &PeriodForSystemTimeConstraint,
31680    ) -> Result<()> {
31681        // PERIOD FOR SYSTEM_TIME (this, expression)
31682        self.write_keyword("PERIOD FOR SYSTEM_TIME");
31683        self.write(" (");
31684        self.generate_expression(&e.this)?;
31685        self.write(", ");
31686        self.generate_expression(&e.expression)?;
31687        self.write(")");
31688        Ok(())
31689    }
31690
31691    fn generate_pivot_alias(&mut self, e: &PivotAlias) -> Result<()> {
31692        // value AS alias
31693        // The alias can be an identifier or an expression (e.g., string concatenation)
31694        self.generate_expression(&e.this)?;
31695        self.write_space();
31696        self.write_keyword("AS");
31697        self.write_space();
31698        // When target dialect uses identifiers for UNPIVOT aliases, convert literals to identifiers
31699        if self.config.unpivot_aliases_are_identifiers {
31700            match &e.alias {
31701                Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
31702                    let Literal::String(s) = lit.as_ref() else { unreachable!() };
31703                    // Convert string literal to identifier
31704                    self.generate_identifier(&Identifier::new(s.clone()))?;
31705                }
31706                Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(_)) => {
31707                    let Literal::Number(n) = lit.as_ref() else { unreachable!() };
31708                    // Convert number literal to quoted identifier
31709                    let mut id = Identifier::new(n.clone());
31710                    id.quoted = true;
31711                    self.generate_identifier(&id)?;
31712                }
31713                other => {
31714                    self.generate_expression(other)?;
31715                }
31716            }
31717        } else {
31718            self.generate_expression(&e.alias)?;
31719        }
31720        Ok(())
31721    }
31722
31723    fn generate_pivot_any(&mut self, e: &PivotAny) -> Result<()> {
31724        // ANY or ANY [expression]
31725        self.write_keyword("ANY");
31726        if let Some(this) = &e.this {
31727            self.write_space();
31728            self.generate_expression(this)?;
31729        }
31730        Ok(())
31731    }
31732
31733    fn generate_predict(&mut self, e: &Predict) -> Result<()> {
31734        // ML.PREDICT(MODEL this, expression, [params_struct])
31735        self.write_keyword("ML.PREDICT");
31736        self.write("(");
31737        self.write_keyword("MODEL");
31738        self.write_space();
31739        self.generate_expression(&e.this)?;
31740        self.write(", ");
31741        self.generate_expression(&e.expression)?;
31742        if let Some(params) = &e.params_struct {
31743            self.write(", ");
31744            self.generate_expression(params)?;
31745        }
31746        self.write(")");
31747        Ok(())
31748    }
31749
31750    fn generate_previous_day(&mut self, e: &PreviousDay) -> Result<()> {
31751        // PREVIOUS_DAY(this, expression)
31752        self.write_keyword("PREVIOUS_DAY");
31753        self.write("(");
31754        self.generate_expression(&e.this)?;
31755        self.write(", ");
31756        self.generate_expression(&e.expression)?;
31757        self.write(")");
31758        Ok(())
31759    }
31760
31761    fn generate_primary_key(&mut self, e: &PrimaryKey) -> Result<()> {
31762        // PRIMARY KEY [name] (columns) [INCLUDE (...)] [options]
31763        self.write_keyword("PRIMARY KEY");
31764        if let Some(name) = &e.this {
31765            self.write_space();
31766            self.generate_expression(name)?;
31767        }
31768        if !e.expressions.is_empty() {
31769            self.write(" (");
31770            for (i, expr) in e.expressions.iter().enumerate() {
31771                if i > 0 {
31772                    self.write(", ");
31773                }
31774                self.generate_expression(expr)?;
31775            }
31776            self.write(")");
31777        }
31778        if let Some(include) = &e.include {
31779            self.write_space();
31780            self.generate_expression(include)?;
31781        }
31782        if !e.options.is_empty() {
31783            self.write_space();
31784            for (i, opt) in e.options.iter().enumerate() {
31785                if i > 0 {
31786                    self.write_space();
31787                }
31788                self.generate_expression(opt)?;
31789            }
31790        }
31791        Ok(())
31792    }
31793
31794    fn generate_primary_key_column_constraint(
31795        &mut self,
31796        _e: &PrimaryKeyColumnConstraint,
31797    ) -> Result<()> {
31798        // PRIMARY KEY constraint at column level
31799        self.write_keyword("PRIMARY KEY");
31800        Ok(())
31801    }
31802
31803    fn generate_path_column_constraint(&mut self, e: &PathColumnConstraint) -> Result<()> {
31804        // PATH 'xpath' constraint for XMLTABLE/JSON_TABLE columns
31805        self.write_keyword("PATH");
31806        self.write_space();
31807        self.generate_expression(&e.this)?;
31808        Ok(())
31809    }
31810
31811    fn generate_projection_def(&mut self, e: &ProjectionDef) -> Result<()> {
31812        // PROJECTION this (expression)
31813        self.write_keyword("PROJECTION");
31814        self.write_space();
31815        self.generate_expression(&e.this)?;
31816        self.write(" (");
31817        self.generate_expression(&e.expression)?;
31818        self.write(")");
31819        Ok(())
31820    }
31821
31822    fn generate_properties(&mut self, e: &Properties) -> Result<()> {
31823        // Properties list
31824        for (i, prop) in e.expressions.iter().enumerate() {
31825            if i > 0 {
31826                self.write(", ");
31827            }
31828            self.generate_expression(prop)?;
31829        }
31830        Ok(())
31831    }
31832
31833    fn generate_property(&mut self, e: &Property) -> Result<()> {
31834        // name=value
31835        self.generate_expression(&e.this)?;
31836        if let Some(value) = &e.value {
31837            self.write("=");
31838            self.generate_expression(value)?;
31839        }
31840        Ok(())
31841    }
31842
31843    fn generate_options_property(&mut self, e: &OptionsProperty) -> Result<()> {
31844        self.write_keyword("OPTIONS");
31845        if e.entries.is_empty() {
31846            self.write(" ()");
31847            return Ok(());
31848        }
31849
31850        if self.config.pretty {
31851            self.write(" (");
31852            self.write_newline();
31853            self.indent_level += 1;
31854            for (i, entry) in e.entries.iter().enumerate() {
31855                if i > 0 {
31856                    self.write(",");
31857                    self.write_newline();
31858                }
31859                self.write_indent();
31860                self.generate_identifier(&entry.key)?;
31861                self.write("=");
31862                self.generate_expression(&entry.value)?;
31863            }
31864            self.indent_level -= 1;
31865            self.write_newline();
31866            self.write(")");
31867        } else {
31868            self.write(" (");
31869            for (i, entry) in e.entries.iter().enumerate() {
31870                if i > 0 {
31871                    self.write(", ");
31872                }
31873                self.generate_identifier(&entry.key)?;
31874                self.write("=");
31875                self.generate_expression(&entry.value)?;
31876            }
31877            self.write(")");
31878        }
31879        Ok(())
31880    }
31881
31882    /// Generate BigQuery-style OPTIONS clause: OPTIONS (key=value, key=value, ...)
31883    fn generate_options_clause(&mut self, options: &[Expression]) -> Result<()> {
31884        self.write_keyword("OPTIONS");
31885        self.write(" (");
31886        for (i, opt) in options.iter().enumerate() {
31887            if i > 0 {
31888                self.write(", ");
31889            }
31890            self.generate_option_expression(opt)?;
31891        }
31892        self.write(")");
31893        Ok(())
31894    }
31895
31896    /// Generate Doris/StarRocks-style PROPERTIES clause: PROPERTIES ('key'='value', 'key'='value', ...)
31897    fn generate_properties_clause(&mut self, properties: &[Expression]) -> Result<()> {
31898        self.write_keyword("PROPERTIES");
31899        self.write(" (");
31900        for (i, prop) in properties.iter().enumerate() {
31901            if i > 0 {
31902                self.write(", ");
31903            }
31904            self.generate_option_expression(prop)?;
31905        }
31906        self.write(")");
31907        Ok(())
31908    }
31909
31910    /// Generate Databricks-style ENVIRONMENT clause: ENVIRONMENT (key = 'value', key = 'value', ...)
31911    fn generate_environment_clause(&mut self, environment: &[Expression]) -> Result<()> {
31912        self.write_keyword("ENVIRONMENT");
31913        self.write(" (");
31914        for (i, env_item) in environment.iter().enumerate() {
31915            if i > 0 {
31916                self.write(", ");
31917            }
31918            self.generate_environment_expression(env_item)?;
31919        }
31920        self.write(")");
31921        Ok(())
31922    }
31923
31924    /// Generate an environment expression with spaces around =
31925    fn generate_environment_expression(&mut self, expr: &Expression) -> Result<()> {
31926        match expr {
31927            Expression::Eq(eq) => {
31928                // Generate key = value with spaces (Databricks ENVIRONMENT style)
31929                self.generate_expression(&eq.left)?;
31930                self.write(" = ");
31931                self.generate_expression(&eq.right)?;
31932                Ok(())
31933            }
31934            _ => self.generate_expression(expr),
31935        }
31936    }
31937
31938    /// Generate Hive-style TBLPROPERTIES clause: TBLPROPERTIES ('key'='value', ...)
31939    fn generate_tblproperties_clause(&mut self, options: &[Expression]) -> Result<()> {
31940        self.write_keyword("TBLPROPERTIES");
31941        if self.config.pretty {
31942            self.write(" (");
31943            self.write_newline();
31944            self.indent_level += 1;
31945            for (i, opt) in options.iter().enumerate() {
31946                if i > 0 {
31947                    self.write(",");
31948                    self.write_newline();
31949                }
31950                self.write_indent();
31951                self.generate_option_expression(opt)?;
31952            }
31953            self.indent_level -= 1;
31954            self.write_newline();
31955            self.write(")");
31956        } else {
31957            self.write(" (");
31958            for (i, opt) in options.iter().enumerate() {
31959                if i > 0 {
31960                    self.write(", ");
31961                }
31962                self.generate_option_expression(opt)?;
31963            }
31964            self.write(")");
31965        }
31966        Ok(())
31967    }
31968
31969    /// Generate an option expression without spaces around =
31970    fn generate_option_expression(&mut self, expr: &Expression) -> Result<()> {
31971        match expr {
31972            Expression::Eq(eq) => {
31973                // Generate key=value without spaces
31974                self.generate_expression(&eq.left)?;
31975                self.write("=");
31976                self.generate_expression(&eq.right)?;
31977                Ok(())
31978            }
31979            _ => self.generate_expression(expr),
31980        }
31981    }
31982
31983    fn generate_pseudo_type(&mut self, e: &PseudoType) -> Result<()> {
31984        // Just output the name
31985        self.generate_expression(&e.this)?;
31986        Ok(())
31987    }
31988
31989    fn generate_put(&mut self, e: &PutStmt) -> Result<()> {
31990        // PUT source_file @stage [options]
31991        self.write_keyword("PUT");
31992        self.write_space();
31993
31994        // Source file path - preserve original quoting
31995        if e.source_quoted {
31996            self.write("'");
31997            self.write(&e.source);
31998            self.write("'");
31999        } else {
32000            self.write(&e.source);
32001        }
32002
32003        self.write_space();
32004
32005        // Target stage reference - output the string directly (includes @)
32006        if let Expression::Literal(lit) = &e.target {
32007            if let Literal::String(s) = lit.as_ref() {
32008            self.write(s);
32009        }
32010        } else {
32011            self.generate_expression(&e.target)?;
32012        }
32013
32014        // Optional parameters: KEY=VALUE
32015        for param in &e.params {
32016            self.write_space();
32017            self.write(&param.name);
32018            if let Some(ref value) = param.value {
32019                self.write("=");
32020                self.generate_expression(value)?;
32021            }
32022        }
32023
32024        Ok(())
32025    }
32026
32027    fn generate_quantile(&mut self, e: &Quantile) -> Result<()> {
32028        // QUANTILE(this, quantile)
32029        self.write_keyword("QUANTILE");
32030        self.write("(");
32031        self.generate_expression(&e.this)?;
32032        if let Some(quantile) = &e.quantile {
32033            self.write(", ");
32034            self.generate_expression(quantile)?;
32035        }
32036        self.write(")");
32037        Ok(())
32038    }
32039
32040    fn generate_query_band(&mut self, e: &QueryBand) -> Result<()> {
32041        // QUERY_BAND = this [UPDATE] [FOR scope]
32042        if matches!(
32043            self.config.dialect,
32044            Some(crate::dialects::DialectType::Teradata)
32045        ) {
32046            self.write_keyword("SET");
32047            self.write_space();
32048        }
32049        self.write_keyword("QUERY_BAND");
32050        self.write(" = ");
32051        self.generate_expression(&e.this)?;
32052        if e.update.is_some() {
32053            self.write_space();
32054            self.write_keyword("UPDATE");
32055        }
32056        if let Some(scope) = &e.scope {
32057            self.write_space();
32058            self.write_keyword("FOR");
32059            self.write_space();
32060            self.generate_expression(scope)?;
32061        }
32062        Ok(())
32063    }
32064
32065    fn generate_query_option(&mut self, e: &QueryOption) -> Result<()> {
32066        // this = expression
32067        self.generate_expression(&e.this)?;
32068        if let Some(expression) = &e.expression {
32069            self.write(" = ");
32070            self.generate_expression(expression)?;
32071        }
32072        Ok(())
32073    }
32074
32075    fn generate_query_transform(&mut self, e: &QueryTransform) -> Result<()> {
32076        // TRANSFORM (expressions) [row_format_before] [RECORDWRITER record_writer] USING command_script [AS schema] [row_format_after] [RECORDREADER record_reader]
32077        self.write_keyword("TRANSFORM");
32078        self.write("(");
32079        for (i, expr) in e.expressions.iter().enumerate() {
32080            if i > 0 {
32081                self.write(", ");
32082            }
32083            self.generate_expression(expr)?;
32084        }
32085        self.write(")");
32086        if let Some(row_format_before) = &e.row_format_before {
32087            self.write_space();
32088            self.generate_expression(row_format_before)?;
32089        }
32090        if let Some(record_writer) = &e.record_writer {
32091            self.write_space();
32092            self.write_keyword("RECORDWRITER");
32093            self.write_space();
32094            self.generate_expression(record_writer)?;
32095        }
32096        if let Some(command_script) = &e.command_script {
32097            self.write_space();
32098            self.write_keyword("USING");
32099            self.write_space();
32100            self.generate_expression(command_script)?;
32101        }
32102        if let Some(schema) = &e.schema {
32103            self.write_space();
32104            self.write_keyword("AS");
32105            self.write_space();
32106            self.generate_expression(schema)?;
32107        }
32108        if let Some(row_format_after) = &e.row_format_after {
32109            self.write_space();
32110            self.generate_expression(row_format_after)?;
32111        }
32112        if let Some(record_reader) = &e.record_reader {
32113            self.write_space();
32114            self.write_keyword("RECORDREADER");
32115            self.write_space();
32116            self.generate_expression(record_reader)?;
32117        }
32118        Ok(())
32119    }
32120
32121    fn generate_randn(&mut self, e: &Randn) -> Result<()> {
32122        // RANDN([seed])
32123        self.write_keyword("RANDN");
32124        self.write("(");
32125        if let Some(this) = &e.this {
32126            self.generate_expression(this)?;
32127        }
32128        self.write(")");
32129        Ok(())
32130    }
32131
32132    fn generate_randstr(&mut self, e: &Randstr) -> Result<()> {
32133        // RANDSTR(this, [generator])
32134        self.write_keyword("RANDSTR");
32135        self.write("(");
32136        self.generate_expression(&e.this)?;
32137        if let Some(generator) = &e.generator {
32138            self.write(", ");
32139            self.generate_expression(generator)?;
32140        }
32141        self.write(")");
32142        Ok(())
32143    }
32144
32145    fn generate_range_bucket(&mut self, e: &RangeBucket) -> Result<()> {
32146        // RANGE_BUCKET(this, expression)
32147        self.write_keyword("RANGE_BUCKET");
32148        self.write("(");
32149        self.generate_expression(&e.this)?;
32150        self.write(", ");
32151        self.generate_expression(&e.expression)?;
32152        self.write(")");
32153        Ok(())
32154    }
32155
32156    fn generate_range_n(&mut self, e: &RangeN) -> Result<()> {
32157        // RANGE_N(this BETWEEN expressions [EACH each])
32158        self.write_keyword("RANGE_N");
32159        self.write("(");
32160        self.generate_expression(&e.this)?;
32161        self.write_space();
32162        self.write_keyword("BETWEEN");
32163        self.write_space();
32164        for (i, expr) in e.expressions.iter().enumerate() {
32165            if i > 0 {
32166                self.write(", ");
32167            }
32168            self.generate_expression(expr)?;
32169        }
32170        if let Some(each) = &e.each {
32171            self.write_space();
32172            self.write_keyword("EACH");
32173            self.write_space();
32174            self.generate_expression(each)?;
32175        }
32176        self.write(")");
32177        Ok(())
32178    }
32179
32180    fn generate_read_csv(&mut self, e: &ReadCSV) -> Result<()> {
32181        // READ_CSV(this, expressions...)
32182        self.write_keyword("READ_CSV");
32183        self.write("(");
32184        self.generate_expression(&e.this)?;
32185        for expr in &e.expressions {
32186            self.write(", ");
32187            self.generate_expression(expr)?;
32188        }
32189        self.write(")");
32190        Ok(())
32191    }
32192
32193    fn generate_read_parquet(&mut self, e: &ReadParquet) -> Result<()> {
32194        // READ_PARQUET(expressions...)
32195        self.write_keyword("READ_PARQUET");
32196        self.write("(");
32197        for (i, expr) in e.expressions.iter().enumerate() {
32198            if i > 0 {
32199                self.write(", ");
32200            }
32201            self.generate_expression(expr)?;
32202        }
32203        self.write(")");
32204        Ok(())
32205    }
32206
32207    fn generate_recursive_with_search(&mut self, e: &RecursiveWithSearch) -> Result<()> {
32208        // SEARCH kind FIRST BY this SET expression [USING using]
32209        // or CYCLE this SET expression [USING using]
32210        if e.kind == "CYCLE" {
32211            self.write_keyword("CYCLE");
32212        } else {
32213            self.write_keyword("SEARCH");
32214            self.write_space();
32215            self.write(&e.kind);
32216            self.write_space();
32217            self.write_keyword("FIRST BY");
32218        }
32219        self.write_space();
32220        self.generate_expression(&e.this)?;
32221        self.write_space();
32222        self.write_keyword("SET");
32223        self.write_space();
32224        self.generate_expression(&e.expression)?;
32225        if let Some(using) = &e.using {
32226            self.write_space();
32227            self.write_keyword("USING");
32228            self.write_space();
32229            self.generate_expression(using)?;
32230        }
32231        Ok(())
32232    }
32233
32234    fn generate_reduce(&mut self, e: &Reduce) -> Result<()> {
32235        // REDUCE(this, initial, merge, [finish])
32236        self.write_keyword("REDUCE");
32237        self.write("(");
32238        self.generate_expression(&e.this)?;
32239        if let Some(initial) = &e.initial {
32240            self.write(", ");
32241            self.generate_expression(initial)?;
32242        }
32243        if let Some(merge) = &e.merge {
32244            self.write(", ");
32245            self.generate_expression(merge)?;
32246        }
32247        if let Some(finish) = &e.finish {
32248            self.write(", ");
32249            self.generate_expression(finish)?;
32250        }
32251        self.write(")");
32252        Ok(())
32253    }
32254
32255    fn generate_reference(&mut self, e: &Reference) -> Result<()> {
32256        // REFERENCES this (expressions) [options]
32257        self.write_keyword("REFERENCES");
32258        self.write_space();
32259        self.generate_expression(&e.this)?;
32260        if !e.expressions.is_empty() {
32261            self.write(" (");
32262            for (i, expr) in e.expressions.iter().enumerate() {
32263                if i > 0 {
32264                    self.write(", ");
32265                }
32266                self.generate_expression(expr)?;
32267            }
32268            self.write(")");
32269        }
32270        for opt in &e.options {
32271            self.write_space();
32272            self.generate_expression(opt)?;
32273        }
32274        Ok(())
32275    }
32276
32277    fn generate_refresh(&mut self, e: &Refresh) -> Result<()> {
32278        // REFRESH [kind] this
32279        self.write_keyword("REFRESH");
32280        if !e.kind.is_empty() {
32281            self.write_space();
32282            self.write_keyword(&e.kind);
32283        }
32284        self.write_space();
32285        self.generate_expression(&e.this)?;
32286        Ok(())
32287    }
32288
32289    fn generate_refresh_trigger_property(&mut self, e: &RefreshTriggerProperty) -> Result<()> {
32290        // Doris REFRESH clause: REFRESH method ON kind [EVERY n UNIT] [STARTS 'datetime']
32291        self.write_keyword("REFRESH");
32292        self.write_space();
32293        self.write_keyword(&e.method);
32294
32295        if let Some(ref kind) = e.kind {
32296            self.write_space();
32297            self.write_keyword("ON");
32298            self.write_space();
32299            self.write_keyword(kind);
32300
32301            // EVERY n UNIT
32302            if let Some(ref every) = e.every {
32303                self.write_space();
32304                self.write_keyword("EVERY");
32305                self.write_space();
32306                self.generate_expression(every)?;
32307                if let Some(ref unit) = e.unit {
32308                    self.write_space();
32309                    self.write_keyword(unit);
32310                }
32311            }
32312
32313            // STARTS 'datetime'
32314            if let Some(ref starts) = e.starts {
32315                self.write_space();
32316                self.write_keyword("STARTS");
32317                self.write_space();
32318                self.generate_expression(starts)?;
32319            }
32320        }
32321        Ok(())
32322    }
32323
32324    fn generate_regexp_count(&mut self, e: &RegexpCount) -> Result<()> {
32325        // REGEXP_COUNT(this, expression, position, parameters)
32326        self.write_keyword("REGEXP_COUNT");
32327        self.write("(");
32328        self.generate_expression(&e.this)?;
32329        self.write(", ");
32330        self.generate_expression(&e.expression)?;
32331        if let Some(position) = &e.position {
32332            self.write(", ");
32333            self.generate_expression(position)?;
32334        }
32335        if let Some(parameters) = &e.parameters {
32336            self.write(", ");
32337            self.generate_expression(parameters)?;
32338        }
32339        self.write(")");
32340        Ok(())
32341    }
32342
32343    fn generate_regexp_extract_all(&mut self, e: &RegexpExtractAll) -> Result<()> {
32344        // REGEXP_EXTRACT_ALL(this, expression, group, parameters, position, occurrence)
32345        self.write_keyword("REGEXP_EXTRACT_ALL");
32346        self.write("(");
32347        self.generate_expression(&e.this)?;
32348        self.write(", ");
32349        self.generate_expression(&e.expression)?;
32350        if let Some(group) = &e.group {
32351            self.write(", ");
32352            self.generate_expression(group)?;
32353        }
32354        self.write(")");
32355        Ok(())
32356    }
32357
32358    fn generate_regexp_full_match(&mut self, e: &RegexpFullMatch) -> Result<()> {
32359        // REGEXP_FULL_MATCH(this, expression)
32360        self.write_keyword("REGEXP_FULL_MATCH");
32361        self.write("(");
32362        self.generate_expression(&e.this)?;
32363        self.write(", ");
32364        self.generate_expression(&e.expression)?;
32365        self.write(")");
32366        Ok(())
32367    }
32368
32369    fn generate_regexp_i_like(&mut self, e: &RegexpILike) -> Result<()> {
32370        use crate::dialects::DialectType;
32371        // PostgreSQL/Redshift uses ~* operator for case-insensitive regex matching
32372        if matches!(
32373            self.config.dialect,
32374            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
32375        ) && e.flag.is_none()
32376        {
32377            self.generate_expression(&e.this)?;
32378            self.write(" ~* ");
32379            self.generate_expression(&e.expression)?;
32380        } else if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
32381            // Snowflake uses REGEXP_LIKE(x, pattern, 'i')
32382            self.write_keyword("REGEXP_LIKE");
32383            self.write("(");
32384            self.generate_expression(&e.this)?;
32385            self.write(", ");
32386            self.generate_expression(&e.expression)?;
32387            self.write(", ");
32388            if let Some(flag) = &e.flag {
32389                self.generate_expression(flag)?;
32390            } else {
32391                self.write("'i'");
32392            }
32393            self.write(")");
32394        } else {
32395            // this REGEXP_ILIKE expression or REGEXP_ILIKE(this, expression, flag)
32396            self.generate_expression(&e.this)?;
32397            self.write_space();
32398            self.write_keyword("REGEXP_ILIKE");
32399            self.write_space();
32400            self.generate_expression(&e.expression)?;
32401            if let Some(flag) = &e.flag {
32402                self.write(", ");
32403                self.generate_expression(flag)?;
32404            }
32405        }
32406        Ok(())
32407    }
32408
32409    fn generate_regexp_instr(&mut self, e: &RegexpInstr) -> Result<()> {
32410        // REGEXP_INSTR(this, expression, position, occurrence, option, parameters, group)
32411        self.write_keyword("REGEXP_INSTR");
32412        self.write("(");
32413        self.generate_expression(&e.this)?;
32414        self.write(", ");
32415        self.generate_expression(&e.expression)?;
32416        if let Some(position) = &e.position {
32417            self.write(", ");
32418            self.generate_expression(position)?;
32419        }
32420        if let Some(occurrence) = &e.occurrence {
32421            self.write(", ");
32422            self.generate_expression(occurrence)?;
32423        }
32424        if let Some(option) = &e.option {
32425            self.write(", ");
32426            self.generate_expression(option)?;
32427        }
32428        if let Some(parameters) = &e.parameters {
32429            self.write(", ");
32430            self.generate_expression(parameters)?;
32431        }
32432        if let Some(group) = &e.group {
32433            self.write(", ");
32434            self.generate_expression(group)?;
32435        }
32436        self.write(")");
32437        Ok(())
32438    }
32439
32440    fn generate_regexp_split(&mut self, e: &RegexpSplit) -> Result<()> {
32441        // REGEXP_SPLIT(this, expression, limit)
32442        self.write_keyword("REGEXP_SPLIT");
32443        self.write("(");
32444        self.generate_expression(&e.this)?;
32445        self.write(", ");
32446        self.generate_expression(&e.expression)?;
32447        if let Some(limit) = &e.limit {
32448            self.write(", ");
32449            self.generate_expression(limit)?;
32450        }
32451        self.write(")");
32452        Ok(())
32453    }
32454
32455    fn generate_regr_avgx(&mut self, e: &RegrAvgx) -> Result<()> {
32456        // REGR_AVGX(this, expression)
32457        self.write_keyword("REGR_AVGX");
32458        self.write("(");
32459        self.generate_expression(&e.this)?;
32460        self.write(", ");
32461        self.generate_expression(&e.expression)?;
32462        self.write(")");
32463        Ok(())
32464    }
32465
32466    fn generate_regr_avgy(&mut self, e: &RegrAvgy) -> Result<()> {
32467        // REGR_AVGY(this, expression)
32468        self.write_keyword("REGR_AVGY");
32469        self.write("(");
32470        self.generate_expression(&e.this)?;
32471        self.write(", ");
32472        self.generate_expression(&e.expression)?;
32473        self.write(")");
32474        Ok(())
32475    }
32476
32477    fn generate_regr_count(&mut self, e: &RegrCount) -> Result<()> {
32478        // REGR_COUNT(this, expression)
32479        self.write_keyword("REGR_COUNT");
32480        self.write("(");
32481        self.generate_expression(&e.this)?;
32482        self.write(", ");
32483        self.generate_expression(&e.expression)?;
32484        self.write(")");
32485        Ok(())
32486    }
32487
32488    fn generate_regr_intercept(&mut self, e: &RegrIntercept) -> Result<()> {
32489        // REGR_INTERCEPT(this, expression)
32490        self.write_keyword("REGR_INTERCEPT");
32491        self.write("(");
32492        self.generate_expression(&e.this)?;
32493        self.write(", ");
32494        self.generate_expression(&e.expression)?;
32495        self.write(")");
32496        Ok(())
32497    }
32498
32499    fn generate_regr_r2(&mut self, e: &RegrR2) -> Result<()> {
32500        // REGR_R2(this, expression)
32501        self.write_keyword("REGR_R2");
32502        self.write("(");
32503        self.generate_expression(&e.this)?;
32504        self.write(", ");
32505        self.generate_expression(&e.expression)?;
32506        self.write(")");
32507        Ok(())
32508    }
32509
32510    fn generate_regr_slope(&mut self, e: &RegrSlope) -> Result<()> {
32511        // REGR_SLOPE(this, expression)
32512        self.write_keyword("REGR_SLOPE");
32513        self.write("(");
32514        self.generate_expression(&e.this)?;
32515        self.write(", ");
32516        self.generate_expression(&e.expression)?;
32517        self.write(")");
32518        Ok(())
32519    }
32520
32521    fn generate_regr_sxx(&mut self, e: &RegrSxx) -> Result<()> {
32522        // REGR_SXX(this, expression)
32523        self.write_keyword("REGR_SXX");
32524        self.write("(");
32525        self.generate_expression(&e.this)?;
32526        self.write(", ");
32527        self.generate_expression(&e.expression)?;
32528        self.write(")");
32529        Ok(())
32530    }
32531
32532    fn generate_regr_sxy(&mut self, e: &RegrSxy) -> Result<()> {
32533        // REGR_SXY(this, expression)
32534        self.write_keyword("REGR_SXY");
32535        self.write("(");
32536        self.generate_expression(&e.this)?;
32537        self.write(", ");
32538        self.generate_expression(&e.expression)?;
32539        self.write(")");
32540        Ok(())
32541    }
32542
32543    fn generate_regr_syy(&mut self, e: &RegrSyy) -> Result<()> {
32544        // REGR_SYY(this, expression)
32545        self.write_keyword("REGR_SYY");
32546        self.write("(");
32547        self.generate_expression(&e.this)?;
32548        self.write(", ");
32549        self.generate_expression(&e.expression)?;
32550        self.write(")");
32551        Ok(())
32552    }
32553
32554    fn generate_regr_valx(&mut self, e: &RegrValx) -> Result<()> {
32555        // REGR_VALX(this, expression)
32556        self.write_keyword("REGR_VALX");
32557        self.write("(");
32558        self.generate_expression(&e.this)?;
32559        self.write(", ");
32560        self.generate_expression(&e.expression)?;
32561        self.write(")");
32562        Ok(())
32563    }
32564
32565    fn generate_regr_valy(&mut self, e: &RegrValy) -> Result<()> {
32566        // REGR_VALY(this, expression)
32567        self.write_keyword("REGR_VALY");
32568        self.write("(");
32569        self.generate_expression(&e.this)?;
32570        self.write(", ");
32571        self.generate_expression(&e.expression)?;
32572        self.write(")");
32573        Ok(())
32574    }
32575
32576    fn generate_remote_with_connection_model_property(
32577        &mut self,
32578        e: &RemoteWithConnectionModelProperty,
32579    ) -> Result<()> {
32580        // REMOTE WITH CONNECTION this
32581        self.write_keyword("REMOTE WITH CONNECTION");
32582        self.write_space();
32583        self.generate_expression(&e.this)?;
32584        Ok(())
32585    }
32586
32587    fn generate_rename_column(&mut self, e: &RenameColumn) -> Result<()> {
32588        // RENAME COLUMN [IF EXISTS] this TO new_name
32589        self.write_keyword("RENAME COLUMN");
32590        if e.exists {
32591            self.write_space();
32592            self.write_keyword("IF EXISTS");
32593        }
32594        self.write_space();
32595        self.generate_expression(&e.this)?;
32596        if let Some(to) = &e.to {
32597            self.write_space();
32598            self.write_keyword("TO");
32599            self.write_space();
32600            self.generate_expression(to)?;
32601        }
32602        Ok(())
32603    }
32604
32605    fn generate_replace_partition(&mut self, e: &ReplacePartition) -> Result<()> {
32606        // REPLACE PARTITION expression [FROM source]
32607        self.write_keyword("REPLACE PARTITION");
32608        self.write_space();
32609        self.generate_expression(&e.expression)?;
32610        if let Some(source) = &e.source {
32611            self.write_space();
32612            self.write_keyword("FROM");
32613            self.write_space();
32614            self.generate_expression(source)?;
32615        }
32616        Ok(())
32617    }
32618
32619    fn generate_returning(&mut self, e: &Returning) -> Result<()> {
32620        // RETURNING expressions [INTO into]
32621        // TSQL and Fabric use OUTPUT instead of RETURNING
32622        let keyword = match self.config.dialect {
32623            Some(DialectType::TSQL) | Some(DialectType::Fabric) => "OUTPUT",
32624            _ => "RETURNING",
32625        };
32626        self.write_keyword(keyword);
32627        self.write_space();
32628        for (i, expr) in e.expressions.iter().enumerate() {
32629            if i > 0 {
32630                self.write(", ");
32631            }
32632            self.generate_expression(expr)?;
32633        }
32634        if let Some(into) = &e.into {
32635            self.write_space();
32636            self.write_keyword("INTO");
32637            self.write_space();
32638            self.generate_expression(into)?;
32639        }
32640        Ok(())
32641    }
32642
32643    fn generate_output_clause(&mut self, output: &OutputClause) -> Result<()> {
32644        // OUTPUT expressions [INTO into_table]
32645        self.write_space();
32646        self.write_keyword("OUTPUT");
32647        self.write_space();
32648        for (i, expr) in output.columns.iter().enumerate() {
32649            if i > 0 {
32650                self.write(", ");
32651            }
32652            self.generate_expression(expr)?;
32653        }
32654        if let Some(into_table) = &output.into_table {
32655            self.write_space();
32656            self.write_keyword("INTO");
32657            self.write_space();
32658            self.generate_expression(into_table)?;
32659        }
32660        Ok(())
32661    }
32662
32663    fn generate_returns_property(&mut self, e: &ReturnsProperty) -> Result<()> {
32664        // RETURNS [TABLE] this [NULL ON NULL INPUT | CALLED ON NULL INPUT]
32665        self.write_keyword("RETURNS");
32666        if e.is_table.is_some() {
32667            self.write_space();
32668            self.write_keyword("TABLE");
32669        }
32670        if let Some(table) = &e.table {
32671            self.write_space();
32672            self.generate_expression(table)?;
32673        } else if let Some(this) = &e.this {
32674            self.write_space();
32675            self.generate_expression(this)?;
32676        }
32677        if e.null.is_some() {
32678            self.write_space();
32679            self.write_keyword("NULL ON NULL INPUT");
32680        }
32681        Ok(())
32682    }
32683
32684    fn generate_rollback(&mut self, e: &Rollback) -> Result<()> {
32685        // ROLLBACK [TRANSACTION [transaction_name]] [TO savepoint]
32686        self.write_keyword("ROLLBACK");
32687
32688        // TSQL always uses ROLLBACK TRANSACTION
32689        if e.this.is_none()
32690            && matches!(
32691                self.config.dialect,
32692                Some(DialectType::TSQL) | Some(DialectType::Fabric)
32693            )
32694        {
32695            self.write_space();
32696            self.write_keyword("TRANSACTION");
32697        }
32698
32699        // Check if this has TRANSACTION keyword or transaction name
32700        if let Some(this) = &e.this {
32701            // Check if it's just the "TRANSACTION" marker or an actual transaction name
32702            let is_transaction_marker = matches!(
32703                this.as_ref(),
32704                Expression::Identifier(id) if id.name == "TRANSACTION"
32705            );
32706
32707            self.write_space();
32708            self.write_keyword("TRANSACTION");
32709
32710            // If it's a real transaction name, output it
32711            if !is_transaction_marker {
32712                self.write_space();
32713                self.generate_expression(this)?;
32714            }
32715        }
32716
32717        // Output TO savepoint
32718        if let Some(savepoint) = &e.savepoint {
32719            self.write_space();
32720            self.write_keyword("TO");
32721            self.write_space();
32722            self.generate_expression(savepoint)?;
32723        }
32724        Ok(())
32725    }
32726
32727    fn generate_rollup(&mut self, e: &Rollup) -> Result<()> {
32728        // Python: return f"ROLLUP {self.wrap(expressions)}" if expressions else "WITH ROLLUP"
32729        if e.expressions.is_empty() {
32730            self.write_keyword("WITH ROLLUP");
32731        } else {
32732            self.write_keyword("ROLLUP");
32733            self.write("(");
32734            for (i, expr) in e.expressions.iter().enumerate() {
32735                if i > 0 {
32736                    self.write(", ");
32737                }
32738                self.generate_expression(expr)?;
32739            }
32740            self.write(")");
32741        }
32742        Ok(())
32743    }
32744
32745    fn generate_row_format_delimited_property(
32746        &mut self,
32747        e: &RowFormatDelimitedProperty,
32748    ) -> Result<()> {
32749        // ROW FORMAT DELIMITED [FIELDS TERMINATED BY ...] [ESCAPED BY ...] [COLLECTION ITEMS TERMINATED BY ...] [MAP KEYS TERMINATED BY ...] [LINES TERMINATED BY ...] [NULL DEFINED AS ...]
32750        self.write_keyword("ROW FORMAT DELIMITED");
32751        if let Some(fields) = &e.fields {
32752            self.write_space();
32753            self.write_keyword("FIELDS TERMINATED BY");
32754            self.write_space();
32755            self.generate_expression(fields)?;
32756        }
32757        if let Some(escaped) = &e.escaped {
32758            self.write_space();
32759            self.write_keyword("ESCAPED BY");
32760            self.write_space();
32761            self.generate_expression(escaped)?;
32762        }
32763        if let Some(items) = &e.collection_items {
32764            self.write_space();
32765            self.write_keyword("COLLECTION ITEMS TERMINATED BY");
32766            self.write_space();
32767            self.generate_expression(items)?;
32768        }
32769        if let Some(keys) = &e.map_keys {
32770            self.write_space();
32771            self.write_keyword("MAP KEYS TERMINATED BY");
32772            self.write_space();
32773            self.generate_expression(keys)?;
32774        }
32775        if let Some(lines) = &e.lines {
32776            self.write_space();
32777            self.write_keyword("LINES TERMINATED BY");
32778            self.write_space();
32779            self.generate_expression(lines)?;
32780        }
32781        if let Some(null) = &e.null {
32782            self.write_space();
32783            self.write_keyword("NULL DEFINED AS");
32784            self.write_space();
32785            self.generate_expression(null)?;
32786        }
32787        if let Some(serde) = &e.serde {
32788            self.write_space();
32789            self.generate_expression(serde)?;
32790        }
32791        Ok(())
32792    }
32793
32794    fn generate_row_format_property(&mut self, e: &RowFormatProperty) -> Result<()> {
32795        // ROW FORMAT this
32796        self.write_keyword("ROW FORMAT");
32797        self.write_space();
32798        self.generate_expression(&e.this)?;
32799        Ok(())
32800    }
32801
32802    fn generate_row_format_serde_property(&mut self, e: &RowFormatSerdeProperty) -> Result<()> {
32803        // ROW FORMAT SERDE this [WITH SERDEPROPERTIES (...)]
32804        self.write_keyword("ROW FORMAT SERDE");
32805        self.write_space();
32806        self.generate_expression(&e.this)?;
32807        if let Some(props) = &e.serde_properties {
32808            self.write_space();
32809            // SerdeProperties generates its own "[WITH] SERDEPROPERTIES (...)"
32810            self.generate_expression(props)?;
32811        }
32812        Ok(())
32813    }
32814
32815    fn generate_sha2(&mut self, e: &SHA2) -> Result<()> {
32816        // SHA2(this, length)
32817        self.write_keyword("SHA2");
32818        self.write("(");
32819        self.generate_expression(&e.this)?;
32820        if let Some(length) = e.length {
32821            self.write(", ");
32822            self.write(&length.to_string());
32823        }
32824        self.write(")");
32825        Ok(())
32826    }
32827
32828    fn generate_sha2_digest(&mut self, e: &SHA2Digest) -> Result<()> {
32829        // SHA2_DIGEST(this, length)
32830        self.write_keyword("SHA2_DIGEST");
32831        self.write("(");
32832        self.generate_expression(&e.this)?;
32833        if let Some(length) = e.length {
32834            self.write(", ");
32835            self.write(&length.to_string());
32836        }
32837        self.write(")");
32838        Ok(())
32839    }
32840
32841    fn generate_safe_add(&mut self, e: &SafeAdd) -> Result<()> {
32842        let name = if matches!(
32843            self.config.dialect,
32844            Some(crate::dialects::DialectType::Spark)
32845                | Some(crate::dialects::DialectType::Databricks)
32846        ) {
32847            "TRY_ADD"
32848        } else {
32849            "SAFE_ADD"
32850        };
32851        self.write_keyword(name);
32852        self.write("(");
32853        self.generate_expression(&e.this)?;
32854        self.write(", ");
32855        self.generate_expression(&e.expression)?;
32856        self.write(")");
32857        Ok(())
32858    }
32859
32860    fn generate_safe_divide(&mut self, e: &SafeDivide) -> Result<()> {
32861        // SAFE_DIVIDE(this, expression)
32862        self.write_keyword("SAFE_DIVIDE");
32863        self.write("(");
32864        self.generate_expression(&e.this)?;
32865        self.write(", ");
32866        self.generate_expression(&e.expression)?;
32867        self.write(")");
32868        Ok(())
32869    }
32870
32871    fn generate_safe_multiply(&mut self, e: &SafeMultiply) -> Result<()> {
32872        let name = if matches!(
32873            self.config.dialect,
32874            Some(crate::dialects::DialectType::Spark)
32875                | Some(crate::dialects::DialectType::Databricks)
32876        ) {
32877            "TRY_MULTIPLY"
32878        } else {
32879            "SAFE_MULTIPLY"
32880        };
32881        self.write_keyword(name);
32882        self.write("(");
32883        self.generate_expression(&e.this)?;
32884        self.write(", ");
32885        self.generate_expression(&e.expression)?;
32886        self.write(")");
32887        Ok(())
32888    }
32889
32890    fn generate_safe_subtract(&mut self, e: &SafeSubtract) -> Result<()> {
32891        let name = if matches!(
32892            self.config.dialect,
32893            Some(crate::dialects::DialectType::Spark)
32894                | Some(crate::dialects::DialectType::Databricks)
32895        ) {
32896            "TRY_SUBTRACT"
32897        } else {
32898            "SAFE_SUBTRACT"
32899        };
32900        self.write_keyword(name);
32901        self.write("(");
32902        self.generate_expression(&e.this)?;
32903        self.write(", ");
32904        self.generate_expression(&e.expression)?;
32905        self.write(")");
32906        Ok(())
32907    }
32908
32909    /// Generate the body of a USING SAMPLE or TABLESAMPLE clause:
32910    /// METHOD (size UNIT) [REPEATABLE (seed)]
32911    fn generate_sample_body(&mut self, sample: &Sample) -> Result<()> {
32912        // Handle BUCKET sampling: TABLESAMPLE (BUCKET n OUT OF m [ON col])
32913        if matches!(sample.method, SampleMethod::Bucket) {
32914            self.write(" (");
32915            self.write_keyword("BUCKET");
32916            self.write_space();
32917            if let Some(ref num) = sample.bucket_numerator {
32918                self.generate_expression(num)?;
32919            }
32920            self.write_space();
32921            self.write_keyword("OUT OF");
32922            self.write_space();
32923            if let Some(ref denom) = sample.bucket_denominator {
32924                self.generate_expression(denom)?;
32925            }
32926            if let Some(ref field) = sample.bucket_field {
32927                self.write_space();
32928                self.write_keyword("ON");
32929                self.write_space();
32930                self.generate_expression(field)?;
32931            }
32932            self.write(")");
32933            return Ok(());
32934        }
32935
32936        // Output method name if explicitly specified, or for dialects that always require it
32937        let is_snowflake = matches!(
32938            self.config.dialect,
32939            Some(crate::dialects::DialectType::Snowflake)
32940        );
32941        let is_postgres = matches!(
32942            self.config.dialect,
32943            Some(crate::dialects::DialectType::PostgreSQL)
32944                | Some(crate::dialects::DialectType::Redshift)
32945        );
32946        // Databricks and Spark don't output method names
32947        let is_databricks = matches!(
32948            self.config.dialect,
32949            Some(crate::dialects::DialectType::Databricks)
32950        );
32951        let is_spark = matches!(
32952            self.config.dialect,
32953            Some(crate::dialects::DialectType::Spark)
32954        );
32955        let suppress_method = is_databricks || is_spark || sample.suppress_method_output;
32956        // PostgreSQL always outputs BERNOULLI for BERNOULLI samples
32957        let force_method = is_postgres && matches!(sample.method, SampleMethod::Bernoulli);
32958        if !suppress_method && (sample.explicit_method || is_snowflake || force_method) {
32959            self.write_space();
32960            if !sample.explicit_method && (is_snowflake || force_method) {
32961                // Snowflake/PostgreSQL defaults to BERNOULLI when no method is specified
32962                self.write_keyword("BERNOULLI");
32963            } else {
32964                match sample.method {
32965                    SampleMethod::Bernoulli => self.write_keyword("BERNOULLI"),
32966                    SampleMethod::System => self.write_keyword("SYSTEM"),
32967                    SampleMethod::Block => self.write_keyword("BLOCK"),
32968                    SampleMethod::Row => self.write_keyword("ROW"),
32969                    SampleMethod::Reservoir => self.write_keyword("RESERVOIR"),
32970                    SampleMethod::Percent => self.write_keyword("SYSTEM"),
32971                    SampleMethod::Bucket => {} // handled above
32972                }
32973            }
32974        }
32975
32976        // Output size, with or without parentheses depending on dialect
32977        let emit_size_no_parens = !self.config.tablesample_requires_parens;
32978        if emit_size_no_parens {
32979            self.write_space();
32980            match &sample.size {
32981                Expression::Tuple(tuple) => {
32982                    for (i, expr) in tuple.expressions.iter().enumerate() {
32983                        if i > 0 {
32984                            self.write(", ");
32985                        }
32986                        self.generate_expression(expr)?;
32987                    }
32988                }
32989                expr => self.generate_expression(expr)?,
32990            }
32991        } else {
32992            self.write(" (");
32993            self.generate_expression(&sample.size)?;
32994        }
32995
32996        // Determine unit
32997        let is_rows_method = matches!(
32998            sample.method,
32999            SampleMethod::Reservoir | SampleMethod::Row | SampleMethod::Bucket
33000        );
33001        let is_percent = matches!(
33002            sample.method,
33003            SampleMethod::Percent
33004                | SampleMethod::System
33005                | SampleMethod::Bernoulli
33006                | SampleMethod::Block
33007        );
33008
33009        // For Snowflake, PostgreSQL, and Presto/Trino, only output ROWS/PERCENT when the user explicitly wrote it (unit_after_size).
33010        // These dialects use bare numbers for percentage by default in TABLESAMPLE METHOD(size) syntax.
33011        // For Databricks and Spark, always output PERCENT for percentage samples.
33012        let is_presto = matches!(
33013            self.config.dialect,
33014            Some(crate::dialects::DialectType::Presto)
33015                | Some(crate::dialects::DialectType::Trino)
33016                | Some(crate::dialects::DialectType::Athena)
33017        );
33018        let should_output_unit = if is_databricks || is_spark {
33019            // Always output PERCENT for percentage-based methods, or ROWS for row-based methods
33020            is_percent || is_rows_method || sample.unit_after_size
33021        } else if is_snowflake || is_postgres || is_presto {
33022            sample.unit_after_size
33023        } else {
33024            sample.unit_after_size || (sample.explicit_method && (is_rows_method || is_percent))
33025        };
33026
33027        if should_output_unit {
33028            self.write_space();
33029            if sample.is_percent {
33030                self.write_keyword("PERCENT");
33031            } else if is_rows_method && !sample.unit_after_size {
33032                self.write_keyword("ROWS");
33033            } else if sample.unit_after_size {
33034                match sample.method {
33035                    SampleMethod::Percent
33036                    | SampleMethod::System
33037                    | SampleMethod::Bernoulli
33038                    | SampleMethod::Block => {
33039                        self.write_keyword("PERCENT");
33040                    }
33041                    SampleMethod::Row | SampleMethod::Reservoir => {
33042                        self.write_keyword("ROWS");
33043                    }
33044                    _ => self.write_keyword("ROWS"),
33045                }
33046            } else {
33047                self.write_keyword("PERCENT");
33048            }
33049        }
33050
33051        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
33052            if let Some(ref offset) = sample.offset {
33053                self.write_space();
33054                self.write_keyword("OFFSET");
33055                self.write_space();
33056                self.generate_expression(offset)?;
33057            }
33058        }
33059        if !emit_size_no_parens {
33060            self.write(")");
33061        }
33062
33063        Ok(())
33064    }
33065
33066    fn generate_sample_property(&mut self, e: &SampleProperty) -> Result<()> {
33067        // SAMPLE this (ClickHouse uses SAMPLE BY)
33068        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
33069            self.write_keyword("SAMPLE BY");
33070        } else {
33071            self.write_keyword("SAMPLE");
33072        }
33073        self.write_space();
33074        self.generate_expression(&e.this)?;
33075        Ok(())
33076    }
33077
33078    fn generate_schema(&mut self, e: &Schema) -> Result<()> {
33079        // this (expressions...)
33080        if let Some(this) = &e.this {
33081            self.generate_expression(this)?;
33082        }
33083        if !e.expressions.is_empty() {
33084            // Add space before column list if there's a preceding expression
33085            if e.this.is_some() {
33086                self.write_space();
33087            }
33088            self.write("(");
33089            for (i, expr) in e.expressions.iter().enumerate() {
33090                if i > 0 {
33091                    self.write(", ");
33092                }
33093                self.generate_expression(expr)?;
33094            }
33095            self.write(")");
33096        }
33097        Ok(())
33098    }
33099
33100    fn generate_schema_comment_property(&mut self, e: &SchemaCommentProperty) -> Result<()> {
33101        // COMMENT this
33102        self.write_keyword("COMMENT");
33103        self.write_space();
33104        self.generate_expression(&e.this)?;
33105        Ok(())
33106    }
33107
33108    fn generate_scope_resolution(&mut self, e: &ScopeResolution) -> Result<()> {
33109        // [this::]expression
33110        if let Some(this) = &e.this {
33111            self.generate_expression(this)?;
33112            self.write("::");
33113        }
33114        self.generate_expression(&e.expression)?;
33115        Ok(())
33116    }
33117
33118    fn generate_search(&mut self, e: &Search) -> Result<()> {
33119        // SEARCH(this, expression, [json_scope], [analyzer], [analyzer_options], [search_mode])
33120        self.write_keyword("SEARCH");
33121        self.write("(");
33122        self.generate_expression(&e.this)?;
33123        self.write(", ");
33124        self.generate_expression(&e.expression)?;
33125        if let Some(json_scope) = &e.json_scope {
33126            self.write(", ");
33127            self.generate_expression(json_scope)?;
33128        }
33129        if let Some(analyzer) = &e.analyzer {
33130            self.write(", ");
33131            self.generate_expression(analyzer)?;
33132        }
33133        if let Some(analyzer_options) = &e.analyzer_options {
33134            self.write(", ");
33135            self.generate_expression(analyzer_options)?;
33136        }
33137        if let Some(search_mode) = &e.search_mode {
33138            self.write(", ");
33139            self.generate_expression(search_mode)?;
33140        }
33141        self.write(")");
33142        Ok(())
33143    }
33144
33145    fn generate_search_ip(&mut self, e: &SearchIp) -> Result<()> {
33146        // SEARCH_IP(this, expression)
33147        self.write_keyword("SEARCH_IP");
33148        self.write("(");
33149        self.generate_expression(&e.this)?;
33150        self.write(", ");
33151        self.generate_expression(&e.expression)?;
33152        self.write(")");
33153        Ok(())
33154    }
33155
33156    fn generate_security_property(&mut self, e: &SecurityProperty) -> Result<()> {
33157        // SECURITY this
33158        self.write_keyword("SECURITY");
33159        self.write_space();
33160        self.generate_expression(&e.this)?;
33161        Ok(())
33162    }
33163
33164    fn generate_semantic_view(&mut self, e: &SemanticView) -> Result<()> {
33165        // SEMANTIC_VIEW(this [METRICS ...] [DIMENSIONS ...] [FACTS ...] [WHERE ...])
33166        self.write("SEMANTIC_VIEW(");
33167
33168        if self.config.pretty {
33169            // Pretty print: each clause on its own line
33170            self.write_newline();
33171            self.indent_level += 1;
33172            self.write_indent();
33173            self.generate_expression(&e.this)?;
33174
33175            if let Some(metrics) = &e.metrics {
33176                self.write_newline();
33177                self.write_indent();
33178                self.write_keyword("METRICS");
33179                self.write_space();
33180                self.generate_semantic_view_tuple(metrics)?;
33181            }
33182            if let Some(dimensions) = &e.dimensions {
33183                self.write_newline();
33184                self.write_indent();
33185                self.write_keyword("DIMENSIONS");
33186                self.write_space();
33187                self.generate_semantic_view_tuple(dimensions)?;
33188            }
33189            if let Some(facts) = &e.facts {
33190                self.write_newline();
33191                self.write_indent();
33192                self.write_keyword("FACTS");
33193                self.write_space();
33194                self.generate_semantic_view_tuple(facts)?;
33195            }
33196            if let Some(where_) = &e.where_ {
33197                self.write_newline();
33198                self.write_indent();
33199                self.write_keyword("WHERE");
33200                self.write_space();
33201                self.generate_expression(where_)?;
33202            }
33203            self.write_newline();
33204            self.indent_level -= 1;
33205            self.write_indent();
33206        } else {
33207            // Compact: all on one line
33208            self.generate_expression(&e.this)?;
33209            if let Some(metrics) = &e.metrics {
33210                self.write_space();
33211                self.write_keyword("METRICS");
33212                self.write_space();
33213                self.generate_semantic_view_tuple(metrics)?;
33214            }
33215            if let Some(dimensions) = &e.dimensions {
33216                self.write_space();
33217                self.write_keyword("DIMENSIONS");
33218                self.write_space();
33219                self.generate_semantic_view_tuple(dimensions)?;
33220            }
33221            if let Some(facts) = &e.facts {
33222                self.write_space();
33223                self.write_keyword("FACTS");
33224                self.write_space();
33225                self.generate_semantic_view_tuple(facts)?;
33226            }
33227            if let Some(where_) = &e.where_ {
33228                self.write_space();
33229                self.write_keyword("WHERE");
33230                self.write_space();
33231                self.generate_expression(where_)?;
33232            }
33233        }
33234        self.write(")");
33235        Ok(())
33236    }
33237
33238    /// Helper for SEMANTIC_VIEW tuple contents (without parentheses)
33239    fn generate_semantic_view_tuple(&mut self, expr: &Expression) -> Result<()> {
33240        if let Expression::Tuple(t) = expr {
33241            for (i, e) in t.expressions.iter().enumerate() {
33242                if i > 0 {
33243                    self.write(", ");
33244                }
33245                self.generate_expression(e)?;
33246            }
33247        } else {
33248            self.generate_expression(expr)?;
33249        }
33250        Ok(())
33251    }
33252
33253    fn generate_sequence_properties(&mut self, e: &SequenceProperties) -> Result<()> {
33254        // [START WITH start] [INCREMENT BY increment] [MINVALUE minvalue] [MAXVALUE maxvalue] [CACHE cache] [OWNED BY owned]
33255        if let Some(start) = &e.start {
33256            self.write_keyword("START WITH");
33257            self.write_space();
33258            self.generate_expression(start)?;
33259        }
33260        if let Some(increment) = &e.increment {
33261            self.write_space();
33262            self.write_keyword("INCREMENT BY");
33263            self.write_space();
33264            self.generate_expression(increment)?;
33265        }
33266        if let Some(minvalue) = &e.minvalue {
33267            self.write_space();
33268            self.write_keyword("MINVALUE");
33269            self.write_space();
33270            self.generate_expression(minvalue)?;
33271        }
33272        if let Some(maxvalue) = &e.maxvalue {
33273            self.write_space();
33274            self.write_keyword("MAXVALUE");
33275            self.write_space();
33276            self.generate_expression(maxvalue)?;
33277        }
33278        if let Some(cache) = &e.cache {
33279            self.write_space();
33280            self.write_keyword("CACHE");
33281            self.write_space();
33282            self.generate_expression(cache)?;
33283        }
33284        if let Some(owned) = &e.owned {
33285            self.write_space();
33286            self.write_keyword("OWNED BY");
33287            self.write_space();
33288            self.generate_expression(owned)?;
33289        }
33290        for opt in &e.options {
33291            self.write_space();
33292            self.generate_expression(opt)?;
33293        }
33294        Ok(())
33295    }
33296
33297    fn generate_serde_properties(&mut self, e: &SerdeProperties) -> Result<()> {
33298        // [WITH] SERDEPROPERTIES (expressions)
33299        if e.with_.is_some() {
33300            self.write_keyword("WITH");
33301            self.write_space();
33302        }
33303        self.write_keyword("SERDEPROPERTIES");
33304        self.write(" (");
33305        for (i, expr) in e.expressions.iter().enumerate() {
33306            if i > 0 {
33307                self.write(", ");
33308            }
33309            // Generate key=value without spaces around =
33310            match expr {
33311                Expression::Eq(eq) => {
33312                    self.generate_expression(&eq.left)?;
33313                    self.write("=");
33314                    self.generate_expression(&eq.right)?;
33315                }
33316                _ => self.generate_expression(expr)?,
33317            }
33318        }
33319        self.write(")");
33320        Ok(())
33321    }
33322
33323    fn generate_session_parameter(&mut self, e: &SessionParameter) -> Result<()> {
33324        // @@[kind.]this
33325        self.write("@@");
33326        if let Some(kind) = &e.kind {
33327            self.write(kind);
33328            self.write(".");
33329        }
33330        self.generate_expression(&e.this)?;
33331        Ok(())
33332    }
33333
33334    fn generate_set(&mut self, e: &Set) -> Result<()> {
33335        // SET/UNSET [TAG] expressions
33336        if e.unset.is_some() {
33337            self.write_keyword("UNSET");
33338        } else {
33339            self.write_keyword("SET");
33340        }
33341        if e.tag.is_some() {
33342            self.write_space();
33343            self.write_keyword("TAG");
33344        }
33345        if !e.expressions.is_empty() {
33346            self.write_space();
33347            for (i, expr) in e.expressions.iter().enumerate() {
33348                if i > 0 {
33349                    self.write(", ");
33350                }
33351                self.generate_expression(expr)?;
33352            }
33353        }
33354        Ok(())
33355    }
33356
33357    fn generate_set_config_property(&mut self, e: &SetConfigProperty) -> Result<()> {
33358        // SET this or SETCONFIG this
33359        self.write_keyword("SET");
33360        self.write_space();
33361        self.generate_expression(&e.this)?;
33362        Ok(())
33363    }
33364
33365    fn generate_set_item(&mut self, e: &SetItem) -> Result<()> {
33366        // [kind] name = value
33367        if let Some(kind) = &e.kind {
33368            self.write_keyword(kind);
33369            self.write_space();
33370        }
33371        self.generate_expression(&e.name)?;
33372        self.write(" = ");
33373        self.generate_expression(&e.value)?;
33374        Ok(())
33375    }
33376
33377    fn generate_set_operation(&mut self, e: &SetOperation) -> Result<()> {
33378        // [WITH ...] this UNION|INTERSECT|EXCEPT [ALL|DISTINCT] [BY NAME] expression
33379        if let Some(with_) = &e.with_ {
33380            self.generate_expression(with_)?;
33381            self.write_space();
33382        }
33383        self.generate_expression(&e.this)?;
33384        self.write_space();
33385        // kind should be UNION, INTERSECT, EXCEPT, etc.
33386        if let Some(kind) = &e.kind {
33387            self.write_keyword(kind);
33388        }
33389        if e.distinct {
33390            self.write_space();
33391            self.write_keyword("DISTINCT");
33392        } else {
33393            self.write_space();
33394            self.write_keyword("ALL");
33395        }
33396        if e.by_name.is_some() {
33397            self.write_space();
33398            self.write_keyword("BY NAME");
33399        }
33400        self.write_space();
33401        self.generate_expression(&e.expression)?;
33402        Ok(())
33403    }
33404
33405    fn generate_set_property(&mut self, e: &SetProperty) -> Result<()> {
33406        // SET or MULTISET
33407        if e.multi.is_some() {
33408            self.write_keyword("MULTISET");
33409        } else {
33410            self.write_keyword("SET");
33411        }
33412        Ok(())
33413    }
33414
33415    fn generate_settings_property(&mut self, e: &SettingsProperty) -> Result<()> {
33416        // SETTINGS expressions
33417        self.write_keyword("SETTINGS");
33418        if self.config.pretty && e.expressions.len() > 1 {
33419            // Pretty print: each setting on its own line, indented
33420            self.indent_level += 1;
33421            for (i, expr) in e.expressions.iter().enumerate() {
33422                if i > 0 {
33423                    self.write(",");
33424                }
33425                self.write_newline();
33426                self.write_indent();
33427                self.generate_expression(expr)?;
33428            }
33429            self.indent_level -= 1;
33430        } else {
33431            self.write_space();
33432            for (i, expr) in e.expressions.iter().enumerate() {
33433                if i > 0 {
33434                    self.write(", ");
33435                }
33436                self.generate_expression(expr)?;
33437            }
33438        }
33439        Ok(())
33440    }
33441
33442    fn generate_sharing_property(&mut self, e: &SharingProperty) -> Result<()> {
33443        // SHARING = this
33444        self.write_keyword("SHARING");
33445        if let Some(this) = &e.this {
33446            self.write(" = ");
33447            self.generate_expression(this)?;
33448        }
33449        Ok(())
33450    }
33451
33452    fn generate_slice(&mut self, e: &Slice) -> Result<()> {
33453        // Python array slicing: begin:end:step
33454        if let Some(begin) = &e.this {
33455            self.generate_expression(begin)?;
33456        }
33457        self.write(":");
33458        if let Some(end) = &e.expression {
33459            self.generate_expression(end)?;
33460        }
33461        if let Some(step) = &e.step {
33462            self.write(":");
33463            self.generate_expression(step)?;
33464        }
33465        Ok(())
33466    }
33467
33468    fn generate_sort_array(&mut self, e: &SortArray) -> Result<()> {
33469        // SORT_ARRAY(this, asc)
33470        self.write_keyword("SORT_ARRAY");
33471        self.write("(");
33472        self.generate_expression(&e.this)?;
33473        if let Some(asc) = &e.asc {
33474            self.write(", ");
33475            self.generate_expression(asc)?;
33476        }
33477        self.write(")");
33478        Ok(())
33479    }
33480
33481    fn generate_sort_by(&mut self, e: &SortBy) -> Result<()> {
33482        // SORT BY expressions
33483        self.write_keyword("SORT BY");
33484        self.write_space();
33485        for (i, expr) in e.expressions.iter().enumerate() {
33486            if i > 0 {
33487                self.write(", ");
33488            }
33489            self.generate_ordered(expr)?;
33490        }
33491        Ok(())
33492    }
33493
33494    fn generate_sort_key_property(&mut self, e: &SortKeyProperty) -> Result<()> {
33495        // [COMPOUND] SORTKEY(col1, col2, ...) - no space before paren
33496        if e.compound.is_some() {
33497            self.write_keyword("COMPOUND");
33498            self.write_space();
33499        }
33500        self.write_keyword("SORTKEY");
33501        self.write("(");
33502        // If this is a Tuple, unwrap its contents to avoid double parentheses
33503        if let Expression::Tuple(t) = e.this.as_ref() {
33504            for (i, expr) in t.expressions.iter().enumerate() {
33505                if i > 0 {
33506                    self.write(", ");
33507                }
33508                self.generate_expression(expr)?;
33509            }
33510        } else {
33511            self.generate_expression(&e.this)?;
33512        }
33513        self.write(")");
33514        Ok(())
33515    }
33516
33517    fn generate_split_part(&mut self, e: &SplitPart) -> Result<()> {
33518        // SPLIT_PART(this, delimiter, part_index)
33519        self.write_keyword("SPLIT_PART");
33520        self.write("(");
33521        self.generate_expression(&e.this)?;
33522        if let Some(delimiter) = &e.delimiter {
33523            self.write(", ");
33524            self.generate_expression(delimiter)?;
33525        }
33526        if let Some(part_index) = &e.part_index {
33527            self.write(", ");
33528            self.generate_expression(part_index)?;
33529        }
33530        self.write(")");
33531        Ok(())
33532    }
33533
33534    fn generate_sql_read_write_property(&mut self, e: &SqlReadWriteProperty) -> Result<()> {
33535        // READS SQL DATA or MODIFIES SQL DATA, etc.
33536        self.generate_expression(&e.this)?;
33537        Ok(())
33538    }
33539
33540    fn generate_sql_security_property(&mut self, e: &SqlSecurityProperty) -> Result<()> {
33541        // SQL SECURITY DEFINER or SQL SECURITY INVOKER
33542        self.write_keyword("SQL SECURITY");
33543        self.write_space();
33544        self.generate_expression(&e.this)?;
33545        Ok(())
33546    }
33547
33548    fn generate_st_distance(&mut self, e: &StDistance) -> Result<()> {
33549        // ST_DISTANCE(this, expression, [use_spheroid])
33550        self.write_keyword("ST_DISTANCE");
33551        self.write("(");
33552        self.generate_expression(&e.this)?;
33553        self.write(", ");
33554        self.generate_expression(&e.expression)?;
33555        if let Some(use_spheroid) = &e.use_spheroid {
33556            self.write(", ");
33557            self.generate_expression(use_spheroid)?;
33558        }
33559        self.write(")");
33560        Ok(())
33561    }
33562
33563    fn generate_st_point(&mut self, e: &StPoint) -> Result<()> {
33564        // ST_POINT(this, expression)
33565        self.write_keyword("ST_POINT");
33566        self.write("(");
33567        self.generate_expression(&e.this)?;
33568        self.write(", ");
33569        self.generate_expression(&e.expression)?;
33570        self.write(")");
33571        Ok(())
33572    }
33573
33574    fn generate_stability_property(&mut self, e: &StabilityProperty) -> Result<()> {
33575        // IMMUTABLE, STABLE, VOLATILE
33576        self.generate_expression(&e.this)?;
33577        Ok(())
33578    }
33579
33580    fn generate_standard_hash(&mut self, e: &StandardHash) -> Result<()> {
33581        // STANDARD_HASH(this, [expression])
33582        self.write_keyword("STANDARD_HASH");
33583        self.write("(");
33584        self.generate_expression(&e.this)?;
33585        if let Some(expression) = &e.expression {
33586            self.write(", ");
33587            self.generate_expression(expression)?;
33588        }
33589        self.write(")");
33590        Ok(())
33591    }
33592
33593    fn generate_storage_handler_property(&mut self, e: &StorageHandlerProperty) -> Result<()> {
33594        // STORED BY this
33595        self.write_keyword("STORED BY");
33596        self.write_space();
33597        self.generate_expression(&e.this)?;
33598        Ok(())
33599    }
33600
33601    fn generate_str_position(&mut self, e: &StrPosition) -> Result<()> {
33602        // STRPOS(this, substr) or STRPOS(this, substr, position)
33603        // Different dialects have different function names
33604        use crate::dialects::DialectType;
33605        if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
33606            // Snowflake: CHARINDEX(substr, str[, position])
33607            self.write_keyword("CHARINDEX");
33608            self.write("(");
33609            if let Some(substr) = &e.substr {
33610                self.generate_expression(substr)?;
33611                self.write(", ");
33612            }
33613            self.generate_expression(&e.this)?;
33614            if let Some(position) = &e.position {
33615                self.write(", ");
33616                self.generate_expression(position)?;
33617            }
33618            self.write(")");
33619        } else if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
33620            self.write_keyword("POSITION");
33621            self.write("(");
33622            self.generate_expression(&e.this)?;
33623            if let Some(substr) = &e.substr {
33624                self.write(", ");
33625                self.generate_expression(substr)?;
33626            }
33627            if let Some(position) = &e.position {
33628                self.write(", ");
33629                self.generate_expression(position)?;
33630            }
33631            if let Some(occurrence) = &e.occurrence {
33632                self.write(", ");
33633                self.generate_expression(occurrence)?;
33634            }
33635            self.write(")");
33636        } else if matches!(
33637            self.config.dialect,
33638            Some(DialectType::SQLite)
33639                | Some(DialectType::Oracle)
33640                | Some(DialectType::BigQuery)
33641                | Some(DialectType::Teradata)
33642        ) {
33643            self.write_keyword("INSTR");
33644            self.write("(");
33645            self.generate_expression(&e.this)?;
33646            if let Some(substr) = &e.substr {
33647                self.write(", ");
33648                self.generate_expression(substr)?;
33649            }
33650            if let Some(position) = &e.position {
33651                self.write(", ");
33652                self.generate_expression(position)?;
33653            } else if e.occurrence.is_some() {
33654                // INSTR requires a position arg before occurrence: INSTR(str, substr, start, nth)
33655                // Default start position is 1
33656                self.write(", 1");
33657            }
33658            if let Some(occurrence) = &e.occurrence {
33659                self.write(", ");
33660                self.generate_expression(occurrence)?;
33661            }
33662            self.write(")");
33663        } else if matches!(
33664            self.config.dialect,
33665            Some(DialectType::MySQL)
33666                | Some(DialectType::SingleStore)
33667                | Some(DialectType::Doris)
33668                | Some(DialectType::StarRocks)
33669                | Some(DialectType::Hive)
33670                | Some(DialectType::Spark)
33671                | Some(DialectType::Databricks)
33672        ) {
33673            // LOCATE(substr, str[, position]) - substr first
33674            self.write_keyword("LOCATE");
33675            self.write("(");
33676            if let Some(substr) = &e.substr {
33677                self.generate_expression(substr)?;
33678                self.write(", ");
33679            }
33680            self.generate_expression(&e.this)?;
33681            if let Some(position) = &e.position {
33682                self.write(", ");
33683                self.generate_expression(position)?;
33684            }
33685            self.write(")");
33686        } else if matches!(self.config.dialect, Some(DialectType::TSQL)) {
33687            // CHARINDEX(substr, str[, position])
33688            self.write_keyword("CHARINDEX");
33689            self.write("(");
33690            if let Some(substr) = &e.substr {
33691                self.generate_expression(substr)?;
33692                self.write(", ");
33693            }
33694            self.generate_expression(&e.this)?;
33695            if let Some(position) = &e.position {
33696                self.write(", ");
33697                self.generate_expression(position)?;
33698            }
33699            self.write(")");
33700        } else if matches!(
33701            self.config.dialect,
33702            Some(DialectType::PostgreSQL)
33703                | Some(DialectType::Materialize)
33704                | Some(DialectType::RisingWave)
33705                | Some(DialectType::Redshift)
33706        ) {
33707            // POSITION(substr IN str) syntax
33708            self.write_keyword("POSITION");
33709            self.write("(");
33710            if let Some(substr) = &e.substr {
33711                self.generate_expression(substr)?;
33712                self.write(" IN ");
33713            }
33714            self.generate_expression(&e.this)?;
33715            self.write(")");
33716        } else {
33717            self.write_keyword("STRPOS");
33718            self.write("(");
33719            self.generate_expression(&e.this)?;
33720            if let Some(substr) = &e.substr {
33721                self.write(", ");
33722                self.generate_expression(substr)?;
33723            }
33724            if let Some(position) = &e.position {
33725                self.write(", ");
33726                self.generate_expression(position)?;
33727            }
33728            if let Some(occurrence) = &e.occurrence {
33729                self.write(", ");
33730                self.generate_expression(occurrence)?;
33731            }
33732            self.write(")");
33733        }
33734        Ok(())
33735    }
33736
33737    fn generate_str_to_date(&mut self, e: &StrToDate) -> Result<()> {
33738        match self.config.dialect {
33739            Some(DialectType::Spark) | Some(DialectType::Databricks) | Some(DialectType::Hive) => {
33740                // TO_DATE(this, java_format)
33741                self.write_keyword("TO_DATE");
33742                self.write("(");
33743                self.generate_expression(&e.this)?;
33744                if let Some(format) = &e.format {
33745                    self.write(", '");
33746                    self.write(&Self::strftime_to_java_format(format));
33747                    self.write("'");
33748                }
33749                self.write(")");
33750            }
33751            Some(DialectType::DuckDB) => {
33752                // CAST(STRPTIME(this, format) AS DATE)
33753                self.write_keyword("CAST");
33754                self.write("(");
33755                self.write_keyword("STRPTIME");
33756                self.write("(");
33757                self.generate_expression(&e.this)?;
33758                if let Some(format) = &e.format {
33759                    self.write(", '");
33760                    self.write(format);
33761                    self.write("'");
33762                }
33763                self.write(")");
33764                self.write_keyword(" AS ");
33765                self.write_keyword("DATE");
33766                self.write(")");
33767            }
33768            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift) => {
33769                // TO_DATE(this, pg_format)
33770                self.write_keyword("TO_DATE");
33771                self.write("(");
33772                self.generate_expression(&e.this)?;
33773                if let Some(format) = &e.format {
33774                    self.write(", '");
33775                    self.write(&Self::strftime_to_postgres_format(format));
33776                    self.write("'");
33777                }
33778                self.write(")");
33779            }
33780            Some(DialectType::BigQuery) => {
33781                // PARSE_DATE(format, this) - note: format comes first for BigQuery
33782                self.write_keyword("PARSE_DATE");
33783                self.write("(");
33784                if let Some(format) = &e.format {
33785                    self.write("'");
33786                    self.write(format);
33787                    self.write("'");
33788                    self.write(", ");
33789                }
33790                self.generate_expression(&e.this)?;
33791                self.write(")");
33792            }
33793            Some(DialectType::Teradata) => {
33794                // CAST(this AS DATE FORMAT 'teradata_fmt')
33795                self.write_keyword("CAST");
33796                self.write("(");
33797                self.generate_expression(&e.this)?;
33798                self.write_keyword(" AS ");
33799                self.write_keyword("DATE");
33800                if let Some(format) = &e.format {
33801                    self.write_keyword(" FORMAT ");
33802                    self.write("'");
33803                    self.write(&Self::strftime_to_teradata_format(format));
33804                    self.write("'");
33805                }
33806                self.write(")");
33807            }
33808            _ => {
33809                // STR_TO_DATE(this, format) - MySQL default
33810                self.write_keyword("STR_TO_DATE");
33811                self.write("(");
33812                self.generate_expression(&e.this)?;
33813                if let Some(format) = &e.format {
33814                    self.write(", '");
33815                    self.write(format);
33816                    self.write("'");
33817                }
33818                self.write(")");
33819            }
33820        }
33821        Ok(())
33822    }
33823
33824    /// Convert strftime format to Teradata date format (YYYY, DD, MM, etc.)
33825    fn strftime_to_teradata_format(fmt: &str) -> String {
33826        let mut result = String::with_capacity(fmt.len() * 2);
33827        let bytes = fmt.as_bytes();
33828        let len = bytes.len();
33829        let mut i = 0;
33830        while i < len {
33831            if bytes[i] == b'%' && i + 1 < len {
33832                let replacement = match bytes[i + 1] {
33833                    b'Y' => "YYYY",
33834                    b'y' => "YY",
33835                    b'm' => "MM",
33836                    b'B' => "MMMM",
33837                    b'b' => "MMM",
33838                    b'd' => "DD",
33839                    b'j' => "DDD",
33840                    b'H' => "HH",
33841                    b'M' => "MI",
33842                    b'S' => "SS",
33843                    b'f' => "SSSSSS",
33844                    b'A' => "EEEE",
33845                    b'a' => "EEE",
33846                    _ => {
33847                        result.push('%');
33848                        i += 1;
33849                        continue;
33850                    }
33851                };
33852                result.push_str(replacement);
33853                i += 2;
33854            } else {
33855                result.push(bytes[i] as char);
33856                i += 1;
33857            }
33858        }
33859        result
33860    }
33861
33862    /// Convert strftime format (%Y, %m, %d, etc.) to Java date format (yyyy, MM, dd, etc.)
33863    /// Public static version for use by other modules
33864    pub fn strftime_to_java_format_static(fmt: &str) -> String {
33865        Self::strftime_to_java_format(fmt)
33866    }
33867
33868    /// Convert strftime format (%Y, %m, %d, etc.) to Java date format (yyyy, MM, dd, etc.)
33869    fn strftime_to_java_format(fmt: &str) -> String {
33870        let mut result = String::with_capacity(fmt.len() * 2);
33871        let bytes = fmt.as_bytes();
33872        let len = bytes.len();
33873        let mut i = 0;
33874        while i < len {
33875            if bytes[i] == b'%' && i + 1 < len {
33876                // Check for non-padded variants (%-X)
33877                if bytes[i + 1] == b'-' && i + 2 < len {
33878                    let replacement = match bytes[i + 2] {
33879                        b'd' => "d",
33880                        b'm' => "M",
33881                        b'H' => "H",
33882                        b'M' => "m",
33883                        b'S' => "s",
33884                        _ => {
33885                            result.push('%');
33886                            i += 1;
33887                            continue;
33888                        }
33889                    };
33890                    result.push_str(replacement);
33891                    i += 3;
33892                } else {
33893                    let replacement = match bytes[i + 1] {
33894                        b'Y' => "yyyy",
33895                        b'y' => "yy",
33896                        b'm' => "MM",
33897                        b'B' => "MMMM",
33898                        b'b' => "MMM",
33899                        b'd' => "dd",
33900                        b'j' => "DDD",
33901                        b'H' => "HH",
33902                        b'M' => "mm",
33903                        b'S' => "ss",
33904                        b'f' => "SSSSSS",
33905                        b'A' => "EEEE",
33906                        b'a' => "EEE",
33907                        _ => {
33908                            result.push('%');
33909                            i += 1;
33910                            continue;
33911                        }
33912                    };
33913                    result.push_str(replacement);
33914                    i += 2;
33915                }
33916            } else {
33917                result.push(bytes[i] as char);
33918                i += 1;
33919            }
33920        }
33921        result
33922    }
33923
33924    /// Convert strftime format (%Y, %m, %d, etc.) to .NET date format for TSQL FORMAT()
33925    /// Similar to Java but uses ffffff for microseconds instead of SSSSSS
33926    fn strftime_to_tsql_format(fmt: &str) -> String {
33927        let mut result = String::with_capacity(fmt.len() * 2);
33928        let bytes = fmt.as_bytes();
33929        let len = bytes.len();
33930        let mut i = 0;
33931        while i < len {
33932            if bytes[i] == b'%' && i + 1 < len {
33933                // Check for non-padded variants (%-X)
33934                if bytes[i + 1] == b'-' && i + 2 < len {
33935                    let replacement = match bytes[i + 2] {
33936                        b'd' => "d",
33937                        b'm' => "M",
33938                        b'H' => "H",
33939                        b'M' => "m",
33940                        b'S' => "s",
33941                        _ => {
33942                            result.push('%');
33943                            i += 1;
33944                            continue;
33945                        }
33946                    };
33947                    result.push_str(replacement);
33948                    i += 3;
33949                } else {
33950                    let replacement = match bytes[i + 1] {
33951                        b'Y' => "yyyy",
33952                        b'y' => "yy",
33953                        b'm' => "MM",
33954                        b'B' => "MMMM",
33955                        b'b' => "MMM",
33956                        b'd' => "dd",
33957                        b'j' => "DDD",
33958                        b'H' => "HH",
33959                        b'M' => "mm",
33960                        b'S' => "ss",
33961                        b'f' => "ffffff",
33962                        b'A' => "dddd",
33963                        b'a' => "ddd",
33964                        _ => {
33965                            result.push('%');
33966                            i += 1;
33967                            continue;
33968                        }
33969                    };
33970                    result.push_str(replacement);
33971                    i += 2;
33972                }
33973            } else {
33974                result.push(bytes[i] as char);
33975                i += 1;
33976            }
33977        }
33978        result
33979    }
33980
33981    /// Decompose a JSON path string like "$.y[0].z" into individual parts: ["y", "0", "z"]
33982    /// This is used for PostgreSQL/Redshift JSON_EXTRACT_PATH / JSON_EXTRACT_PATH_TEXT
33983    fn decompose_json_path(path: &str) -> Vec<String> {
33984        let mut parts = Vec::new();
33985        // Strip leading $ and optional .
33986        let path = if path.starts_with("$.") {
33987            &path[2..]
33988        } else if path.starts_with('$') {
33989            &path[1..]
33990        } else {
33991            path
33992        };
33993        if path.is_empty() {
33994            return parts;
33995        }
33996        let mut current = String::new();
33997        let chars: Vec<char> = path.chars().collect();
33998        let mut i = 0;
33999        while i < chars.len() {
34000            match chars[i] {
34001                '.' => {
34002                    if !current.is_empty() {
34003                        parts.push(current.clone());
34004                        current.clear();
34005                    }
34006                    i += 1;
34007                }
34008                '[' => {
34009                    if !current.is_empty() {
34010                        parts.push(current.clone());
34011                        current.clear();
34012                    }
34013                    i += 1;
34014                    // Read the content inside brackets
34015                    let mut bracket_content = String::new();
34016                    while i < chars.len() && chars[i] != ']' {
34017                        // Skip quotes inside brackets
34018                        if chars[i] == '"' || chars[i] == '\'' {
34019                            let quote = chars[i];
34020                            i += 1;
34021                            while i < chars.len() && chars[i] != quote {
34022                                bracket_content.push(chars[i]);
34023                                i += 1;
34024                            }
34025                            if i < chars.len() {
34026                                i += 1;
34027                            } // skip closing quote
34028                        } else {
34029                            bracket_content.push(chars[i]);
34030                            i += 1;
34031                        }
34032                    }
34033                    if i < chars.len() {
34034                        i += 1;
34035                    } // skip ]
34036                      // Skip wildcard [*] - don't add as a part
34037                    if bracket_content != "*" {
34038                        parts.push(bracket_content);
34039                    }
34040                }
34041                _ => {
34042                    current.push(chars[i]);
34043                    i += 1;
34044                }
34045            }
34046        }
34047        if !current.is_empty() {
34048            parts.push(current);
34049        }
34050        parts
34051    }
34052
34053    /// Convert strftime format to PostgreSQL date format (YYYY, MM, DD, etc.)
34054    fn strftime_to_postgres_format(fmt: &str) -> String {
34055        let mut result = String::with_capacity(fmt.len() * 2);
34056        let bytes = fmt.as_bytes();
34057        let len = bytes.len();
34058        let mut i = 0;
34059        while i < len {
34060            if bytes[i] == b'%' && i + 1 < len {
34061                // Check for non-padded variants (%-X)
34062                if bytes[i + 1] == b'-' && i + 2 < len {
34063                    let replacement = match bytes[i + 2] {
34064                        b'd' => "FMDD",
34065                        b'm' => "FMMM",
34066                        b'H' => "FMHH24",
34067                        b'M' => "FMMI",
34068                        b'S' => "FMSS",
34069                        _ => {
34070                            result.push('%');
34071                            i += 1;
34072                            continue;
34073                        }
34074                    };
34075                    result.push_str(replacement);
34076                    i += 3;
34077                } else {
34078                    let replacement = match bytes[i + 1] {
34079                        b'Y' => "YYYY",
34080                        b'y' => "YY",
34081                        b'm' => "MM",
34082                        b'B' => "Month",
34083                        b'b' => "Mon",
34084                        b'd' => "DD",
34085                        b'j' => "DDD",
34086                        b'H' => "HH24",
34087                        b'M' => "MI",
34088                        b'S' => "SS",
34089                        b'f' => "US",
34090                        b'A' => "Day",
34091                        b'a' => "Dy",
34092                        _ => {
34093                            result.push('%');
34094                            i += 1;
34095                            continue;
34096                        }
34097                    };
34098                    result.push_str(replacement);
34099                    i += 2;
34100                }
34101            } else {
34102                result.push(bytes[i] as char);
34103                i += 1;
34104            }
34105        }
34106        result
34107    }
34108
34109    /// Convert strftime format to Snowflake date format (yyyy, mm, DD, etc.)
34110    fn strftime_to_snowflake_format(fmt: &str) -> String {
34111        let mut result = String::with_capacity(fmt.len() * 2);
34112        let bytes = fmt.as_bytes();
34113        let len = bytes.len();
34114        let mut i = 0;
34115        while i < len {
34116            if bytes[i] == b'%' && i + 1 < len {
34117                // Check for non-padded variants (%-X)
34118                if bytes[i + 1] == b'-' && i + 2 < len {
34119                    let replacement = match bytes[i + 2] {
34120                        b'd' => "dd",
34121                        b'm' => "mm",
34122                        _ => {
34123                            result.push('%');
34124                            i += 1;
34125                            continue;
34126                        }
34127                    };
34128                    result.push_str(replacement);
34129                    i += 3;
34130                } else {
34131                    let replacement = match bytes[i + 1] {
34132                        b'Y' => "yyyy",
34133                        b'y' => "yy",
34134                        b'm' => "mm",
34135                        b'd' => "DD",
34136                        b'H' => "hh24",
34137                        b'M' => "mi",
34138                        b'S' => "ss",
34139                        b'f' => "ff",
34140                        _ => {
34141                            result.push('%');
34142                            i += 1;
34143                            continue;
34144                        }
34145                    };
34146                    result.push_str(replacement);
34147                    i += 2;
34148                }
34149            } else {
34150                result.push(bytes[i] as char);
34151                i += 1;
34152            }
34153        }
34154        result
34155    }
34156
34157    fn generate_str_to_map(&mut self, e: &StrToMap) -> Result<()> {
34158        // STR_TO_MAP(this, pair_delim, key_value_delim)
34159        self.write_keyword("STR_TO_MAP");
34160        self.write("(");
34161        self.generate_expression(&e.this)?;
34162        // Spark/Hive: STR_TO_MAP needs explicit default delimiters
34163        let needs_defaults = matches!(
34164            self.config.dialect,
34165            Some(DialectType::Spark) | Some(DialectType::Hive) | Some(DialectType::Databricks)
34166        );
34167        if let Some(pair_delim) = &e.pair_delim {
34168            self.write(", ");
34169            self.generate_expression(pair_delim)?;
34170        } else if needs_defaults {
34171            self.write(", ','");
34172        }
34173        if let Some(key_value_delim) = &e.key_value_delim {
34174            self.write(", ");
34175            self.generate_expression(key_value_delim)?;
34176        } else if needs_defaults {
34177            self.write(", ':'");
34178        }
34179        self.write(")");
34180        Ok(())
34181    }
34182
34183    fn generate_str_to_time(&mut self, e: &StrToTime) -> Result<()> {
34184        // Detect format style: strftime (starts with %) vs Snowflake/Java
34185        let is_strftime = e.format.contains('%');
34186        // Helper: get strftime format from whatever style is stored
34187        let to_strftime = |f: &str| -> String {
34188            if is_strftime {
34189                f.to_string()
34190            } else {
34191                Self::snowflake_format_to_strftime(f)
34192            }
34193        };
34194        // Helper: get Java format
34195        let to_java = |f: &str| -> String {
34196            if is_strftime {
34197                Self::strftime_to_java_format(f)
34198            } else {
34199                Self::snowflake_format_to_spark(f)
34200            }
34201        };
34202        // Helper: get PG format
34203        let to_pg = |f: &str| -> String {
34204            if is_strftime {
34205                Self::strftime_to_postgres_format(f)
34206            } else {
34207                Self::convert_strptime_to_postgres_format(f)
34208            }
34209        };
34210
34211        match self.config.dialect {
34212            Some(DialectType::Exasol) => {
34213                self.write_keyword("TO_DATE");
34214                self.write("(");
34215                self.generate_expression(&e.this)?;
34216                self.write(", '");
34217                self.write(&Self::convert_strptime_to_exasol_format(&e.format));
34218                self.write("'");
34219                self.write(")");
34220            }
34221            Some(DialectType::BigQuery) => {
34222                // BigQuery: PARSE_TIMESTAMP(format, value) - note swapped args
34223                let fmt = to_strftime(&e.format);
34224                // BigQuery normalizes: %Y-%m-%d -> %F, %H:%M:%S -> %T
34225                let fmt = fmt.replace("%Y-%m-%d", "%F").replace("%H:%M:%S", "%T");
34226                self.write_keyword("PARSE_TIMESTAMP");
34227                self.write("('");
34228                self.write(&fmt);
34229                self.write("', ");
34230                self.generate_expression(&e.this)?;
34231                self.write(")");
34232            }
34233            Some(DialectType::Hive) => {
34234                // Hive: CAST(x AS TIMESTAMP) for simple date formats
34235                // Check both the raw format and the converted format (in case it's already Java)
34236                let java_fmt = to_java(&e.format);
34237                if java_fmt == "yyyy-MM-dd HH:mm:ss"
34238                    || java_fmt == "yyyy-MM-dd"
34239                    || e.format == "yyyy-MM-dd HH:mm:ss"
34240                    || e.format == "yyyy-MM-dd"
34241                {
34242                    self.write_keyword("CAST");
34243                    self.write("(");
34244                    self.generate_expression(&e.this)?;
34245                    self.write(" ");
34246                    self.write_keyword("AS TIMESTAMP");
34247                    self.write(")");
34248                } else {
34249                    // CAST(FROM_UNIXTIME(UNIX_TIMESTAMP(x, java_fmt)) AS TIMESTAMP)
34250                    self.write_keyword("CAST");
34251                    self.write("(");
34252                    self.write_keyword("FROM_UNIXTIME");
34253                    self.write("(");
34254                    self.write_keyword("UNIX_TIMESTAMP");
34255                    self.write("(");
34256                    self.generate_expression(&e.this)?;
34257                    self.write(", '");
34258                    self.write(&java_fmt);
34259                    self.write("')");
34260                    self.write(") ");
34261                    self.write_keyword("AS TIMESTAMP");
34262                    self.write(")");
34263                }
34264            }
34265            Some(DialectType::Spark) | Some(DialectType::Databricks) => {
34266                // Spark: TO_TIMESTAMP(value, java_format)
34267                let java_fmt = to_java(&e.format);
34268                self.write_keyword("TO_TIMESTAMP");
34269                self.write("(");
34270                self.generate_expression(&e.this)?;
34271                self.write(", '");
34272                self.write(&java_fmt);
34273                self.write("')");
34274            }
34275            Some(DialectType::MySQL) => {
34276                // MySQL: STR_TO_DATE(value, format)
34277                let mut fmt = to_strftime(&e.format);
34278                // MySQL uses %e for non-padded day, %T for %H:%M:%S
34279                fmt = fmt.replace("%-d", "%e");
34280                fmt = fmt.replace("%-m", "%c");
34281                fmt = fmt.replace("%H:%M:%S", "%T");
34282                self.write_keyword("STR_TO_DATE");
34283                self.write("(");
34284                self.generate_expression(&e.this)?;
34285                self.write(", '");
34286                self.write(&fmt);
34287                self.write("')");
34288            }
34289            Some(DialectType::Drill) => {
34290                // Drill: TO_TIMESTAMP(value, java_format) with T quoted in single quotes
34291                let java_fmt = to_java(&e.format);
34292                // Drill quotes literal T character: T -> ''T'' (double-quoted within SQL string literal)
34293                let java_fmt = java_fmt.replace('T', "''T''");
34294                self.write_keyword("TO_TIMESTAMP");
34295                self.write("(");
34296                self.generate_expression(&e.this)?;
34297                self.write(", '");
34298                self.write(&java_fmt);
34299                self.write("')");
34300            }
34301            Some(DialectType::Presto) | Some(DialectType::Trino) | Some(DialectType::Athena) => {
34302                // Presto: DATE_PARSE(value, strftime_format)
34303                let mut fmt = to_strftime(&e.format);
34304                // Presto uses %e for non-padded day, %T for %H:%M:%S
34305                fmt = fmt.replace("%-d", "%e");
34306                fmt = fmt.replace("%-m", "%c");
34307                fmt = fmt.replace("%H:%M:%S", "%T");
34308                self.write_keyword("DATE_PARSE");
34309                self.write("(");
34310                self.generate_expression(&e.this)?;
34311                self.write(", '");
34312                self.write(&fmt);
34313                self.write("')");
34314            }
34315            Some(DialectType::DuckDB) => {
34316                // DuckDB: STRPTIME(value, strftime_format)
34317                let fmt = to_strftime(&e.format);
34318                self.write_keyword("STRPTIME");
34319                self.write("(");
34320                self.generate_expression(&e.this)?;
34321                self.write(", '");
34322                self.write(&fmt);
34323                self.write("')");
34324            }
34325            Some(DialectType::PostgreSQL)
34326            | Some(DialectType::Redshift)
34327            | Some(DialectType::Materialize) => {
34328                // PostgreSQL/Redshift/Materialize: TO_TIMESTAMP(value, pg_format)
34329                let pg_fmt = to_pg(&e.format);
34330                self.write_keyword("TO_TIMESTAMP");
34331                self.write("(");
34332                self.generate_expression(&e.this)?;
34333                self.write(", '");
34334                self.write(&pg_fmt);
34335                self.write("')");
34336            }
34337            Some(DialectType::Oracle) => {
34338                // Oracle: TO_TIMESTAMP(value, pg_format)
34339                let pg_fmt = to_pg(&e.format);
34340                self.write_keyword("TO_TIMESTAMP");
34341                self.write("(");
34342                self.generate_expression(&e.this)?;
34343                self.write(", '");
34344                self.write(&pg_fmt);
34345                self.write("')");
34346            }
34347            Some(DialectType::Snowflake) => {
34348                // Snowflake: TO_TIMESTAMP(value, format) - native format
34349                self.write_keyword("TO_TIMESTAMP");
34350                self.write("(");
34351                self.generate_expression(&e.this)?;
34352                self.write(", '");
34353                self.write(&e.format);
34354                self.write("')");
34355            }
34356            _ => {
34357                // Default: STR_TO_TIME(this, format)
34358                self.write_keyword("STR_TO_TIME");
34359                self.write("(");
34360                self.generate_expression(&e.this)?;
34361                self.write(", '");
34362                self.write(&e.format);
34363                self.write("'");
34364                self.write(")");
34365            }
34366        }
34367        Ok(())
34368    }
34369
34370    /// Convert Snowflake normalized format to strftime-style (%Y, %m, etc.)
34371    fn snowflake_format_to_strftime(format: &str) -> String {
34372        let mut result = String::new();
34373        let chars: Vec<char> = format.chars().collect();
34374        let mut i = 0;
34375        while i < chars.len() {
34376            let remaining = &format[i..];
34377            if remaining.starts_with("yyyy") {
34378                result.push_str("%Y");
34379                i += 4;
34380            } else if remaining.starts_with("yy") {
34381                result.push_str("%y");
34382                i += 2;
34383            } else if remaining.starts_with("mmmm") {
34384                result.push_str("%B"); // full month name
34385                i += 4;
34386            } else if remaining.starts_with("mon") {
34387                result.push_str("%b"); // abbreviated month
34388                i += 3;
34389            } else if remaining.starts_with("mm") {
34390                result.push_str("%m");
34391                i += 2;
34392            } else if remaining.starts_with("DD") {
34393                result.push_str("%d");
34394                i += 2;
34395            } else if remaining.starts_with("dy") {
34396                result.push_str("%a"); // abbreviated day name
34397                i += 2;
34398            } else if remaining.starts_with("hh24") {
34399                result.push_str("%H");
34400                i += 4;
34401            } else if remaining.starts_with("hh12") {
34402                result.push_str("%I");
34403                i += 4;
34404            } else if remaining.starts_with("hh") {
34405                result.push_str("%H");
34406                i += 2;
34407            } else if remaining.starts_with("mi") {
34408                result.push_str("%M");
34409                i += 2;
34410            } else if remaining.starts_with("ss") {
34411                result.push_str("%S");
34412                i += 2;
34413            } else if remaining.starts_with("ff") {
34414                // Fractional seconds
34415                result.push_str("%f");
34416                i += 2;
34417                // Skip digits after ff (ff3, ff6, ff9)
34418                while i < chars.len() && chars[i].is_ascii_digit() {
34419                    i += 1;
34420                }
34421            } else if remaining.starts_with("am") || remaining.starts_with("pm") {
34422                result.push_str("%p");
34423                i += 2;
34424            } else if remaining.starts_with("tz") {
34425                result.push_str("%Z");
34426                i += 2;
34427            } else {
34428                result.push(chars[i]);
34429                i += 1;
34430            }
34431        }
34432        result
34433    }
34434
34435    /// Convert Snowflake normalized format to Spark format (Java-style)
34436    fn snowflake_format_to_spark(format: &str) -> String {
34437        let mut result = String::new();
34438        let chars: Vec<char> = format.chars().collect();
34439        let mut i = 0;
34440        while i < chars.len() {
34441            let remaining = &format[i..];
34442            if remaining.starts_with("yyyy") {
34443                result.push_str("yyyy");
34444                i += 4;
34445            } else if remaining.starts_with("yy") {
34446                result.push_str("yy");
34447                i += 2;
34448            } else if remaining.starts_with("mmmm") {
34449                result.push_str("MMMM"); // full month name
34450                i += 4;
34451            } else if remaining.starts_with("mon") {
34452                result.push_str("MMM"); // abbreviated month
34453                i += 3;
34454            } else if remaining.starts_with("mm") {
34455                result.push_str("MM");
34456                i += 2;
34457            } else if remaining.starts_with("DD") {
34458                result.push_str("dd");
34459                i += 2;
34460            } else if remaining.starts_with("dy") {
34461                result.push_str("EEE"); // abbreviated day name
34462                i += 2;
34463            } else if remaining.starts_with("hh24") {
34464                result.push_str("HH");
34465                i += 4;
34466            } else if remaining.starts_with("hh12") {
34467                result.push_str("hh");
34468                i += 4;
34469            } else if remaining.starts_with("hh") {
34470                result.push_str("HH");
34471                i += 2;
34472            } else if remaining.starts_with("mi") {
34473                result.push_str("mm");
34474                i += 2;
34475            } else if remaining.starts_with("ss") {
34476                result.push_str("ss");
34477                i += 2;
34478            } else if remaining.starts_with("ff") {
34479                result.push_str("SSS"); // milliseconds
34480                i += 2;
34481                // Skip digits after ff
34482                while i < chars.len() && chars[i].is_ascii_digit() {
34483                    i += 1;
34484                }
34485            } else if remaining.starts_with("am") || remaining.starts_with("pm") {
34486                result.push_str("a");
34487                i += 2;
34488            } else if remaining.starts_with("tz") {
34489                result.push_str("z");
34490                i += 2;
34491            } else {
34492                result.push(chars[i]);
34493                i += 1;
34494            }
34495        }
34496        result
34497    }
34498
34499    fn generate_str_to_unix(&mut self, e: &StrToUnix) -> Result<()> {
34500        match self.config.dialect {
34501            Some(DialectType::DuckDB) => {
34502                // DuckDB: EPOCH(STRPTIME(value, format))
34503                self.write_keyword("EPOCH");
34504                self.write("(");
34505                self.write_keyword("STRPTIME");
34506                self.write("(");
34507                if let Some(this) = &e.this {
34508                    self.generate_expression(this)?;
34509                }
34510                if let Some(format) = &e.format {
34511                    self.write(", '");
34512                    self.write(format);
34513                    self.write("'");
34514                }
34515                self.write("))");
34516            }
34517            Some(DialectType::Hive) => {
34518                // Hive: UNIX_TIMESTAMP(value, java_format) - convert C fmt to Java
34519                self.write_keyword("UNIX_TIMESTAMP");
34520                self.write("(");
34521                if let Some(this) = &e.this {
34522                    self.generate_expression(this)?;
34523                }
34524                if let Some(format) = &e.format {
34525                    let java_fmt = Self::strftime_to_java_format(format);
34526                    if java_fmt != "yyyy-MM-dd HH:mm:ss" {
34527                        self.write(", '");
34528                        self.write(&java_fmt);
34529                        self.write("'");
34530                    }
34531                }
34532                self.write(")");
34533            }
34534            Some(DialectType::Doris) | Some(DialectType::StarRocks) => {
34535                // Doris/StarRocks: UNIX_TIMESTAMP(value, format) - C format
34536                self.write_keyword("UNIX_TIMESTAMP");
34537                self.write("(");
34538                if let Some(this) = &e.this {
34539                    self.generate_expression(this)?;
34540                }
34541                if let Some(format) = &e.format {
34542                    self.write(", '");
34543                    self.write(format);
34544                    self.write("'");
34545                }
34546                self.write(")");
34547            }
34548            Some(DialectType::Presto) | Some(DialectType::Trino) => {
34549                // Presto: TO_UNIXTIME(COALESCE(TRY(DATE_PARSE(CAST(value AS VARCHAR), c_format)),
34550                //   PARSE_DATETIME(DATE_FORMAT(CAST(value AS TIMESTAMP), c_format), java_format)))
34551                let c_fmt = e.format.as_deref().unwrap_or("%Y-%m-%d %T");
34552                let java_fmt = Self::strftime_to_java_format(c_fmt);
34553                self.write_keyword("TO_UNIXTIME");
34554                self.write("(");
34555                self.write_keyword("COALESCE");
34556                self.write("(");
34557                self.write_keyword("TRY");
34558                self.write("(");
34559                self.write_keyword("DATE_PARSE");
34560                self.write("(");
34561                self.write_keyword("CAST");
34562                self.write("(");
34563                if let Some(this) = &e.this {
34564                    self.generate_expression(this)?;
34565                }
34566                self.write(" ");
34567                self.write_keyword("AS VARCHAR");
34568                self.write("), '");
34569                self.write(c_fmt);
34570                self.write("')), ");
34571                self.write_keyword("PARSE_DATETIME");
34572                self.write("(");
34573                self.write_keyword("DATE_FORMAT");
34574                self.write("(");
34575                self.write_keyword("CAST");
34576                self.write("(");
34577                if let Some(this) = &e.this {
34578                    self.generate_expression(this)?;
34579                }
34580                self.write(" ");
34581                self.write_keyword("AS TIMESTAMP");
34582                self.write("), '");
34583                self.write(c_fmt);
34584                self.write("'), '");
34585                self.write(&java_fmt);
34586                self.write("')))");
34587            }
34588            Some(DialectType::Spark) | Some(DialectType::Databricks) => {
34589                // Spark: UNIX_TIMESTAMP(value, java_format)
34590                self.write_keyword("UNIX_TIMESTAMP");
34591                self.write("(");
34592                if let Some(this) = &e.this {
34593                    self.generate_expression(this)?;
34594                }
34595                if let Some(format) = &e.format {
34596                    let java_fmt = Self::strftime_to_java_format(format);
34597                    self.write(", '");
34598                    self.write(&java_fmt);
34599                    self.write("'");
34600                }
34601                self.write(")");
34602            }
34603            _ => {
34604                // Default: STR_TO_UNIX(this, format)
34605                self.write_keyword("STR_TO_UNIX");
34606                self.write("(");
34607                if let Some(this) = &e.this {
34608                    self.generate_expression(this)?;
34609                }
34610                if let Some(format) = &e.format {
34611                    self.write(", '");
34612                    self.write(format);
34613                    self.write("'");
34614                }
34615                self.write(")");
34616            }
34617        }
34618        Ok(())
34619    }
34620
34621    fn generate_string_to_array(&mut self, e: &StringToArray) -> Result<()> {
34622        // STRING_TO_ARRAY(this, delimiter, null_string)
34623        self.write_keyword("STRING_TO_ARRAY");
34624        self.write("(");
34625        self.generate_expression(&e.this)?;
34626        if let Some(expression) = &e.expression {
34627            self.write(", ");
34628            self.generate_expression(expression)?;
34629        }
34630        if let Some(null_val) = &e.null {
34631            self.write(", ");
34632            self.generate_expression(null_val)?;
34633        }
34634        self.write(")");
34635        Ok(())
34636    }
34637
34638    fn generate_struct(&mut self, e: &Struct) -> Result<()> {
34639        if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
34640            // Snowflake: OBJECT_CONSTRUCT('key', value, 'key', value, ...)
34641            self.write_keyword("OBJECT_CONSTRUCT");
34642            self.write("(");
34643            for (i, (name, expr)) in e.fields.iter().enumerate() {
34644                if i > 0 {
34645                    self.write(", ");
34646                }
34647                if let Some(name) = name {
34648                    self.write("'");
34649                    self.write(name);
34650                    self.write("'");
34651                    self.write(", ");
34652                } else {
34653                    self.write("'_");
34654                    self.write(&i.to_string());
34655                    self.write("'");
34656                    self.write(", ");
34657                }
34658                self.generate_expression(expr)?;
34659            }
34660            self.write(")");
34661        } else if self.config.struct_curly_brace_notation {
34662            // DuckDB-style: {'key': value, ...}
34663            self.write("{");
34664            for (i, (name, expr)) in e.fields.iter().enumerate() {
34665                if i > 0 {
34666                    self.write(", ");
34667                }
34668                if let Some(name) = name {
34669                    // Quote the key as a string literal
34670                    self.write("'");
34671                    self.write(name);
34672                    self.write("'");
34673                    self.write(": ");
34674                } else {
34675                    // Unnamed field: use positional key
34676                    self.write("'_");
34677                    self.write(&i.to_string());
34678                    self.write("'");
34679                    self.write(": ");
34680                }
34681                self.generate_expression(expr)?;
34682            }
34683            self.write("}");
34684        } else {
34685            // Standard SQL struct notation
34686            // BigQuery/Spark/Databricks use: STRUCT(value AS name, ...)
34687            // Others (Presto etc.) use: STRUCT(name AS value, ...) or ROW(value, ...)
34688            let value_as_name = matches!(
34689                self.config.dialect,
34690                Some(DialectType::BigQuery)
34691                    | Some(DialectType::Spark)
34692                    | Some(DialectType::Databricks)
34693                    | Some(DialectType::Hive)
34694            );
34695            self.write_keyword("STRUCT");
34696            self.write("(");
34697            for (i, (name, expr)) in e.fields.iter().enumerate() {
34698                if i > 0 {
34699                    self.write(", ");
34700                }
34701                if let Some(name) = name {
34702                    if value_as_name {
34703                        // STRUCT(value AS name)
34704                        self.generate_expression(expr)?;
34705                        self.write_space();
34706                        self.write_keyword("AS");
34707                        self.write_space();
34708                        // Quote name if it contains spaces or special chars
34709                        let needs_quoting = name.contains(' ') || name.contains('-');
34710                        if needs_quoting {
34711                            if matches!(
34712                                self.config.dialect,
34713                                Some(DialectType::Spark)
34714                                    | Some(DialectType::Databricks)
34715                                    | Some(DialectType::Hive)
34716                            ) {
34717                                self.write("`");
34718                                self.write(name);
34719                                self.write("`");
34720                            } else {
34721                                self.write(name);
34722                            }
34723                        } else {
34724                            self.write(name);
34725                        }
34726                    } else {
34727                        // STRUCT(name AS value)
34728                        self.write(name);
34729                        self.write_space();
34730                        self.write_keyword("AS");
34731                        self.write_space();
34732                        self.generate_expression(expr)?;
34733                    }
34734                } else {
34735                    self.generate_expression(expr)?;
34736                }
34737            }
34738            self.write(")");
34739        }
34740        Ok(())
34741    }
34742
34743    fn generate_stuff(&mut self, e: &Stuff) -> Result<()> {
34744        // STUFF(this, start, length, expression)
34745        self.write_keyword("STUFF");
34746        self.write("(");
34747        self.generate_expression(&e.this)?;
34748        if let Some(start) = &e.start {
34749            self.write(", ");
34750            self.generate_expression(start)?;
34751        }
34752        if let Some(length) = e.length {
34753            self.write(", ");
34754            self.write(&length.to_string());
34755        }
34756        self.write(", ");
34757        self.generate_expression(&e.expression)?;
34758        self.write(")");
34759        Ok(())
34760    }
34761
34762    fn generate_substring_index(&mut self, e: &SubstringIndex) -> Result<()> {
34763        // SUBSTRING_INDEX(this, delimiter, count)
34764        self.write_keyword("SUBSTRING_INDEX");
34765        self.write("(");
34766        self.generate_expression(&e.this)?;
34767        if let Some(delimiter) = &e.delimiter {
34768            self.write(", ");
34769            self.generate_expression(delimiter)?;
34770        }
34771        if let Some(count) = &e.count {
34772            self.write(", ");
34773            self.generate_expression(count)?;
34774        }
34775        self.write(")");
34776        Ok(())
34777    }
34778
34779    fn generate_summarize(&mut self, e: &Summarize) -> Result<()> {
34780        // SUMMARIZE [TABLE] this
34781        self.write_keyword("SUMMARIZE");
34782        if e.table.is_some() {
34783            self.write_space();
34784            self.write_keyword("TABLE");
34785        }
34786        self.write_space();
34787        self.generate_expression(&e.this)?;
34788        Ok(())
34789    }
34790
34791    fn generate_systimestamp(&mut self, _e: &Systimestamp) -> Result<()> {
34792        // SYSTIMESTAMP
34793        self.write_keyword("SYSTIMESTAMP");
34794        Ok(())
34795    }
34796
34797    fn generate_table_alias(&mut self, e: &TableAlias) -> Result<()> {
34798        // alias (columns...)
34799        if let Some(this) = &e.this {
34800            self.generate_expression(this)?;
34801        }
34802        if !e.columns.is_empty() {
34803            self.write("(");
34804            for (i, col) in e.columns.iter().enumerate() {
34805                if i > 0 {
34806                    self.write(", ");
34807                }
34808                self.generate_expression(col)?;
34809            }
34810            self.write(")");
34811        }
34812        Ok(())
34813    }
34814
34815    fn generate_table_from_rows(&mut self, e: &TableFromRows) -> Result<()> {
34816        // TABLE(this) [AS alias]
34817        self.write_keyword("TABLE");
34818        self.write("(");
34819        self.generate_expression(&e.this)?;
34820        self.write(")");
34821        if let Some(alias) = &e.alias {
34822            self.write_space();
34823            self.write_keyword("AS");
34824            self.write_space();
34825            self.write(alias);
34826        }
34827        Ok(())
34828    }
34829
34830    fn generate_rows_from(&mut self, e: &RowsFrom) -> Result<()> {
34831        // ROWS FROM (func1(...) AS alias1(...), func2(...) AS alias2(...)) [WITH ORDINALITY] [AS alias(...)]
34832        self.write_keyword("ROWS FROM");
34833        self.write(" (");
34834        for (i, expr) in e.expressions.iter().enumerate() {
34835            if i > 0 {
34836                self.write(", ");
34837            }
34838            // Each expression is either:
34839            // - A plain function (no alias)
34840            // - A Tuple(function, TableAlias) for: FUNC() AS alias(col type, ...)
34841            match expr {
34842                Expression::Tuple(tuple) if tuple.expressions.len() == 2 => {
34843                    // First element is the function, second is the TableAlias
34844                    self.generate_expression(&tuple.expressions[0])?;
34845                    self.write_space();
34846                    self.write_keyword("AS");
34847                    self.write_space();
34848                    self.generate_expression(&tuple.expressions[1])?;
34849                }
34850                _ => {
34851                    self.generate_expression(expr)?;
34852                }
34853            }
34854        }
34855        self.write(")");
34856        if e.ordinality {
34857            self.write_space();
34858            self.write_keyword("WITH ORDINALITY");
34859        }
34860        if let Some(alias) = &e.alias {
34861            self.write_space();
34862            self.write_keyword("AS");
34863            self.write_space();
34864            self.generate_expression(alias)?;
34865        }
34866        Ok(())
34867    }
34868
34869    fn generate_table_sample(&mut self, e: &TableSample) -> Result<()> {
34870        use crate::dialects::DialectType;
34871
34872        // New wrapper pattern: expression + Sample struct
34873        if let (Some(this), Some(sample)) = (&e.this, &e.sample) {
34874            // For alias_post_tablesample dialects (Spark, Hive, Oracle): output base expr, TABLESAMPLE, then alias
34875            if self.config.alias_post_tablesample {
34876                // Handle Subquery with alias and Alias wrapper
34877                if let Expression::Subquery(ref s) = **this {
34878                    if let Some(ref alias) = s.alias {
34879                        // Create a clone without alias for output
34880                        let mut subquery_no_alias = (**s).clone();
34881                        subquery_no_alias.alias = None;
34882                        subquery_no_alias.column_aliases = Vec::new();
34883                        self.generate_expression(&Expression::Subquery(Box::new(
34884                            subquery_no_alias,
34885                        )))?;
34886                        self.write_space();
34887                        self.write_keyword("TABLESAMPLE");
34888                        self.generate_sample_body(sample)?;
34889                        if let Some(ref seed) = sample.seed {
34890                            self.write_space();
34891                            let use_seed = sample.use_seed_keyword
34892                                && !matches!(
34893                                    self.config.dialect,
34894                                    Some(crate::dialects::DialectType::Databricks)
34895                                        | Some(crate::dialects::DialectType::Spark)
34896                                );
34897                            if use_seed {
34898                                self.write_keyword("SEED");
34899                            } else {
34900                                self.write_keyword("REPEATABLE");
34901                            }
34902                            self.write(" (");
34903                            self.generate_expression(seed)?;
34904                            self.write(")");
34905                        }
34906                        self.write_space();
34907                        self.write_keyword("AS");
34908                        self.write_space();
34909                        self.generate_identifier(alias)?;
34910                        return Ok(());
34911                    }
34912                } else if let Expression::Alias(ref a) = **this {
34913                    // Output the base expression without alias
34914                    self.generate_expression(&a.this)?;
34915                    self.write_space();
34916                    self.write_keyword("TABLESAMPLE");
34917                    self.generate_sample_body(sample)?;
34918                    if let Some(ref seed) = sample.seed {
34919                        self.write_space();
34920                        let use_seed = sample.use_seed_keyword
34921                            && !matches!(
34922                                self.config.dialect,
34923                                Some(crate::dialects::DialectType::Databricks)
34924                                    | Some(crate::dialects::DialectType::Spark)
34925                            );
34926                        if use_seed {
34927                            self.write_keyword("SEED");
34928                        } else {
34929                            self.write_keyword("REPEATABLE");
34930                        }
34931                        self.write(" (");
34932                        self.generate_expression(seed)?;
34933                        self.write(")");
34934                    }
34935                    // Output alias after TABLESAMPLE
34936                    self.write_space();
34937                    self.write_keyword("AS");
34938                    self.write_space();
34939                    self.generate_identifier(&a.alias)?;
34940                    return Ok(());
34941                }
34942            }
34943            // Default: generate wrapped expression first, then TABLESAMPLE
34944            self.generate_expression(this)?;
34945            self.write_space();
34946            self.write_keyword("TABLESAMPLE");
34947            self.generate_sample_body(sample)?;
34948            // Seed for table-level sample
34949            if let Some(ref seed) = sample.seed {
34950                self.write_space();
34951                // Databricks uses REPEATABLE, not SEED
34952                let use_seed = sample.use_seed_keyword
34953                    && !matches!(
34954                        self.config.dialect,
34955                        Some(crate::dialects::DialectType::Databricks)
34956                            | Some(crate::dialects::DialectType::Spark)
34957                    );
34958                if use_seed {
34959                    self.write_keyword("SEED");
34960                } else {
34961                    self.write_keyword("REPEATABLE");
34962                }
34963                self.write(" (");
34964                self.generate_expression(seed)?;
34965                self.write(")");
34966            }
34967            return Ok(());
34968        }
34969
34970        // Legacy pattern: TABLESAMPLE [method] (expressions) or TABLESAMPLE method BUCKET numerator OUT OF denominator
34971        self.write_keyword("TABLESAMPLE");
34972        if let Some(method) = &e.method {
34973            self.write_space();
34974            self.write_keyword(method);
34975        } else if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
34976            // Snowflake defaults to BERNOULLI when no method is specified
34977            self.write_space();
34978            self.write_keyword("BERNOULLI");
34979        }
34980        if let (Some(numerator), Some(denominator)) = (&e.bucket_numerator, &e.bucket_denominator) {
34981            self.write_space();
34982            self.write_keyword("BUCKET");
34983            self.write_space();
34984            self.generate_expression(numerator)?;
34985            self.write_space();
34986            self.write_keyword("OUT OF");
34987            self.write_space();
34988            self.generate_expression(denominator)?;
34989            if let Some(field) = &e.bucket_field {
34990                self.write_space();
34991                self.write_keyword("ON");
34992                self.write_space();
34993                self.generate_expression(field)?;
34994            }
34995        } else if !e.expressions.is_empty() {
34996            self.write(" (");
34997            for (i, expr) in e.expressions.iter().enumerate() {
34998                if i > 0 {
34999                    self.write(", ");
35000                }
35001                self.generate_expression(expr)?;
35002            }
35003            self.write(")");
35004        } else if let Some(percent) = &e.percent {
35005            self.write(" (");
35006            self.generate_expression(percent)?;
35007            self.write_space();
35008            self.write_keyword("PERCENT");
35009            self.write(")");
35010        }
35011        Ok(())
35012    }
35013
35014    fn generate_tag(&mut self, e: &Tag) -> Result<()> {
35015        // [prefix]this[postfix]
35016        if let Some(prefix) = &e.prefix {
35017            self.generate_expression(prefix)?;
35018        }
35019        if let Some(this) = &e.this {
35020            self.generate_expression(this)?;
35021        }
35022        if let Some(postfix) = &e.postfix {
35023            self.generate_expression(postfix)?;
35024        }
35025        Ok(())
35026    }
35027
35028    fn generate_tags(&mut self, e: &Tags) -> Result<()> {
35029        // TAG (expressions)
35030        self.write_keyword("TAG");
35031        self.write(" (");
35032        for (i, expr) in e.expressions.iter().enumerate() {
35033            if i > 0 {
35034                self.write(", ");
35035            }
35036            self.generate_expression(expr)?;
35037        }
35038        self.write(")");
35039        Ok(())
35040    }
35041
35042    fn generate_temporary_property(&mut self, e: &TemporaryProperty) -> Result<()> {
35043        // TEMPORARY or TEMP or [this] TEMPORARY
35044        if let Some(this) = &e.this {
35045            self.generate_expression(this)?;
35046            self.write_space();
35047        }
35048        self.write_keyword("TEMPORARY");
35049        Ok(())
35050    }
35051
35052    /// Generate a Time function expression
35053    /// For most dialects: TIME('value')
35054    fn generate_time_func(&mut self, e: &UnaryFunc) -> Result<()> {
35055        // Standard: TIME(value)
35056        self.write_keyword("TIME");
35057        self.write("(");
35058        self.generate_expression(&e.this)?;
35059        self.write(")");
35060        Ok(())
35061    }
35062
35063    fn generate_time_add(&mut self, e: &TimeAdd) -> Result<()> {
35064        // TIME_ADD(this, expression, unit)
35065        self.write_keyword("TIME_ADD");
35066        self.write("(");
35067        self.generate_expression(&e.this)?;
35068        self.write(", ");
35069        self.generate_expression(&e.expression)?;
35070        if let Some(unit) = &e.unit {
35071            self.write(", ");
35072            self.write_keyword(unit);
35073        }
35074        self.write(")");
35075        Ok(())
35076    }
35077
35078    fn generate_time_diff(&mut self, e: &TimeDiff) -> Result<()> {
35079        // TIME_DIFF(this, expression, unit)
35080        self.write_keyword("TIME_DIFF");
35081        self.write("(");
35082        self.generate_expression(&e.this)?;
35083        self.write(", ");
35084        self.generate_expression(&e.expression)?;
35085        if let Some(unit) = &e.unit {
35086            self.write(", ");
35087            self.write_keyword(unit);
35088        }
35089        self.write(")");
35090        Ok(())
35091    }
35092
35093    fn generate_time_from_parts(&mut self, e: &TimeFromParts) -> Result<()> {
35094        // TIME_FROM_PARTS(hour, minute, second, nanosecond)
35095        self.write_keyword("TIME_FROM_PARTS");
35096        self.write("(");
35097        let mut first = true;
35098        if let Some(hour) = &e.hour {
35099            self.generate_expression(hour)?;
35100            first = false;
35101        }
35102        if let Some(minute) = &e.min {
35103            if !first {
35104                self.write(", ");
35105            }
35106            self.generate_expression(minute)?;
35107            first = false;
35108        }
35109        if let Some(second) = &e.sec {
35110            if !first {
35111                self.write(", ");
35112            }
35113            self.generate_expression(second)?;
35114            first = false;
35115        }
35116        if let Some(ns) = &e.nano {
35117            if !first {
35118                self.write(", ");
35119            }
35120            self.generate_expression(ns)?;
35121        }
35122        self.write(")");
35123        Ok(())
35124    }
35125
35126    fn generate_time_slice(&mut self, e: &TimeSlice) -> Result<()> {
35127        // TIME_SLICE(this, expression, unit)
35128        self.write_keyword("TIME_SLICE");
35129        self.write("(");
35130        self.generate_expression(&e.this)?;
35131        self.write(", ");
35132        self.generate_expression(&e.expression)?;
35133        self.write(", ");
35134        self.write_keyword(&e.unit);
35135        self.write(")");
35136        Ok(())
35137    }
35138
35139    fn generate_time_str_to_time(&mut self, e: &TimeStrToTime) -> Result<()> {
35140        // TIME_STR_TO_TIME(this)
35141        self.write_keyword("TIME_STR_TO_TIME");
35142        self.write("(");
35143        self.generate_expression(&e.this)?;
35144        self.write(")");
35145        Ok(())
35146    }
35147
35148    fn generate_time_sub(&mut self, e: &TimeSub) -> Result<()> {
35149        // TIME_SUB(this, expression, unit)
35150        self.write_keyword("TIME_SUB");
35151        self.write("(");
35152        self.generate_expression(&e.this)?;
35153        self.write(", ");
35154        self.generate_expression(&e.expression)?;
35155        if let Some(unit) = &e.unit {
35156            self.write(", ");
35157            self.write_keyword(unit);
35158        }
35159        self.write(")");
35160        Ok(())
35161    }
35162
35163    fn generate_time_to_str(&mut self, e: &TimeToStr) -> Result<()> {
35164        match self.config.dialect {
35165            Some(DialectType::Exasol) => {
35166                // Exasol uses TO_CHAR with Exasol-specific format
35167                self.write_keyword("TO_CHAR");
35168                self.write("(");
35169                self.generate_expression(&e.this)?;
35170                self.write(", '");
35171                self.write(&Self::convert_strptime_to_exasol_format(&e.format));
35172                self.write("'");
35173                self.write(")");
35174            }
35175            Some(DialectType::PostgreSQL)
35176            | Some(DialectType::Redshift)
35177            | Some(DialectType::Materialize) => {
35178                // PostgreSQL/Redshift/Materialize uses TO_CHAR with PG-specific format
35179                self.write_keyword("TO_CHAR");
35180                self.write("(");
35181                self.generate_expression(&e.this)?;
35182                self.write(", '");
35183                self.write(&Self::convert_strptime_to_postgres_format(&e.format));
35184                self.write("'");
35185                self.write(")");
35186            }
35187            Some(DialectType::Oracle) => {
35188                // Oracle uses TO_CHAR with PG-like format
35189                self.write_keyword("TO_CHAR");
35190                self.write("(");
35191                self.generate_expression(&e.this)?;
35192                self.write(", '");
35193                self.write(&Self::convert_strptime_to_postgres_format(&e.format));
35194                self.write("'");
35195                self.write(")");
35196            }
35197            Some(DialectType::Drill) => {
35198                // Drill: TO_CHAR with Java format
35199                self.write_keyword("TO_CHAR");
35200                self.write("(");
35201                self.generate_expression(&e.this)?;
35202                self.write(", '");
35203                self.write(&Self::strftime_to_java_format(&e.format));
35204                self.write("'");
35205                self.write(")");
35206            }
35207            Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
35208                // TSQL: FORMAT(value, format) with .NET-style format
35209                self.write_keyword("FORMAT");
35210                self.write("(");
35211                self.generate_expression(&e.this)?;
35212                self.write(", '");
35213                self.write(&Self::strftime_to_tsql_format(&e.format));
35214                self.write("'");
35215                self.write(")");
35216            }
35217            Some(DialectType::DuckDB) => {
35218                // DuckDB: STRFTIME(value, format) - keeps C format
35219                self.write_keyword("STRFTIME");
35220                self.write("(");
35221                self.generate_expression(&e.this)?;
35222                self.write(", '");
35223                self.write(&e.format);
35224                self.write("'");
35225                self.write(")");
35226            }
35227            Some(DialectType::BigQuery) => {
35228                // BigQuery: FORMAT_DATE(format, value) - note swapped arg order
35229                // Normalize: %Y-%m-%d -> %F, %H:%M:%S -> %T
35230                let fmt = e.format.replace("%Y-%m-%d", "%F").replace("%H:%M:%S", "%T");
35231                self.write_keyword("FORMAT_DATE");
35232                self.write("('");
35233                self.write(&fmt);
35234                self.write("', ");
35235                self.generate_expression(&e.this)?;
35236                self.write(")");
35237            }
35238            Some(DialectType::Hive) | Some(DialectType::Spark) | Some(DialectType::Databricks) => {
35239                // Hive/Spark: DATE_FORMAT(value, java_format)
35240                self.write_keyword("DATE_FORMAT");
35241                self.write("(");
35242                self.generate_expression(&e.this)?;
35243                self.write(", '");
35244                self.write(&Self::strftime_to_java_format(&e.format));
35245                self.write("'");
35246                self.write(")");
35247            }
35248            Some(DialectType::Presto) | Some(DialectType::Trino) | Some(DialectType::Athena) => {
35249                // Presto/Trino: DATE_FORMAT(value, format) - keeps C format
35250                self.write_keyword("DATE_FORMAT");
35251                self.write("(");
35252                self.generate_expression(&e.this)?;
35253                self.write(", '");
35254                self.write(&e.format);
35255                self.write("'");
35256                self.write(")");
35257            }
35258            Some(DialectType::Doris) | Some(DialectType::StarRocks) => {
35259                // Doris/StarRocks: DATE_FORMAT(value, format) - keeps C format
35260                self.write_keyword("DATE_FORMAT");
35261                self.write("(");
35262                self.generate_expression(&e.this)?;
35263                self.write(", '");
35264                self.write(&e.format);
35265                self.write("'");
35266                self.write(")");
35267            }
35268            _ => {
35269                // Default: TIME_TO_STR(this, format)
35270                self.write_keyword("TIME_TO_STR");
35271                self.write("(");
35272                self.generate_expression(&e.this)?;
35273                self.write(", '");
35274                self.write(&e.format);
35275                self.write("'");
35276                self.write(")");
35277            }
35278        }
35279        Ok(())
35280    }
35281
35282    fn generate_time_to_unix(&mut self, e: &crate::expressions::UnaryFunc) -> Result<()> {
35283        match self.config.dialect {
35284            Some(DialectType::DuckDB) => {
35285                // DuckDB: EPOCH(x)
35286                self.write_keyword("EPOCH");
35287                self.write("(");
35288                self.generate_expression(&e.this)?;
35289                self.write(")");
35290            }
35291            Some(DialectType::Hive)
35292            | Some(DialectType::Spark)
35293            | Some(DialectType::Databricks)
35294            | Some(DialectType::Doris)
35295            | Some(DialectType::StarRocks)
35296            | Some(DialectType::Drill) => {
35297                // Hive/Spark/Doris/StarRocks/Drill: UNIX_TIMESTAMP(x)
35298                self.write_keyword("UNIX_TIMESTAMP");
35299                self.write("(");
35300                self.generate_expression(&e.this)?;
35301                self.write(")");
35302            }
35303            Some(DialectType::Presto) | Some(DialectType::Trino) => {
35304                // Presto: TO_UNIXTIME(x)
35305                self.write_keyword("TO_UNIXTIME");
35306                self.write("(");
35307                self.generate_expression(&e.this)?;
35308                self.write(")");
35309            }
35310            _ => {
35311                // Default: TIME_TO_UNIX(x)
35312                self.write_keyword("TIME_TO_UNIX");
35313                self.write("(");
35314                self.generate_expression(&e.this)?;
35315                self.write(")");
35316            }
35317        }
35318        Ok(())
35319    }
35320
35321    fn generate_time_str_to_date(&mut self, e: &crate::expressions::UnaryFunc) -> Result<()> {
35322        match self.config.dialect {
35323            Some(DialectType::Hive) => {
35324                // Hive: TO_DATE(x)
35325                self.write_keyword("TO_DATE");
35326                self.write("(");
35327                self.generate_expression(&e.this)?;
35328                self.write(")");
35329            }
35330            _ => {
35331                // Default: TIME_STR_TO_DATE(x)
35332                self.write_keyword("TIME_STR_TO_DATE");
35333                self.write("(");
35334                self.generate_expression(&e.this)?;
35335                self.write(")");
35336            }
35337        }
35338        Ok(())
35339    }
35340
35341    fn generate_time_trunc(&mut self, e: &TimeTrunc) -> Result<()> {
35342        // TIME_TRUNC(this, unit)
35343        self.write_keyword("TIME_TRUNC");
35344        self.write("(");
35345        self.generate_expression(&e.this)?;
35346        self.write(", ");
35347        self.write_keyword(&e.unit);
35348        self.write(")");
35349        Ok(())
35350    }
35351
35352    fn generate_time_unit(&mut self, e: &TimeUnit) -> Result<()> {
35353        // Just output the unit name
35354        if let Some(unit) = &e.unit {
35355            self.write_keyword(unit);
35356        }
35357        Ok(())
35358    }
35359
35360    /// Generate a Timestamp function expression
35361    /// For Exasol: {ts'value'} -> TO_TIMESTAMP('value')
35362    /// For other dialects: TIMESTAMP('value')
35363    fn generate_timestamp_func(&mut self, e: &TimestampFunc) -> Result<()> {
35364        use crate::dialects::DialectType;
35365        use crate::expressions::Literal;
35366
35367        match self.config.dialect {
35368            // Exasol uses TO_TIMESTAMP for Timestamp expressions
35369            Some(DialectType::Exasol) => {
35370                self.write_keyword("TO_TIMESTAMP");
35371                self.write("(");
35372                // Extract the string value from the expression if it's a string literal
35373                if let Some(this) = &e.this {
35374                    match this.as_ref() {
35375                        Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
35376                            let Literal::String(s) = lit.as_ref() else { unreachable!() };
35377                            self.write("'");
35378                            self.write(s);
35379                            self.write("'");
35380                        }
35381                        _ => {
35382                            self.generate_expression(this)?;
35383                        }
35384                    }
35385                }
35386                self.write(")");
35387            }
35388            // Standard: TIMESTAMP(value) or TIMESTAMP(value, zone)
35389            _ => {
35390                self.write_keyword("TIMESTAMP");
35391                self.write("(");
35392                if let Some(this) = &e.this {
35393                    self.generate_expression(this)?;
35394                }
35395                if let Some(zone) = &e.zone {
35396                    self.write(", ");
35397                    self.generate_expression(zone)?;
35398                }
35399                self.write(")");
35400            }
35401        }
35402        Ok(())
35403    }
35404
35405    fn generate_timestamp_add(&mut self, e: &TimestampAdd) -> Result<()> {
35406        // TIMESTAMP_ADD(this, expression, unit)
35407        self.write_keyword("TIMESTAMP_ADD");
35408        self.write("(");
35409        self.generate_expression(&e.this)?;
35410        self.write(", ");
35411        self.generate_expression(&e.expression)?;
35412        if let Some(unit) = &e.unit {
35413            self.write(", ");
35414            self.write_keyword(unit);
35415        }
35416        self.write(")");
35417        Ok(())
35418    }
35419
35420    fn generate_timestamp_diff(&mut self, e: &TimestampDiff) -> Result<()> {
35421        // TIMESTAMP_DIFF(this, expression, unit)
35422        self.write_keyword("TIMESTAMP_DIFF");
35423        self.write("(");
35424        self.generate_expression(&e.this)?;
35425        self.write(", ");
35426        self.generate_expression(&e.expression)?;
35427        if let Some(unit) = &e.unit {
35428            self.write(", ");
35429            self.write_keyword(unit);
35430        }
35431        self.write(")");
35432        Ok(())
35433    }
35434
35435    fn generate_timestamp_from_parts(&mut self, e: &TimestampFromParts) -> Result<()> {
35436        // TIMESTAMP_FROM_PARTS(this, expression)
35437        self.write_keyword("TIMESTAMP_FROM_PARTS");
35438        self.write("(");
35439        if let Some(this) = &e.this {
35440            self.generate_expression(this)?;
35441        }
35442        if let Some(expression) = &e.expression {
35443            self.write(", ");
35444            self.generate_expression(expression)?;
35445        }
35446        if let Some(zone) = &e.zone {
35447            self.write(", ");
35448            self.generate_expression(zone)?;
35449        }
35450        if let Some(milli) = &e.milli {
35451            self.write(", ");
35452            self.generate_expression(milli)?;
35453        }
35454        self.write(")");
35455        Ok(())
35456    }
35457
35458    fn generate_timestamp_sub(&mut self, e: &TimestampSub) -> Result<()> {
35459        // TIMESTAMP_SUB(this, INTERVAL expression unit)
35460        self.write_keyword("TIMESTAMP_SUB");
35461        self.write("(");
35462        self.generate_expression(&e.this)?;
35463        self.write(", ");
35464        self.write_keyword("INTERVAL");
35465        self.write_space();
35466        self.generate_expression(&e.expression)?;
35467        if let Some(unit) = &e.unit {
35468            self.write_space();
35469            self.write_keyword(unit);
35470        }
35471        self.write(")");
35472        Ok(())
35473    }
35474
35475    fn generate_timestamp_tz_from_parts(&mut self, e: &TimestampTzFromParts) -> Result<()> {
35476        // TIMESTAMP_TZ_FROM_PARTS(...)
35477        self.write_keyword("TIMESTAMP_TZ_FROM_PARTS");
35478        self.write("(");
35479        if let Some(zone) = &e.zone {
35480            self.generate_expression(zone)?;
35481        }
35482        self.write(")");
35483        Ok(())
35484    }
35485
35486    fn generate_to_binary(&mut self, e: &ToBinary) -> Result<()> {
35487        // TO_BINARY(this, [format])
35488        self.write_keyword("TO_BINARY");
35489        self.write("(");
35490        self.generate_expression(&e.this)?;
35491        if let Some(format) = &e.format {
35492            self.write(", '");
35493            self.write(format);
35494            self.write("'");
35495        }
35496        self.write(")");
35497        Ok(())
35498    }
35499
35500    fn generate_to_boolean(&mut self, e: &ToBoolean) -> Result<()> {
35501        // TO_BOOLEAN(this)
35502        self.write_keyword("TO_BOOLEAN");
35503        self.write("(");
35504        self.generate_expression(&e.this)?;
35505        self.write(")");
35506        Ok(())
35507    }
35508
35509    fn generate_to_char(&mut self, e: &ToChar) -> Result<()> {
35510        // TO_CHAR(this, [format], [nlsparam])
35511        self.write_keyword("TO_CHAR");
35512        self.write("(");
35513        self.generate_expression(&e.this)?;
35514        if let Some(format) = &e.format {
35515            self.write(", '");
35516            self.write(format);
35517            self.write("'");
35518        }
35519        if let Some(nlsparam) = &e.nlsparam {
35520            self.write(", ");
35521            self.generate_expression(nlsparam)?;
35522        }
35523        self.write(")");
35524        Ok(())
35525    }
35526
35527    fn generate_to_decfloat(&mut self, e: &ToDecfloat) -> Result<()> {
35528        // TO_DECFLOAT(this, [format])
35529        self.write_keyword("TO_DECFLOAT");
35530        self.write("(");
35531        self.generate_expression(&e.this)?;
35532        if let Some(format) = &e.format {
35533            self.write(", '");
35534            self.write(format);
35535            self.write("'");
35536        }
35537        self.write(")");
35538        Ok(())
35539    }
35540
35541    fn generate_to_double(&mut self, e: &ToDouble) -> Result<()> {
35542        // TO_DOUBLE(this, [format])
35543        self.write_keyword("TO_DOUBLE");
35544        self.write("(");
35545        self.generate_expression(&e.this)?;
35546        if let Some(format) = &e.format {
35547            self.write(", '");
35548            self.write(format);
35549            self.write("'");
35550        }
35551        self.write(")");
35552        Ok(())
35553    }
35554
35555    fn generate_to_file(&mut self, e: &ToFile) -> Result<()> {
35556        // TO_FILE(this, path)
35557        self.write_keyword("TO_FILE");
35558        self.write("(");
35559        self.generate_expression(&e.this)?;
35560        if let Some(path) = &e.path {
35561            self.write(", ");
35562            self.generate_expression(path)?;
35563        }
35564        self.write(")");
35565        Ok(())
35566    }
35567
35568    fn generate_to_number(&mut self, e: &ToNumber) -> Result<()> {
35569        // TO_NUMBER or TRY_TO_NUMBER (this, [format], [precision], [scale])
35570        // If safe flag is set, output TRY_TO_NUMBER
35571        let is_safe = e.safe.is_some();
35572        if is_safe {
35573            self.write_keyword("TRY_TO_NUMBER");
35574        } else {
35575            self.write_keyword("TO_NUMBER");
35576        }
35577        self.write("(");
35578        self.generate_expression(&e.this)?;
35579        if let Some(format) = &e.format {
35580            self.write(", ");
35581            self.generate_expression(format)?;
35582        }
35583        if let Some(nlsparam) = &e.nlsparam {
35584            self.write(", ");
35585            self.generate_expression(nlsparam)?;
35586        }
35587        if let Some(precision) = &e.precision {
35588            self.write(", ");
35589            self.generate_expression(precision)?;
35590        }
35591        if let Some(scale) = &e.scale {
35592            self.write(", ");
35593            self.generate_expression(scale)?;
35594        }
35595        self.write(")");
35596        Ok(())
35597    }
35598
35599    fn generate_to_table_property(&mut self, e: &ToTableProperty) -> Result<()> {
35600        // TO_TABLE this
35601        self.write_keyword("TO_TABLE");
35602        self.write_space();
35603        self.generate_expression(&e.this)?;
35604        Ok(())
35605    }
35606
35607    fn generate_transaction(&mut self, e: &Transaction) -> Result<()> {
35608        // Check mark to determine the format
35609        let mark_text = e.mark.as_ref().map(|m| match m.as_ref() {
35610            Expression::Identifier(id) => id.name.clone(),
35611            Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => { let Literal::String(s) = lit.as_ref() else { unreachable!() }; s.clone() },
35612            _ => String::new(),
35613        });
35614
35615        let is_start = mark_text.as_ref().map_or(false, |s| s == "START");
35616        let has_transaction_keyword = mark_text.as_ref().map_or(false, |s| s == "TRANSACTION");
35617        let has_with_mark = e.mark.as_ref().map_or(false, |m| {
35618            matches!(m.as_ref(), Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)))
35619        });
35620
35621        // For Presto/Trino: always use START TRANSACTION
35622        let use_start_transaction = matches!(
35623            self.config.dialect,
35624            Some(DialectType::Presto) | Some(DialectType::Trino) | Some(DialectType::Athena)
35625        );
35626        // For most dialects: strip TRANSACTION keyword
35627        let strip_transaction = matches!(
35628            self.config.dialect,
35629            Some(DialectType::Snowflake)
35630                | Some(DialectType::PostgreSQL)
35631                | Some(DialectType::Redshift)
35632                | Some(DialectType::MySQL)
35633                | Some(DialectType::Hive)
35634                | Some(DialectType::Spark)
35635                | Some(DialectType::Databricks)
35636                | Some(DialectType::DuckDB)
35637                | Some(DialectType::Oracle)
35638                | Some(DialectType::Doris)
35639                | Some(DialectType::StarRocks)
35640                | Some(DialectType::Materialize)
35641                | Some(DialectType::ClickHouse)
35642        );
35643
35644        if is_start || use_start_transaction {
35645            // START TRANSACTION [modes]
35646            self.write_keyword("START TRANSACTION");
35647            if let Some(modes) = &e.modes {
35648                self.write_space();
35649                self.generate_expression(modes)?;
35650            }
35651        } else {
35652            // BEGIN [DEFERRED|IMMEDIATE|EXCLUSIVE] [TRANSACTION] [transaction_name] [WITH MARK 'desc']
35653            self.write_keyword("BEGIN");
35654
35655            // Check if `this` is a transaction kind (DEFERRED/IMMEDIATE/EXCLUSIVE)
35656            let is_kind = e.this.as_ref().map_or(false, |t| {
35657                if let Expression::Identifier(id) = t.as_ref() {
35658                    id.name.eq_ignore_ascii_case("DEFERRED")
35659                        || id.name.eq_ignore_ascii_case("IMMEDIATE")
35660                        || id.name.eq_ignore_ascii_case("EXCLUSIVE")
35661                } else {
35662                    false
35663                }
35664            });
35665
35666            // Output kind before TRANSACTION keyword
35667            if is_kind {
35668                if let Some(this) = &e.this {
35669                    self.write_space();
35670                    if let Expression::Identifier(id) = this.as_ref() {
35671                        self.write_keyword(&id.name);
35672                    }
35673                }
35674            }
35675
35676            // Output TRANSACTION keyword if it was present and target supports it
35677            if (has_transaction_keyword || has_with_mark) && !strip_transaction {
35678                self.write_space();
35679                self.write_keyword("TRANSACTION");
35680            }
35681
35682            // Output transaction name (not kind)
35683            if !is_kind {
35684                if let Some(this) = &e.this {
35685                    self.write_space();
35686                    self.generate_expression(this)?;
35687                }
35688            }
35689
35690            // Output WITH MARK 'description' for TSQL
35691            if has_with_mark {
35692                self.write_space();
35693                self.write_keyword("WITH MARK");
35694                if let Some(Expression::Literal(lit)) = e.mark.as_deref() {
35695                    if let Literal::String(desc) = lit.as_ref() {
35696                    if !desc.is_empty() {
35697                        self.write_space();
35698                        self.write(&format!("'{}'", desc));
35699                    }
35700                }
35701                }
35702            }
35703
35704            // Output modes (isolation levels, etc.)
35705            if let Some(modes) = &e.modes {
35706                self.write_space();
35707                self.generate_expression(modes)?;
35708            }
35709        }
35710        Ok(())
35711    }
35712
35713    fn generate_transform(&mut self, e: &Transform) -> Result<()> {
35714        // TRANSFORM(this, expression)
35715        self.write_keyword("TRANSFORM");
35716        self.write("(");
35717        self.generate_expression(&e.this)?;
35718        self.write(", ");
35719        self.generate_expression(&e.expression)?;
35720        self.write(")");
35721        Ok(())
35722    }
35723
35724    fn generate_transform_model_property(&mut self, e: &TransformModelProperty) -> Result<()> {
35725        // TRANSFORM(expressions)
35726        self.write_keyword("TRANSFORM");
35727        self.write("(");
35728        if self.config.pretty && !e.expressions.is_empty() {
35729            self.indent_level += 1;
35730            for (i, expr) in e.expressions.iter().enumerate() {
35731                if i > 0 {
35732                    self.write(",");
35733                }
35734                self.write_newline();
35735                self.write_indent();
35736                self.generate_expression(expr)?;
35737            }
35738            self.indent_level -= 1;
35739            self.write_newline();
35740            self.write(")");
35741        } else {
35742            for (i, expr) in e.expressions.iter().enumerate() {
35743                if i > 0 {
35744                    self.write(", ");
35745                }
35746                self.generate_expression(expr)?;
35747            }
35748            self.write(")");
35749        }
35750        Ok(())
35751    }
35752
35753    fn generate_transient_property(&mut self, e: &TransientProperty) -> Result<()> {
35754        use crate::dialects::DialectType;
35755        // TRANSIENT is Snowflake-specific; skip for other dialects
35756        if let Some(this) = &e.this {
35757            self.generate_expression(this)?;
35758            if matches!(self.config.dialect, Some(DialectType::Snowflake) | None) {
35759                self.write_space();
35760            }
35761        }
35762        if matches!(self.config.dialect, Some(DialectType::Snowflake) | None) {
35763            self.write_keyword("TRANSIENT");
35764        }
35765        Ok(())
35766    }
35767
35768    fn generate_translate(&mut self, e: &Translate) -> Result<()> {
35769        // TRANSLATE(this, from_, to)
35770        self.write_keyword("TRANSLATE");
35771        self.write("(");
35772        self.generate_expression(&e.this)?;
35773        if let Some(from) = &e.from_ {
35774            self.write(", ");
35775            self.generate_expression(from)?;
35776        }
35777        if let Some(to) = &e.to {
35778            self.write(", ");
35779            self.generate_expression(to)?;
35780        }
35781        self.write(")");
35782        Ok(())
35783    }
35784
35785    fn generate_translate_characters(&mut self, e: &TranslateCharacters) -> Result<()> {
35786        // TRANSLATE(this USING expression)
35787        self.write_keyword("TRANSLATE");
35788        self.write("(");
35789        self.generate_expression(&e.this)?;
35790        self.write_space();
35791        self.write_keyword("USING");
35792        self.write_space();
35793        self.generate_expression(&e.expression)?;
35794        if e.with_error.is_some() {
35795            self.write_space();
35796            self.write_keyword("WITH ERROR");
35797        }
35798        self.write(")");
35799        Ok(())
35800    }
35801
35802    fn generate_truncate_table(&mut self, e: &TruncateTable) -> Result<()> {
35803        // TRUNCATE TABLE table1, table2, ...
35804        self.write_keyword("TRUNCATE TABLE");
35805        self.write_space();
35806        for (i, expr) in e.expressions.iter().enumerate() {
35807            if i > 0 {
35808                self.write(", ");
35809            }
35810            self.generate_expression(expr)?;
35811        }
35812        Ok(())
35813    }
35814
35815    fn generate_try_base64_decode_binary(&mut self, e: &TryBase64DecodeBinary) -> Result<()> {
35816        // TRY_BASE64_DECODE_BINARY(this, [alphabet])
35817        self.write_keyword("TRY_BASE64_DECODE_BINARY");
35818        self.write("(");
35819        self.generate_expression(&e.this)?;
35820        if let Some(alphabet) = &e.alphabet {
35821            self.write(", ");
35822            self.generate_expression(alphabet)?;
35823        }
35824        self.write(")");
35825        Ok(())
35826    }
35827
35828    fn generate_try_base64_decode_string(&mut self, e: &TryBase64DecodeString) -> Result<()> {
35829        // TRY_BASE64_DECODE_STRING(this, [alphabet])
35830        self.write_keyword("TRY_BASE64_DECODE_STRING");
35831        self.write("(");
35832        self.generate_expression(&e.this)?;
35833        if let Some(alphabet) = &e.alphabet {
35834            self.write(", ");
35835            self.generate_expression(alphabet)?;
35836        }
35837        self.write(")");
35838        Ok(())
35839    }
35840
35841    fn generate_try_to_decfloat(&mut self, e: &TryToDecfloat) -> Result<()> {
35842        // TRY_TO_DECFLOAT(this, [format])
35843        self.write_keyword("TRY_TO_DECFLOAT");
35844        self.write("(");
35845        self.generate_expression(&e.this)?;
35846        if let Some(format) = &e.format {
35847            self.write(", '");
35848            self.write(format);
35849            self.write("'");
35850        }
35851        self.write(")");
35852        Ok(())
35853    }
35854
35855    fn generate_ts_or_ds_add(&mut self, e: &TsOrDsAdd) -> Result<()> {
35856        // TS_OR_DS_ADD(this, expression, [unit], [return_type])
35857        self.write_keyword("TS_OR_DS_ADD");
35858        self.write("(");
35859        self.generate_expression(&e.this)?;
35860        self.write(", ");
35861        self.generate_expression(&e.expression)?;
35862        if let Some(unit) = &e.unit {
35863            self.write(", ");
35864            self.write_keyword(unit);
35865        }
35866        if let Some(return_type) = &e.return_type {
35867            self.write(", ");
35868            self.generate_expression(return_type)?;
35869        }
35870        self.write(")");
35871        Ok(())
35872    }
35873
35874    fn generate_ts_or_ds_diff(&mut self, e: &TsOrDsDiff) -> Result<()> {
35875        // TS_OR_DS_DIFF(this, expression, [unit])
35876        self.write_keyword("TS_OR_DS_DIFF");
35877        self.write("(");
35878        self.generate_expression(&e.this)?;
35879        self.write(", ");
35880        self.generate_expression(&e.expression)?;
35881        if let Some(unit) = &e.unit {
35882            self.write(", ");
35883            self.write_keyword(unit);
35884        }
35885        self.write(")");
35886        Ok(())
35887    }
35888
35889    fn generate_ts_or_ds_to_date(&mut self, e: &TsOrDsToDate) -> Result<()> {
35890        let default_time_format = "%Y-%m-%d %H:%M:%S";
35891        let default_date_format = "%Y-%m-%d";
35892        let has_non_default_format = e.format.as_ref().map_or(false, |f| {
35893            f != default_time_format && f != default_date_format
35894        });
35895
35896        if has_non_default_format {
35897            // With non-default format: dialect-specific handling
35898            let fmt = e.format.as_ref().unwrap();
35899            match self.config.dialect {
35900                Some(DialectType::MySQL) | Some(DialectType::StarRocks) => {
35901                    // MySQL/StarRocks: STR_TO_DATE(x, fmt) - no CAST wrapper
35902                    // STR_TO_DATE is the MySQL-native form of StrToTime
35903                    let str_to_time = crate::expressions::StrToTime {
35904                        this: Box::new((*e.this).clone()),
35905                        format: fmt.clone(),
35906                        zone: None,
35907                        safe: None,
35908                        target_type: None,
35909                    };
35910                    self.generate_str_to_time(&str_to_time)?;
35911                }
35912                Some(DialectType::Hive)
35913                | Some(DialectType::Spark)
35914                | Some(DialectType::Databricks) => {
35915                    // Hive/Spark: TO_DATE(x, java_fmt)
35916                    self.write_keyword("TO_DATE");
35917                    self.write("(");
35918                    self.generate_expression(&e.this)?;
35919                    self.write(", '");
35920                    self.write(&Self::strftime_to_java_format(fmt));
35921                    self.write("')");
35922                }
35923                Some(DialectType::Snowflake) => {
35924                    // Snowflake: TO_DATE(x, snowflake_fmt)
35925                    self.write_keyword("TO_DATE");
35926                    self.write("(");
35927                    self.generate_expression(&e.this)?;
35928                    self.write(", '");
35929                    self.write(&Self::strftime_to_snowflake_format(fmt));
35930                    self.write("')");
35931                }
35932                Some(DialectType::Doris) => {
35933                    // Doris: TO_DATE(x) - ignores format
35934                    self.write_keyword("TO_DATE");
35935                    self.write("(");
35936                    self.generate_expression(&e.this)?;
35937                    self.write(")");
35938                }
35939                _ => {
35940                    // Default: CAST(STR_TO_TIME(x, fmt) AS DATE)
35941                    self.write_keyword("CAST");
35942                    self.write("(");
35943                    let str_to_time = crate::expressions::StrToTime {
35944                        this: Box::new((*e.this).clone()),
35945                        format: fmt.clone(),
35946                        zone: None,
35947                        safe: None,
35948                        target_type: None,
35949                    };
35950                    self.generate_str_to_time(&str_to_time)?;
35951                    self.write_keyword(" AS ");
35952                    self.write_keyword("DATE");
35953                    self.write(")");
35954                }
35955            }
35956        } else {
35957            // Without format (or default format): simple date conversion
35958            match self.config.dialect {
35959                Some(DialectType::MySQL)
35960                | Some(DialectType::SQLite)
35961                | Some(DialectType::StarRocks) => {
35962                    // MySQL/SQLite/StarRocks: DATE(x)
35963                    self.write_keyword("DATE");
35964                    self.write("(");
35965                    self.generate_expression(&e.this)?;
35966                    self.write(")");
35967                }
35968                Some(DialectType::Hive)
35969                | Some(DialectType::Spark)
35970                | Some(DialectType::Databricks)
35971                | Some(DialectType::Snowflake)
35972                | Some(DialectType::Doris) => {
35973                    // Hive/Spark/Databricks/Snowflake/Doris: TO_DATE(x)
35974                    self.write_keyword("TO_DATE");
35975                    self.write("(");
35976                    self.generate_expression(&e.this)?;
35977                    self.write(")");
35978                }
35979                Some(DialectType::Presto)
35980                | Some(DialectType::Trino)
35981                | Some(DialectType::Athena) => {
35982                    // Presto/Trino: CAST(CAST(x AS TIMESTAMP) AS DATE)
35983                    self.write_keyword("CAST");
35984                    self.write("(");
35985                    self.write_keyword("CAST");
35986                    self.write("(");
35987                    self.generate_expression(&e.this)?;
35988                    self.write_keyword(" AS ");
35989                    self.write_keyword("TIMESTAMP");
35990                    self.write(")");
35991                    self.write_keyword(" AS ");
35992                    self.write_keyword("DATE");
35993                    self.write(")");
35994                }
35995                Some(DialectType::ClickHouse) => {
35996                    // ClickHouse: CAST(x AS Nullable(DATE))
35997                    self.write_keyword("CAST");
35998                    self.write("(");
35999                    self.generate_expression(&e.this)?;
36000                    self.write_keyword(" AS ");
36001                    self.write("Nullable(DATE)");
36002                    self.write(")");
36003                }
36004                _ => {
36005                    // Default: CAST(x AS DATE)
36006                    self.write_keyword("CAST");
36007                    self.write("(");
36008                    self.generate_expression(&e.this)?;
36009                    self.write_keyword(" AS ");
36010                    self.write_keyword("DATE");
36011                    self.write(")");
36012                }
36013            }
36014        }
36015        Ok(())
36016    }
36017
36018    fn generate_ts_or_ds_to_time(&mut self, e: &TsOrDsToTime) -> Result<()> {
36019        // TS_OR_DS_TO_TIME(this, [format])
36020        self.write_keyword("TS_OR_DS_TO_TIME");
36021        self.write("(");
36022        self.generate_expression(&e.this)?;
36023        if let Some(format) = &e.format {
36024            self.write(", '");
36025            self.write(format);
36026            self.write("'");
36027        }
36028        self.write(")");
36029        Ok(())
36030    }
36031
36032    fn generate_unhex(&mut self, e: &Unhex) -> Result<()> {
36033        // UNHEX(this, [expression])
36034        self.write_keyword("UNHEX");
36035        self.write("(");
36036        self.generate_expression(&e.this)?;
36037        if let Some(expression) = &e.expression {
36038            self.write(", ");
36039            self.generate_expression(expression)?;
36040        }
36041        self.write(")");
36042        Ok(())
36043    }
36044
36045    fn generate_unicode_string(&mut self, e: &UnicodeString) -> Result<()> {
36046        // U&this [UESCAPE escape]
36047        self.write("U&");
36048        self.generate_expression(&e.this)?;
36049        if let Some(escape) = &e.escape {
36050            self.write_space();
36051            self.write_keyword("UESCAPE");
36052            self.write_space();
36053            self.generate_expression(escape)?;
36054        }
36055        Ok(())
36056    }
36057
36058    fn generate_uniform(&mut self, e: &Uniform) -> Result<()> {
36059        // UNIFORM(this, expression, [gen], [seed])
36060        self.write_keyword("UNIFORM");
36061        self.write("(");
36062        self.generate_expression(&e.this)?;
36063        self.write(", ");
36064        self.generate_expression(&e.expression)?;
36065        if let Some(gen) = &e.gen {
36066            self.write(", ");
36067            self.generate_expression(gen)?;
36068        }
36069        if let Some(seed) = &e.seed {
36070            self.write(", ");
36071            self.generate_expression(seed)?;
36072        }
36073        self.write(")");
36074        Ok(())
36075    }
36076
36077    fn generate_unique_column_constraint(&mut self, e: &UniqueColumnConstraint) -> Result<()> {
36078        // UNIQUE [NULLS NOT DISTINCT] [this] [index_type] [on_conflict] [options]
36079        self.write_keyword("UNIQUE");
36080        // Output NULLS NOT DISTINCT if nulls is set (PostgreSQL 15+ feature)
36081        if e.nulls.is_some() {
36082            self.write(" NULLS NOT DISTINCT");
36083        }
36084        if let Some(this) = &e.this {
36085            self.write_space();
36086            self.generate_expression(this)?;
36087        }
36088        if let Some(index_type) = &e.index_type {
36089            self.write(" USING ");
36090            self.generate_expression(index_type)?;
36091        }
36092        if let Some(on_conflict) = &e.on_conflict {
36093            self.write_space();
36094            self.generate_expression(on_conflict)?;
36095        }
36096        for opt in &e.options {
36097            self.write_space();
36098            self.generate_expression(opt)?;
36099        }
36100        Ok(())
36101    }
36102
36103    fn generate_unique_key_property(&mut self, e: &UniqueKeyProperty) -> Result<()> {
36104        // UNIQUE KEY (expressions)
36105        self.write_keyword("UNIQUE KEY");
36106        self.write(" (");
36107        for (i, expr) in e.expressions.iter().enumerate() {
36108            if i > 0 {
36109                self.write(", ");
36110            }
36111            self.generate_expression(expr)?;
36112        }
36113        self.write(")");
36114        Ok(())
36115    }
36116
36117    fn generate_rollup_property(&mut self, e: &RollupProperty) -> Result<()> {
36118        // ROLLUP (r1(col1, col2), r2(col1))
36119        self.write_keyword("ROLLUP");
36120        self.write(" (");
36121        for (i, index) in e.expressions.iter().enumerate() {
36122            if i > 0 {
36123                self.write(", ");
36124            }
36125            self.generate_identifier(&index.name)?;
36126            self.write("(");
36127            for (j, col) in index.expressions.iter().enumerate() {
36128                if j > 0 {
36129                    self.write(", ");
36130                }
36131                self.generate_identifier(col)?;
36132            }
36133            self.write(")");
36134        }
36135        self.write(")");
36136        Ok(())
36137    }
36138
36139    fn generate_unix_to_str(&mut self, e: &UnixToStr) -> Result<()> {
36140        match self.config.dialect {
36141            Some(DialectType::DuckDB) => {
36142                // DuckDB: STRFTIME(TO_TIMESTAMP(value), format)
36143                self.write_keyword("STRFTIME");
36144                self.write("(");
36145                self.write_keyword("TO_TIMESTAMP");
36146                self.write("(");
36147                self.generate_expression(&e.this)?;
36148                self.write("), '");
36149                if let Some(format) = &e.format {
36150                    self.write(format);
36151                }
36152                self.write("')");
36153            }
36154            Some(DialectType::Hive) => {
36155                // Hive: FROM_UNIXTIME(value, format) - elide format when it's the default
36156                self.write_keyword("FROM_UNIXTIME");
36157                self.write("(");
36158                self.generate_expression(&e.this)?;
36159                if let Some(format) = &e.format {
36160                    if format != "yyyy-MM-dd HH:mm:ss" {
36161                        self.write(", '");
36162                        self.write(format);
36163                        self.write("'");
36164                    }
36165                }
36166                self.write(")");
36167            }
36168            Some(DialectType::Presto) | Some(DialectType::Trino) => {
36169                // Presto: DATE_FORMAT(FROM_UNIXTIME(value), format)
36170                self.write_keyword("DATE_FORMAT");
36171                self.write("(");
36172                self.write_keyword("FROM_UNIXTIME");
36173                self.write("(");
36174                self.generate_expression(&e.this)?;
36175                self.write("), '");
36176                if let Some(format) = &e.format {
36177                    self.write(format);
36178                }
36179                self.write("')");
36180            }
36181            Some(DialectType::Spark) | Some(DialectType::Databricks) => {
36182                // Spark: FROM_UNIXTIME(value, format)
36183                self.write_keyword("FROM_UNIXTIME");
36184                self.write("(");
36185                self.generate_expression(&e.this)?;
36186                if let Some(format) = &e.format {
36187                    self.write(", '");
36188                    self.write(format);
36189                    self.write("'");
36190                }
36191                self.write(")");
36192            }
36193            _ => {
36194                // Default: UNIX_TO_STR(this, [format])
36195                self.write_keyword("UNIX_TO_STR");
36196                self.write("(");
36197                self.generate_expression(&e.this)?;
36198                if let Some(format) = &e.format {
36199                    self.write(", '");
36200                    self.write(format);
36201                    self.write("'");
36202                }
36203                self.write(")");
36204            }
36205        }
36206        Ok(())
36207    }
36208
36209    fn generate_unix_to_time(&mut self, e: &UnixToTime) -> Result<()> {
36210        use crate::dialects::DialectType;
36211        let scale = e.scale.unwrap_or(0); // 0 = seconds
36212
36213        match self.config.dialect {
36214            Some(DialectType::Snowflake) => {
36215                // Snowflake: TO_TIMESTAMP(value[, scale]) - skip scale for seconds (0)
36216                self.write_keyword("TO_TIMESTAMP");
36217                self.write("(");
36218                self.generate_expression(&e.this)?;
36219                if let Some(s) = e.scale {
36220                    if s > 0 {
36221                        self.write(", ");
36222                        self.write(&s.to_string());
36223                    }
36224                }
36225                self.write(")");
36226            }
36227            Some(DialectType::BigQuery) => {
36228                // BigQuery: TIMESTAMP_SECONDS(value) / TIMESTAMP_MILLIS(value)
36229                // or TIMESTAMP_SECONDS(CAST(value / POWER(10, scale) AS INT64)) for other scales
36230                match scale {
36231                    0 => {
36232                        self.write_keyword("TIMESTAMP_SECONDS");
36233                        self.write("(");
36234                        self.generate_expression(&e.this)?;
36235                        self.write(")");
36236                    }
36237                    3 => {
36238                        self.write_keyword("TIMESTAMP_MILLIS");
36239                        self.write("(");
36240                        self.generate_expression(&e.this)?;
36241                        self.write(")");
36242                    }
36243                    6 => {
36244                        self.write_keyword("TIMESTAMP_MICROS");
36245                        self.write("(");
36246                        self.generate_expression(&e.this)?;
36247                        self.write(")");
36248                    }
36249                    _ => {
36250                        // TIMESTAMP_SECONDS(CAST(value / POWER(10, scale) AS INT64))
36251                        self.write_keyword("TIMESTAMP_SECONDS");
36252                        self.write("(CAST(");
36253                        self.generate_expression(&e.this)?;
36254                        self.write(&format!(" / POWER(10, {}) AS INT64))", scale));
36255                    }
36256                }
36257            }
36258            Some(DialectType::Spark) => {
36259                // Spark: CAST(FROM_UNIXTIME(value) AS TIMESTAMP) for scale=0
36260                // TIMESTAMP_MILLIS(value) for scale=3
36261                // TIMESTAMP_MICROS(value) for scale=6
36262                // TIMESTAMP_SECONDS(value / POWER(10, scale)) for other scales
36263                match scale {
36264                    0 => {
36265                        self.write_keyword("CAST");
36266                        self.write("(");
36267                        self.write_keyword("FROM_UNIXTIME");
36268                        self.write("(");
36269                        self.generate_expression(&e.this)?;
36270                        self.write(") ");
36271                        self.write_keyword("AS TIMESTAMP");
36272                        self.write(")");
36273                    }
36274                    3 => {
36275                        self.write_keyword("TIMESTAMP_MILLIS");
36276                        self.write("(");
36277                        self.generate_expression(&e.this)?;
36278                        self.write(")");
36279                    }
36280                    6 => {
36281                        self.write_keyword("TIMESTAMP_MICROS");
36282                        self.write("(");
36283                        self.generate_expression(&e.this)?;
36284                        self.write(")");
36285                    }
36286                    _ => {
36287                        self.write_keyword("TIMESTAMP_SECONDS");
36288                        self.write("(");
36289                        self.generate_expression(&e.this)?;
36290                        self.write(&format!(" / POWER(10, {}))", scale));
36291                    }
36292                }
36293            }
36294            Some(DialectType::Databricks) => {
36295                // Databricks: CAST(FROM_UNIXTIME(value) AS TIMESTAMP) for scale=0
36296                // TIMESTAMP_MILLIS(value) for scale=3
36297                // TIMESTAMP_MICROS(value) for scale=6
36298                match scale {
36299                    0 => {
36300                        self.write_keyword("CAST");
36301                        self.write("(");
36302                        self.write_keyword("FROM_UNIXTIME");
36303                        self.write("(");
36304                        self.generate_expression(&e.this)?;
36305                        self.write(") ");
36306                        self.write_keyword("AS TIMESTAMP");
36307                        self.write(")");
36308                    }
36309                    3 => {
36310                        self.write_keyword("TIMESTAMP_MILLIS");
36311                        self.write("(");
36312                        self.generate_expression(&e.this)?;
36313                        self.write(")");
36314                    }
36315                    6 => {
36316                        self.write_keyword("TIMESTAMP_MICROS");
36317                        self.write("(");
36318                        self.generate_expression(&e.this)?;
36319                        self.write(")");
36320                    }
36321                    _ => {
36322                        self.write_keyword("TIMESTAMP_SECONDS");
36323                        self.write("(");
36324                        self.generate_expression(&e.this)?;
36325                        self.write(&format!(" / POWER(10, {}))", scale));
36326                    }
36327                }
36328            }
36329            Some(DialectType::Hive) => {
36330                // Hive: FROM_UNIXTIME(value)
36331                if scale == 0 {
36332                    self.write_keyword("FROM_UNIXTIME");
36333                    self.write("(");
36334                    self.generate_expression(&e.this)?;
36335                    self.write(")");
36336                } else {
36337                    self.write_keyword("FROM_UNIXTIME");
36338                    self.write("(");
36339                    self.generate_expression(&e.this)?;
36340                    self.write(&format!(" / POWER(10, {})", scale));
36341                    self.write(")");
36342                }
36343            }
36344            Some(DialectType::Presto) | Some(DialectType::Trino) => {
36345                // Presto: FROM_UNIXTIME(CAST(value AS DOUBLE) / POW(10, scale)) for scale > 0
36346                // FROM_UNIXTIME(value) for scale=0
36347                if scale == 0 {
36348                    self.write_keyword("FROM_UNIXTIME");
36349                    self.write("(");
36350                    self.generate_expression(&e.this)?;
36351                    self.write(")");
36352                } else {
36353                    self.write_keyword("FROM_UNIXTIME");
36354                    self.write("(CAST(");
36355                    self.generate_expression(&e.this)?;
36356                    self.write(&format!(" AS DOUBLE) / POW(10, {}))", scale));
36357                }
36358            }
36359            Some(DialectType::DuckDB) => {
36360                // DuckDB: TO_TIMESTAMP(value) for scale=0
36361                // EPOCH_MS(value) for scale=3
36362                // MAKE_TIMESTAMP(value) for scale=6
36363                match scale {
36364                    0 => {
36365                        self.write_keyword("TO_TIMESTAMP");
36366                        self.write("(");
36367                        self.generate_expression(&e.this)?;
36368                        self.write(")");
36369                    }
36370                    3 => {
36371                        self.write_keyword("EPOCH_MS");
36372                        self.write("(");
36373                        self.generate_expression(&e.this)?;
36374                        self.write(")");
36375                    }
36376                    6 => {
36377                        self.write_keyword("MAKE_TIMESTAMP");
36378                        self.write("(");
36379                        self.generate_expression(&e.this)?;
36380                        self.write(")");
36381                    }
36382                    _ => {
36383                        self.write_keyword("TO_TIMESTAMP");
36384                        self.write("(");
36385                        self.generate_expression(&e.this)?;
36386                        self.write(&format!(" / POWER(10, {}))", scale));
36387                        self.write_keyword(" AT TIME ZONE");
36388                        self.write(" 'UTC'");
36389                    }
36390                }
36391            }
36392            Some(DialectType::Doris) | Some(DialectType::StarRocks) => {
36393                // Doris/StarRocks: FROM_UNIXTIME(value)
36394                self.write_keyword("FROM_UNIXTIME");
36395                self.write("(");
36396                self.generate_expression(&e.this)?;
36397                self.write(")");
36398            }
36399            Some(DialectType::Oracle) => {
36400                // Oracle: TO_DATE('1970-01-01', 'YYYY-MM-DD') + (x / 86400)
36401                self.write("TO_DATE('1970-01-01', 'YYYY-MM-DD') + (");
36402                self.generate_expression(&e.this)?;
36403                self.write(" / 86400)");
36404            }
36405            Some(DialectType::Redshift) => {
36406                // Redshift: (TIMESTAMP 'epoch' + value * INTERVAL '1 SECOND') for scale=0
36407                // (TIMESTAMP 'epoch' + (value / POWER(10, scale)) * INTERVAL '1 SECOND') for scale > 0
36408                self.write("(TIMESTAMP 'epoch' + ");
36409                if scale == 0 {
36410                    self.generate_expression(&e.this)?;
36411                } else {
36412                    self.write("(");
36413                    self.generate_expression(&e.this)?;
36414                    self.write(&format!(" / POWER(10, {}))", scale));
36415                }
36416                self.write(" * INTERVAL '1 SECOND')");
36417            }
36418            Some(DialectType::Exasol) => {
36419                // Exasol: FROM_POSIX_TIME(value)
36420                self.write_keyword("FROM_POSIX_TIME");
36421                self.write("(");
36422                self.generate_expression(&e.this)?;
36423                self.write(")");
36424            }
36425            _ => {
36426                // Default: TO_TIMESTAMP(value[, scale])
36427                self.write_keyword("TO_TIMESTAMP");
36428                self.write("(");
36429                self.generate_expression(&e.this)?;
36430                if let Some(s) = e.scale {
36431                    self.write(", ");
36432                    self.write(&s.to_string());
36433                }
36434                self.write(")");
36435            }
36436        }
36437        Ok(())
36438    }
36439
36440    fn generate_unpivot_columns(&mut self, e: &UnpivotColumns) -> Result<()> {
36441        // NAME col VALUE col1, col2, ...
36442        if !matches!(&*e.this, Expression::Null(_)) {
36443            self.write_keyword("NAME");
36444            self.write_space();
36445            self.generate_expression(&e.this)?;
36446        }
36447        if !e.expressions.is_empty() {
36448            self.write_space();
36449            self.write_keyword("VALUE");
36450            self.write_space();
36451            for (i, expr) in e.expressions.iter().enumerate() {
36452                if i > 0 {
36453                    self.write(", ");
36454                }
36455                self.generate_expression(expr)?;
36456            }
36457        }
36458        Ok(())
36459    }
36460
36461    fn generate_user_defined_function(&mut self, e: &UserDefinedFunction) -> Result<()> {
36462        // this(expressions) or (this)(expressions)
36463        if e.wrapped.is_some() {
36464            self.write("(");
36465        }
36466        self.generate_expression(&e.this)?;
36467        if e.wrapped.is_some() {
36468            self.write(")");
36469        }
36470        self.write("(");
36471        for (i, expr) in e.expressions.iter().enumerate() {
36472            if i > 0 {
36473                self.write(", ");
36474            }
36475            self.generate_expression(expr)?;
36476        }
36477        self.write(")");
36478        Ok(())
36479    }
36480
36481    fn generate_using_template_property(&mut self, e: &UsingTemplateProperty) -> Result<()> {
36482        // USING TEMPLATE this
36483        self.write_keyword("USING TEMPLATE");
36484        self.write_space();
36485        self.generate_expression(&e.this)?;
36486        Ok(())
36487    }
36488
36489    fn generate_utc_time(&mut self, _e: &UtcTime) -> Result<()> {
36490        // UTC_TIME
36491        self.write_keyword("UTC_TIME");
36492        Ok(())
36493    }
36494
36495    fn generate_utc_timestamp(&mut self, _e: &UtcTimestamp) -> Result<()> {
36496        if matches!(self.config.dialect, Some(crate::dialects::DialectType::ClickHouse)) {
36497            self.write_keyword("CURRENT_TIMESTAMP");
36498            self.write("('UTC')");
36499        } else {
36500            self.write_keyword("UTC_TIMESTAMP");
36501        }
36502        Ok(())
36503    }
36504
36505    fn generate_uuid(&mut self, e: &Uuid) -> Result<()> {
36506        use crate::dialects::DialectType;
36507        // Choose UUID function name based on target dialect
36508        let func_name = match self.config.dialect {
36509            Some(DialectType::Snowflake) => "UUID_STRING",
36510            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift) => "GEN_RANDOM_UUID",
36511            Some(DialectType::BigQuery) => "GENERATE_UUID",
36512            _ => {
36513                if let Some(name) = &e.name {
36514                    name.as_str()
36515                } else {
36516                    "UUID"
36517                }
36518            }
36519        };
36520        self.write_keyword(func_name);
36521        self.write("(");
36522        if let Some(this) = &e.this {
36523            self.generate_expression(this)?;
36524        }
36525        self.write(")");
36526        Ok(())
36527    }
36528
36529    fn generate_var_map(&mut self, e: &VarMap) -> Result<()> {
36530        // MAP(key1, value1, key2, value2, ...)
36531        self.write_keyword("MAP");
36532        self.write("(");
36533        let mut first = true;
36534        for (k, v) in e.keys.iter().zip(e.values.iter()) {
36535            if !first {
36536                self.write(", ");
36537            }
36538            self.generate_expression(k)?;
36539            self.write(", ");
36540            self.generate_expression(v)?;
36541            first = false;
36542        }
36543        self.write(")");
36544        Ok(())
36545    }
36546
36547    fn generate_vector_search(&mut self, e: &VectorSearch) -> Result<()> {
36548        // VECTOR_SEARCH(this, column_to_search, query_table, query_column_to_search, top_k, distance_type, ...)
36549        self.write_keyword("VECTOR_SEARCH");
36550        self.write("(");
36551        self.generate_expression(&e.this)?;
36552        if let Some(col) = &e.column_to_search {
36553            self.write(", ");
36554            self.generate_expression(col)?;
36555        }
36556        if let Some(query_table) = &e.query_table {
36557            self.write(", ");
36558            self.generate_expression(query_table)?;
36559        }
36560        if let Some(query_col) = &e.query_column_to_search {
36561            self.write(", ");
36562            self.generate_expression(query_col)?;
36563        }
36564        if let Some(top_k) = &e.top_k {
36565            self.write(", ");
36566            self.generate_expression(top_k)?;
36567        }
36568        if let Some(dist_type) = &e.distance_type {
36569            self.write(", ");
36570            self.generate_expression(dist_type)?;
36571        }
36572        self.write(")");
36573        Ok(())
36574    }
36575
36576    fn generate_version(&mut self, e: &Version) -> Result<()> {
36577        // Python: f"FOR {expression.name} {kind} {expr}"
36578        // e.this = Identifier("TIMESTAMP" or "VERSION")
36579        // e.kind = "AS OF" (or "BETWEEN", etc.)
36580        // e.expression = the value expression
36581        // Hive does NOT use the FOR prefix for time travel
36582        use crate::dialects::DialectType;
36583        let skip_for = matches!(
36584            self.config.dialect,
36585            Some(DialectType::Hive) | Some(DialectType::Spark) | Some(DialectType::Databricks)
36586        );
36587        if !skip_for {
36588            self.write_keyword("FOR");
36589            self.write_space();
36590        }
36591        // Extract the name from this (which is an Identifier expression)
36592        match e.this.as_ref() {
36593            Expression::Identifier(ident) => {
36594                self.write_keyword(&ident.name);
36595            }
36596            _ => {
36597                self.generate_expression(&e.this)?;
36598            }
36599        }
36600        self.write_space();
36601        self.write_keyword(&e.kind);
36602        if let Some(expression) = &e.expression {
36603            self.write_space();
36604            self.generate_expression(expression)?;
36605        }
36606        Ok(())
36607    }
36608
36609    fn generate_view_attribute_property(&mut self, e: &ViewAttributeProperty) -> Result<()> {
36610        // Python: return self.sql(expression, "this")
36611        self.generate_expression(&e.this)?;
36612        Ok(())
36613    }
36614
36615    fn generate_volatile_property(&mut self, e: &VolatileProperty) -> Result<()> {
36616        // Python: return "VOLATILE" if expression.args.get("this") is None else "NOT VOLATILE"
36617        if e.this.is_some() {
36618            self.write_keyword("NOT VOLATILE");
36619        } else {
36620            self.write_keyword("VOLATILE");
36621        }
36622        Ok(())
36623    }
36624
36625    fn generate_watermark_column_constraint(
36626        &mut self,
36627        e: &WatermarkColumnConstraint,
36628    ) -> Result<()> {
36629        // Python: f"WATERMARK FOR {self.sql(expression, 'this')} AS {self.sql(expression, 'expression')}"
36630        self.write_keyword("WATERMARK FOR");
36631        self.write_space();
36632        self.generate_expression(&e.this)?;
36633        self.write_space();
36634        self.write_keyword("AS");
36635        self.write_space();
36636        self.generate_expression(&e.expression)?;
36637        Ok(())
36638    }
36639
36640    fn generate_week(&mut self, e: &Week) -> Result<()> {
36641        // Python: return self.func("WEEK", expression.this, expression.args.get("mode"))
36642        self.write_keyword("WEEK");
36643        self.write("(");
36644        self.generate_expression(&e.this)?;
36645        if let Some(mode) = &e.mode {
36646            self.write(", ");
36647            self.generate_expression(mode)?;
36648        }
36649        self.write(")");
36650        Ok(())
36651    }
36652
36653    fn generate_when(&mut self, e: &When) -> Result<()> {
36654        // Python: WHEN {matched}{source}{condition} THEN {then}
36655        // matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED"
36656        // source = " BY SOURCE" if MATCHED_BY_SOURCE and expression.args.get("source") else ""
36657        self.write_keyword("WHEN");
36658        self.write_space();
36659
36660        // Check if matched
36661        if let Some(matched) = &e.matched {
36662            // Check the expression - if it's a boolean true, use MATCHED, otherwise NOT MATCHED
36663            match matched.as_ref() {
36664                Expression::Boolean(b) if b.value => {
36665                    self.write_keyword("MATCHED");
36666                }
36667                _ => {
36668                    self.write_keyword("NOT MATCHED");
36669                }
36670            }
36671        } else {
36672            self.write_keyword("NOT MATCHED");
36673        }
36674
36675        // BY SOURCE / BY TARGET
36676        // source = Boolean(true) means BY SOURCE, Boolean(false) means BY TARGET
36677        // BY TARGET is the default and typically omitted in output
36678        // Only emit if the dialect supports BY SOURCE syntax
36679        if self.config.matched_by_source {
36680            if let Some(source) = &e.source {
36681                if let Expression::Boolean(b) = source.as_ref() {
36682                    if b.value {
36683                        // BY SOURCE
36684                        self.write_space();
36685                        self.write_keyword("BY SOURCE");
36686                    }
36687                    // BY TARGET (b.value == false) is omitted as it's the default
36688                } else {
36689                    // For non-boolean source, output as BY SOURCE (legacy behavior)
36690                    self.write_space();
36691                    self.write_keyword("BY SOURCE");
36692                }
36693            }
36694        }
36695
36696        // Condition
36697        if let Some(condition) = &e.condition {
36698            self.write_space();
36699            self.write_keyword("AND");
36700            self.write_space();
36701            self.generate_expression(condition)?;
36702        }
36703
36704        self.write_space();
36705        self.write_keyword("THEN");
36706        self.write_space();
36707
36708        // Generate the then expression (could be INSERT, UPDATE, DELETE)
36709        // MERGE actions are stored as Tuples with the action keyword as first element
36710        self.generate_merge_action(&e.then)?;
36711
36712        Ok(())
36713    }
36714
36715    fn generate_merge_action(&mut self, action: &Expression) -> Result<()> {
36716        match action {
36717            Expression::Tuple(tuple) => {
36718                let elements = &tuple.expressions;
36719                if elements.is_empty() {
36720                    return self.generate_expression(action);
36721                }
36722                // Check if first element is a Var (INSERT, UPDATE, DELETE, etc.)
36723                match &elements[0] {
36724                    Expression::Var(v) if v.this == "INSERT" => {
36725                        self.write_keyword("INSERT");
36726                        // Spark: INSERT * (insert all columns)
36727                        if elements.len() > 1 && matches!(&elements[1], Expression::Star(_)) {
36728                            self.write(" *");
36729                        } else {
36730                            let mut values_idx = 1;
36731                            // Check if second element is column list (Tuple)
36732                            if elements.len() > 1 {
36733                                if let Expression::Tuple(cols) = &elements[1] {
36734                                    // Could be columns or values - if there's a third element, second is columns
36735                                    if elements.len() > 2 {
36736                                        // Second is columns, third is values
36737                                        self.write(" (");
36738                                        for (i, col) in cols.expressions.iter().enumerate() {
36739                                            if i > 0 {
36740                                                self.write(", ");
36741                                            }
36742                                            // Strip MERGE target qualifiers from INSERT column list
36743                                            if !self.merge_strip_qualifiers.is_empty() {
36744                                                let stripped = self.strip_merge_qualifier(col);
36745                                                self.generate_expression(&stripped)?;
36746                                            } else {
36747                                                self.generate_expression(col)?;
36748                                            }
36749                                        }
36750                                        self.write(")");
36751                                        values_idx = 2;
36752                                    } else {
36753                                        // Only two elements: INSERT + values (no explicit columns)
36754                                        values_idx = 1;
36755                                    }
36756                                }
36757                            }
36758                            // Generate VALUES clause
36759                            if values_idx < elements.len() {
36760                                // Check if it's INSERT ROW (BigQuery) — no VALUES keyword needed
36761                                let is_row = matches!(&elements[values_idx], Expression::Var(v) if v.this == "ROW");
36762                                if !is_row {
36763                                    self.write_space();
36764                                    self.write_keyword("VALUES");
36765                                }
36766                                self.write(" ");
36767                                if let Expression::Tuple(vals) = &elements[values_idx] {
36768                                    self.write("(");
36769                                    for (i, val) in vals.expressions.iter().enumerate() {
36770                                        if i > 0 {
36771                                            self.write(", ");
36772                                        }
36773                                        self.generate_expression(val)?;
36774                                    }
36775                                    self.write(")");
36776                                } else {
36777                                    self.generate_expression(&elements[values_idx])?;
36778                                }
36779                            }
36780                        } // close else for INSERT * check
36781                    }
36782                    Expression::Var(v) if v.this == "UPDATE" => {
36783                        self.write_keyword("UPDATE");
36784                        // Spark: UPDATE * (update all columns)
36785                        if elements.len() > 1 && matches!(&elements[1], Expression::Star(_)) {
36786                            self.write(" *");
36787                        } else if elements.len() > 1 {
36788                            self.write_space();
36789                            self.write_keyword("SET");
36790                            // In pretty mode, put assignments on next line with extra indent
36791                            if self.config.pretty {
36792                                self.write_newline();
36793                                self.indent_level += 1;
36794                                self.write_indent();
36795                            } else {
36796                                self.write_space();
36797                            }
36798                            if let Expression::Tuple(assignments) = &elements[1] {
36799                                for (i, assignment) in assignments.expressions.iter().enumerate() {
36800                                    if i > 0 {
36801                                        if self.config.pretty {
36802                                            self.write(",");
36803                                            self.write_newline();
36804                                            self.write_indent();
36805                                        } else {
36806                                            self.write(", ");
36807                                        }
36808                                    }
36809                                    // Strip MERGE target qualifiers from left side of UPDATE SET
36810                                    if !self.merge_strip_qualifiers.is_empty() {
36811                                        self.generate_merge_set_assignment(assignment)?;
36812                                    } else {
36813                                        self.generate_expression(assignment)?;
36814                                    }
36815                                }
36816                            } else {
36817                                self.generate_expression(&elements[1])?;
36818                            }
36819                            if self.config.pretty {
36820                                self.indent_level -= 1;
36821                            }
36822                        }
36823                    }
36824                    _ => {
36825                        // Fallback: generic tuple generation
36826                        self.generate_expression(action)?;
36827                    }
36828                }
36829            }
36830            Expression::Var(v)
36831                if v.this == "INSERT"
36832                    || v.this == "UPDATE"
36833                    || v.this == "DELETE"
36834                    || v.this == "DO NOTHING" =>
36835            {
36836                self.write_keyword(&v.this);
36837            }
36838            _ => {
36839                self.generate_expression(action)?;
36840            }
36841        }
36842        Ok(())
36843    }
36844
36845    /// Generate a MERGE UPDATE SET assignment, stripping target table qualifier from left side
36846    fn generate_merge_set_assignment(&mut self, assignment: &Expression) -> Result<()> {
36847        match assignment {
36848            Expression::Eq(eq) => {
36849                // Strip qualifier from the left side if it matches a MERGE target name
36850                let stripped_left = self.strip_merge_qualifier(&eq.left);
36851                self.generate_expression(&stripped_left)?;
36852                self.write(" = ");
36853                self.generate_expression(&eq.right)?;
36854                Ok(())
36855            }
36856            other => self.generate_expression(other),
36857        }
36858    }
36859
36860    /// Strip table qualifier from a column reference if it matches a MERGE target name
36861    fn strip_merge_qualifier(&self, expr: &Expression) -> Expression {
36862        match expr {
36863            Expression::Column(col) => {
36864                if let Some(ref table_ident) = col.table {
36865                    if self
36866                        .merge_strip_qualifiers
36867                        .iter()
36868                        .any(|n| n.eq_ignore_ascii_case(&table_ident.name))
36869                    {
36870                        // Strip the table qualifier
36871                        let mut col = col.clone();
36872                        col.table = None;
36873                        return Expression::Column(col);
36874                    }
36875                }
36876                expr.clone()
36877            }
36878            Expression::Dot(dot) => {
36879                // table.column -> column (strip qualifier)
36880                if let Expression::Identifier(id) = &dot.this {
36881                    if self
36882                        .merge_strip_qualifiers
36883                        .iter()
36884                        .any(|n| n.eq_ignore_ascii_case(&id.name))
36885                    {
36886                        return Expression::Identifier(dot.field.clone());
36887                    }
36888                }
36889                expr.clone()
36890            }
36891            _ => expr.clone(),
36892        }
36893    }
36894
36895    fn generate_whens(&mut self, e: &Whens) -> Result<()> {
36896        // Python: return self.expressions(expression, sep=" ", indent=False)
36897        for (i, expr) in e.expressions.iter().enumerate() {
36898            if i > 0 {
36899                // In pretty mode, each WHEN clause on its own line
36900                if self.config.pretty {
36901                    self.write_newline();
36902                    self.write_indent();
36903                } else {
36904                    self.write_space();
36905                }
36906            }
36907            self.generate_expression(expr)?;
36908        }
36909        Ok(())
36910    }
36911
36912    fn generate_where(&mut self, e: &Where) -> Result<()> {
36913        // Python: return f"{self.seg('WHERE')}{self.sep()}{this}"
36914        self.write_keyword("WHERE");
36915        self.write_space();
36916        self.generate_expression(&e.this)?;
36917        Ok(())
36918    }
36919
36920    fn generate_width_bucket(&mut self, e: &WidthBucket) -> Result<()> {
36921        // Python: return self.func("WIDTH_BUCKET", expression.this, ...)
36922        self.write_keyword("WIDTH_BUCKET");
36923        self.write("(");
36924        self.generate_expression(&e.this)?;
36925        if let Some(min_value) = &e.min_value {
36926            self.write(", ");
36927            self.generate_expression(min_value)?;
36928        }
36929        if let Some(max_value) = &e.max_value {
36930            self.write(", ");
36931            self.generate_expression(max_value)?;
36932        }
36933        if let Some(num_buckets) = &e.num_buckets {
36934            self.write(", ");
36935            self.generate_expression(num_buckets)?;
36936        }
36937        self.write(")");
36938        Ok(())
36939    }
36940
36941    fn generate_window(&mut self, e: &WindowSpec) -> Result<()> {
36942        // Window specification: PARTITION BY ... ORDER BY ... frame
36943        self.generate_window_spec(e)
36944    }
36945
36946    fn generate_window_spec(&mut self, e: &WindowSpec) -> Result<()> {
36947        // Window specification: PARTITION BY ... ORDER BY ... frame
36948        let mut has_content = false;
36949
36950        // PARTITION BY
36951        if !e.partition_by.is_empty() {
36952            self.write_keyword("PARTITION BY");
36953            self.write_space();
36954            for (i, expr) in e.partition_by.iter().enumerate() {
36955                if i > 0 {
36956                    self.write(", ");
36957                }
36958                self.generate_expression(expr)?;
36959            }
36960            has_content = true;
36961        }
36962
36963        // ORDER BY
36964        if !e.order_by.is_empty() {
36965            if has_content {
36966                self.write_space();
36967            }
36968            self.write_keyword("ORDER BY");
36969            self.write_space();
36970            for (i, ordered) in e.order_by.iter().enumerate() {
36971                if i > 0 {
36972                    self.write(", ");
36973                }
36974                self.generate_expression(&ordered.this)?;
36975                if ordered.desc {
36976                    self.write_space();
36977                    self.write_keyword("DESC");
36978                } else if ordered.explicit_asc {
36979                    self.write_space();
36980                    self.write_keyword("ASC");
36981                }
36982                if let Some(nulls_first) = ordered.nulls_first {
36983                    self.write_space();
36984                    self.write_keyword("NULLS");
36985                    self.write_space();
36986                    if nulls_first {
36987                        self.write_keyword("FIRST");
36988                    } else {
36989                        self.write_keyword("LAST");
36990                    }
36991                }
36992            }
36993            has_content = true;
36994        }
36995
36996        // Frame specification
36997        if let Some(frame) = &e.frame {
36998            if has_content {
36999                self.write_space();
37000            }
37001            self.generate_window_frame(frame)?;
37002        }
37003
37004        Ok(())
37005    }
37006
37007    fn generate_with_data_property(&mut self, e: &WithDataProperty) -> Result<()> {
37008        // Python: f"WITH {'NO ' if expression.args.get('no') else ''}DATA"
37009        self.write_keyword("WITH");
37010        self.write_space();
37011        if e.no.is_some() {
37012            self.write_keyword("NO");
37013            self.write_space();
37014        }
37015        self.write_keyword("DATA");
37016
37017        // statistics
37018        if let Some(statistics) = &e.statistics {
37019            self.write_space();
37020            self.write_keyword("AND");
37021            self.write_space();
37022            // Check if statistics is true or false
37023            match statistics.as_ref() {
37024                Expression::Boolean(b) if !b.value => {
37025                    self.write_keyword("NO");
37026                    self.write_space();
37027                }
37028                _ => {}
37029            }
37030            self.write_keyword("STATISTICS");
37031        }
37032        Ok(())
37033    }
37034
37035    fn generate_with_fill(&mut self, e: &WithFill) -> Result<()> {
37036        // Python: f"WITH FILL{from_sql}{to_sql}{step_sql}{interpolate}"
37037        self.write_keyword("WITH FILL");
37038
37039        if let Some(from_) = &e.from_ {
37040            self.write_space();
37041            self.write_keyword("FROM");
37042            self.write_space();
37043            self.generate_expression(from_)?;
37044        }
37045
37046        if let Some(to) = &e.to {
37047            self.write_space();
37048            self.write_keyword("TO");
37049            self.write_space();
37050            self.generate_expression(to)?;
37051        }
37052
37053        if let Some(step) = &e.step {
37054            self.write_space();
37055            self.write_keyword("STEP");
37056            self.write_space();
37057            self.generate_expression(step)?;
37058        }
37059
37060        if let Some(staleness) = &e.staleness {
37061            self.write_space();
37062            self.write_keyword("STALENESS");
37063            self.write_space();
37064            self.generate_expression(staleness)?;
37065        }
37066
37067        if let Some(interpolate) = &e.interpolate {
37068            self.write_space();
37069            self.write_keyword("INTERPOLATE");
37070            self.write(" (");
37071            // INTERPOLATE items use reversed alias format: name AS expression
37072            self.generate_interpolate_item(interpolate)?;
37073            self.write(")");
37074        }
37075
37076        Ok(())
37077    }
37078
37079    /// Generate INTERPOLATE items with reversed alias format (name AS expression)
37080    fn generate_interpolate_item(&mut self, expr: &Expression) -> Result<()> {
37081        match expr {
37082            Expression::Alias(alias) => {
37083                // Output as: alias_name AS expression
37084                self.generate_identifier(&alias.alias)?;
37085                self.write_space();
37086                self.write_keyword("AS");
37087                self.write_space();
37088                self.generate_expression(&alias.this)?;
37089            }
37090            Expression::Tuple(tuple) => {
37091                for (i, item) in tuple.expressions.iter().enumerate() {
37092                    if i > 0 {
37093                        self.write(", ");
37094                    }
37095                    self.generate_interpolate_item(item)?;
37096                }
37097            }
37098            other => {
37099                self.generate_expression(other)?;
37100            }
37101        }
37102        Ok(())
37103    }
37104
37105    fn generate_with_journal_table_property(&mut self, e: &WithJournalTableProperty) -> Result<()> {
37106        // Python: return f"WITH JOURNAL TABLE={self.sql(expression, 'this')}"
37107        self.write_keyword("WITH JOURNAL TABLE");
37108        self.write("=");
37109        self.generate_expression(&e.this)?;
37110        Ok(())
37111    }
37112
37113    fn generate_with_operator(&mut self, e: &WithOperator) -> Result<()> {
37114        // Python: return f"{self.sql(expression, 'this')} WITH {self.sql(expression, 'op')}"
37115        self.generate_expression(&e.this)?;
37116        self.write_space();
37117        self.write_keyword("WITH");
37118        self.write_space();
37119        self.write_keyword(&e.op);
37120        Ok(())
37121    }
37122
37123    fn generate_with_procedure_options(&mut self, e: &WithProcedureOptions) -> Result<()> {
37124        // Python: return f"WITH {self.expressions(expression, flat=True)}"
37125        self.write_keyword("WITH");
37126        self.write_space();
37127        for (i, expr) in e.expressions.iter().enumerate() {
37128            if i > 0 {
37129                self.write(", ");
37130            }
37131            self.generate_expression(expr)?;
37132        }
37133        Ok(())
37134    }
37135
37136    fn generate_with_schema_binding_property(
37137        &mut self,
37138        e: &WithSchemaBindingProperty,
37139    ) -> Result<()> {
37140        // Python: return f"WITH {self.sql(expression, 'this')}"
37141        self.write_keyword("WITH");
37142        self.write_space();
37143        self.generate_expression(&e.this)?;
37144        Ok(())
37145    }
37146
37147    fn generate_with_system_versioning_property(
37148        &mut self,
37149        e: &WithSystemVersioningProperty,
37150    ) -> Result<()> {
37151        // Python: complex logic for SYSTEM_VERSIONING with options
37152        // SYSTEM_VERSIONING=ON(HISTORY_TABLE=..., DATA_CONSISTENCY_CHECK=..., HISTORY_RETENTION_PERIOD=...)
37153        // or SYSTEM_VERSIONING=ON/OFF
37154        // with WITH(...) wrapper if with_ is set
37155
37156        let mut parts = Vec::new();
37157
37158        if let Some(this) = &e.this {
37159            // HISTORY_TABLE=...
37160            let mut s = String::from("HISTORY_TABLE=");
37161            let mut gen = Generator::new();
37162            gen.generate_expression(this)?;
37163            s.push_str(&gen.output);
37164            parts.push(s);
37165        }
37166
37167        if let Some(data_consistency) = &e.data_consistency {
37168            let mut s = String::from("DATA_CONSISTENCY_CHECK=");
37169            let mut gen = Generator::new();
37170            gen.generate_expression(data_consistency)?;
37171            s.push_str(&gen.output);
37172            parts.push(s);
37173        }
37174
37175        if let Some(retention_period) = &e.retention_period {
37176            let mut s = String::from("HISTORY_RETENTION_PERIOD=");
37177            let mut gen = Generator::new();
37178            gen.generate_expression(retention_period)?;
37179            s.push_str(&gen.output);
37180            parts.push(s);
37181        }
37182
37183        self.write_keyword("SYSTEM_VERSIONING");
37184        self.write("=");
37185
37186        if !parts.is_empty() {
37187            self.write_keyword("ON");
37188            self.write("(");
37189            self.write(&parts.join(", "));
37190            self.write(")");
37191        } else if e.on.is_some() {
37192            self.write_keyword("ON");
37193        } else {
37194            self.write_keyword("OFF");
37195        }
37196
37197        // Wrap in WITH(...) if with_ is set
37198        if e.with_.is_some() {
37199            let inner = self.output.clone();
37200            self.output.clear();
37201            self.write("WITH(");
37202            self.write(&inner);
37203            self.write(")");
37204        }
37205
37206        Ok(())
37207    }
37208
37209    fn generate_with_table_hint(&mut self, e: &WithTableHint) -> Result<()> {
37210        // Python: f"WITH ({self.expressions(expression, flat=True)})"
37211        self.write_keyword("WITH");
37212        self.write(" (");
37213        for (i, expr) in e.expressions.iter().enumerate() {
37214            if i > 0 {
37215                self.write(", ");
37216            }
37217            self.generate_expression(expr)?;
37218        }
37219        self.write(")");
37220        Ok(())
37221    }
37222
37223    fn generate_xml_element(&mut self, e: &XMLElement) -> Result<()> {
37224        // Python: prefix = "EVALNAME" if expression.args.get("evalname") else "NAME"
37225        // return self.func("XMLELEMENT", name, *expression.expressions)
37226        self.write_keyword("XMLELEMENT");
37227        self.write("(");
37228
37229        if e.evalname.is_some() {
37230            self.write_keyword("EVALNAME");
37231        } else {
37232            self.write_keyword("NAME");
37233        }
37234        self.write_space();
37235        self.generate_expression(&e.this)?;
37236
37237        for expr in &e.expressions {
37238            self.write(", ");
37239            self.generate_expression(expr)?;
37240        }
37241        self.write(")");
37242        Ok(())
37243    }
37244
37245    fn generate_xml_get(&mut self, e: &XMLGet) -> Result<()> {
37246        // XMLGET(this, expression [, instance])
37247        self.write_keyword("XMLGET");
37248        self.write("(");
37249        self.generate_expression(&e.this)?;
37250        self.write(", ");
37251        self.generate_expression(&e.expression)?;
37252        if let Some(instance) = &e.instance {
37253            self.write(", ");
37254            self.generate_expression(instance)?;
37255        }
37256        self.write(")");
37257        Ok(())
37258    }
37259
37260    fn generate_xml_key_value_option(&mut self, e: &XMLKeyValueOption) -> Result<()> {
37261        // Python: this + optional (expr)
37262        self.generate_expression(&e.this)?;
37263        if let Some(expression) = &e.expression {
37264            self.write("(");
37265            self.generate_expression(expression)?;
37266            self.write(")");
37267        }
37268        Ok(())
37269    }
37270
37271    fn generate_xml_table(&mut self, e: &XMLTable) -> Result<()> {
37272        // Python: XMLTABLE(namespaces + this + passing + by_ref + columns)
37273        self.write_keyword("XMLTABLE");
37274        self.write("(");
37275
37276        if self.config.pretty {
37277            self.indent_level += 1;
37278            self.write_newline();
37279            self.write_indent();
37280            self.generate_expression(&e.this)?;
37281
37282            if let Some(passing) = &e.passing {
37283                self.write_newline();
37284                self.write_indent();
37285                self.write_keyword("PASSING");
37286                if let Expression::Tuple(tuple) = passing.as_ref() {
37287                    for expr in &tuple.expressions {
37288                        self.write_newline();
37289                        self.indent_level += 1;
37290                        self.write_indent();
37291                        self.generate_expression(expr)?;
37292                        self.indent_level -= 1;
37293                    }
37294                } else {
37295                    self.write_newline();
37296                    self.indent_level += 1;
37297                    self.write_indent();
37298                    self.generate_expression(passing)?;
37299                    self.indent_level -= 1;
37300                }
37301            }
37302
37303            if e.by_ref.is_some() {
37304                self.write_newline();
37305                self.write_indent();
37306                self.write_keyword("RETURNING SEQUENCE BY REF");
37307            }
37308
37309            if !e.columns.is_empty() {
37310                self.write_newline();
37311                self.write_indent();
37312                self.write_keyword("COLUMNS");
37313                for (i, col) in e.columns.iter().enumerate() {
37314                    self.write_newline();
37315                    self.indent_level += 1;
37316                    self.write_indent();
37317                    self.generate_expression(col)?;
37318                    self.indent_level -= 1;
37319                    if i < e.columns.len() - 1 {
37320                        self.write(",");
37321                    }
37322                }
37323            }
37324
37325            self.indent_level -= 1;
37326            self.write_newline();
37327            self.write_indent();
37328            self.write(")");
37329            return Ok(());
37330        }
37331
37332        // Namespaces - unwrap Tuple to generate comma-separated list without parentheses
37333        if let Some(namespaces) = &e.namespaces {
37334            self.write_keyword("XMLNAMESPACES");
37335            self.write("(");
37336            // Unwrap Tuple if present to avoid extra parentheses
37337            if let Expression::Tuple(tuple) = namespaces.as_ref() {
37338                for (i, expr) in tuple.expressions.iter().enumerate() {
37339                    if i > 0 {
37340                        self.write(", ");
37341                    }
37342                    // Python pattern: if it's an Alias, output as-is; otherwise prepend DEFAULT
37343                    // See xmlnamespace_sql in generator.py
37344                    if !matches!(expr, Expression::Alias(_)) {
37345                        self.write_keyword("DEFAULT");
37346                        self.write_space();
37347                    }
37348                    self.generate_expression(expr)?;
37349                }
37350            } else {
37351                // Single namespace - check if DEFAULT
37352                if !matches!(namespaces.as_ref(), Expression::Alias(_)) {
37353                    self.write_keyword("DEFAULT");
37354                    self.write_space();
37355                }
37356                self.generate_expression(namespaces)?;
37357            }
37358            self.write("), ");
37359        }
37360
37361        // XPath expression
37362        self.generate_expression(&e.this)?;
37363
37364        // PASSING clause - unwrap Tuple to generate comma-separated list without parentheses
37365        if let Some(passing) = &e.passing {
37366            self.write_space();
37367            self.write_keyword("PASSING");
37368            self.write_space();
37369            // Unwrap Tuple if present to avoid extra parentheses
37370            if let Expression::Tuple(tuple) = passing.as_ref() {
37371                for (i, expr) in tuple.expressions.iter().enumerate() {
37372                    if i > 0 {
37373                        self.write(", ");
37374                    }
37375                    self.generate_expression(expr)?;
37376                }
37377            } else {
37378                self.generate_expression(passing)?;
37379            }
37380        }
37381
37382        // RETURNING SEQUENCE BY REF
37383        if e.by_ref.is_some() {
37384            self.write_space();
37385            self.write_keyword("RETURNING SEQUENCE BY REF");
37386        }
37387
37388        // COLUMNS clause
37389        if !e.columns.is_empty() {
37390            self.write_space();
37391            self.write_keyword("COLUMNS");
37392            self.write_space();
37393            for (i, col) in e.columns.iter().enumerate() {
37394                if i > 0 {
37395                    self.write(", ");
37396                }
37397                self.generate_expression(col)?;
37398            }
37399        }
37400
37401        self.write(")");
37402        Ok(())
37403    }
37404
37405    fn generate_xor(&mut self, e: &Xor) -> Result<()> {
37406        // Python: return self.connector_sql(expression, "XOR", stack)
37407        // Handles: this XOR expression or expressions joined by XOR
37408        if let Some(this) = &e.this {
37409            self.generate_expression(this)?;
37410            if let Some(expression) = &e.expression {
37411                self.write_space();
37412                self.write_keyword("XOR");
37413                self.write_space();
37414                self.generate_expression(expression)?;
37415            }
37416        }
37417
37418        // Handle multiple expressions
37419        for (i, expr) in e.expressions.iter().enumerate() {
37420            if i > 0 || e.this.is_some() {
37421                self.write_space();
37422                self.write_keyword("XOR");
37423                self.write_space();
37424            }
37425            self.generate_expression(expr)?;
37426        }
37427        Ok(())
37428    }
37429
37430    fn generate_zipf(&mut self, e: &Zipf) -> Result<()> {
37431        // ZIPF(this, elementcount [, gen])
37432        self.write_keyword("ZIPF");
37433        self.write("(");
37434        self.generate_expression(&e.this)?;
37435        if let Some(elementcount) = &e.elementcount {
37436            self.write(", ");
37437            self.generate_expression(elementcount)?;
37438        }
37439        if let Some(gen) = &e.gen {
37440            self.write(", ");
37441            self.generate_expression(gen)?;
37442        }
37443        self.write(")");
37444        Ok(())
37445    }
37446}
37447
37448impl Default for Generator {
37449    fn default() -> Self {
37450        Self::new()
37451    }
37452}
37453
37454#[cfg(test)]
37455mod tests {
37456    use super::*;
37457    use crate::parser::Parser;
37458
37459    fn roundtrip(sql: &str) -> String {
37460        let ast = Parser::parse_sql(sql).unwrap();
37461        Generator::sql(&ast[0]).unwrap()
37462    }
37463
37464    #[test]
37465    fn test_simple_select() {
37466        let result = roundtrip("SELECT 1");
37467        assert_eq!(result, "SELECT 1");
37468    }
37469
37470    #[test]
37471    fn test_select_from() {
37472        let result = roundtrip("SELECT a, b FROM t");
37473        assert_eq!(result, "SELECT a, b FROM t");
37474    }
37475
37476    #[test]
37477    fn test_select_where() {
37478        let result = roundtrip("SELECT * FROM t WHERE x = 1");
37479        assert_eq!(result, "SELECT * FROM t WHERE x = 1");
37480    }
37481
37482    #[test]
37483    fn test_select_join() {
37484        let result = roundtrip("SELECT * FROM a JOIN b ON a.id = b.id");
37485        assert_eq!(result, "SELECT * FROM a JOIN b ON a.id = b.id");
37486    }
37487
37488    #[test]
37489    fn test_insert() {
37490        let result = roundtrip("INSERT INTO t (a, b) VALUES (1, 2)");
37491        assert_eq!(result, "INSERT INTO t (a, b) VALUES (1, 2)");
37492    }
37493
37494    #[test]
37495    fn test_pretty_print() {
37496        let ast = Parser::parse_sql("SELECT a, b FROM t WHERE x = 1").unwrap();
37497        let result = Generator::pretty_sql(&ast[0]).unwrap();
37498        assert!(result.contains('\n'));
37499    }
37500
37501    #[test]
37502    fn test_window_function() {
37503        let result = roundtrip("SELECT ROW_NUMBER() OVER (PARTITION BY category ORDER BY id)");
37504        assert_eq!(
37505            result,
37506            "SELECT ROW_NUMBER() OVER (PARTITION BY category ORDER BY id)"
37507        );
37508    }
37509
37510    #[test]
37511    fn test_window_function_with_frame() {
37512        let result = roundtrip("SELECT SUM(amount) OVER (ORDER BY order_date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)");
37513        assert_eq!(result, "SELECT SUM(amount) OVER (ORDER BY order_date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)");
37514    }
37515
37516    #[test]
37517    fn test_aggregate_with_filter() {
37518        let result = roundtrip("SELECT COUNT(*) FILTER (WHERE status = 1) FROM orders");
37519        assert_eq!(
37520            result,
37521            "SELECT COUNT(*) FILTER(WHERE status = 1) FROM orders"
37522        );
37523    }
37524
37525    #[test]
37526    fn test_subscript() {
37527        let result = roundtrip("SELECT arr[0]");
37528        assert_eq!(result, "SELECT arr[0]");
37529    }
37530
37531    // DDL tests
37532    #[test]
37533    fn test_create_table() {
37534        let result = roundtrip("CREATE TABLE users (id INT, name VARCHAR(100))");
37535        assert_eq!(result, "CREATE TABLE users (id INT, name VARCHAR(100))");
37536    }
37537
37538    #[test]
37539    fn test_create_table_with_constraints() {
37540        let result = roundtrip(
37541            "CREATE TABLE users (id INT PRIMARY KEY, email VARCHAR(255) UNIQUE NOT NULL)",
37542        );
37543        assert_eq!(
37544            result,
37545            "CREATE TABLE users (id INT PRIMARY KEY, email VARCHAR(255) UNIQUE NOT NULL)"
37546        );
37547    }
37548
37549    #[test]
37550    fn test_create_table_if_not_exists() {
37551        let result = roundtrip("CREATE TABLE IF NOT EXISTS t (id INT)");
37552        assert_eq!(result, "CREATE TABLE IF NOT EXISTS t (id INT)");
37553    }
37554
37555    #[test]
37556    fn test_drop_table() {
37557        let result = roundtrip("DROP TABLE users");
37558        assert_eq!(result, "DROP TABLE users");
37559    }
37560
37561    #[test]
37562    fn test_drop_table_if_exists_cascade() {
37563        let result = roundtrip("DROP TABLE IF EXISTS users CASCADE");
37564        assert_eq!(result, "DROP TABLE IF EXISTS users CASCADE");
37565    }
37566
37567    #[test]
37568    fn test_alter_table_add_column() {
37569        let result = roundtrip("ALTER TABLE users ADD COLUMN email VARCHAR(255)");
37570        assert_eq!(result, "ALTER TABLE users ADD COLUMN email VARCHAR(255)");
37571    }
37572
37573    #[test]
37574    fn test_alter_table_drop_column() {
37575        let result = roundtrip("ALTER TABLE users DROP COLUMN email");
37576        assert_eq!(result, "ALTER TABLE users DROP COLUMN email");
37577    }
37578
37579    #[test]
37580    fn test_create_index() {
37581        let result = roundtrip("CREATE INDEX idx_name ON users(name)");
37582        assert_eq!(result, "CREATE INDEX idx_name ON users(name)");
37583    }
37584
37585    #[test]
37586    fn test_create_unique_index() {
37587        let result = roundtrip("CREATE UNIQUE INDEX idx_email ON users(email)");
37588        assert_eq!(result, "CREATE UNIQUE INDEX idx_email ON users(email)");
37589    }
37590
37591    #[test]
37592    fn test_drop_index() {
37593        let result = roundtrip("DROP INDEX idx_name");
37594        assert_eq!(result, "DROP INDEX idx_name");
37595    }
37596
37597    #[test]
37598    fn test_create_view() {
37599        let result = roundtrip("CREATE VIEW active_users AS SELECT * FROM users WHERE active = 1");
37600        assert_eq!(
37601            result,
37602            "CREATE VIEW active_users AS SELECT * FROM users WHERE active = 1"
37603        );
37604    }
37605
37606    #[test]
37607    fn test_drop_view() {
37608        let result = roundtrip("DROP VIEW active_users");
37609        assert_eq!(result, "DROP VIEW active_users");
37610    }
37611
37612    #[test]
37613    fn test_truncate() {
37614        let result = roundtrip("TRUNCATE TABLE users");
37615        assert_eq!(result, "TRUNCATE TABLE users");
37616    }
37617
37618    #[test]
37619    fn test_string_literal_escaping_default() {
37620        // Default: double single quotes
37621        let result = roundtrip("SELECT 'hello'");
37622        assert_eq!(result, "SELECT 'hello'");
37623
37624        // Single quotes are doubled
37625        let result = roundtrip("SELECT 'it''s a test'");
37626        assert_eq!(result, "SELECT 'it''s a test'");
37627    }
37628
37629    #[test]
37630    fn test_not_in_style_prefix_default_generic() {
37631        let result = roundtrip("SELECT id FROM users WHERE status NOT IN ('deleted', 'banned')");
37632        assert_eq!(
37633            result,
37634            "SELECT id FROM users WHERE NOT status IN ('deleted', 'banned')"
37635        );
37636    }
37637
37638    #[test]
37639    fn test_not_in_style_infix_generic_override() {
37640        let ast =
37641            Parser::parse_sql("SELECT id FROM users WHERE status NOT IN ('deleted', 'banned')")
37642                .unwrap();
37643        let config = GeneratorConfig {
37644            not_in_style: NotInStyle::Infix,
37645            ..Default::default()
37646        };
37647        let mut gen = Generator::with_config(config);
37648        let result = gen.generate(&ast[0]).unwrap();
37649        assert_eq!(
37650            result,
37651            "SELECT id FROM users WHERE status NOT IN ('deleted', 'banned')"
37652        );
37653    }
37654
37655    #[test]
37656    fn test_string_literal_escaping_mysql() {
37657        use crate::dialects::DialectType;
37658
37659        let config = GeneratorConfig {
37660            dialect: Some(DialectType::MySQL),
37661            ..Default::default()
37662        };
37663
37664        let ast = Parser::parse_sql("SELECT 'hello'").unwrap();
37665        let mut gen = Generator::with_config(config.clone());
37666        let result = gen.generate(&ast[0]).unwrap();
37667        assert_eq!(result, "SELECT 'hello'");
37668
37669        // MySQL uses SQL standard quote doubling for escaping (matches Python sqlglot)
37670        let ast = Parser::parse_sql("SELECT 'it''s'").unwrap();
37671        let mut gen = Generator::with_config(config.clone());
37672        let result = gen.generate(&ast[0]).unwrap();
37673        assert_eq!(result, "SELECT 'it''s'");
37674    }
37675
37676    #[test]
37677    fn test_string_literal_escaping_postgres() {
37678        use crate::dialects::DialectType;
37679
37680        let config = GeneratorConfig {
37681            dialect: Some(DialectType::PostgreSQL),
37682            ..Default::default()
37683        };
37684
37685        let ast = Parser::parse_sql("SELECT 'hello'").unwrap();
37686        let mut gen = Generator::with_config(config.clone());
37687        let result = gen.generate(&ast[0]).unwrap();
37688        assert_eq!(result, "SELECT 'hello'");
37689
37690        // PostgreSQL uses doubled quotes for regular strings
37691        let ast = Parser::parse_sql("SELECT 'it''s'").unwrap();
37692        let mut gen = Generator::with_config(config.clone());
37693        let result = gen.generate(&ast[0]).unwrap();
37694        assert_eq!(result, "SELECT 'it''s'");
37695    }
37696
37697    #[test]
37698    fn test_string_literal_escaping_bigquery() {
37699        use crate::dialects::DialectType;
37700
37701        let config = GeneratorConfig {
37702            dialect: Some(DialectType::BigQuery),
37703            ..Default::default()
37704        };
37705
37706        let ast = Parser::parse_sql("SELECT 'hello'").unwrap();
37707        let mut gen = Generator::with_config(config.clone());
37708        let result = gen.generate(&ast[0]).unwrap();
37709        assert_eq!(result, "SELECT 'hello'");
37710
37711        // BigQuery escapes single quotes with backslash
37712        let ast = Parser::parse_sql("SELECT 'it''s'").unwrap();
37713        let mut gen = Generator::with_config(config.clone());
37714        let result = gen.generate(&ast[0]).unwrap();
37715        assert_eq!(result, "SELECT 'it\\'s'");
37716    }
37717
37718    #[test]
37719    fn test_generate_deep_and_chain_without_stack_growth() {
37720        let mut expr = Expression::Eq(Box::new(BinaryOp::new(
37721            Expression::column("c0"),
37722            Expression::number(0),
37723        )));
37724
37725        for i in 1..2500 {
37726            let predicate = Expression::Eq(Box::new(BinaryOp::new(
37727                Expression::column(format!("c{i}")),
37728                Expression::number(i as i64),
37729            )));
37730            expr = Expression::And(Box::new(BinaryOp::new(expr, predicate)));
37731        }
37732
37733        let sql = Generator::sql(&expr).expect("deep AND chain should generate");
37734        assert!(sql.contains("c2499 = 2499"), "{}", sql);
37735    }
37736
37737    #[test]
37738    fn test_generate_deep_or_chain_without_stack_growth() {
37739        let mut expr = Expression::Eq(Box::new(BinaryOp::new(
37740            Expression::column("c0"),
37741            Expression::number(0),
37742        )));
37743
37744        for i in 1..2500 {
37745            let predicate = Expression::Eq(Box::new(BinaryOp::new(
37746                Expression::column(format!("c{i}")),
37747                Expression::number(i as i64),
37748            )));
37749            expr = Expression::Or(Box::new(BinaryOp::new(expr, predicate)));
37750        }
37751
37752        let sql = Generator::sql(&expr).expect("deep OR chain should generate");
37753        assert!(sql.contains("c2499 = 2499"), "{}", sql);
37754    }
37755}