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(_)) => {
2247                let Literal::Number(n) = lit.as_ref() else {
2248                    unreachable!()
2249                };
2250                n.parse::<i64>().ok()
2251            }
2252            Expression::Add(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::Sub(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::Mul(op) => {
2263                let left = Self::try_evaluate_constant(&op.left)?;
2264                let right = Self::try_evaluate_constant(&op.right)?;
2265                Some(left * right)
2266            }
2267            Expression::Div(op) => {
2268                let left = Self::try_evaluate_constant(&op.left)?;
2269                let right = Self::try_evaluate_constant(&op.right)?;
2270                if right != 0 {
2271                    Some(left / right)
2272                } else {
2273                    None
2274                }
2275            }
2276            Expression::Paren(p) => Self::try_evaluate_constant(&p.this),
2277            _ => None,
2278        }
2279    }
2280
2281    /// Check if an identifier is a reserved keyword for the current dialect
2282    fn is_reserved_keyword(&self, name: &str) -> bool {
2283        use crate::dialects::DialectType;
2284        let mut buf = [0u8; 128];
2285        let lower_ref: &str = if name.len() <= 128 {
2286            for (i, b) in name.bytes().enumerate() {
2287                buf[i] = b.to_ascii_lowercase();
2288            }
2289            // SAFETY: input is valid UTF-8 and ASCII lowercase preserves that
2290            std::str::from_utf8(&buf[..name.len()]).unwrap_or(name)
2291        } else {
2292            return false;
2293        };
2294
2295        match self.config.dialect {
2296            Some(DialectType::BigQuery) => reserved_keywords::BIGQUERY_RESERVED.contains(lower_ref),
2297            Some(DialectType::MySQL) | Some(DialectType::TiDB) => {
2298                reserved_keywords::MYSQL_RESERVED.contains(lower_ref)
2299            }
2300            Some(DialectType::Doris) => reserved_keywords::DORIS_RESERVED.contains(lower_ref),
2301            Some(DialectType::SingleStore) => {
2302                reserved_keywords::SINGLESTORE_RESERVED.contains(lower_ref)
2303            }
2304            Some(DialectType::StarRocks) => {
2305                reserved_keywords::STARROCKS_RESERVED.contains(lower_ref)
2306            }
2307            Some(DialectType::PostgreSQL)
2308            | Some(DialectType::CockroachDB)
2309            | Some(DialectType::Materialize)
2310            | Some(DialectType::RisingWave) => {
2311                reserved_keywords::POSTGRES_RESERVED.contains(lower_ref)
2312            }
2313            Some(DialectType::Redshift) => reserved_keywords::REDSHIFT_RESERVED.contains(lower_ref),
2314            // Snowflake: Python sqlglot has RESERVED_KEYWORDS = set() for Snowflake,
2315            // meaning it never quotes identifiers based on reserved word status.
2316            Some(DialectType::Snowflake) => false,
2317            // ClickHouse: don't quote reserved keywords to preserve identity output
2318            Some(DialectType::ClickHouse) => false,
2319            Some(DialectType::DuckDB) => reserved_keywords::DUCKDB_RESERVED.contains(lower_ref),
2320            // Teradata: Python sqlglot has RESERVED_KEYWORDS = set() for Teradata
2321            Some(DialectType::Teradata) => false,
2322            // TSQL, Fabric, Oracle, Spark, Hive, Solr: Python sqlglot has no RESERVED_KEYWORDS for these dialects, so don't quote identifiers
2323            Some(DialectType::TSQL)
2324            | Some(DialectType::Fabric)
2325            | Some(DialectType::Oracle)
2326            | Some(DialectType::Spark)
2327            | Some(DialectType::Databricks)
2328            | Some(DialectType::Hive)
2329            | Some(DialectType::Solr) => false,
2330            Some(DialectType::Presto) | Some(DialectType::Trino) | Some(DialectType::Athena) => {
2331                reserved_keywords::PRESTO_TRINO_RESERVED.contains(lower_ref)
2332            }
2333            Some(DialectType::SQLite) => reserved_keywords::SQLITE_RESERVED.contains(lower_ref),
2334            // For Generic dialect or None, don't add extra quoting to preserve identity
2335            Some(DialectType::Generic) | None => false,
2336            // For other dialects, use standard SQL reserved keywords
2337            _ => reserved_keywords::SQL_RESERVED.contains(lower_ref),
2338        }
2339    }
2340
2341    /// Normalize function name based on dialect settings
2342    fn normalize_func_name<'a>(&self, name: &'a str) -> Cow<'a, str> {
2343        match self.config.normalize_functions {
2344            NormalizeFunctions::Upper => Cow::Owned(name.to_ascii_uppercase()),
2345            NormalizeFunctions::Lower => Cow::Owned(name.to_ascii_lowercase()),
2346            NormalizeFunctions::None => Cow::Borrowed(name),
2347        }
2348    }
2349
2350    /// Generate a SQL string from an AST expression.
2351    ///
2352    /// This is the primary generation method. It clears any previous internal state,
2353    /// walks the expression tree, and returns the resulting SQL text. The output
2354    /// respects the [`GeneratorConfig`] that was supplied at construction time.
2355    ///
2356    /// The generator can be reused across multiple calls; each call to `generate`
2357    /// resets the internal buffer.
2358    pub fn generate(&mut self, expr: &Expression) -> Result<String> {
2359        self.output.clear();
2360        self.unsupported_messages.clear();
2361        self.generate_expression(expr)?;
2362        if self.config.unsupported_level == UnsupportedLevel::Raise
2363            && !self.unsupported_messages.is_empty()
2364        {
2365            return Err(crate::error::Error::generate(
2366                self.format_unsupported_messages(),
2367            ));
2368        }
2369        Ok(std::mem::take(&mut self.output))
2370    }
2371
2372    /// Returns the unsupported diagnostics collected during the most recent generate call.
2373    pub fn unsupported_messages(&self) -> &[String] {
2374        &self.unsupported_messages
2375    }
2376
2377    fn unsupported(&mut self, message: impl Into<String>) -> Result<()> {
2378        let message = message.into();
2379        if self.config.unsupported_level == UnsupportedLevel::Immediate {
2380            return Err(crate::error::Error::generate(message));
2381        }
2382        self.unsupported_messages.push(message);
2383        Ok(())
2384    }
2385
2386    fn write_unsupported_comment(&mut self, message: &str) -> Result<()> {
2387        self.unsupported(message.to_string())?;
2388        self.write("/* ");
2389        self.write(message);
2390        self.write(" */");
2391        Ok(())
2392    }
2393
2394    fn format_unsupported_messages(&self) -> String {
2395        let limit = self.config.max_unsupported.max(1);
2396        if self.unsupported_messages.len() <= limit {
2397            return self.unsupported_messages.join("; ");
2398        }
2399
2400        let mut messages = self
2401            .unsupported_messages
2402            .iter()
2403            .take(limit)
2404            .cloned()
2405            .collect::<Vec<_>>();
2406        messages.push(format!(
2407            "... and {} more",
2408            self.unsupported_messages.len() - limit
2409        ));
2410        messages.join("; ")
2411    }
2412
2413    /// Convenience: generate SQL with the default configuration (no dialect, compact output).
2414    ///
2415    /// This is a static helper that creates a throwaway `Generator` internally.
2416    /// For repeated generation, prefer constructing a `Generator` once and calling
2417    /// [`generate`](Self::generate) on it.
2418    pub fn sql(expr: &Expression) -> Result<String> {
2419        let mut gen = Generator::new();
2420        gen.generate(expr)
2421    }
2422
2423    /// Convenience: generate SQL with pretty-printing enabled (indented, multi-line).
2424    ///
2425    /// Produces human-readable output with newlines and indentation. A trailing
2426    /// semicolon is appended automatically if not already present.
2427    pub fn pretty_sql(expr: &Expression) -> Result<String> {
2428        let config = GeneratorConfig {
2429            pretty: true,
2430            ..Default::default()
2431        };
2432        let mut gen = Generator::with_config(config);
2433        let mut sql = gen.generate(expr)?;
2434        // Add semicolon for pretty output
2435        if !sql.ends_with(';') {
2436            sql.push(';');
2437        }
2438        Ok(sql)
2439    }
2440
2441    fn generate_expression(&mut self, expr: &Expression) -> Result<()> {
2442        match expr {
2443            Expression::Select(select) => self.generate_select(select),
2444            Expression::Union(union) => self.generate_union(union),
2445            Expression::Intersect(intersect) => self.generate_intersect(intersect),
2446            Expression::Except(except) => self.generate_except(except),
2447            Expression::Insert(insert) => self.generate_insert(insert),
2448            Expression::Update(update) => self.generate_update(update),
2449            Expression::Delete(delete) => self.generate_delete(delete),
2450            Expression::Literal(lit) => self.generate_literal(lit),
2451            Expression::Boolean(b) => self.generate_boolean(b),
2452            Expression::Null(_) => {
2453                self.write_keyword("NULL");
2454                Ok(())
2455            }
2456            Expression::Identifier(id) => self.generate_identifier(id),
2457            Expression::Column(col) => self.generate_column(col),
2458            Expression::Pseudocolumn(pc) => self.generate_pseudocolumn(pc),
2459            Expression::Connect(c) => self.generate_connect_expr(c),
2460            Expression::Prior(p) => self.generate_prior(p),
2461            Expression::ConnectByRoot(cbr) => self.generate_connect_by_root(cbr),
2462            Expression::MatchRecognize(mr) => self.generate_match_recognize(mr),
2463            Expression::Table(table) => self.generate_table(table),
2464            Expression::StageReference(sr) => self.generate_stage_reference(sr),
2465            Expression::HistoricalData(hd) => self.generate_historical_data(hd),
2466            Expression::JoinedTable(jt) => self.generate_joined_table(jt),
2467            Expression::Star(star) => self.generate_star(star),
2468            Expression::BracedWildcard(expr) => self.generate_braced_wildcard(expr),
2469            Expression::Alias(alias) => self.generate_alias(alias),
2470            Expression::Cast(cast) => self.generate_cast(cast),
2471            Expression::Collation(coll) => self.generate_collation(coll),
2472            Expression::Case(case) => self.generate_case(case),
2473            Expression::Function(func) => self.generate_function(func),
2474            Expression::FunctionEmits(fe) => self.generate_function_emits(fe),
2475            Expression::AggregateFunction(func) => self.generate_aggregate_function(func),
2476            Expression::WindowFunction(wf) => self.generate_window_function(wf),
2477            Expression::WithinGroup(wg) => self.generate_within_group(wg),
2478            Expression::Interval(interval) => self.generate_interval(interval),
2479
2480            // String functions
2481            Expression::ConcatWs(f) => self.generate_concat_ws(f),
2482            Expression::Substring(f) => self.generate_substring(f),
2483            Expression::Upper(f) => self.generate_unary_func("UPPER", f),
2484            Expression::Lower(f) => self.generate_unary_func("LOWER", f),
2485            Expression::Length(f) => self.generate_unary_func("LENGTH", f),
2486            Expression::Trim(f) => self.generate_trim(f),
2487            Expression::LTrim(f) => self.generate_simple_func("LTRIM", &f.this),
2488            Expression::RTrim(f) => self.generate_simple_func("RTRIM", &f.this),
2489            Expression::Replace(f) => self.generate_replace(f),
2490            Expression::Reverse(f) => self.generate_simple_func("REVERSE", &f.this),
2491            Expression::Left(f) => self.generate_left_right("LEFT", f),
2492            Expression::Right(f) => self.generate_left_right("RIGHT", f),
2493            Expression::Repeat(f) => self.generate_repeat(f),
2494            Expression::Lpad(f) => self.generate_pad("LPAD", f),
2495            Expression::Rpad(f) => self.generate_pad("RPAD", f),
2496            Expression::Split(f) => self.generate_split(f),
2497            Expression::RegexpLike(f) => self.generate_regexp_like(f),
2498            Expression::RegexpReplace(f) => self.generate_regexp_replace(f),
2499            Expression::RegexpExtract(f) => self.generate_regexp_extract(f),
2500            Expression::Overlay(f) => self.generate_overlay(f),
2501
2502            // Math functions
2503            Expression::Abs(f) => self.generate_simple_func("ABS", &f.this),
2504            Expression::Round(f) => self.generate_round(f),
2505            Expression::Floor(f) => self.generate_floor(f),
2506            Expression::Ceil(f) => self.generate_ceil(f),
2507            Expression::Power(f) => self.generate_power(f),
2508            Expression::Sqrt(f) => self.generate_sqrt_cbrt(f, "SQRT", "|/"),
2509            Expression::Cbrt(f) => self.generate_sqrt_cbrt(f, "CBRT", "||/"),
2510            Expression::Ln(f) => self.generate_simple_func("LN", &f.this),
2511            Expression::Log(f) => self.generate_log(f),
2512            Expression::Exp(f) => self.generate_simple_func("EXP", &f.this),
2513            Expression::Sign(f) => self.generate_simple_func("SIGN", &f.this),
2514            Expression::Greatest(f) => self.generate_vararg_func("GREATEST", &f.expressions),
2515            Expression::Least(f) => self.generate_vararg_func("LEAST", &f.expressions),
2516
2517            // Date/time functions
2518            Expression::CurrentDate(_) => {
2519                self.write_keyword("CURRENT_DATE");
2520                Ok(())
2521            }
2522            Expression::CurrentTime(f) => self.generate_current_time(f),
2523            Expression::CurrentTimestamp(f) => self.generate_current_timestamp(f),
2524            Expression::AtTimeZone(f) => self.generate_at_time_zone(f),
2525            Expression::DateAdd(f) => self.generate_date_add(f, "DATE_ADD"),
2526            Expression::DateSub(f) => self.generate_date_add(f, "DATE_SUB"),
2527            Expression::DateDiff(f) => self.generate_datediff(f),
2528            Expression::DateTrunc(f) => self.generate_date_trunc(f),
2529            Expression::Extract(f) => self.generate_extract(f),
2530            Expression::ToDate(f) => self.generate_to_date(f),
2531            Expression::ToTimestamp(f) => self.generate_to_timestamp(f),
2532
2533            // Control flow functions
2534            Expression::Coalesce(f) => {
2535                // Use original function name if preserved (COALESCE, IFNULL)
2536                let func_name = f.original_name.as_deref().unwrap_or("COALESCE");
2537                self.generate_vararg_func(func_name, &f.expressions)
2538            }
2539            Expression::NullIf(f) => self.generate_binary_func("NULLIF", &f.this, &f.expression),
2540            Expression::IfFunc(f) => self.generate_if_func(f),
2541            Expression::IfNull(f) => self.generate_ifnull(f),
2542            Expression::Nvl(f) => self.generate_nvl(f),
2543            Expression::Nvl2(f) => self.generate_nvl2(f),
2544
2545            // Type conversion
2546            Expression::TryCast(cast) => self.generate_try_cast(cast),
2547            Expression::SafeCast(cast) => self.generate_safe_cast(cast),
2548
2549            // Typed aggregate functions
2550            Expression::Count(f) => self.generate_count(f),
2551            Expression::Sum(f) => self.generate_agg_func("SUM", f),
2552            Expression::Avg(f) => self.generate_agg_func("AVG", f),
2553            Expression::Min(f) => self.generate_agg_func("MIN", f),
2554            Expression::Max(f) => self.generate_agg_func("MAX", f),
2555            Expression::GroupConcat(f) => self.generate_group_concat(f),
2556            Expression::StringAgg(f) => self.generate_string_agg(f),
2557            Expression::ListAgg(f) => self.generate_listagg(f),
2558            Expression::ArrayAgg(f) => {
2559                // Allow cross-dialect transforms to override the function name
2560                // (e.g., COLLECT_LIST for Spark)
2561                let override_name = f
2562                    .name
2563                    .as_ref()
2564                    .filter(|n| !n.eq_ignore_ascii_case("ARRAY_AGG"))
2565                    .map(|n| n.to_ascii_uppercase());
2566                match override_name {
2567                    Some(name) => self.generate_agg_func(&name, f),
2568                    None => self.generate_agg_func("ARRAY_AGG", f),
2569                }
2570            }
2571            Expression::ArrayConcatAgg(f) => self.generate_agg_func("ARRAY_CONCAT_AGG", f),
2572            Expression::CountIf(f) => self.generate_agg_func("COUNT_IF", f),
2573            Expression::SumIf(f) => self.generate_sum_if(f),
2574            Expression::Stddev(f) => self.generate_agg_func("STDDEV", f),
2575            Expression::StddevPop(f) => self.generate_agg_func("STDDEV_POP", f),
2576            Expression::StddevSamp(f) => self.generate_stddev_samp(f),
2577            Expression::Variance(f) => self.generate_agg_func("VARIANCE", f),
2578            Expression::VarPop(f) => {
2579                let name = if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
2580                    "VARIANCE_POP"
2581                } else {
2582                    "VAR_POP"
2583                };
2584                self.generate_agg_func(name, f)
2585            }
2586            Expression::VarSamp(f) => self.generate_agg_func("VAR_SAMP", f),
2587            Expression::Skewness(f) => {
2588                let name = match self.config.dialect {
2589                    Some(DialectType::Snowflake) => "SKEW",
2590                    _ => "SKEWNESS",
2591                };
2592                self.generate_agg_func(name, f)
2593            }
2594            Expression::Median(f) => self.generate_agg_func("MEDIAN", f),
2595            Expression::Mode(f) => self.generate_agg_func("MODE", f),
2596            Expression::First(f) => self.generate_agg_func_with_ignore_nulls_bool("FIRST", f),
2597            Expression::Last(f) => self.generate_agg_func_with_ignore_nulls_bool("LAST", f),
2598            Expression::AnyValue(f) => self.generate_agg_func("ANY_VALUE", f),
2599            Expression::ApproxDistinct(f) => {
2600                match self.config.dialect {
2601                    Some(DialectType::Hive)
2602                    | Some(DialectType::Spark)
2603                    | Some(DialectType::Databricks)
2604                    | Some(DialectType::BigQuery) => {
2605                        // These dialects use APPROX_COUNT_DISTINCT (single arg only)
2606                        self.generate_agg_func("APPROX_COUNT_DISTINCT", f)
2607                    }
2608                    Some(DialectType::Redshift) => {
2609                        // Redshift uses APPROXIMATE COUNT(DISTINCT expr)
2610                        self.write_keyword("APPROXIMATE COUNT");
2611                        self.write("(");
2612                        self.write_keyword("DISTINCT");
2613                        self.write(" ");
2614                        self.generate_expression(&f.this)?;
2615                        self.write(")");
2616                        Ok(())
2617                    }
2618                    _ => self.generate_agg_func("APPROX_DISTINCT", f),
2619                }
2620            }
2621            Expression::ApproxCountDistinct(f) => {
2622                self.generate_agg_func("APPROX_COUNT_DISTINCT", f)
2623            }
2624            Expression::ApproxPercentile(f) => self.generate_approx_percentile(f),
2625            Expression::Percentile(f) => self.generate_percentile("PERCENTILE", f),
2626            Expression::LogicalAnd(f) => {
2627                let name = match self.config.dialect {
2628                    Some(DialectType::Snowflake) => "BOOLAND_AGG",
2629                    Some(DialectType::Spark)
2630                    | Some(DialectType::Databricks)
2631                    | Some(DialectType::PostgreSQL)
2632                    | Some(DialectType::DuckDB)
2633                    | Some(DialectType::Redshift) => "BOOL_AND",
2634                    Some(DialectType::Oracle)
2635                    | Some(DialectType::SQLite)
2636                    | Some(DialectType::MySQL) => "MIN",
2637                    _ => "BOOL_AND",
2638                };
2639                self.generate_agg_func(name, f)
2640            }
2641            Expression::LogicalOr(f) => {
2642                let name = match self.config.dialect {
2643                    Some(DialectType::Snowflake) => "BOOLOR_AGG",
2644                    Some(DialectType::Spark)
2645                    | Some(DialectType::Databricks)
2646                    | Some(DialectType::PostgreSQL)
2647                    | Some(DialectType::DuckDB)
2648                    | Some(DialectType::Redshift) => "BOOL_OR",
2649                    Some(DialectType::Oracle)
2650                    | Some(DialectType::SQLite)
2651                    | Some(DialectType::MySQL) => "MAX",
2652                    _ => "BOOL_OR",
2653                };
2654                self.generate_agg_func(name, f)
2655            }
2656
2657            // Typed window functions
2658            Expression::RowNumber(_) => {
2659                if self.config.dialect == Some(DialectType::ClickHouse) {
2660                    self.write("row_number");
2661                } else {
2662                    self.write_keyword("ROW_NUMBER");
2663                }
2664                self.write("()");
2665                Ok(())
2666            }
2667            Expression::Rank(r) => {
2668                self.write_keyword("RANK");
2669                self.write("(");
2670                // Oracle hypothetical rank args: RANK(val1, val2, ...) WITHIN GROUP (ORDER BY ...)
2671                if !r.args.is_empty() {
2672                    for (i, arg) in r.args.iter().enumerate() {
2673                        if i > 0 {
2674                            self.write(", ");
2675                        }
2676                        self.generate_expression(arg)?;
2677                    }
2678                } else if let Some(order_by) = &r.order_by {
2679                    // DuckDB: RANK(ORDER BY col)
2680                    self.write_keyword(" ORDER BY ");
2681                    for (i, ob) in order_by.iter().enumerate() {
2682                        if i > 0 {
2683                            self.write(", ");
2684                        }
2685                        self.generate_ordered(ob)?;
2686                    }
2687                }
2688                self.write(")");
2689                Ok(())
2690            }
2691            Expression::DenseRank(dr) => {
2692                self.write_keyword("DENSE_RANK");
2693                self.write("(");
2694                // Oracle hypothetical rank args: DENSE_RANK(val1, val2, ...) WITHIN GROUP (ORDER BY ...)
2695                for (i, arg) in dr.args.iter().enumerate() {
2696                    if i > 0 {
2697                        self.write(", ");
2698                    }
2699                    self.generate_expression(arg)?;
2700                }
2701                self.write(")");
2702                Ok(())
2703            }
2704            Expression::NTile(f) => self.generate_ntile(f),
2705            Expression::Lead(f) => self.generate_lead_lag("LEAD", f),
2706            Expression::Lag(f) => self.generate_lead_lag("LAG", f),
2707            Expression::FirstValue(f) => {
2708                self.generate_value_func_with_ignore_nulls_bool("FIRST_VALUE", f)
2709            }
2710            Expression::LastValue(f) => {
2711                self.generate_value_func_with_ignore_nulls_bool("LAST_VALUE", f)
2712            }
2713            Expression::NthValue(f) => self.generate_nth_value(f),
2714            Expression::PercentRank(pr) => {
2715                self.write_keyword("PERCENT_RANK");
2716                self.write("(");
2717                // Oracle hypothetical rank args: PERCENT_RANK(val1, val2, ...) WITHIN GROUP (ORDER BY ...)
2718                if !pr.args.is_empty() {
2719                    for (i, arg) in pr.args.iter().enumerate() {
2720                        if i > 0 {
2721                            self.write(", ");
2722                        }
2723                        self.generate_expression(arg)?;
2724                    }
2725                } else if let Some(order_by) = &pr.order_by {
2726                    // DuckDB: PERCENT_RANK(ORDER BY col)
2727                    self.write_keyword(" ORDER BY ");
2728                    for (i, ob) in order_by.iter().enumerate() {
2729                        if i > 0 {
2730                            self.write(", ");
2731                        }
2732                        self.generate_ordered(ob)?;
2733                    }
2734                }
2735                self.write(")");
2736                Ok(())
2737            }
2738            Expression::CumeDist(cd) => {
2739                self.write_keyword("CUME_DIST");
2740                self.write("(");
2741                // Oracle hypothetical rank args: CUME_DIST(val1, val2, ...) WITHIN GROUP (ORDER BY ...)
2742                if !cd.args.is_empty() {
2743                    for (i, arg) in cd.args.iter().enumerate() {
2744                        if i > 0 {
2745                            self.write(", ");
2746                        }
2747                        self.generate_expression(arg)?;
2748                    }
2749                } else if let Some(order_by) = &cd.order_by {
2750                    // DuckDB: CUME_DIST(ORDER BY col)
2751                    self.write_keyword(" ORDER BY ");
2752                    for (i, ob) in order_by.iter().enumerate() {
2753                        if i > 0 {
2754                            self.write(", ");
2755                        }
2756                        self.generate_ordered(ob)?;
2757                    }
2758                }
2759                self.write(")");
2760                Ok(())
2761            }
2762            Expression::PercentileCont(f) => self.generate_percentile("PERCENTILE_CONT", f),
2763            Expression::PercentileDisc(f) => self.generate_percentile("PERCENTILE_DISC", f),
2764
2765            // Additional string functions
2766            Expression::Contains(f) => {
2767                self.generate_binary_func("CONTAINS", &f.this, &f.expression)
2768            }
2769            Expression::StartsWith(f) => {
2770                let name = match self.config.dialect {
2771                    Some(DialectType::Spark) | Some(DialectType::Databricks) => "STARTSWITH",
2772                    _ => "STARTS_WITH",
2773                };
2774                self.generate_binary_func(name, &f.this, &f.expression)
2775            }
2776            Expression::EndsWith(f) => {
2777                let name = match self.config.dialect {
2778                    Some(DialectType::Snowflake) => "ENDSWITH",
2779                    Some(DialectType::Spark) | Some(DialectType::Databricks) => "ENDSWITH",
2780                    Some(DialectType::ClickHouse) => "endsWith",
2781                    _ => "ENDS_WITH",
2782                };
2783                self.generate_binary_func(name, &f.this, &f.expression)
2784            }
2785            Expression::Position(f) => self.generate_position(f),
2786            Expression::Initcap(f) => match self.config.dialect {
2787                Some(DialectType::Presto)
2788                | Some(DialectType::Trino)
2789                | Some(DialectType::Athena) => {
2790                    self.write_keyword("REGEXP_REPLACE");
2791                    self.write("(");
2792                    self.generate_expression(&f.this)?;
2793                    self.write(", '(\\w)(\\w*)', x -> UPPER(x[1]) || LOWER(x[2]))");
2794                    Ok(())
2795                }
2796                _ => self.generate_simple_func("INITCAP", &f.this),
2797            },
2798            Expression::Ascii(f) => self.generate_simple_func("ASCII", &f.this),
2799            Expression::Chr(f) => self.generate_simple_func("CHR", &f.this),
2800            Expression::CharFunc(f) => self.generate_char_func(f),
2801            Expression::Soundex(f) => self.generate_simple_func("SOUNDEX", &f.this),
2802            Expression::Levenshtein(f) => {
2803                self.generate_binary_func("LEVENSHTEIN", &f.this, &f.expression)
2804            }
2805
2806            // Additional math functions
2807            Expression::ModFunc(f) => self.generate_mod_func(f),
2808            Expression::Random(_) => {
2809                self.write_keyword("RANDOM");
2810                self.write("()");
2811                Ok(())
2812            }
2813            Expression::Rand(f) => self.generate_rand(f),
2814            Expression::TruncFunc(f) => self.generate_truncate_func(f),
2815            Expression::Pi(_) => {
2816                self.write_keyword("PI");
2817                self.write("()");
2818                Ok(())
2819            }
2820            Expression::Radians(f) => self.generate_simple_func("RADIANS", &f.this),
2821            Expression::Degrees(f) => self.generate_simple_func("DEGREES", &f.this),
2822            Expression::Sin(f) => self.generate_simple_func("SIN", &f.this),
2823            Expression::Cos(f) => self.generate_simple_func("COS", &f.this),
2824            Expression::Tan(f) => self.generate_simple_func("TAN", &f.this),
2825            Expression::Asin(f) => self.generate_simple_func("ASIN", &f.this),
2826            Expression::Acos(f) => self.generate_simple_func("ACOS", &f.this),
2827            Expression::Atan(f) => self.generate_simple_func("ATAN", &f.this),
2828            Expression::Atan2(f) => {
2829                let name = f.original_name.as_deref().unwrap_or("ATAN2");
2830                self.generate_binary_func(name, &f.this, &f.expression)
2831            }
2832
2833            // Control flow
2834            Expression::Decode(f) => self.generate_decode(f),
2835
2836            // Additional date/time functions
2837            Expression::DateFormat(f) => self.generate_date_format("DATE_FORMAT", f),
2838            Expression::FormatDate(f) => self.generate_date_format("FORMAT_DATE", f),
2839            Expression::Year(f) => self.generate_simple_func("YEAR", &f.this),
2840            Expression::Month(f) => self.generate_simple_func("MONTH", &f.this),
2841            Expression::Day(f) => self.generate_simple_func("DAY", &f.this),
2842            Expression::Hour(f) => self.generate_simple_func("HOUR", &f.this),
2843            Expression::Minute(f) => self.generate_simple_func("MINUTE", &f.this),
2844            Expression::Second(f) => self.generate_simple_func("SECOND", &f.this),
2845            Expression::DayOfWeek(f) => {
2846                let name = match self.config.dialect {
2847                    Some(DialectType::Presto)
2848                    | Some(DialectType::Trino)
2849                    | Some(DialectType::Athena) => "DAY_OF_WEEK",
2850                    Some(DialectType::DuckDB) => "ISODOW",
2851                    _ => "DAYOFWEEK",
2852                };
2853                self.generate_simple_func(name, &f.this)
2854            }
2855            Expression::DayOfMonth(f) => {
2856                let name = match self.config.dialect {
2857                    Some(DialectType::Presto)
2858                    | Some(DialectType::Trino)
2859                    | Some(DialectType::Athena) => "DAY_OF_MONTH",
2860                    _ => "DAYOFMONTH",
2861                };
2862                self.generate_simple_func(name, &f.this)
2863            }
2864            Expression::DayOfYear(f) => {
2865                let name = match self.config.dialect {
2866                    Some(DialectType::Presto)
2867                    | Some(DialectType::Trino)
2868                    | Some(DialectType::Athena) => "DAY_OF_YEAR",
2869                    _ => "DAYOFYEAR",
2870                };
2871                self.generate_simple_func(name, &f.this)
2872            }
2873            Expression::WeekOfYear(f) => {
2874                // Python sqlglot default is WEEK_OF_YEAR; Hive/DuckDB/Spark/MySQL override to WEEKOFYEAR
2875                let name = match self.config.dialect {
2876                    Some(DialectType::Hive)
2877                    | Some(DialectType::DuckDB)
2878                    | Some(DialectType::Spark)
2879                    | Some(DialectType::Databricks)
2880                    | Some(DialectType::MySQL) => "WEEKOFYEAR",
2881                    _ => "WEEK_OF_YEAR",
2882                };
2883                self.generate_simple_func(name, &f.this)
2884            }
2885            Expression::Quarter(f) => self.generate_simple_func("QUARTER", &f.this),
2886            Expression::AddMonths(f) => {
2887                self.generate_binary_func("ADD_MONTHS", &f.this, &f.expression)
2888            }
2889            Expression::MonthsBetween(f) => {
2890                self.generate_binary_func("MONTHS_BETWEEN", &f.this, &f.expression)
2891            }
2892            Expression::LastDay(f) => self.generate_last_day(f),
2893            Expression::NextDay(f) => self.generate_binary_func("NEXT_DAY", &f.this, &f.expression),
2894            Expression::Epoch(f) => self.generate_simple_func("EPOCH", &f.this),
2895            Expression::EpochMs(f) => self.generate_simple_func("EPOCH_MS", &f.this),
2896            Expression::FromUnixtime(f) => self.generate_from_unixtime(f),
2897            Expression::UnixTimestamp(f) => self.generate_unix_timestamp(f),
2898            Expression::MakeDate(f) => self.generate_make_date(f),
2899            Expression::MakeTimestamp(f) => self.generate_make_timestamp(f),
2900            Expression::TimestampTrunc(f) => self.generate_date_trunc(f),
2901
2902            // Array functions
2903            Expression::ArrayFunc(f) => self.generate_array_constructor(f),
2904            Expression::ArrayLength(f) => self.generate_simple_func("ARRAY_LENGTH", &f.this),
2905            Expression::ArraySize(f) => self.generate_simple_func("ARRAY_SIZE", &f.this),
2906            Expression::Cardinality(f) => self.generate_simple_func("CARDINALITY", &f.this),
2907            Expression::ArrayContains(f) => {
2908                self.generate_binary_func("ARRAY_CONTAINS", &f.this, &f.expression)
2909            }
2910            Expression::ArrayPosition(f) => {
2911                self.generate_binary_func("ARRAY_POSITION", &f.this, &f.expression)
2912            }
2913            Expression::ArrayAppend(f) => {
2914                self.generate_binary_func("ARRAY_APPEND", &f.this, &f.expression)
2915            }
2916            Expression::ArrayPrepend(f) => {
2917                self.generate_binary_func("ARRAY_PREPEND", &f.this, &f.expression)
2918            }
2919            Expression::ArrayConcat(f) => self.generate_vararg_func("ARRAY_CONCAT", &f.expressions),
2920            Expression::ArraySort(f) => self.generate_array_sort(f),
2921            Expression::ArrayReverse(f) => self.generate_simple_func("ARRAY_REVERSE", &f.this),
2922            Expression::ArrayDistinct(f) => self.generate_simple_func("ARRAY_DISTINCT", &f.this),
2923            Expression::ArrayJoin(f) => self.generate_array_join("ARRAY_JOIN", f),
2924            Expression::ArrayToString(f) => self.generate_array_join("ARRAY_TO_STRING", f),
2925            Expression::Unnest(f) => self.generate_unnest(f),
2926            Expression::Explode(f) => self.generate_simple_func("EXPLODE", &f.this),
2927            Expression::ExplodeOuter(f) => self.generate_simple_func("EXPLODE_OUTER", &f.this),
2928            Expression::ArrayFilter(f) => self.generate_array_filter(f),
2929            Expression::ArrayTransform(f) => self.generate_array_transform(f),
2930            Expression::ArrayFlatten(f) => self.generate_simple_func("FLATTEN", &f.this),
2931            Expression::ArrayCompact(f) => {
2932                if matches!(self.config.dialect, Some(DialectType::DuckDB)) {
2933                    // DuckDB: ARRAY_COMPACT(arr) -> LIST_FILTER(arr, _u -> NOT _u IS NULL)
2934                    self.write("LIST_FILTER(");
2935                    self.generate_expression(&f.this)?;
2936                    self.write(", _u -> NOT _u IS NULL)");
2937                    Ok(())
2938                } else {
2939                    self.generate_simple_func("ARRAY_COMPACT", &f.this)
2940                }
2941            }
2942            Expression::ArrayIntersect(f) => {
2943                let func_name = f.original_name.as_deref().unwrap_or("ARRAY_INTERSECT");
2944                self.generate_vararg_func(func_name, &f.expressions)
2945            }
2946            Expression::ArrayUnion(f) => {
2947                self.generate_binary_func("ARRAY_UNION", &f.this, &f.expression)
2948            }
2949            Expression::ArrayExcept(f) => {
2950                self.generate_binary_func("ARRAY_EXCEPT", &f.this, &f.expression)
2951            }
2952            Expression::ArrayRemove(f) => {
2953                self.generate_binary_func("ARRAY_REMOVE", &f.this, &f.expression)
2954            }
2955            Expression::ArrayZip(f) => self.generate_vararg_func("ARRAYS_ZIP", &f.expressions),
2956            Expression::Sequence(f) => self.generate_sequence("SEQUENCE", f),
2957            Expression::Generate(f) => self.generate_sequence("GENERATE_SERIES", f),
2958
2959            // Struct functions
2960            Expression::StructFunc(f) => self.generate_struct_constructor(f),
2961            Expression::StructExtract(f) => self.generate_struct_extract(f),
2962            Expression::NamedStruct(f) => self.generate_named_struct(f),
2963
2964            // Map functions
2965            Expression::MapFunc(f) => self.generate_map_constructor(f),
2966            Expression::MapFromEntries(f) => self.generate_simple_func("MAP_FROM_ENTRIES", &f.this),
2967            Expression::MapFromArrays(f) => {
2968                self.generate_binary_func("MAP_FROM_ARRAYS", &f.this, &f.expression)
2969            }
2970            Expression::MapKeys(f) => self.generate_simple_func("MAP_KEYS", &f.this),
2971            Expression::MapValues(f) => self.generate_simple_func("MAP_VALUES", &f.this),
2972            Expression::MapContainsKey(f) => {
2973                self.generate_binary_func("MAP_CONTAINS_KEY", &f.this, &f.expression)
2974            }
2975            Expression::MapConcat(f) => self.generate_vararg_func("MAP_CONCAT", &f.expressions),
2976            Expression::ElementAt(f) => {
2977                self.generate_binary_func("ELEMENT_AT", &f.this, &f.expression)
2978            }
2979            Expression::TransformKeys(f) => self.generate_transform_func("TRANSFORM_KEYS", f),
2980            Expression::TransformValues(f) => self.generate_transform_func("TRANSFORM_VALUES", f),
2981
2982            // JSON functions
2983            Expression::JsonExtract(f) => self.generate_json_extract("JSON_EXTRACT", f),
2984            Expression::JsonExtractScalar(f) => {
2985                self.generate_json_extract("JSON_EXTRACT_SCALAR", f)
2986            }
2987            Expression::JsonExtractPath(f) => self.generate_json_path("JSON_EXTRACT_PATH", f),
2988            Expression::JsonArray(f) => self.generate_vararg_func("JSON_ARRAY", &f.expressions),
2989            Expression::JsonObject(f) => self.generate_json_object(f),
2990            Expression::JsonQuery(f) => self.generate_json_extract("JSON_QUERY", f),
2991            Expression::JsonValue(f) => self.generate_json_extract("JSON_VALUE", f),
2992            Expression::JsonArrayLength(f) => {
2993                self.generate_simple_func("JSON_ARRAY_LENGTH", &f.this)
2994            }
2995            Expression::JsonKeys(f) => self.generate_simple_func("JSON_KEYS", &f.this),
2996            Expression::JsonType(f) => self.generate_simple_func("JSON_TYPE", &f.this),
2997            Expression::ParseJson(f) => {
2998                let name = match self.config.dialect {
2999                    Some(DialectType::Presto)
3000                    | Some(DialectType::Trino)
3001                    | Some(DialectType::Athena) => "JSON_PARSE",
3002                    Some(DialectType::PostgreSQL) | Some(DialectType::Redshift) => {
3003                        // PostgreSQL: CAST(x AS JSON)
3004                        self.write_keyword("CAST");
3005                        self.write("(");
3006                        self.generate_expression(&f.this)?;
3007                        self.write_keyword(" AS ");
3008                        self.write_keyword("JSON");
3009                        self.write(")");
3010                        return Ok(());
3011                    }
3012                    Some(DialectType::Hive)
3013                    | Some(DialectType::Spark)
3014                    | Some(DialectType::MySQL)
3015                    | Some(DialectType::SingleStore)
3016                    | Some(DialectType::TiDB)
3017                    | Some(DialectType::TSQL) => {
3018                        // Hive/Spark/MySQL/TSQL: just emit the string literal
3019                        self.generate_expression(&f.this)?;
3020                        return Ok(());
3021                    }
3022                    Some(DialectType::DuckDB) => "JSON",
3023                    _ => "PARSE_JSON",
3024                };
3025                self.generate_simple_func(name, &f.this)
3026            }
3027            Expression::ToJson(f) => self.generate_simple_func("TO_JSON", &f.this),
3028            Expression::JsonSet(f) => self.generate_json_modify("JSON_SET", f),
3029            Expression::JsonInsert(f) => self.generate_json_modify("JSON_INSERT", f),
3030            Expression::JsonRemove(f) => self.generate_json_path("JSON_REMOVE", f),
3031            Expression::JsonMergePatch(f) => {
3032                self.generate_binary_func("JSON_MERGE_PATCH", &f.this, &f.expression)
3033            }
3034            Expression::JsonArrayAgg(f) => self.generate_json_array_agg(f),
3035            Expression::JsonObjectAgg(f) => self.generate_json_object_agg(f),
3036
3037            // Type casting/conversion
3038            Expression::Convert(f) => self.generate_convert(f),
3039            Expression::Typeof(f) => self.generate_simple_func("TYPEOF", &f.this),
3040
3041            // Additional expressions
3042            Expression::Lambda(f) => self.generate_lambda(f),
3043            Expression::Parameter(f) => self.generate_parameter(f),
3044            Expression::Placeholder(f) => self.generate_placeholder(f),
3045            Expression::NamedArgument(f) => self.generate_named_argument(f),
3046            Expression::TableArgument(f) => self.generate_table_argument(f),
3047            Expression::SqlComment(f) => self.generate_sql_comment(f),
3048
3049            // Additional predicates
3050            Expression::NullSafeEq(op) => self.generate_null_safe_eq(op),
3051            Expression::NullSafeNeq(op) => self.generate_null_safe_neq(op),
3052            Expression::Glob(op) => self.generate_binary_op(op, "GLOB"),
3053            Expression::SimilarTo(f) => self.generate_similar_to(f),
3054            Expression::Any(f) => self.generate_quantified("ANY", f),
3055            Expression::All(f) => self.generate_quantified("ALL", f),
3056            Expression::Overlaps(f) => self.generate_overlaps(f),
3057
3058            // Bitwise operations
3059            Expression::BitwiseLeftShift(op) => {
3060                if matches!(
3061                    self.config.dialect,
3062                    Some(DialectType::Presto) | Some(DialectType::Trino)
3063                ) {
3064                    self.write_keyword("BITWISE_ARITHMETIC_SHIFT_LEFT");
3065                    self.write("(");
3066                    self.generate_expression(&op.left)?;
3067                    self.write(", ");
3068                    self.generate_expression(&op.right)?;
3069                    self.write(")");
3070                    Ok(())
3071                } else if matches!(
3072                    self.config.dialect,
3073                    Some(DialectType::Spark) | Some(DialectType::Databricks)
3074                ) {
3075                    self.write_keyword("SHIFTLEFT");
3076                    self.write("(");
3077                    self.generate_expression(&op.left)?;
3078                    self.write(", ");
3079                    self.generate_expression(&op.right)?;
3080                    self.write(")");
3081                    Ok(())
3082                } else {
3083                    self.generate_binary_op(op, "<<")
3084                }
3085            }
3086            Expression::BitwiseRightShift(op) => {
3087                if matches!(
3088                    self.config.dialect,
3089                    Some(DialectType::Presto) | Some(DialectType::Trino)
3090                ) {
3091                    self.write_keyword("BITWISE_ARITHMETIC_SHIFT_RIGHT");
3092                    self.write("(");
3093                    self.generate_expression(&op.left)?;
3094                    self.write(", ");
3095                    self.generate_expression(&op.right)?;
3096                    self.write(")");
3097                    Ok(())
3098                } else if matches!(
3099                    self.config.dialect,
3100                    Some(DialectType::Spark) | Some(DialectType::Databricks)
3101                ) {
3102                    self.write_keyword("SHIFTRIGHT");
3103                    self.write("(");
3104                    self.generate_expression(&op.left)?;
3105                    self.write(", ");
3106                    self.generate_expression(&op.right)?;
3107                    self.write(")");
3108                    Ok(())
3109                } else {
3110                    self.generate_binary_op(op, ">>")
3111                }
3112            }
3113            Expression::BitwiseAndAgg(f) => self.generate_agg_func("BIT_AND", f),
3114            Expression::BitwiseOrAgg(f) => self.generate_agg_func("BIT_OR", f),
3115            Expression::BitwiseXorAgg(f) => self.generate_agg_func("BIT_XOR", f),
3116
3117            // Array/struct/map access
3118            Expression::Subscript(s) => self.generate_subscript(s),
3119            Expression::Dot(d) => self.generate_dot_access(d),
3120            Expression::MethodCall(m) => self.generate_method_call(m),
3121            Expression::ArraySlice(s) => self.generate_array_slice(s),
3122
3123            Expression::And(op) => self.generate_connector_op(op, ConnectorOperator::And),
3124            Expression::Or(op) => self.generate_connector_op(op, ConnectorOperator::Or),
3125            Expression::Add(op) => self.generate_binary_op(op, "+"),
3126            Expression::Sub(op) => self.generate_binary_op(op, "-"),
3127            Expression::Mul(op) => self.generate_binary_op(op, "*"),
3128            Expression::Div(op) => self.generate_binary_op(op, "/"),
3129            Expression::IntDiv(f) => {
3130                use crate::dialects::DialectType;
3131                if matches!(self.config.dialect, Some(DialectType::DuckDB)) {
3132                    // DuckDB uses // operator for integer division
3133                    self.generate_expression(&f.this)?;
3134                    self.write(" // ");
3135                    self.generate_expression(&f.expression)?;
3136                    Ok(())
3137                } else if matches!(
3138                    self.config.dialect,
3139                    Some(DialectType::Hive | DialectType::Spark | DialectType::Databricks)
3140                ) {
3141                    // Hive/Spark use DIV as an infix operator
3142                    self.generate_expression(&f.this)?;
3143                    self.write(" ");
3144                    self.write_keyword("DIV");
3145                    self.write(" ");
3146                    self.generate_expression(&f.expression)?;
3147                    Ok(())
3148                } else {
3149                    // Other dialects use DIV function
3150                    self.write_keyword("DIV");
3151                    self.write("(");
3152                    self.generate_expression(&f.this)?;
3153                    self.write(", ");
3154                    self.generate_expression(&f.expression)?;
3155                    self.write(")");
3156                    Ok(())
3157                }
3158            }
3159            Expression::Mod(op) => {
3160                if matches!(self.config.dialect, Some(DialectType::Teradata)) {
3161                    self.generate_binary_op(op, "MOD")
3162                } else {
3163                    self.generate_binary_op(op, "%")
3164                }
3165            }
3166            Expression::Eq(op) => self.generate_binary_op(op, "="),
3167            Expression::Neq(op) => self.generate_binary_op(op, "<>"),
3168            Expression::Lt(op) => self.generate_binary_op(op, "<"),
3169            Expression::Lte(op) => self.generate_binary_op(op, "<="),
3170            Expression::Gt(op) => self.generate_binary_op(op, ">"),
3171            Expression::Gte(op) => self.generate_binary_op(op, ">="),
3172            Expression::Like(op) => self.generate_like_op(op, "LIKE"),
3173            Expression::ILike(op) => self.generate_like_op(op, "ILIKE"),
3174            Expression::Match(op) => self.generate_binary_op(op, "MATCH"),
3175            Expression::Concat(op) => {
3176                // In Solr, || is OR, not string concatenation (DPIPE_IS_STRING_CONCAT = False)
3177                if self.config.dialect == Some(DialectType::Solr) {
3178                    self.generate_binary_op(op, "OR")
3179                } else if self.config.dialect == Some(DialectType::MySQL) {
3180                    self.generate_mysql_concat_from_concat(op)
3181                } else {
3182                    self.generate_binary_op(op, "||")
3183                }
3184            }
3185            Expression::BitwiseAnd(op) => {
3186                // Presto/Trino use BITWISE_AND function
3187                if matches!(
3188                    self.config.dialect,
3189                    Some(DialectType::Presto) | Some(DialectType::Trino)
3190                ) {
3191                    self.write_keyword("BITWISE_AND");
3192                    self.write("(");
3193                    self.generate_expression(&op.left)?;
3194                    self.write(", ");
3195                    self.generate_expression(&op.right)?;
3196                    self.write(")");
3197                    Ok(())
3198                } else {
3199                    self.generate_binary_op(op, "&")
3200                }
3201            }
3202            Expression::BitwiseOr(op) => {
3203                // Presto/Trino use BITWISE_OR function
3204                if matches!(
3205                    self.config.dialect,
3206                    Some(DialectType::Presto) | Some(DialectType::Trino)
3207                ) {
3208                    self.write_keyword("BITWISE_OR");
3209                    self.write("(");
3210                    self.generate_expression(&op.left)?;
3211                    self.write(", ");
3212                    self.generate_expression(&op.right)?;
3213                    self.write(")");
3214                    Ok(())
3215                } else {
3216                    self.generate_binary_op(op, "|")
3217                }
3218            }
3219            Expression::BitwiseXor(op) => {
3220                // Presto/Trino use BITWISE_XOR function, PostgreSQL uses #, others use ^
3221                if matches!(
3222                    self.config.dialect,
3223                    Some(DialectType::Presto) | Some(DialectType::Trino)
3224                ) {
3225                    self.write_keyword("BITWISE_XOR");
3226                    self.write("(");
3227                    self.generate_expression(&op.left)?;
3228                    self.write(", ");
3229                    self.generate_expression(&op.right)?;
3230                    self.write(")");
3231                    Ok(())
3232                } else if matches!(self.config.dialect, Some(DialectType::PostgreSQL)) {
3233                    self.generate_binary_op(op, "#")
3234                } else {
3235                    self.generate_binary_op(op, "^")
3236                }
3237            }
3238            Expression::Adjacent(op) => self.generate_binary_op(op, "-|-"),
3239            Expression::TsMatch(op) => self.generate_binary_op(op, "@@"),
3240            Expression::PropertyEQ(op) => self.generate_binary_op(op, ":="),
3241            Expression::ArrayContainsAll(op) => self.generate_binary_op(op, "@>"),
3242            Expression::ArrayContainedBy(op) => self.generate_binary_op(op, "<@"),
3243            Expression::ArrayOverlaps(op) => self.generate_binary_op(op, "&&"),
3244            Expression::JSONBContainsAllTopKeys(op) => self.generate_binary_op(op, "?&"),
3245            Expression::JSONBContainsAnyTopKeys(op) => self.generate_binary_op(op, "?|"),
3246            Expression::JSONBContains(f) => {
3247                // PostgreSQL JSONB contains key operator: a ? b
3248                self.generate_expression(&f.this)?;
3249                self.write_space();
3250                self.write("?");
3251                self.write_space();
3252                self.generate_expression(&f.expression)
3253            }
3254            Expression::JSONBDeleteAtPath(op) => self.generate_binary_op(op, "#-"),
3255            Expression::ExtendsLeft(op) => self.generate_binary_op(op, "&<"),
3256            Expression::ExtendsRight(op) => self.generate_binary_op(op, "&>"),
3257            Expression::Not(op) => self.generate_unary_op(op, "NOT"),
3258            Expression::Neg(op) => self.generate_unary_op(op, "-"),
3259            Expression::BitwiseNot(op) => {
3260                // Presto/Trino use BITWISE_NOT function
3261                if matches!(
3262                    self.config.dialect,
3263                    Some(DialectType::Presto) | Some(DialectType::Trino)
3264                ) {
3265                    self.write_keyword("BITWISE_NOT");
3266                    self.write("(");
3267                    self.generate_expression(&op.this)?;
3268                    self.write(")");
3269                    Ok(())
3270                } else {
3271                    self.generate_unary_op(op, "~")
3272                }
3273            }
3274            Expression::In(in_expr) => self.generate_in(in_expr),
3275            Expression::Between(between) => self.generate_between(between),
3276            Expression::IsNull(is_null) => self.generate_is_null(is_null),
3277            Expression::IsTrue(is_true) => self.generate_is_true(is_true),
3278            Expression::IsFalse(is_false) => self.generate_is_false(is_false),
3279            Expression::IsJson(is_json) => self.generate_is_json(is_json),
3280            Expression::Is(is_expr) => self.generate_is(is_expr),
3281            Expression::Exists(exists) => self.generate_exists(exists),
3282            Expression::MemberOf(member_of) => self.generate_member_of(member_of),
3283            Expression::Subquery(subquery) => self.generate_subquery(subquery),
3284            Expression::Paren(paren) => {
3285                // JoinedTable already outputs its own parentheses, so don't double-wrap
3286                let skip_parens = matches!(&paren.this, Expression::JoinedTable(_));
3287
3288                if !skip_parens {
3289                    self.write("(");
3290                    if self.config.pretty {
3291                        self.write_newline();
3292                        self.indent_level += 1;
3293                        self.write_indent();
3294                    }
3295                }
3296                self.generate_expression(&paren.this)?;
3297                if !skip_parens {
3298                    if self.config.pretty {
3299                        self.write_newline();
3300                        self.indent_level -= 1;
3301                        self.write_indent();
3302                    }
3303                    self.write(")");
3304                }
3305                // Output trailing comments after closing paren
3306                for comment in &paren.trailing_comments {
3307                    self.write(" ");
3308                    self.write_formatted_comment(comment);
3309                }
3310                Ok(())
3311            }
3312            Expression::Array(arr) => self.generate_array(arr),
3313            Expression::Tuple(tuple) => self.generate_tuple(tuple),
3314            Expression::PipeOperator(pipe) => self.generate_pipe_operator(pipe),
3315            Expression::Ordered(ordered) => self.generate_ordered(ordered),
3316            Expression::DataType(dt) => self.generate_data_type(dt),
3317            Expression::Raw(raw) => {
3318                self.write(&raw.sql);
3319                Ok(())
3320            }
3321            Expression::CreateTask(task) => self.generate_create_task(task),
3322            Expression::Command(cmd) => {
3323                self.write(&cmd.this);
3324                Ok(())
3325            }
3326            Expression::Kill(kill) => {
3327                self.write_keyword("KILL");
3328                if let Some(kind) = &kill.kind {
3329                    self.write_space();
3330                    self.write_keyword(kind);
3331                }
3332                self.write_space();
3333                self.generate_expression(&kill.this)?;
3334                Ok(())
3335            }
3336            Expression::Execute(exec) => {
3337                self.write_keyword("EXECUTE");
3338                self.write_space();
3339                self.generate_expression(&exec.this)?;
3340                for (i, param) in exec.parameters.iter().enumerate() {
3341                    if i == 0 {
3342                        self.write_space();
3343                    } else {
3344                        self.write(", ");
3345                    }
3346                    self.write(&param.name);
3347                    // Only write = value for named parameters (not positional)
3348                    if !param.positional {
3349                        self.write(" = ");
3350                        self.generate_expression(&param.value)?;
3351                    }
3352                    if param.output {
3353                        self.write_space();
3354                        self.write_keyword("OUTPUT");
3355                    }
3356                }
3357                if let Some(ref suffix) = exec.suffix {
3358                    self.write_space();
3359                    self.write(suffix);
3360                }
3361                Ok(())
3362            }
3363            Expression::Annotated(annotated) => {
3364                self.generate_expression(&annotated.this)?;
3365                for comment in &annotated.trailing_comments {
3366                    self.write(" ");
3367                    self.write_formatted_comment(comment);
3368                }
3369                Ok(())
3370            }
3371
3372            // DDL statements
3373            Expression::CreateTable(ct) => self.generate_create_table(ct),
3374            Expression::DropTable(dt) => self.generate_drop_table(dt),
3375            Expression::AlterTable(at) => self.generate_alter_table(at),
3376            Expression::CreateIndex(ci) => self.generate_create_index(ci),
3377            Expression::DropIndex(di) => self.generate_drop_index(di),
3378            Expression::CreateView(cv) => self.generate_create_view(cv),
3379            Expression::DropView(dv) => self.generate_drop_view(dv),
3380            Expression::AlterView(av) => self.generate_alter_view(av),
3381            Expression::AlterIndex(ai) => self.generate_alter_index(ai),
3382            Expression::Truncate(tr) => self.generate_truncate(tr),
3383            Expression::Use(u) => self.generate_use(u),
3384            // Phase 4: Additional DDL statements
3385            Expression::CreateSchema(cs) => self.generate_create_schema(cs),
3386            Expression::DropSchema(ds) => self.generate_drop_schema(ds),
3387            Expression::DropNamespace(dn) => self.generate_drop_namespace(dn),
3388            Expression::CreateDatabase(cd) => self.generate_create_database(cd),
3389            Expression::DropDatabase(dd) => self.generate_drop_database(dd),
3390            Expression::CreateFunction(cf) => self.generate_create_function(cf),
3391            Expression::DropFunction(df) => self.generate_drop_function(df),
3392            Expression::CreateProcedure(cp) => self.generate_create_procedure(cp),
3393            Expression::DropProcedure(dp) => self.generate_drop_procedure(dp),
3394            Expression::CreateSequence(cs) => self.generate_create_sequence(cs),
3395            Expression::CreateSynonym(cs) => {
3396                self.write_keyword("CREATE SYNONYM");
3397                self.write_space();
3398                self.generate_table(&cs.name)?;
3399                self.write_space();
3400                self.write_keyword("FOR");
3401                self.write_space();
3402                self.generate_table(&cs.target)?;
3403                Ok(())
3404            }
3405            Expression::DropSequence(ds) => self.generate_drop_sequence(ds),
3406            Expression::AlterSequence(als) => self.generate_alter_sequence(als),
3407            Expression::CreateTrigger(ct) => self.generate_create_trigger(ct),
3408            Expression::DropTrigger(dt) => self.generate_drop_trigger(dt),
3409            Expression::CreateType(ct) => self.generate_create_type(ct),
3410            Expression::DropType(dt) => self.generate_drop_type(dt),
3411            Expression::Describe(d) => self.generate_describe(d),
3412            Expression::Show(s) => self.generate_show(s),
3413
3414            // CACHE/UNCACHE/LOAD TABLE (Spark/Hive)
3415            Expression::Cache(c) => self.generate_cache(c),
3416            Expression::Uncache(u) => self.generate_uncache(u),
3417            Expression::LoadData(l) => self.generate_load_data(l),
3418            Expression::Pragma(p) => self.generate_pragma(p),
3419            Expression::Grant(g) => self.generate_grant(g),
3420            Expression::Revoke(r) => self.generate_revoke(r),
3421            Expression::Comment(c) => self.generate_comment(c),
3422            Expression::SetStatement(s) => self.generate_set_statement(s),
3423
3424            // PIVOT/UNPIVOT
3425            Expression::Pivot(pivot) => self.generate_pivot(pivot),
3426            Expression::Unpivot(unpivot) => self.generate_unpivot(unpivot),
3427
3428            // VALUES table constructor
3429            Expression::Values(values) => self.generate_values(values),
3430
3431            // === BATCH-GENERATED MATCH ARMS (481 variants) ===
3432            Expression::AIAgg(e) => self.generate_ai_agg(e),
3433            Expression::AIClassify(e) => self.generate_ai_classify(e),
3434            Expression::AddPartition(e) => self.generate_add_partition(e),
3435            Expression::AlgorithmProperty(e) => self.generate_algorithm_property(e),
3436            Expression::Aliases(e) => self.generate_aliases(e),
3437            Expression::AllowedValuesProperty(e) => self.generate_allowed_values_property(e),
3438            Expression::AlterColumn(e) => self.generate_alter_column(e),
3439            Expression::AlterSession(e) => self.generate_alter_session(e),
3440            Expression::AlterSet(e) => self.generate_alter_set(e),
3441            Expression::AlterSortKey(e) => self.generate_alter_sort_key(e),
3442            Expression::Analyze(e) => self.generate_analyze(e),
3443            Expression::AnalyzeDelete(e) => self.generate_analyze_delete(e),
3444            Expression::AnalyzeHistogram(e) => self.generate_analyze_histogram(e),
3445            Expression::AnalyzeListChainedRows(e) => self.generate_analyze_list_chained_rows(e),
3446            Expression::AnalyzeSample(e) => self.generate_analyze_sample(e),
3447            Expression::AnalyzeStatistics(e) => self.generate_analyze_statistics(e),
3448            Expression::AnalyzeValidate(e) => self.generate_analyze_validate(e),
3449            Expression::AnalyzeWith(e) => self.generate_analyze_with(e),
3450            Expression::Anonymous(e) => self.generate_anonymous(e),
3451            Expression::AnonymousAggFunc(e) => self.generate_anonymous_agg_func(e),
3452            Expression::Apply(e) => self.generate_apply(e),
3453            Expression::ApproxPercentileEstimate(e) => self.generate_approx_percentile_estimate(e),
3454            Expression::ApproxQuantile(e) => self.generate_approx_quantile(e),
3455            Expression::ApproxQuantiles(e) => self.generate_approx_quantiles(e),
3456            Expression::ApproxTopK(e) => self.generate_approx_top_k(e),
3457            Expression::ApproxTopKAccumulate(e) => self.generate_approx_top_k_accumulate(e),
3458            Expression::ApproxTopKCombine(e) => self.generate_approx_top_k_combine(e),
3459            Expression::ApproxTopKEstimate(e) => self.generate_approx_top_k_estimate(e),
3460            Expression::ApproxTopSum(e) => self.generate_approx_top_sum(e),
3461            Expression::ArgMax(e) => self.generate_arg_max(e),
3462            Expression::ArgMin(e) => self.generate_arg_min(e),
3463            Expression::ArrayAll(e) => self.generate_array_all(e),
3464            Expression::ArrayAny(e) => self.generate_array_any(e),
3465            Expression::ArrayConstructCompact(e) => self.generate_array_construct_compact(e),
3466            Expression::ArraySum(e) => self.generate_array_sum(e),
3467            Expression::AtIndex(e) => self.generate_at_index(e),
3468            Expression::Attach(e) => self.generate_attach(e),
3469            Expression::AttachOption(e) => self.generate_attach_option(e),
3470            Expression::AutoIncrementProperty(e) => self.generate_auto_increment_property(e),
3471            Expression::AutoRefreshProperty(e) => self.generate_auto_refresh_property(e),
3472            Expression::BackupProperty(e) => self.generate_backup_property(e),
3473            Expression::Base64DecodeBinary(e) => self.generate_base64_decode_binary(e),
3474            Expression::Base64DecodeString(e) => self.generate_base64_decode_string(e),
3475            Expression::Base64Encode(e) => self.generate_base64_encode(e),
3476            Expression::BlockCompressionProperty(e) => self.generate_block_compression_property(e),
3477            Expression::Booland(e) => self.generate_booland(e),
3478            Expression::Boolor(e) => self.generate_boolor(e),
3479            Expression::BuildProperty(e) => self.generate_build_property(e),
3480            Expression::ByteString(e) => self.generate_byte_string(e),
3481            Expression::CaseSpecificColumnConstraint(e) => {
3482                self.generate_case_specific_column_constraint(e)
3483            }
3484            Expression::CastToStrType(e) => self.generate_cast_to_str_type(e),
3485            Expression::Changes(e) => self.generate_changes(e),
3486            Expression::CharacterSetColumnConstraint(e) => {
3487                self.generate_character_set_column_constraint(e)
3488            }
3489            Expression::CharacterSetProperty(e) => self.generate_character_set_property(e),
3490            Expression::CheckColumnConstraint(e) => self.generate_check_column_constraint(e),
3491            Expression::AssumeColumnConstraint(e) => self.generate_assume_column_constraint(e),
3492            Expression::CheckJson(e) => self.generate_check_json(e),
3493            Expression::CheckXml(e) => self.generate_check_xml(e),
3494            Expression::ChecksumProperty(e) => self.generate_checksum_property(e),
3495            Expression::Clone(e) => self.generate_clone(e),
3496            Expression::ClusterBy(e) => self.generate_cluster_by(e),
3497            Expression::ClusterByColumnsProperty(e) => self.generate_cluster_by_columns_property(e),
3498            Expression::ClusteredByProperty(e) => self.generate_clustered_by_property(e),
3499            Expression::CollateProperty(e) => self.generate_collate_property(e),
3500            Expression::ColumnConstraint(e) => self.generate_column_constraint(e),
3501            Expression::ColumnDef(e) => self.generate_column_def_expr(e),
3502            Expression::ColumnPosition(e) => self.generate_column_position(e),
3503            Expression::ColumnPrefix(e) => self.generate_column_prefix(e),
3504            Expression::Columns(e) => self.generate_columns(e),
3505            Expression::CombinedAggFunc(e) => self.generate_combined_agg_func(e),
3506            Expression::CombinedParameterizedAgg(e) => self.generate_combined_parameterized_agg(e),
3507            Expression::Commit(e) => self.generate_commit(e),
3508            Expression::Comprehension(e) => self.generate_comprehension(e),
3509            Expression::Compress(e) => self.generate_compress(e),
3510            Expression::CompressColumnConstraint(e) => self.generate_compress_column_constraint(e),
3511            Expression::ComputedColumnConstraint(e) => self.generate_computed_column_constraint(e),
3512            Expression::ConditionalInsert(e) => self.generate_conditional_insert(e),
3513            Expression::Constraint(e) => self.generate_constraint(e),
3514            Expression::ConvertTimezone(e) => self.generate_convert_timezone(e),
3515            Expression::ConvertToCharset(e) => self.generate_convert_to_charset(e),
3516            Expression::Copy(e) => self.generate_copy(e),
3517            Expression::CopyParameter(e) => self.generate_copy_parameter(e),
3518            Expression::Corr(e) => self.generate_corr(e),
3519            Expression::CosineDistance(e) => self.generate_cosine_distance(e),
3520            Expression::CovarPop(e) => self.generate_covar_pop(e),
3521            Expression::CovarSamp(e) => self.generate_covar_samp(e),
3522            Expression::Credentials(e) => self.generate_credentials(e),
3523            Expression::CredentialsProperty(e) => self.generate_credentials_property(e),
3524            Expression::Cte(e) => self.generate_cte(e),
3525            Expression::Cube(e) => self.generate_cube(e),
3526            Expression::CurrentDatetime(e) => self.generate_current_datetime(e),
3527            Expression::CurrentSchema(e) => self.generate_current_schema(e),
3528            Expression::CurrentSchemas(e) => self.generate_current_schemas(e),
3529            Expression::CurrentUser(e) => self.generate_current_user(e),
3530            Expression::DPipe(e) => self.generate_d_pipe(e),
3531            Expression::DataBlocksizeProperty(e) => self.generate_data_blocksize_property(e),
3532            Expression::DataDeletionProperty(e) => self.generate_data_deletion_property(e),
3533            Expression::Date(e) => self.generate_date_func(e),
3534            Expression::DateBin(e) => self.generate_date_bin(e),
3535            Expression::DateFormatColumnConstraint(e) => {
3536                self.generate_date_format_column_constraint(e)
3537            }
3538            Expression::DateFromParts(e) => self.generate_date_from_parts(e),
3539            Expression::Datetime(e) => self.generate_datetime(e),
3540            Expression::DatetimeAdd(e) => self.generate_datetime_add(e),
3541            Expression::DatetimeDiff(e) => self.generate_datetime_diff(e),
3542            Expression::DatetimeSub(e) => self.generate_datetime_sub(e),
3543            Expression::DatetimeTrunc(e) => self.generate_datetime_trunc(e),
3544            Expression::Dayname(e) => self.generate_dayname(e),
3545            Expression::Declare(e) => self.generate_declare(e),
3546            Expression::DeclareItem(e) => self.generate_declare_item(e),
3547            Expression::DecodeCase(e) => self.generate_decode_case(e),
3548            Expression::DecompressBinary(e) => self.generate_decompress_binary(e),
3549            Expression::DecompressString(e) => self.generate_decompress_string(e),
3550            Expression::Decrypt(e) => self.generate_decrypt(e),
3551            Expression::DecryptRaw(e) => self.generate_decrypt_raw(e),
3552            Expression::DefaultColumnConstraint(e) => {
3553                self.write_keyword("DEFAULT");
3554                self.write_space();
3555                self.generate_expression(&e.this)?;
3556                if let Some(ref col) = e.for_column {
3557                    self.write_space();
3558                    self.write_keyword("FOR");
3559                    self.write_space();
3560                    self.generate_identifier(col)?;
3561                }
3562                Ok(())
3563            }
3564            Expression::DefinerProperty(e) => self.generate_definer_property(e),
3565            Expression::Detach(e) => self.generate_detach(e),
3566            Expression::DictProperty(e) => self.generate_dict_property(e),
3567            Expression::DictRange(e) => self.generate_dict_range(e),
3568            Expression::Directory(e) => self.generate_directory(e),
3569            Expression::DistKeyProperty(e) => self.generate_dist_key_property(e),
3570            Expression::DistStyleProperty(e) => self.generate_dist_style_property(e),
3571            Expression::DistributeBy(e) => self.generate_distribute_by(e),
3572            Expression::DistributedByProperty(e) => self.generate_distributed_by_property(e),
3573            Expression::DotProduct(e) => self.generate_dot_product(e),
3574            Expression::DropPartition(e) => self.generate_drop_partition(e),
3575            Expression::DuplicateKeyProperty(e) => self.generate_duplicate_key_property(e),
3576            Expression::Elt(e) => self.generate_elt(e),
3577            Expression::Encode(e) => self.generate_encode(e),
3578            Expression::EncodeProperty(e) => self.generate_encode_property(e),
3579            Expression::Encrypt(e) => self.generate_encrypt(e),
3580            Expression::EncryptRaw(e) => self.generate_encrypt_raw(e),
3581            Expression::EngineProperty(e) => self.generate_engine_property(e),
3582            Expression::EnviromentProperty(e) => self.generate_enviroment_property(e),
3583            Expression::EphemeralColumnConstraint(e) => {
3584                self.generate_ephemeral_column_constraint(e)
3585            }
3586            Expression::EqualNull(e) => self.generate_equal_null(e),
3587            Expression::EuclideanDistance(e) => self.generate_euclidean_distance(e),
3588            Expression::ExecuteAsProperty(e) => self.generate_execute_as_property(e),
3589            Expression::Export(e) => self.generate_export(e),
3590            Expression::ExternalProperty(e) => self.generate_external_property(e),
3591            Expression::FallbackProperty(e) => self.generate_fallback_property(e),
3592            Expression::FarmFingerprint(e) => self.generate_farm_fingerprint(e),
3593            Expression::FeaturesAtTime(e) => self.generate_features_at_time(e),
3594            Expression::Fetch(e) => self.generate_fetch(e),
3595            Expression::FileFormatProperty(e) => self.generate_file_format_property(e),
3596            Expression::Filter(e) => self.generate_filter(e),
3597            Expression::Float64(e) => self.generate_float64(e),
3598            Expression::ForIn(e) => self.generate_for_in(e),
3599            Expression::ForeignKey(e) => self.generate_foreign_key(e),
3600            Expression::Format(e) => self.generate_format(e),
3601            Expression::FormatPhrase(e) => self.generate_format_phrase(e),
3602            Expression::FreespaceProperty(e) => self.generate_freespace_property(e),
3603            Expression::From(e) => self.generate_from(e),
3604            Expression::FromBase(e) => self.generate_from_base(e),
3605            Expression::FromTimeZone(e) => self.generate_from_time_zone(e),
3606            Expression::GapFill(e) => self.generate_gap_fill(e),
3607            Expression::GenerateDateArray(e) => self.generate_generate_date_array(e),
3608            Expression::GenerateEmbedding(e) => self.generate_generate_embedding(e),
3609            Expression::GenerateSeries(e) => self.generate_generate_series(e),
3610            Expression::GenerateTimestampArray(e) => self.generate_generate_timestamp_array(e),
3611            Expression::GeneratedAsIdentityColumnConstraint(e) => {
3612                self.generate_generated_as_identity_column_constraint(e)
3613            }
3614            Expression::GeneratedAsRowColumnConstraint(e) => {
3615                self.generate_generated_as_row_column_constraint(e)
3616            }
3617            Expression::Get(e) => self.generate_get(e),
3618            Expression::GetExtract(e) => self.generate_get_extract(e),
3619            Expression::Getbit(e) => self.generate_getbit(e),
3620            Expression::GrantPrincipal(e) => self.generate_grant_principal(e),
3621            Expression::GrantPrivilege(e) => self.generate_grant_privilege(e),
3622            Expression::Group(e) => self.generate_group(e),
3623            Expression::GroupBy(e) => self.generate_group_by(e),
3624            Expression::Grouping(e) => self.generate_grouping(e),
3625            Expression::GroupingId(e) => self.generate_grouping_id(e),
3626            Expression::GroupingSets(e) => self.generate_grouping_sets(e),
3627            Expression::HashAgg(e) => self.generate_hash_agg(e),
3628            Expression::Having(e) => self.generate_having(e),
3629            Expression::HavingMax(e) => self.generate_having_max(e),
3630            Expression::Heredoc(e) => self.generate_heredoc(e),
3631            Expression::HexEncode(e) => self.generate_hex_encode(e),
3632            Expression::Hll(e) => self.generate_hll(e),
3633            Expression::InOutColumnConstraint(e) => self.generate_in_out_column_constraint(e),
3634            Expression::IncludeProperty(e) => self.generate_include_property(e),
3635            Expression::Index(e) => self.generate_index(e),
3636            Expression::IndexColumnConstraint(e) => self.generate_index_column_constraint(e),
3637            Expression::IndexConstraintOption(e) => self.generate_index_constraint_option(e),
3638            Expression::IndexParameters(e) => self.generate_index_parameters(e),
3639            Expression::IndexTableHint(e) => self.generate_index_table_hint(e),
3640            Expression::InheritsProperty(e) => self.generate_inherits_property(e),
3641            Expression::InputModelProperty(e) => self.generate_input_model_property(e),
3642            Expression::InputOutputFormat(e) => self.generate_input_output_format(e),
3643            Expression::Install(e) => self.generate_install(e),
3644            Expression::IntervalOp(e) => self.generate_interval_op(e),
3645            Expression::IntervalSpan(e) => self.generate_interval_span(e),
3646            Expression::IntoClause(e) => self.generate_into_clause(e),
3647            Expression::Introducer(e) => self.generate_introducer(e),
3648            Expression::IsolatedLoadingProperty(e) => self.generate_isolated_loading_property(e),
3649            Expression::JSON(e) => self.generate_json(e),
3650            Expression::JSONArray(e) => self.generate_json_array(e),
3651            Expression::JSONArrayAgg(e) => self.generate_json_array_agg_struct(e),
3652            Expression::JSONArrayAppend(e) => self.generate_json_array_append(e),
3653            Expression::JSONArrayContains(e) => self.generate_json_array_contains(e),
3654            Expression::JSONArrayInsert(e) => self.generate_json_array_insert(e),
3655            Expression::JSONBExists(e) => self.generate_jsonb_exists(e),
3656            Expression::JSONBExtractScalar(e) => self.generate_jsonb_extract_scalar(e),
3657            Expression::JSONBObjectAgg(e) => self.generate_jsonb_object_agg(e),
3658            Expression::JSONObjectAgg(e) => self.generate_json_object_agg_struct(e),
3659            Expression::JSONColumnDef(e) => self.generate_json_column_def(e),
3660            Expression::JSONExists(e) => self.generate_json_exists(e),
3661            Expression::JSONCast(e) => self.generate_json_cast(e),
3662            Expression::JSONExtract(e) => self.generate_json_extract_path(e),
3663            Expression::JSONExtractArray(e) => self.generate_json_extract_array(e),
3664            Expression::JSONExtractQuote(e) => self.generate_json_extract_quote(e),
3665            Expression::JSONExtractScalar(e) => self.generate_json_extract_scalar(e),
3666            Expression::JSONFormat(e) => self.generate_json_format(e),
3667            Expression::JSONKeyValue(e) => self.generate_json_key_value(e),
3668            Expression::JSONKeys(e) => self.generate_json_keys(e),
3669            Expression::JSONKeysAtDepth(e) => self.generate_json_keys_at_depth(e),
3670            Expression::JSONPath(e) => self.generate_json_path_expr(e),
3671            Expression::JSONPathFilter(e) => self.generate_json_path_filter(e),
3672            Expression::JSONPathKey(e) => self.generate_json_path_key(e),
3673            Expression::JSONPathRecursive(e) => self.generate_json_path_recursive(e),
3674            Expression::JSONPathRoot(_) => self.generate_json_path_root(),
3675            Expression::JSONPathScript(e) => self.generate_json_path_script(e),
3676            Expression::JSONPathSelector(e) => self.generate_json_path_selector(e),
3677            Expression::JSONPathSlice(e) => self.generate_json_path_slice(e),
3678            Expression::JSONPathSubscript(e) => self.generate_json_path_subscript(e),
3679            Expression::JSONPathUnion(e) => self.generate_json_path_union(e),
3680            Expression::JSONRemove(e) => self.generate_json_remove(e),
3681            Expression::JSONSchema(e) => self.generate_json_schema(e),
3682            Expression::JSONSet(e) => self.generate_json_set(e),
3683            Expression::JSONStripNulls(e) => self.generate_json_strip_nulls(e),
3684            Expression::JSONTable(e) => self.generate_json_table(e),
3685            Expression::JSONType(e) => self.generate_json_type(e),
3686            Expression::JSONValue(e) => self.generate_json_value(e),
3687            Expression::JSONValueArray(e) => self.generate_json_value_array(e),
3688            Expression::JarowinklerSimilarity(e) => self.generate_jarowinkler_similarity(e),
3689            Expression::JoinHint(e) => self.generate_join_hint(e),
3690            Expression::JournalProperty(e) => self.generate_journal_property(e),
3691            Expression::LanguageProperty(e) => self.generate_language_property(e),
3692            Expression::Lateral(e) => self.generate_lateral(e),
3693            Expression::LikeProperty(e) => self.generate_like_property(e),
3694            Expression::Limit(e) => self.generate_limit(e),
3695            Expression::LimitOptions(e) => self.generate_limit_options(e),
3696            Expression::List(e) => self.generate_list(e),
3697            Expression::ToMap(e) => self.generate_tomap(e),
3698            Expression::Localtime(e) => self.generate_localtime(e),
3699            Expression::Localtimestamp(e) => self.generate_localtimestamp(e),
3700            Expression::LocationProperty(e) => self.generate_location_property(e),
3701            Expression::Lock(e) => self.generate_lock(e),
3702            Expression::LockProperty(e) => self.generate_lock_property(e),
3703            Expression::LockingProperty(e) => self.generate_locking_property(e),
3704            Expression::LockingStatement(e) => self.generate_locking_statement(e),
3705            Expression::LogProperty(e) => self.generate_log_property(e),
3706            Expression::MD5Digest(e) => self.generate_md5_digest(e),
3707            Expression::MLForecast(e) => self.generate_ml_forecast(e),
3708            Expression::MLTranslate(e) => self.generate_ml_translate(e),
3709            Expression::MakeInterval(e) => self.generate_make_interval(e),
3710            Expression::ManhattanDistance(e) => self.generate_manhattan_distance(e),
3711            Expression::Map(e) => self.generate_map(e),
3712            Expression::MapCat(e) => self.generate_map_cat(e),
3713            Expression::MapDelete(e) => self.generate_map_delete(e),
3714            Expression::MapInsert(e) => self.generate_map_insert(e),
3715            Expression::MapPick(e) => self.generate_map_pick(e),
3716            Expression::MaskingPolicyColumnConstraint(e) => {
3717                self.generate_masking_policy_column_constraint(e)
3718            }
3719            Expression::MatchAgainst(e) => self.generate_match_against(e),
3720            Expression::MatchRecognizeMeasure(e) => self.generate_match_recognize_measure(e),
3721            Expression::MaterializedProperty(e) => self.generate_materialized_property(e),
3722            Expression::Merge(e) => self.generate_merge(e),
3723            Expression::MergeBlockRatioProperty(e) => self.generate_merge_block_ratio_property(e),
3724            Expression::MergeTreeTTL(e) => self.generate_merge_tree_ttl(e),
3725            Expression::MergeTreeTTLAction(e) => self.generate_merge_tree_ttl_action(e),
3726            Expression::Minhash(e) => self.generate_minhash(e),
3727            Expression::ModelAttribute(e) => self.generate_model_attribute(e),
3728            Expression::Monthname(e) => self.generate_monthname(e),
3729            Expression::MultitableInserts(e) => self.generate_multitable_inserts(e),
3730            Expression::NextValueFor(e) => self.generate_next_value_for(e),
3731            Expression::Normal(e) => self.generate_normal(e),
3732            Expression::Normalize(e) => self.generate_normalize(e),
3733            Expression::NotNullColumnConstraint(e) => self.generate_not_null_column_constraint(e),
3734            Expression::Nullif(e) => self.generate_nullif(e),
3735            Expression::NumberToStr(e) => self.generate_number_to_str(e),
3736            Expression::ObjectAgg(e) => self.generate_object_agg(e),
3737            Expression::ObjectIdentifier(e) => self.generate_object_identifier(e),
3738            Expression::ObjectInsert(e) => self.generate_object_insert(e),
3739            Expression::Offset(e) => self.generate_offset(e),
3740            Expression::Qualify(e) => self.generate_qualify(e),
3741            Expression::OnCluster(e) => self.generate_on_cluster(e),
3742            Expression::OnCommitProperty(e) => self.generate_on_commit_property(e),
3743            Expression::OnCondition(e) => self.generate_on_condition(e),
3744            Expression::OnConflict(e) => self.generate_on_conflict(e),
3745            Expression::OnProperty(e) => self.generate_on_property(e),
3746            Expression::Opclass(e) => self.generate_opclass(e),
3747            Expression::OpenJSON(e) => self.generate_open_json(e),
3748            Expression::OpenJSONColumnDef(e) => self.generate_open_json_column_def(e),
3749            Expression::Operator(e) => self.generate_operator(e),
3750            Expression::OrderBy(e) => self.generate_order_by(e),
3751            Expression::OutputModelProperty(e) => self.generate_output_model_property(e),
3752            Expression::OverflowTruncateBehavior(e) => self.generate_overflow_truncate_behavior(e),
3753            Expression::ParameterizedAgg(e) => self.generate_parameterized_agg(e),
3754            Expression::ParseDatetime(e) => self.generate_parse_datetime(e),
3755            Expression::ParseIp(e) => self.generate_parse_ip(e),
3756            Expression::ParseJSON(e) => self.generate_parse_json(e),
3757            Expression::ParseTime(e) => self.generate_parse_time(e),
3758            Expression::ParseUrl(e) => self.generate_parse_url(e),
3759            Expression::Partition(e) => self.generate_partition_expr(e),
3760            Expression::PartitionBoundSpec(e) => self.generate_partition_bound_spec(e),
3761            Expression::PartitionByListProperty(e) => self.generate_partition_by_list_property(e),
3762            Expression::PartitionByRangeProperty(e) => self.generate_partition_by_range_property(e),
3763            Expression::PartitionByRangePropertyDynamic(e) => {
3764                self.generate_partition_by_range_property_dynamic(e)
3765            }
3766            Expression::PartitionByTruncate(e) => self.generate_partition_by_truncate(e),
3767            Expression::PartitionList(e) => self.generate_partition_list(e),
3768            Expression::PartitionRange(e) => self.generate_partition_range(e),
3769            Expression::PartitionByProperty(e) => self.generate_partition_by_property(e),
3770            Expression::PartitionedByBucket(e) => self.generate_partitioned_by_bucket(e),
3771            Expression::PartitionedByProperty(e) => self.generate_partitioned_by_property(e),
3772            Expression::PartitionedOfProperty(e) => self.generate_partitioned_of_property(e),
3773            Expression::PeriodForSystemTimeConstraint(e) => {
3774                self.generate_period_for_system_time_constraint(e)
3775            }
3776            Expression::PivotAlias(e) => self.generate_pivot_alias(e),
3777            Expression::PivotAny(e) => self.generate_pivot_any(e),
3778            Expression::Predict(e) => self.generate_predict(e),
3779            Expression::PreviousDay(e) => self.generate_previous_day(e),
3780            Expression::PrimaryKey(e) => self.generate_primary_key(e),
3781            Expression::PrimaryKeyColumnConstraint(e) => {
3782                self.generate_primary_key_column_constraint(e)
3783            }
3784            Expression::PathColumnConstraint(e) => self.generate_path_column_constraint(e),
3785            Expression::ProjectionDef(e) => self.generate_projection_def(e),
3786            Expression::OptionsProperty(e) => self.generate_options_property(e),
3787            Expression::Properties(e) => self.generate_properties(e),
3788            Expression::Property(e) => self.generate_property(e),
3789            Expression::PseudoType(e) => self.generate_pseudo_type(e),
3790            Expression::Put(e) => self.generate_put(e),
3791            Expression::Quantile(e) => self.generate_quantile(e),
3792            Expression::QueryBand(e) => self.generate_query_band(e),
3793            Expression::QueryOption(e) => self.generate_query_option(e),
3794            Expression::QueryTransform(e) => self.generate_query_transform(e),
3795            Expression::Randn(e) => self.generate_randn(e),
3796            Expression::Randstr(e) => self.generate_randstr(e),
3797            Expression::RangeBucket(e) => self.generate_range_bucket(e),
3798            Expression::RangeN(e) => self.generate_range_n(e),
3799            Expression::ReadCSV(e) => self.generate_read_csv(e),
3800            Expression::ReadParquet(e) => self.generate_read_parquet(e),
3801            Expression::RecursiveWithSearch(e) => self.generate_recursive_with_search(e),
3802            Expression::Reduce(e) => self.generate_reduce(e),
3803            Expression::Reference(e) => self.generate_reference(e),
3804            Expression::Refresh(e) => self.generate_refresh(e),
3805            Expression::RefreshTriggerProperty(e) => self.generate_refresh_trigger_property(e),
3806            Expression::RegexpCount(e) => self.generate_regexp_count(e),
3807            Expression::RegexpExtractAll(e) => self.generate_regexp_extract_all(e),
3808            Expression::RegexpFullMatch(e) => self.generate_regexp_full_match(e),
3809            Expression::RegexpILike(e) => self.generate_regexp_i_like(e),
3810            Expression::RegexpInstr(e) => self.generate_regexp_instr(e),
3811            Expression::RegexpSplit(e) => self.generate_regexp_split(e),
3812            Expression::RegrAvgx(e) => self.generate_regr_avgx(e),
3813            Expression::RegrAvgy(e) => self.generate_regr_avgy(e),
3814            Expression::RegrCount(e) => self.generate_regr_count(e),
3815            Expression::RegrIntercept(e) => self.generate_regr_intercept(e),
3816            Expression::RegrR2(e) => self.generate_regr_r2(e),
3817            Expression::RegrSlope(e) => self.generate_regr_slope(e),
3818            Expression::RegrSxx(e) => self.generate_regr_sxx(e),
3819            Expression::RegrSxy(e) => self.generate_regr_sxy(e),
3820            Expression::RegrSyy(e) => self.generate_regr_syy(e),
3821            Expression::RegrValx(e) => self.generate_regr_valx(e),
3822            Expression::RegrValy(e) => self.generate_regr_valy(e),
3823            Expression::RemoteWithConnectionModelProperty(e) => {
3824                self.generate_remote_with_connection_model_property(e)
3825            }
3826            Expression::RenameColumn(e) => self.generate_rename_column(e),
3827            Expression::ReplacePartition(e) => self.generate_replace_partition(e),
3828            Expression::Returning(e) => self.generate_returning(e),
3829            Expression::ReturnsProperty(e) => self.generate_returns_property(e),
3830            Expression::Rollback(e) => self.generate_rollback(e),
3831            Expression::Rollup(e) => self.generate_rollup(e),
3832            Expression::RowFormatDelimitedProperty(e) => {
3833                self.generate_row_format_delimited_property(e)
3834            }
3835            Expression::RowFormatProperty(e) => self.generate_row_format_property(e),
3836            Expression::RowFormatSerdeProperty(e) => self.generate_row_format_serde_property(e),
3837            Expression::SHA2(e) => self.generate_sha2(e),
3838            Expression::SHA2Digest(e) => self.generate_sha2_digest(e),
3839            Expression::SafeAdd(e) => self.generate_safe_add(e),
3840            Expression::SafeDivide(e) => self.generate_safe_divide(e),
3841            Expression::SafeMultiply(e) => self.generate_safe_multiply(e),
3842            Expression::SafeSubtract(e) => self.generate_safe_subtract(e),
3843            Expression::SampleProperty(e) => self.generate_sample_property(e),
3844            Expression::Schema(e) => self.generate_schema(e),
3845            Expression::SchemaCommentProperty(e) => self.generate_schema_comment_property(e),
3846            Expression::ScopeResolution(e) => self.generate_scope_resolution(e),
3847            Expression::Search(e) => self.generate_search(e),
3848            Expression::SearchIp(e) => self.generate_search_ip(e),
3849            Expression::SecurityProperty(e) => self.generate_security_property(e),
3850            Expression::SemanticView(e) => self.generate_semantic_view(e),
3851            Expression::SequenceProperties(e) => self.generate_sequence_properties(e),
3852            Expression::SerdeProperties(e) => self.generate_serde_properties(e),
3853            Expression::SessionParameter(e) => self.generate_session_parameter(e),
3854            Expression::Set(e) => self.generate_set(e),
3855            Expression::SetConfigProperty(e) => self.generate_set_config_property(e),
3856            Expression::SetItem(e) => self.generate_set_item(e),
3857            Expression::SetOperation(e) => self.generate_set_operation(e),
3858            Expression::SetProperty(e) => self.generate_set_property(e),
3859            Expression::SettingsProperty(e) => self.generate_settings_property(e),
3860            Expression::SharingProperty(e) => self.generate_sharing_property(e),
3861            Expression::Slice(e) => self.generate_slice(e),
3862            Expression::SortArray(e) => self.generate_sort_array(e),
3863            Expression::SortBy(e) => self.generate_sort_by(e),
3864            Expression::SortKeyProperty(e) => self.generate_sort_key_property(e),
3865            Expression::SplitPart(e) => self.generate_split_part(e),
3866            Expression::SqlReadWriteProperty(e) => self.generate_sql_read_write_property(e),
3867            Expression::SqlSecurityProperty(e) => self.generate_sql_security_property(e),
3868            Expression::StDistance(e) => self.generate_st_distance(e),
3869            Expression::StPoint(e) => self.generate_st_point(e),
3870            Expression::StabilityProperty(e) => self.generate_stability_property(e),
3871            Expression::StandardHash(e) => self.generate_standard_hash(e),
3872            Expression::StorageHandlerProperty(e) => self.generate_storage_handler_property(e),
3873            Expression::StrPosition(e) => self.generate_str_position(e),
3874            Expression::StrToDate(e) => self.generate_str_to_date(e),
3875            Expression::DateStrToDate(f) => self.generate_simple_func("DATE_STR_TO_DATE", &f.this),
3876            Expression::DateToDateStr(f) => self.generate_simple_func("DATE_TO_DATE_STR", &f.this),
3877            Expression::StrToMap(e) => self.generate_str_to_map(e),
3878            Expression::StrToTime(e) => self.generate_str_to_time(e),
3879            Expression::StrToUnix(e) => self.generate_str_to_unix(e),
3880            Expression::StringToArray(e) => self.generate_string_to_array(e),
3881            Expression::Struct(e) => self.generate_struct(e),
3882            Expression::Stuff(e) => self.generate_stuff(e),
3883            Expression::SubstringIndex(e) => self.generate_substring_index(e),
3884            Expression::Summarize(e) => self.generate_summarize(e),
3885            Expression::Systimestamp(e) => self.generate_systimestamp(e),
3886            Expression::TableAlias(e) => self.generate_table_alias(e),
3887            Expression::TableFromRows(e) => self.generate_table_from_rows(e),
3888            Expression::RowsFrom(e) => self.generate_rows_from(e),
3889            Expression::TableSample(e) => self.generate_table_sample(e),
3890            Expression::Tag(e) => self.generate_tag(e),
3891            Expression::Tags(e) => self.generate_tags(e),
3892            Expression::TemporaryProperty(e) => self.generate_temporary_property(e),
3893            Expression::Time(e) => self.generate_time_func(e),
3894            Expression::TimeAdd(e) => self.generate_time_add(e),
3895            Expression::TimeDiff(e) => self.generate_time_diff(e),
3896            Expression::TimeFromParts(e) => self.generate_time_from_parts(e),
3897            Expression::TimeSlice(e) => self.generate_time_slice(e),
3898            Expression::TimeStrToDate(e) => self.generate_time_str_to_date(e),
3899            Expression::TimeStrToTime(e) => self.generate_time_str_to_time(e),
3900            Expression::TimeSub(e) => self.generate_time_sub(e),
3901            Expression::TimeToStr(e) => self.generate_time_to_str(e),
3902            Expression::TimeToUnix(e) => self.generate_time_to_unix(e),
3903            Expression::TimeTrunc(e) => self.generate_time_trunc(e),
3904            Expression::TimeUnit(e) => self.generate_time_unit(e),
3905            Expression::Timestamp(e) => self.generate_timestamp_func(e),
3906            Expression::TimestampAdd(e) => self.generate_timestamp_add(e),
3907            Expression::TimestampDiff(e) => self.generate_timestamp_diff(e),
3908            Expression::TimestampFromParts(e) => self.generate_timestamp_from_parts(e),
3909            Expression::TimestampSub(e) => self.generate_timestamp_sub(e),
3910            Expression::TimestampTzFromParts(e) => self.generate_timestamp_tz_from_parts(e),
3911            Expression::ToBinary(e) => self.generate_to_binary(e),
3912            Expression::ToBoolean(e) => self.generate_to_boolean(e),
3913            Expression::ToChar(e) => self.generate_to_char(e),
3914            Expression::ToDecfloat(e) => self.generate_to_decfloat(e),
3915            Expression::ToDouble(e) => self.generate_to_double(e),
3916            Expression::ToFile(e) => self.generate_to_file(e),
3917            Expression::ToNumber(e) => self.generate_to_number(e),
3918            Expression::ToTableProperty(e) => self.generate_to_table_property(e),
3919            Expression::Transaction(e) => self.generate_transaction(e),
3920            Expression::Transform(e) => self.generate_transform(e),
3921            Expression::TransformModelProperty(e) => self.generate_transform_model_property(e),
3922            Expression::TransientProperty(e) => self.generate_transient_property(e),
3923            Expression::Translate(e) => self.generate_translate(e),
3924            Expression::TranslateCharacters(e) => self.generate_translate_characters(e),
3925            Expression::TruncateTable(e) => self.generate_truncate_table(e),
3926            Expression::TryBase64DecodeBinary(e) => self.generate_try_base64_decode_binary(e),
3927            Expression::TryBase64DecodeString(e) => self.generate_try_base64_decode_string(e),
3928            Expression::TryToDecfloat(e) => self.generate_try_to_decfloat(e),
3929            Expression::TsOrDsAdd(e) => self.generate_ts_or_ds_add(e),
3930            Expression::TsOrDsDiff(e) => self.generate_ts_or_ds_diff(e),
3931            Expression::TsOrDsToDate(e) => self.generate_ts_or_ds_to_date(e),
3932            Expression::TsOrDsToTime(e) => self.generate_ts_or_ds_to_time(e),
3933            Expression::Unhex(e) => self.generate_unhex(e),
3934            Expression::UnicodeString(e) => self.generate_unicode_string(e),
3935            Expression::Uniform(e) => self.generate_uniform(e),
3936            Expression::UniqueColumnConstraint(e) => self.generate_unique_column_constraint(e),
3937            Expression::UniqueKeyProperty(e) => self.generate_unique_key_property(e),
3938            Expression::RollupProperty(e) => self.generate_rollup_property(e),
3939            Expression::UnixToStr(e) => self.generate_unix_to_str(e),
3940            Expression::UnixToTime(e) => self.generate_unix_to_time(e),
3941            Expression::UnpivotColumns(e) => self.generate_unpivot_columns(e),
3942            Expression::UserDefinedFunction(e) => self.generate_user_defined_function(e),
3943            Expression::UsingTemplateProperty(e) => self.generate_using_template_property(e),
3944            Expression::UtcTime(e) => self.generate_utc_time(e),
3945            Expression::UtcTimestamp(e) => self.generate_utc_timestamp(e),
3946            Expression::Uuid(e) => self.generate_uuid(e),
3947            Expression::Var(v) => {
3948                if matches!(self.config.dialect, Some(DialectType::MySQL))
3949                    && v.this.len() > 2
3950                    && (v.this.starts_with("0x") || v.this.starts_with("0X"))
3951                    && !v.this[2..].chars().all(|c| c.is_ascii_hexdigit())
3952                {
3953                    return self.generate_identifier(&Identifier {
3954                        name: v.this.clone(),
3955                        quoted: true,
3956                        trailing_comments: Vec::new(),
3957                        span: None,
3958                    });
3959                }
3960                self.write(&v.this);
3961                Ok(())
3962            }
3963            Expression::Variadic(e) => {
3964                self.write_keyword("VARIADIC");
3965                self.write_space();
3966                self.generate_expression(&e.this)?;
3967                Ok(())
3968            }
3969            Expression::VarMap(e) => self.generate_var_map(e),
3970            Expression::VectorSearch(e) => self.generate_vector_search(e),
3971            Expression::Version(e) => self.generate_version(e),
3972            Expression::ViewAttributeProperty(e) => self.generate_view_attribute_property(e),
3973            Expression::VolatileProperty(e) => self.generate_volatile_property(e),
3974            Expression::WatermarkColumnConstraint(e) => {
3975                self.generate_watermark_column_constraint(e)
3976            }
3977            Expression::Week(e) => self.generate_week(e),
3978            Expression::When(e) => self.generate_when(e),
3979            Expression::Whens(e) => self.generate_whens(e),
3980            Expression::Where(e) => self.generate_where(e),
3981            Expression::WidthBucket(e) => self.generate_width_bucket(e),
3982            Expression::Window(e) => self.generate_window(e),
3983            Expression::WindowSpec(e) => self.generate_window_spec(e),
3984            Expression::WithDataProperty(e) => self.generate_with_data_property(e),
3985            Expression::WithFill(e) => self.generate_with_fill(e),
3986            Expression::WithJournalTableProperty(e) => self.generate_with_journal_table_property(e),
3987            Expression::WithOperator(e) => self.generate_with_operator(e),
3988            Expression::WithProcedureOptions(e) => self.generate_with_procedure_options(e),
3989            Expression::WithSchemaBindingProperty(e) => {
3990                self.generate_with_schema_binding_property(e)
3991            }
3992            Expression::WithSystemVersioningProperty(e) => {
3993                self.generate_with_system_versioning_property(e)
3994            }
3995            Expression::WithTableHint(e) => self.generate_with_table_hint(e),
3996            Expression::XMLElement(e) => self.generate_xml_element(e),
3997            Expression::XMLGet(e) => self.generate_xml_get(e),
3998            Expression::XMLKeyValueOption(e) => self.generate_xml_key_value_option(e),
3999            Expression::XMLTable(e) => self.generate_xml_table(e),
4000            Expression::Xor(e) => self.generate_xor(e),
4001            Expression::Zipf(e) => self.generate_zipf(e),
4002            _ => self.write_unsupported_comment("unsupported expression"),
4003        }
4004    }
4005
4006    fn generate_select(&mut self, select: &Select) -> Result<()> {
4007        use crate::dialects::DialectType;
4008
4009        // Redshift-style EXCLUDE: for dialects other than Redshift, wrap in a derived table
4010        // e.g., SELECT *, col4 EXCLUDE (col2, col3) FROM t
4011        //   → SELECT * EXCLUDE (col2, col3) FROM (SELECT *, col4 FROM t)
4012        if let Some(exclude) = &select.exclude {
4013            if !exclude.is_empty() && !matches!(self.config.dialect, Some(DialectType::Redshift)) {
4014                // Build the inner select (same as original but without exclude)
4015                let mut inner_select = select.clone();
4016                inner_select.exclude = None;
4017                let inner_expr = Expression::Select(Box::new(inner_select));
4018
4019                // Build the subquery
4020                let subquery = crate::expressions::Subquery {
4021                    this: inner_expr,
4022                    alias: None,
4023                    column_aliases: Vec::new(),
4024                    order_by: None,
4025                    limit: None,
4026                    offset: None,
4027                    distribute_by: None,
4028                    sort_by: None,
4029                    cluster_by: None,
4030                    lateral: false,
4031                    modifiers_inside: false,
4032                    trailing_comments: Vec::new(),
4033                    inferred_type: None,
4034                };
4035
4036                // Build the outer select: SELECT * EXCLUDE (cols) FROM (inner)
4037                let star = Expression::Star(crate::expressions::Star {
4038                    table: None,
4039                    except: Some(
4040                        exclude
4041                            .iter()
4042                            .map(|e| match e {
4043                                Expression::Column(col) => col.name.clone(),
4044                                Expression::Identifier(id) => id.clone(),
4045                                _ => crate::expressions::Identifier::new("unknown".to_string()),
4046                            })
4047                            .collect(),
4048                    ),
4049                    replace: None,
4050                    rename: None,
4051                    trailing_comments: Vec::new(),
4052                    span: None,
4053                });
4054
4055                let outer_select = Select {
4056                    expressions: vec![star],
4057                    from: Some(crate::expressions::From {
4058                        expressions: vec![Expression::Subquery(Box::new(subquery))],
4059                    }),
4060                    ..Select::new()
4061                };
4062
4063                return self.generate_select(&outer_select);
4064            }
4065        }
4066
4067        // Output leading comments before SELECT
4068        for comment in &select.leading_comments {
4069            self.write_formatted_comment(comment);
4070            self.write(" ");
4071        }
4072
4073        // WITH clause
4074        if let Some(with) = &select.with {
4075            self.generate_with(with)?;
4076            if self.config.pretty {
4077                self.write_newline();
4078                self.write_indent();
4079            } else {
4080                self.write_space();
4081            }
4082        }
4083
4084        // Output post-SELECT comments (comments that appeared after SELECT keyword)
4085        // These are output BEFORE SELECT, as Python SQLGlot normalizes them this way
4086        for comment in &select.post_select_comments {
4087            self.write_formatted_comment(comment);
4088            self.write(" ");
4089        }
4090
4091        self.write_keyword("SELECT");
4092
4093        // Generate query hint if present /*+ ... */
4094        if let Some(hint) = &select.hint {
4095            self.generate_hint(hint)?;
4096        }
4097
4098        // For SQL Server, convert LIMIT to TOP (structural transformation)
4099        // But only when there's no OFFSET (otherwise use OFFSET/FETCH syntax)
4100        // TOP clause (SQL Server style - before DISTINCT)
4101        let use_top_from_limit = matches!(self.config.dialect, Some(DialectType::TSQL))
4102            && select.top.is_none()
4103            && select.limit.is_some()
4104            && select.offset.is_none(); // Don't use TOP when there's OFFSET
4105
4106        // For TOP-supporting dialects: DISTINCT before TOP
4107        // For non-TOP dialects: TOP is converted to LIMIT later; DISTINCT goes here
4108        let is_top_dialect = matches!(
4109            self.config.dialect,
4110            Some(DialectType::TSQL) | Some(DialectType::Teradata) | Some(DialectType::Fabric)
4111        );
4112        let keep_top_verbatim = !is_top_dialect
4113            && select.limit.is_none()
4114            && select
4115                .top
4116                .as_ref()
4117                .map_or(false, |top| top.percent || top.with_ties);
4118
4119        if select.distinct && (is_top_dialect || select.top.is_some()) {
4120            self.write_space();
4121            self.write_keyword("DISTINCT");
4122        }
4123
4124        if is_top_dialect || keep_top_verbatim {
4125            if let Some(top) = &select.top {
4126                self.write_space();
4127                self.write_keyword("TOP");
4128                if top.parenthesized {
4129                    self.write(" (");
4130                    self.generate_expression(&top.this)?;
4131                    self.write(")");
4132                } else {
4133                    self.write_space();
4134                    self.generate_expression(&top.this)?;
4135                }
4136                if top.percent {
4137                    self.write_space();
4138                    self.write_keyword("PERCENT");
4139                }
4140                if top.with_ties {
4141                    self.write_space();
4142                    self.write_keyword("WITH TIES");
4143                }
4144            } else if use_top_from_limit {
4145                // Convert LIMIT to TOP for SQL Server (only when no OFFSET)
4146                if let Some(limit) = &select.limit {
4147                    self.write_space();
4148                    self.write_keyword("TOP");
4149                    // Use parentheses for complex expressions, but not for simple literals
4150                    let is_simple_literal = matches!(&limit.this, Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(_)));
4151                    if is_simple_literal {
4152                        self.write_space();
4153                        self.generate_expression(&limit.this)?;
4154                    } else {
4155                        self.write(" (");
4156                        self.generate_expression(&limit.this)?;
4157                        self.write(")");
4158                    }
4159                }
4160            }
4161        }
4162
4163        if select.distinct && !is_top_dialect && select.top.is_none() {
4164            self.write_space();
4165            self.write_keyword("DISTINCT");
4166        }
4167
4168        // DISTINCT ON clause (PostgreSQL)
4169        if let Some(distinct_on) = &select.distinct_on {
4170            self.write_space();
4171            self.write_keyword("ON");
4172            self.write(" (");
4173            for (i, expr) in distinct_on.iter().enumerate() {
4174                if i > 0 {
4175                    self.write(", ");
4176                }
4177                self.generate_expression(expr)?;
4178            }
4179            self.write(")");
4180        }
4181
4182        // MySQL operation modifiers (HIGH_PRIORITY, STRAIGHT_JOIN, SQL_CALC_FOUND_ROWS, etc.)
4183        for modifier in &select.operation_modifiers {
4184            self.write_space();
4185            self.write_keyword(modifier);
4186        }
4187
4188        // BigQuery SELECT AS STRUCT / SELECT AS VALUE
4189        if let Some(kind) = &select.kind {
4190            self.write_space();
4191            self.write_keyword("AS");
4192            self.write_space();
4193            self.write_keyword(kind);
4194        }
4195
4196        // Expressions (only if there are any)
4197        if !select.expressions.is_empty() {
4198            if self.config.pretty {
4199                self.write_newline();
4200                self.indent_level += 1;
4201            } else {
4202                self.write_space();
4203            }
4204        }
4205
4206        for (i, expr) in select.expressions.iter().enumerate() {
4207            if i > 0 {
4208                self.write(",");
4209                if self.config.pretty {
4210                    self.write_newline();
4211                } else {
4212                    self.write_space();
4213                }
4214            }
4215            if self.config.pretty {
4216                self.write_indent();
4217            }
4218            self.generate_expression(expr)?;
4219        }
4220
4221        if self.config.pretty && !select.expressions.is_empty() {
4222            self.indent_level -= 1;
4223        }
4224
4225        // Redshift-style EXCLUDE clause at the end of the projection list
4226        // For Redshift dialect: append EXCLUDE (col1, col2) after the expressions
4227        // For other dialects (DuckDB, Snowflake): this is handled by wrapping in a derived table
4228        // (done after the full select is generated below)
4229        if let Some(exclude) = &select.exclude {
4230            if !exclude.is_empty() && matches!(self.config.dialect, Some(DialectType::Redshift)) {
4231                self.write_space();
4232                self.write_keyword("EXCLUDE");
4233                self.write(" (");
4234                for (i, col) in exclude.iter().enumerate() {
4235                    if i > 0 {
4236                        self.write(", ");
4237                    }
4238                    self.generate_expression(col)?;
4239                }
4240                self.write(")");
4241            }
4242        }
4243
4244        // INTO clause (SELECT ... INTO table_name)
4245        // Also handles Oracle PL/SQL: BULK COLLECT INTO v1, v2, ...
4246        if let Some(into) = &select.into {
4247            if self.config.pretty {
4248                self.write_newline();
4249                self.write_indent();
4250            } else {
4251                self.write_space();
4252            }
4253            if into.bulk_collect {
4254                self.write_keyword("BULK COLLECT INTO");
4255            } else {
4256                self.write_keyword("INTO");
4257            }
4258            if into.temporary {
4259                self.write_space();
4260                self.write_keyword("TEMPORARY");
4261            }
4262            if into.unlogged {
4263                self.write_space();
4264                self.write_keyword("UNLOGGED");
4265            }
4266            self.write_space();
4267            // If we have multiple expressions, output them comma-separated
4268            if !into.expressions.is_empty() {
4269                for (i, expr) in into.expressions.iter().enumerate() {
4270                    if i > 0 {
4271                        self.write(", ");
4272                    }
4273                    self.generate_expression(expr)?;
4274                }
4275            } else {
4276                self.generate_expression(&into.this)?;
4277            }
4278        }
4279
4280        // FROM clause
4281        if let Some(from) = &select.from {
4282            if self.config.pretty {
4283                self.write_newline();
4284                self.write_indent();
4285            } else {
4286                self.write_space();
4287            }
4288            self.write_keyword("FROM");
4289            self.write_space();
4290
4291            // BigQuery, Hive, Spark, Databricks, SQLite, and ClickHouse prefer explicit CROSS JOIN over comma syntax for multiple tables
4292            // But keep commas when TABLESAMPLE is present (Spark/Hive handle TABLESAMPLE differently with commas)
4293            // Also keep commas when the source dialect is Generic/None and target is one of these dialects
4294            // (Python sqlglot: the Hive/Spark parser marks comma joins as CROSS, but Generic parser keeps them implicit)
4295            let has_tablesample = from
4296                .expressions
4297                .iter()
4298                .any(|e| matches!(e, Expression::TableSample(_)));
4299            let is_cross_join_dialect = matches!(
4300                self.config.dialect,
4301                Some(DialectType::BigQuery)
4302                    | Some(DialectType::Hive)
4303                    | Some(DialectType::Spark)
4304                    | Some(DialectType::Databricks)
4305                    | Some(DialectType::SQLite)
4306                    | Some(DialectType::ClickHouse)
4307            );
4308            // Skip CROSS JOIN conversion when source is Generic/None and target is a CROSS JOIN dialect
4309            // This matches Python sqlglot where comma-to-CROSS-JOIN is done in the dialect's parser, not generator
4310            let source_is_same_as_target = self.config.source_dialect.is_some()
4311                && self.config.source_dialect == self.config.dialect;
4312            let source_is_cross_join_dialect = matches!(
4313                self.config.source_dialect,
4314                Some(DialectType::BigQuery)
4315                    | Some(DialectType::Hive)
4316                    | Some(DialectType::Spark)
4317                    | Some(DialectType::Databricks)
4318                    | Some(DialectType::SQLite)
4319                    | Some(DialectType::ClickHouse)
4320            );
4321            let use_cross_join = !has_tablesample
4322                && is_cross_join_dialect
4323                && (source_is_same_as_target
4324                    || source_is_cross_join_dialect
4325                    || self.config.source_dialect.is_none());
4326
4327            // Snowflake wraps standalone VALUES in FROM clause with parentheses
4328            let wrap_values_in_parens = matches!(self.config.dialect, Some(DialectType::Snowflake));
4329
4330            for (i, expr) in from.expressions.iter().enumerate() {
4331                if i > 0 {
4332                    if use_cross_join {
4333                        self.write(" CROSS JOIN ");
4334                    } else {
4335                        self.write(", ");
4336                    }
4337                }
4338                if wrap_values_in_parens && matches!(expr, Expression::Values(_)) {
4339                    self.write("(");
4340                    self.generate_expression(expr)?;
4341                    self.write(")");
4342                } else {
4343                    self.generate_expression(expr)?;
4344                }
4345                // Output leading comments that were on the table name before FROM
4346                // (e.g., FROM \n/* comment */\n tbl PIVOT(...) -> ... PIVOT(...) /* comment */)
4347                let leading = Self::extract_table_leading_comments(expr);
4348                for comment in &leading {
4349                    self.write_space();
4350                    self.write_formatted_comment(comment);
4351                }
4352            }
4353        }
4354
4355        // JOINs - handle nested join structure for pretty printing
4356        // Deferred-condition joins "own" the non-deferred joins that follow them
4357        // until the next deferred join or end of list
4358        if self.config.pretty {
4359            self.generate_joins_with_nesting(&select.joins)?;
4360        } else {
4361            for join in &select.joins {
4362                self.generate_join(join)?;
4363            }
4364            // Output deferred ON/USING conditions (right-to-left, which is reverse order)
4365            for join in select.joins.iter().rev() {
4366                if join.deferred_condition {
4367                    self.generate_join_condition(join)?;
4368                }
4369            }
4370        }
4371
4372        // LATERAL VIEW clauses (Hive/Spark)
4373        for (lv_idx, lateral_view) in select.lateral_views.iter().enumerate() {
4374            self.generate_lateral_view(lateral_view, lv_idx)?;
4375        }
4376
4377        // PREWHERE (ClickHouse)
4378        if let Some(prewhere) = &select.prewhere {
4379            self.write_clause_condition("PREWHERE", prewhere)?;
4380        }
4381
4382        // WHERE
4383        if let Some(where_clause) = &select.where_clause {
4384            self.write_clause_condition("WHERE", &where_clause.this)?;
4385        }
4386
4387        // CONNECT BY (Oracle hierarchical queries)
4388        if let Some(connect) = &select.connect {
4389            self.generate_connect(connect)?;
4390        }
4391
4392        // GROUP BY
4393        if let Some(group_by) = &select.group_by {
4394            if self.config.pretty {
4395                // Output leading comments on their own lines before GROUP BY
4396                for comment in &group_by.comments {
4397                    self.write_newline();
4398                    self.write_indent();
4399                    self.write_formatted_comment(comment);
4400                }
4401                self.write_newline();
4402                self.write_indent();
4403            } else {
4404                self.write_space();
4405                // In non-pretty mode, output comments inline
4406                for comment in &group_by.comments {
4407                    self.write_formatted_comment(comment);
4408                    self.write_space();
4409                }
4410            }
4411            self.write_keyword("GROUP BY");
4412            // Handle ALL/DISTINCT modifier: Some(true) = ALL, Some(false) = DISTINCT
4413            match group_by.all {
4414                Some(true) => {
4415                    self.write_space();
4416                    self.write_keyword("ALL");
4417                }
4418                Some(false) => {
4419                    self.write_space();
4420                    self.write_keyword("DISTINCT");
4421                }
4422                None => {}
4423            }
4424            if !group_by.expressions.is_empty() {
4425                // Check for trailing WITH CUBE or WITH ROLLUP (Hive/MySQL syntax)
4426                // These are represented as Cube/Rollup expressions with empty expressions at the end
4427                let mut trailing_cube = false;
4428                let mut trailing_rollup = false;
4429                let mut plain_expressions: Vec<&Expression> = Vec::new();
4430                let mut grouping_sets_expressions: Vec<&Expression> = Vec::new();
4431                let mut cube_expressions: Vec<&Expression> = Vec::new();
4432                let mut rollup_expressions: Vec<&Expression> = Vec::new();
4433
4434                for expr in &group_by.expressions {
4435                    match expr {
4436                        Expression::Cube(c) if c.expressions.is_empty() => {
4437                            trailing_cube = true;
4438                        }
4439                        Expression::Rollup(r) if r.expressions.is_empty() => {
4440                            trailing_rollup = true;
4441                        }
4442                        Expression::Function(f) if f.name == "CUBE" => {
4443                            cube_expressions.push(expr);
4444                        }
4445                        Expression::Function(f) if f.name == "ROLLUP" => {
4446                            rollup_expressions.push(expr);
4447                        }
4448                        Expression::Function(f) if f.name == "GROUPING SETS" => {
4449                            grouping_sets_expressions.push(expr);
4450                        }
4451                        _ => {
4452                            plain_expressions.push(expr);
4453                        }
4454                    }
4455                }
4456
4457                // Reorder: plain expressions first, then GROUPING SETS, CUBE, ROLLUP
4458                let mut regular_expressions: Vec<&Expression> = Vec::new();
4459                regular_expressions.extend(plain_expressions);
4460                regular_expressions.extend(grouping_sets_expressions);
4461                regular_expressions.extend(cube_expressions);
4462                regular_expressions.extend(rollup_expressions);
4463
4464                if self.config.pretty {
4465                    self.write_newline();
4466                    self.indent_level += 1;
4467                    self.write_indent();
4468                } else {
4469                    self.write_space();
4470                }
4471
4472                for (i, expr) in regular_expressions.iter().enumerate() {
4473                    if i > 0 {
4474                        if self.config.pretty {
4475                            self.write(",");
4476                            self.write_newline();
4477                            self.write_indent();
4478                        } else {
4479                            self.write(", ");
4480                        }
4481                    }
4482                    self.generate_expression(expr)?;
4483                }
4484
4485                if self.config.pretty {
4486                    self.indent_level -= 1;
4487                }
4488
4489                // Output trailing WITH CUBE or WITH ROLLUP
4490                if trailing_cube {
4491                    self.write_space();
4492                    self.write_keyword("WITH CUBE");
4493                } else if trailing_rollup {
4494                    self.write_space();
4495                    self.write_keyword("WITH ROLLUP");
4496                }
4497            }
4498
4499            // ClickHouse: WITH TOTALS
4500            if group_by.totals {
4501                self.write_space();
4502                self.write_keyword("WITH TOTALS");
4503            }
4504        }
4505
4506        // HAVING
4507        if let Some(having) = &select.having {
4508            if self.config.pretty {
4509                // Output leading comments on their own lines before HAVING
4510                for comment in &having.comments {
4511                    self.write_newline();
4512                    self.write_indent();
4513                    self.write_formatted_comment(comment);
4514                }
4515            } else {
4516                for comment in &having.comments {
4517                    self.write_space();
4518                    self.write_formatted_comment(comment);
4519                }
4520            }
4521            self.write_clause_condition("HAVING", &having.this)?;
4522        }
4523
4524        // QUALIFY and WINDOW clause ordering depends on input SQL
4525        if select.qualify_after_window {
4526            // WINDOW before QUALIFY (DuckDB style)
4527            if let Some(windows) = &select.windows {
4528                self.write_window_clause(windows)?;
4529            }
4530            if let Some(qualify) = &select.qualify {
4531                self.write_clause_condition("QUALIFY", &qualify.this)?;
4532            }
4533        } else {
4534            // QUALIFY before WINDOW (Snowflake/BigQuery default)
4535            if let Some(qualify) = &select.qualify {
4536                self.write_clause_condition("QUALIFY", &qualify.this)?;
4537            }
4538            if let Some(windows) = &select.windows {
4539                self.write_window_clause(windows)?;
4540            }
4541        }
4542
4543        // DISTRIBUTE BY (Hive/Spark)
4544        if let Some(distribute_by) = &select.distribute_by {
4545            self.write_clause_expressions("DISTRIBUTE BY", &distribute_by.expressions)?;
4546        }
4547
4548        // CLUSTER BY (Hive/Spark)
4549        if let Some(cluster_by) = &select.cluster_by {
4550            self.write_order_clause("CLUSTER BY", &cluster_by.expressions)?;
4551        }
4552
4553        // SORT BY (Hive/Spark - comes before ORDER BY)
4554        if let Some(sort_by) = &select.sort_by {
4555            self.write_order_clause("SORT BY", &sort_by.expressions)?;
4556        }
4557
4558        // ORDER BY (or ORDER SIBLINGS BY for Oracle hierarchical queries)
4559        if let Some(order_by) = &select.order_by {
4560            if self.config.pretty {
4561                // Output leading comments on their own lines before ORDER BY
4562                for comment in &order_by.comments {
4563                    self.write_newline();
4564                    self.write_indent();
4565                    self.write_formatted_comment(comment);
4566                }
4567            } else {
4568                for comment in &order_by.comments {
4569                    self.write_space();
4570                    self.write_formatted_comment(comment);
4571                }
4572            }
4573            let keyword = if order_by.siblings {
4574                "ORDER SIBLINGS BY"
4575            } else {
4576                "ORDER BY"
4577            };
4578            self.write_order_clause(keyword, &order_by.expressions)?;
4579        }
4580
4581        // TSQL: FETCH requires ORDER BY. If there's a FETCH but no ORDER BY, add ORDER BY (SELECT NULL) OFFSET 0 ROWS
4582        if select.order_by.is_none()
4583            && select.fetch.is_some()
4584            && matches!(
4585                self.config.dialect,
4586                Some(DialectType::TSQL) | Some(DialectType::Fabric)
4587            )
4588        {
4589            if self.config.pretty {
4590                self.write_newline();
4591                self.write_indent();
4592            } else {
4593                self.write_space();
4594            }
4595            self.write_keyword("ORDER BY (SELECT NULL) OFFSET 0 ROWS");
4596        }
4597
4598        // LIMIT and OFFSET
4599        // PostgreSQL and others use: LIMIT count OFFSET offset
4600        // SQL Server uses: OFFSET ... FETCH (no LIMIT)
4601        // Presto/Trino uses: OFFSET n LIMIT m (offset before limit)
4602        let is_presto_like = matches!(
4603            self.config.dialect,
4604            Some(DialectType::Presto) | Some(DialectType::Trino)
4605        );
4606
4607        if is_presto_like && select.offset.is_some() {
4608            // Presto/Trino syntax: OFFSET n LIMIT m (offset comes first)
4609            if let Some(offset) = &select.offset {
4610                if self.config.pretty {
4611                    self.write_newline();
4612                    self.write_indent();
4613                } else {
4614                    self.write_space();
4615                }
4616                self.write_keyword("OFFSET");
4617                self.write_space();
4618                self.write_limit_expr(&offset.this)?;
4619                if offset.rows == Some(true) {
4620                    self.write_space();
4621                    self.write_keyword("ROWS");
4622                }
4623            }
4624            if let Some(limit) = &select.limit {
4625                if self.config.pretty {
4626                    self.write_newline();
4627                    self.write_indent();
4628                } else {
4629                    self.write_space();
4630                }
4631                self.write_keyword("LIMIT");
4632                self.write_space();
4633                self.write_limit_expr(&limit.this)?;
4634                if limit.percent {
4635                    self.write_space();
4636                    self.write_keyword("PERCENT");
4637                }
4638                // Emit any comments that were captured from before the LIMIT keyword
4639                for comment in &limit.comments {
4640                    self.write(" ");
4641                    self.write_formatted_comment(comment);
4642                }
4643            }
4644        } else {
4645            // Check if FETCH will be converted to LIMIT (used for ordering)
4646            let fetch_as_limit = select.fetch.as_ref().map_or(false, |fetch| {
4647                !fetch.percent
4648                    && !fetch.with_ties
4649                    && fetch.count.is_some()
4650                    && matches!(
4651                        self.config.dialect,
4652                        Some(DialectType::Spark)
4653                            | Some(DialectType::Hive)
4654                            | Some(DialectType::DuckDB)
4655                            | Some(DialectType::SQLite)
4656                            | Some(DialectType::MySQL)
4657                            | Some(DialectType::BigQuery)
4658                            | Some(DialectType::Databricks)
4659                            | Some(DialectType::StarRocks)
4660                            | Some(DialectType::Doris)
4661                            | Some(DialectType::Athena)
4662                            | Some(DialectType::ClickHouse)
4663                            | Some(DialectType::Redshift)
4664                    )
4665            });
4666
4667            // Standard LIMIT clause (skip for SQL Server - we use TOP or OFFSET/FETCH instead)
4668            if let Some(limit) = &select.limit {
4669                // SQL Server uses TOP (no OFFSET) or OFFSET/FETCH (with OFFSET) instead of LIMIT
4670                if !matches!(self.config.dialect, Some(DialectType::TSQL)) {
4671                    if self.config.pretty {
4672                        self.write_newline();
4673                        self.write_indent();
4674                    } else {
4675                        self.write_space();
4676                    }
4677                    self.write_keyword("LIMIT");
4678                    self.write_space();
4679                    self.write_limit_expr(&limit.this)?;
4680                    if limit.percent {
4681                        self.write_space();
4682                        self.write_keyword("PERCENT");
4683                    }
4684                    // Emit any comments that were captured from before the LIMIT keyword
4685                    for comment in &limit.comments {
4686                        self.write(" ");
4687                        self.write_formatted_comment(comment);
4688                    }
4689                }
4690            }
4691
4692            // Convert TOP to LIMIT for non-TOP dialects
4693            if select.top.is_some() && !is_top_dialect && select.limit.is_none() {
4694                if let Some(top) = &select.top {
4695                    if !top.percent && !top.with_ties {
4696                        if self.config.pretty {
4697                            self.write_newline();
4698                            self.write_indent();
4699                        } else {
4700                            self.write_space();
4701                        }
4702                        self.write_keyword("LIMIT");
4703                        self.write_space();
4704                        self.generate_expression(&top.this)?;
4705                    }
4706                }
4707            }
4708
4709            // If FETCH will be converted to LIMIT and there's also OFFSET,
4710            // emit LIMIT from FETCH BEFORE the OFFSET
4711            if fetch_as_limit && select.offset.is_some() {
4712                if let Some(fetch) = &select.fetch {
4713                    if self.config.pretty {
4714                        self.write_newline();
4715                        self.write_indent();
4716                    } else {
4717                        self.write_space();
4718                    }
4719                    self.write_keyword("LIMIT");
4720                    self.write_space();
4721                    self.generate_expression(fetch.count.as_ref().unwrap())?;
4722                }
4723            }
4724
4725            // OFFSET
4726            // In SQL Server, OFFSET requires ORDER BY and uses different syntax
4727            // OFFSET x ROWS FETCH NEXT y ROWS ONLY
4728            if let Some(offset) = &select.offset {
4729                if self.config.pretty {
4730                    self.write_newline();
4731                    self.write_indent();
4732                } else {
4733                    self.write_space();
4734                }
4735                if matches!(self.config.dialect, Some(DialectType::TSQL)) {
4736                    // SQL Server 2012+ OFFSET ... FETCH syntax
4737                    self.write_keyword("OFFSET");
4738                    self.write_space();
4739                    self.write_limit_expr(&offset.this)?;
4740                    self.write_space();
4741                    self.write_keyword("ROWS");
4742                    // If there was a LIMIT, use FETCH NEXT ... ROWS ONLY
4743                    if let Some(limit) = &select.limit {
4744                        self.write_space();
4745                        self.write_keyword("FETCH NEXT");
4746                        self.write_space();
4747                        self.write_limit_expr(&limit.this)?;
4748                        self.write_space();
4749                        self.write_keyword("ROWS ONLY");
4750                    }
4751                } else {
4752                    self.write_keyword("OFFSET");
4753                    self.write_space();
4754                    self.write_limit_expr(&offset.this)?;
4755                    // Output ROWS keyword if it was in the original SQL
4756                    if offset.rows == Some(true) {
4757                        self.write_space();
4758                        self.write_keyword("ROWS");
4759                    }
4760                }
4761            }
4762        }
4763
4764        // ClickHouse LIMIT BY clause (after LIMIT/OFFSET)
4765        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
4766            if let Some(limit_by) = &select.limit_by {
4767                if !limit_by.is_empty() {
4768                    self.write_space();
4769                    self.write_keyword("BY");
4770                    self.write_space();
4771                    for (i, expr) in limit_by.iter().enumerate() {
4772                        if i > 0 {
4773                            self.write(", ");
4774                        }
4775                        self.generate_expression(expr)?;
4776                    }
4777                }
4778            }
4779        }
4780
4781        // ClickHouse SETTINGS and FORMAT modifiers (after LIMIT/OFFSET)
4782        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
4783            if let Some(settings) = &select.settings {
4784                if self.config.pretty {
4785                    self.write_newline();
4786                    self.write_indent();
4787                } else {
4788                    self.write_space();
4789                }
4790                self.write_keyword("SETTINGS");
4791                self.write_space();
4792                for (i, expr) in settings.iter().enumerate() {
4793                    if i > 0 {
4794                        self.write(", ");
4795                    }
4796                    self.generate_expression(expr)?;
4797                }
4798            }
4799
4800            if let Some(format_expr) = &select.format {
4801                if self.config.pretty {
4802                    self.write_newline();
4803                    self.write_indent();
4804                } else {
4805                    self.write_space();
4806                }
4807                self.write_keyword("FORMAT");
4808                self.write_space();
4809                self.generate_expression(format_expr)?;
4810            }
4811        }
4812
4813        // FETCH FIRST/NEXT
4814        if let Some(fetch) = &select.fetch {
4815            // Check if we already emitted LIMIT from FETCH before OFFSET
4816            let fetch_already_as_limit = select.offset.is_some()
4817                && !fetch.percent
4818                && !fetch.with_ties
4819                && fetch.count.is_some()
4820                && matches!(
4821                    self.config.dialect,
4822                    Some(DialectType::Spark)
4823                        | Some(DialectType::Hive)
4824                        | Some(DialectType::DuckDB)
4825                        | Some(DialectType::SQLite)
4826                        | Some(DialectType::MySQL)
4827                        | Some(DialectType::BigQuery)
4828                        | Some(DialectType::Databricks)
4829                        | Some(DialectType::StarRocks)
4830                        | Some(DialectType::Doris)
4831                        | Some(DialectType::Athena)
4832                        | Some(DialectType::ClickHouse)
4833                        | Some(DialectType::Redshift)
4834                );
4835
4836            if fetch_already_as_limit {
4837                // Already emitted as LIMIT before OFFSET, skip
4838            } else {
4839                if self.config.pretty {
4840                    self.write_newline();
4841                    self.write_indent();
4842                } else {
4843                    self.write_space();
4844                }
4845
4846                // Convert FETCH to LIMIT for dialects that prefer LIMIT syntax
4847                let use_limit = !fetch.percent
4848                    && !fetch.with_ties
4849                    && fetch.count.is_some()
4850                    && matches!(
4851                        self.config.dialect,
4852                        Some(DialectType::Spark)
4853                            | Some(DialectType::Hive)
4854                            | Some(DialectType::DuckDB)
4855                            | Some(DialectType::SQLite)
4856                            | Some(DialectType::MySQL)
4857                            | Some(DialectType::BigQuery)
4858                            | Some(DialectType::Databricks)
4859                            | Some(DialectType::StarRocks)
4860                            | Some(DialectType::Doris)
4861                            | Some(DialectType::Athena)
4862                            | Some(DialectType::ClickHouse)
4863                            | Some(DialectType::Redshift)
4864                    );
4865
4866                if use_limit {
4867                    self.write_keyword("LIMIT");
4868                    self.write_space();
4869                    self.generate_expression(fetch.count.as_ref().unwrap())?;
4870                } else {
4871                    self.write_keyword("FETCH");
4872                    self.write_space();
4873                    self.write_keyword(&fetch.direction);
4874                    if let Some(ref count) = fetch.count {
4875                        self.write_space();
4876                        self.generate_expression(count)?;
4877                    }
4878                    if fetch.percent {
4879                        self.write_space();
4880                        self.write_keyword("PERCENT");
4881                    }
4882                    if fetch.rows {
4883                        self.write_space();
4884                        self.write_keyword("ROWS");
4885                    }
4886                    if fetch.with_ties {
4887                        self.write_space();
4888                        self.write_keyword("WITH TIES");
4889                    } else {
4890                        self.write_space();
4891                        self.write_keyword("ONLY");
4892                    }
4893                }
4894            } // close fetch_already_as_limit else
4895        }
4896
4897        // SAMPLE / TABLESAMPLE
4898        if let Some(sample) = &select.sample {
4899            use crate::dialects::DialectType;
4900            if self.config.pretty {
4901                self.write_newline();
4902            } else {
4903                self.write_space();
4904            }
4905
4906            if sample.is_using_sample {
4907                // DuckDB USING SAMPLE: METHOD (size UNIT) [REPEATABLE (seed)]
4908                self.write_keyword("USING SAMPLE");
4909                self.generate_sample_body(sample)?;
4910            } else {
4911                self.write_keyword("TABLESAMPLE");
4912
4913                // Snowflake defaults to BERNOULLI when no explicit method is given
4914                let snowflake_bernoulli =
4915                    matches!(self.config.dialect, Some(DialectType::Snowflake))
4916                        && !sample.explicit_method;
4917                if snowflake_bernoulli {
4918                    self.write_space();
4919                    self.write_keyword("BERNOULLI");
4920                }
4921
4922                // Handle BUCKET sampling: TABLESAMPLE (BUCKET 1 OUT OF 5 ON x)
4923                if matches!(sample.method, SampleMethod::Bucket) {
4924                    self.write_space();
4925                    self.write("(");
4926                    self.write_keyword("BUCKET");
4927                    self.write_space();
4928                    if let Some(ref num) = sample.bucket_numerator {
4929                        self.generate_expression(num)?;
4930                    }
4931                    self.write_space();
4932                    self.write_keyword("OUT OF");
4933                    self.write_space();
4934                    if let Some(ref denom) = sample.bucket_denominator {
4935                        self.generate_expression(denom)?;
4936                    }
4937                    if let Some(ref field) = sample.bucket_field {
4938                        self.write_space();
4939                        self.write_keyword("ON");
4940                        self.write_space();
4941                        self.generate_expression(field)?;
4942                    }
4943                    self.write(")");
4944                } else if sample.unit_after_size {
4945                    // Syntax: TABLESAMPLE [METHOD] (size ROWS) or TABLESAMPLE [METHOD] (size PERCENT)
4946                    if sample.explicit_method && sample.method_before_size {
4947                        self.write_space();
4948                        match sample.method {
4949                            SampleMethod::Bernoulli => self.write_keyword("BERNOULLI"),
4950                            SampleMethod::System => self.write_keyword("SYSTEM"),
4951                            SampleMethod::Block => self.write_keyword("BLOCK"),
4952                            SampleMethod::Row => self.write_keyword("ROW"),
4953                            SampleMethod::Reservoir => self.write_keyword("RESERVOIR"),
4954                            _ => {}
4955                        }
4956                    }
4957                    self.write(" (");
4958                    self.generate_expression(&sample.size)?;
4959                    self.write_space();
4960                    match sample.method {
4961                        SampleMethod::Percent => self.write_keyword("PERCENT"),
4962                        SampleMethod::Row => self.write_keyword("ROWS"),
4963                        SampleMethod::Reservoir => self.write_keyword("ROWS"),
4964                        _ => {
4965                            self.write_keyword("PERCENT");
4966                        }
4967                    }
4968                    self.write(")");
4969                } else {
4970                    // Syntax: TABLESAMPLE METHOD (size)
4971                    self.write_space();
4972                    match sample.method {
4973                        SampleMethod::Bernoulli => self.write_keyword("BERNOULLI"),
4974                        SampleMethod::System => self.write_keyword("SYSTEM"),
4975                        SampleMethod::Block => self.write_keyword("BLOCK"),
4976                        SampleMethod::Row => self.write_keyword("ROW"),
4977                        SampleMethod::Percent => self.write_keyword("BERNOULLI"),
4978                        SampleMethod::Bucket => {}
4979                        SampleMethod::Reservoir => self.write_keyword("RESERVOIR"),
4980                    }
4981                    self.write(" (");
4982                    self.generate_expression(&sample.size)?;
4983                    if matches!(sample.method, SampleMethod::Percent) {
4984                        self.write_space();
4985                        self.write_keyword("PERCENT");
4986                    }
4987                    self.write(")");
4988                }
4989            }
4990
4991            if let Some(seed) = &sample.seed {
4992                self.write_space();
4993                // Databricks/Spark use REPEATABLE, not SEED
4994                let use_seed = sample.use_seed_keyword
4995                    && !matches!(
4996                        self.config.dialect,
4997                        Some(crate::dialects::DialectType::Databricks)
4998                            | Some(crate::dialects::DialectType::Spark)
4999                    );
5000                if use_seed {
5001                    self.write_keyword("SEED");
5002                } else {
5003                    self.write_keyword("REPEATABLE");
5004                }
5005                self.write(" (");
5006                self.generate_expression(seed)?;
5007                self.write(")");
5008            }
5009        }
5010
5011        // FOR UPDATE/SHARE locks
5012        // Skip locking clauses for dialects that don't support them
5013        if self.config.locking_reads_supported {
5014            for lock in &select.locks {
5015                if self.config.pretty {
5016                    self.write_newline();
5017                    self.write_indent();
5018                } else {
5019                    self.write_space();
5020                }
5021                self.generate_lock(lock)?;
5022            }
5023        }
5024
5025        // FOR XML clause (T-SQL)
5026        if !select.for_xml.is_empty() {
5027            if self.config.pretty {
5028                self.write_newline();
5029                self.write_indent();
5030            } else {
5031                self.write_space();
5032            }
5033            self.write_keyword("FOR XML");
5034            for (i, opt) in select.for_xml.iter().enumerate() {
5035                if self.config.pretty {
5036                    if i > 0 {
5037                        self.write(",");
5038                    }
5039                    self.write_newline();
5040                    self.write_indent();
5041                    self.write("  "); // extra indent for options
5042                } else {
5043                    if i > 0 {
5044                        self.write(",");
5045                    }
5046                    self.write_space();
5047                }
5048                self.generate_for_xml_option(opt)?;
5049            }
5050        }
5051
5052        // TSQL: OPTION clause
5053        if let Some(ref option) = select.option {
5054            if matches!(
5055                self.config.dialect,
5056                Some(crate::dialects::DialectType::TSQL)
5057                    | Some(crate::dialects::DialectType::Fabric)
5058            ) {
5059                self.write_space();
5060                self.write(option);
5061            }
5062        }
5063
5064        Ok(())
5065    }
5066
5067    /// Generate a single FOR XML option
5068    fn generate_for_xml_option(&mut self, opt: &Expression) -> Result<()> {
5069        match opt {
5070            Expression::QueryOption(qo) => {
5071                // Extract the option name from Var
5072                if let Expression::Var(var) = &*qo.this {
5073                    self.write(&var.this);
5074                } else {
5075                    self.generate_expression(&qo.this)?;
5076                }
5077                // If there's an expression (like PATH('element')), output it in parens
5078                if let Some(expr) = &qo.expression {
5079                    self.write("(");
5080                    self.generate_expression(expr)?;
5081                    self.write(")");
5082                }
5083            }
5084            _ => {
5085                self.generate_expression(opt)?;
5086            }
5087        }
5088        Ok(())
5089    }
5090
5091    fn generate_with(&mut self, with: &With) -> Result<()> {
5092        use crate::dialects::DialectType;
5093
5094        // Output leading comments before WITH
5095        for comment in &with.leading_comments {
5096            self.write_formatted_comment(comment);
5097            self.write(" ");
5098        }
5099        self.write_keyword("WITH");
5100        if with.recursive && self.config.cte_recursive_keyword_required {
5101            self.write_space();
5102            self.write_keyword("RECURSIVE");
5103        }
5104        self.write_space();
5105
5106        // BigQuery doesn't support column aliases in CTE definitions
5107        let skip_cte_columns = matches!(self.config.dialect, Some(DialectType::BigQuery));
5108
5109        for (i, cte) in with.ctes.iter().enumerate() {
5110            if i > 0 {
5111                self.write(",");
5112                if self.config.pretty {
5113                    self.write_space();
5114                } else {
5115                    self.write(" ");
5116                }
5117            }
5118            if matches!(self.config.dialect, Some(DialectType::ClickHouse)) && !cte.alias_first {
5119                self.generate_expression(&cte.this)?;
5120                self.write_space();
5121                self.write_keyword("AS");
5122                self.write_space();
5123                self.generate_identifier(&cte.alias)?;
5124                continue;
5125            }
5126            self.generate_identifier(&cte.alias)?;
5127            // Output CTE comments after alias name, before AS
5128            for comment in &cte.comments {
5129                self.write_space();
5130                self.write_formatted_comment(comment);
5131            }
5132            if !cte.columns.is_empty() && !skip_cte_columns {
5133                self.write("(");
5134                for (j, col) in cte.columns.iter().enumerate() {
5135                    if j > 0 {
5136                        self.write(", ");
5137                    }
5138                    self.generate_identifier(col)?;
5139                }
5140                self.write(")");
5141            }
5142            // USING KEY (columns) for DuckDB recursive CTEs
5143            if !cte.key_expressions.is_empty() {
5144                self.write_space();
5145                self.write_keyword("USING KEY");
5146                self.write(" (");
5147                for (i, key) in cte.key_expressions.iter().enumerate() {
5148                    if i > 0 {
5149                        self.write(", ");
5150                    }
5151                    self.generate_identifier(key)?;
5152                }
5153                self.write(")");
5154            }
5155            self.write_space();
5156            self.write_keyword("AS");
5157            // MATERIALIZED / NOT MATERIALIZED
5158            if let Some(materialized) = cte.materialized {
5159                self.write_space();
5160                if materialized {
5161                    self.write_keyword("MATERIALIZED");
5162                } else {
5163                    self.write_keyword("NOT MATERIALIZED");
5164                }
5165            }
5166            self.write(" (");
5167            if self.config.pretty {
5168                self.write_newline();
5169                self.indent_level += 1;
5170                self.write_indent();
5171            }
5172            // For Spark/Databricks, VALUES in a CTE must be wrapped with SELECT * FROM
5173            // e.g., WITH t AS (VALUES ('foo_val') AS t(foo1)) -> WITH t AS (SELECT * FROM VALUES ('foo_val') AS t(foo1))
5174            let wrap_values_in_select = matches!(
5175                self.config.dialect,
5176                Some(DialectType::Spark) | Some(DialectType::Databricks)
5177            ) && matches!(&cte.this, Expression::Values(_));
5178
5179            if wrap_values_in_select {
5180                self.write_keyword("SELECT");
5181                self.write(" * ");
5182                self.write_keyword("FROM");
5183                self.write_space();
5184            }
5185            self.generate_expression(&cte.this)?;
5186            if self.config.pretty {
5187                self.write_newline();
5188                self.indent_level -= 1;
5189                self.write_indent();
5190            }
5191            self.write(")");
5192        }
5193
5194        // Generate SEARCH/CYCLE clause if present
5195        if let Some(search) = &with.search {
5196            self.write_space();
5197            self.generate_expression(search)?;
5198        }
5199
5200        Ok(())
5201    }
5202
5203    /// Generate joins with proper nesting structure for pretty printing.
5204    /// Deferred-condition joins "own" the non-deferred joins that follow them
5205    /// within the same nesting_group.
5206    fn generate_joins_with_nesting(&mut self, joins: &[Join]) -> Result<()> {
5207        let mut i = 0;
5208        while i < joins.len() {
5209            if joins[i].deferred_condition {
5210                let parent_group = joins[i].nesting_group;
5211
5212                // This join owns the following non-deferred joins in the same nesting_group
5213                // First output the join keyword and table (without condition)
5214                self.generate_join_without_condition(&joins[i])?;
5215
5216                // Find the range of child joins: same nesting_group and not deferred
5217                let child_start = i + 1;
5218                let mut child_end = child_start;
5219                while child_end < joins.len()
5220                    && !joins[child_end].deferred_condition
5221                    && joins[child_end].nesting_group == parent_group
5222                {
5223                    child_end += 1;
5224                }
5225
5226                // Output child joins with extra indentation
5227                if child_start < child_end {
5228                    self.indent_level += 1;
5229                    for j in child_start..child_end {
5230                        self.generate_join(&joins[j])?;
5231                    }
5232                    self.indent_level -= 1;
5233                }
5234
5235                // Output the deferred condition at the parent level
5236                self.generate_join_condition(&joins[i])?;
5237
5238                i = child_end;
5239            } else {
5240                // Regular join (no nesting)
5241                self.generate_join(&joins[i])?;
5242                i += 1;
5243            }
5244        }
5245        Ok(())
5246    }
5247
5248    /// Generate a join's keyword and table reference, but not its ON/USING condition.
5249    /// Used for deferred-condition joins where the condition is output after child joins.
5250    fn generate_join_without_condition(&mut self, join: &Join) -> Result<()> {
5251        // Save and temporarily clear the condition to prevent generate_join from outputting it
5252        // We achieve this by creating a modified copy
5253        let mut join_copy = join.clone();
5254        join_copy.on = None;
5255        join_copy.using = Vec::new();
5256        join_copy.deferred_condition = false;
5257        self.generate_join(&join_copy)
5258    }
5259
5260    fn generate_join(&mut self, join: &Join) -> Result<()> {
5261        // Implicit (comma) joins: output as ", table" instead of "CROSS JOIN table"
5262        if join.kind == JoinKind::Implicit {
5263            self.write(",");
5264            if self.config.pretty {
5265                self.write_newline();
5266                self.write_indent();
5267            } else {
5268                self.write_space();
5269            }
5270            self.generate_expression(&join.this)?;
5271            return Ok(());
5272        }
5273
5274        if self.config.pretty {
5275            self.write_newline();
5276            self.write_indent();
5277        } else {
5278            self.write_space();
5279        }
5280
5281        // Helper: format hint suffix (e.g., " LOOP" or "")
5282        // Only include join hints for dialects that support them
5283        let hint_str = if self.config.join_hints {
5284            join.join_hint
5285                .as_ref()
5286                .map(|h| format!(" {}", h))
5287                .unwrap_or_default()
5288        } else {
5289            String::new()
5290        };
5291
5292        let clickhouse_join_keyword =
5293            if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
5294                if let Some(hint) = &join.join_hint {
5295                    let mut global = false;
5296                    let mut strictness: Option<&'static str> = None;
5297                    for part in hint.split_whitespace() {
5298                        if part.eq_ignore_ascii_case("GLOBAL") {
5299                            global = true;
5300                        } else if part.eq_ignore_ascii_case("ANY") {
5301                            strictness = Some("ANY");
5302                        } else if part.eq_ignore_ascii_case("ASOF") {
5303                            strictness = Some("ASOF");
5304                        } else if part.eq_ignore_ascii_case("SEMI") {
5305                            strictness = Some("SEMI");
5306                        } else if part.eq_ignore_ascii_case("ANTI") {
5307                            strictness = Some("ANTI");
5308                        }
5309                    }
5310
5311                    if global || strictness.is_some() {
5312                        let join_type = match join.kind {
5313                            JoinKind::Left => {
5314                                if join.use_outer_keyword {
5315                                    "LEFT OUTER"
5316                                } else if join.use_inner_keyword {
5317                                    "LEFT INNER"
5318                                } else {
5319                                    "LEFT"
5320                                }
5321                            }
5322                            JoinKind::Right => {
5323                                if join.use_outer_keyword {
5324                                    "RIGHT OUTER"
5325                                } else if join.use_inner_keyword {
5326                                    "RIGHT INNER"
5327                                } else {
5328                                    "RIGHT"
5329                                }
5330                            }
5331                            JoinKind::Full => {
5332                                if join.use_outer_keyword {
5333                                    "FULL OUTER"
5334                                } else {
5335                                    "FULL"
5336                                }
5337                            }
5338                            JoinKind::Inner => {
5339                                if join.use_inner_keyword {
5340                                    "INNER"
5341                                } else {
5342                                    ""
5343                                }
5344                            }
5345                            _ => "",
5346                        };
5347
5348                        let mut parts = Vec::new();
5349                        if global {
5350                            parts.push("GLOBAL");
5351                        }
5352                        if !join_type.is_empty() {
5353                            parts.push(join_type);
5354                        }
5355                        if let Some(strict) = strictness {
5356                            parts.push(strict);
5357                        }
5358                        parts.push("JOIN");
5359                        Some(parts.join(" "))
5360                    } else {
5361                        None
5362                    }
5363                } else {
5364                    None
5365                }
5366            } else {
5367                None
5368            };
5369
5370        // Output any comments associated with this join
5371        // In pretty mode, comments go on their own line before the join keyword
5372        // In non-pretty mode, comments go inline before the join keyword
5373        if !join.comments.is_empty() {
5374            if self.config.pretty {
5375                // In pretty mode, go back before the newline+indent we just wrote
5376                // and output comments on their own lines
5377                // We need to output comments BEFORE the join keyword on separate lines
5378                // Trim the trailing newline+indent we already wrote
5379                let trimmed = self.output.trim_end().len();
5380                self.output.truncate(trimmed);
5381                for comment in &join.comments {
5382                    self.write_newline();
5383                    self.write_indent();
5384                    self.write_formatted_comment(comment);
5385                }
5386                self.write_newline();
5387                self.write_indent();
5388            } else {
5389                for comment in &join.comments {
5390                    self.write_formatted_comment(comment);
5391                    self.write_space();
5392                }
5393            }
5394        }
5395
5396        let directed_str = if join.directed { " DIRECTED" } else { "" };
5397
5398        if let Some(keyword) = clickhouse_join_keyword {
5399            self.write_keyword(&keyword);
5400        } else {
5401            match join.kind {
5402                JoinKind::Inner => {
5403                    if join.use_inner_keyword {
5404                        if hint_str.is_empty() && directed_str.is_empty() {
5405                            self.write_keyword("INNER JOIN");
5406                        } else {
5407                            self.write_keyword("INNER");
5408                            if !hint_str.is_empty() {
5409                                self.write_keyword(&hint_str);
5410                            }
5411                            if !directed_str.is_empty() {
5412                                self.write_keyword(directed_str);
5413                            }
5414                            self.write_keyword(" JOIN");
5415                        }
5416                    } else {
5417                        if !hint_str.is_empty() {
5418                            self.write_keyword(hint_str.trim());
5419                            self.write_keyword(" ");
5420                        }
5421                        if !directed_str.is_empty() {
5422                            self.write_keyword("DIRECTED ");
5423                        }
5424                        self.write_keyword("JOIN");
5425                    }
5426                }
5427                JoinKind::Left => {
5428                    if join.use_outer_keyword {
5429                        if hint_str.is_empty() && directed_str.is_empty() {
5430                            self.write_keyword("LEFT OUTER JOIN");
5431                        } else {
5432                            self.write_keyword("LEFT OUTER");
5433                            if !hint_str.is_empty() {
5434                                self.write_keyword(&hint_str);
5435                            }
5436                            if !directed_str.is_empty() {
5437                                self.write_keyword(directed_str);
5438                            }
5439                            self.write_keyword(" JOIN");
5440                        }
5441                    } else if join.use_inner_keyword {
5442                        if hint_str.is_empty() && directed_str.is_empty() {
5443                            self.write_keyword("LEFT INNER JOIN");
5444                        } else {
5445                            self.write_keyword("LEFT INNER");
5446                            if !hint_str.is_empty() {
5447                                self.write_keyword(&hint_str);
5448                            }
5449                            if !directed_str.is_empty() {
5450                                self.write_keyword(directed_str);
5451                            }
5452                            self.write_keyword(" JOIN");
5453                        }
5454                    } else {
5455                        if hint_str.is_empty() && directed_str.is_empty() {
5456                            self.write_keyword("LEFT JOIN");
5457                        } else {
5458                            self.write_keyword("LEFT");
5459                            if !hint_str.is_empty() {
5460                                self.write_keyword(&hint_str);
5461                            }
5462                            if !directed_str.is_empty() {
5463                                self.write_keyword(directed_str);
5464                            }
5465                            self.write_keyword(" JOIN");
5466                        }
5467                    }
5468                }
5469                JoinKind::Right => {
5470                    if join.use_outer_keyword {
5471                        if hint_str.is_empty() && directed_str.is_empty() {
5472                            self.write_keyword("RIGHT OUTER JOIN");
5473                        } else {
5474                            self.write_keyword("RIGHT OUTER");
5475                            if !hint_str.is_empty() {
5476                                self.write_keyword(&hint_str);
5477                            }
5478                            if !directed_str.is_empty() {
5479                                self.write_keyword(directed_str);
5480                            }
5481                            self.write_keyword(" JOIN");
5482                        }
5483                    } else if join.use_inner_keyword {
5484                        if hint_str.is_empty() && directed_str.is_empty() {
5485                            self.write_keyword("RIGHT INNER JOIN");
5486                        } else {
5487                            self.write_keyword("RIGHT INNER");
5488                            if !hint_str.is_empty() {
5489                                self.write_keyword(&hint_str);
5490                            }
5491                            if !directed_str.is_empty() {
5492                                self.write_keyword(directed_str);
5493                            }
5494                            self.write_keyword(" JOIN");
5495                        }
5496                    } else {
5497                        if hint_str.is_empty() && directed_str.is_empty() {
5498                            self.write_keyword("RIGHT JOIN");
5499                        } else {
5500                            self.write_keyword("RIGHT");
5501                            if !hint_str.is_empty() {
5502                                self.write_keyword(&hint_str);
5503                            }
5504                            if !directed_str.is_empty() {
5505                                self.write_keyword(directed_str);
5506                            }
5507                            self.write_keyword(" JOIN");
5508                        }
5509                    }
5510                }
5511                JoinKind::Full => {
5512                    if join.use_outer_keyword {
5513                        if hint_str.is_empty() && directed_str.is_empty() {
5514                            self.write_keyword("FULL OUTER JOIN");
5515                        } else {
5516                            self.write_keyword("FULL OUTER");
5517                            if !hint_str.is_empty() {
5518                                self.write_keyword(&hint_str);
5519                            }
5520                            if !directed_str.is_empty() {
5521                                self.write_keyword(directed_str);
5522                            }
5523                            self.write_keyword(" JOIN");
5524                        }
5525                    } else {
5526                        if hint_str.is_empty() && directed_str.is_empty() {
5527                            self.write_keyword("FULL JOIN");
5528                        } else {
5529                            self.write_keyword("FULL");
5530                            if !hint_str.is_empty() {
5531                                self.write_keyword(&hint_str);
5532                            }
5533                            if !directed_str.is_empty() {
5534                                self.write_keyword(directed_str);
5535                            }
5536                            self.write_keyword(" JOIN");
5537                        }
5538                    }
5539                }
5540                JoinKind::Outer => {
5541                    if directed_str.is_empty() {
5542                        self.write_keyword("OUTER JOIN");
5543                    } else {
5544                        self.write_keyword("OUTER");
5545                        self.write_keyword(directed_str);
5546                        self.write_keyword(" JOIN");
5547                    }
5548                }
5549                JoinKind::Cross => {
5550                    if directed_str.is_empty() {
5551                        self.write_keyword("CROSS JOIN");
5552                    } else {
5553                        self.write_keyword("CROSS");
5554                        self.write_keyword(directed_str);
5555                        self.write_keyword(" JOIN");
5556                    }
5557                }
5558                JoinKind::Natural => {
5559                    if join.use_inner_keyword {
5560                        if directed_str.is_empty() {
5561                            self.write_keyword("NATURAL INNER JOIN");
5562                        } else {
5563                            self.write_keyword("NATURAL INNER");
5564                            self.write_keyword(directed_str);
5565                            self.write_keyword(" JOIN");
5566                        }
5567                    } else {
5568                        if directed_str.is_empty() {
5569                            self.write_keyword("NATURAL JOIN");
5570                        } else {
5571                            self.write_keyword("NATURAL");
5572                            self.write_keyword(directed_str);
5573                            self.write_keyword(" JOIN");
5574                        }
5575                    }
5576                }
5577                JoinKind::NaturalLeft => {
5578                    if join.use_outer_keyword {
5579                        if directed_str.is_empty() {
5580                            self.write_keyword("NATURAL LEFT OUTER JOIN");
5581                        } else {
5582                            self.write_keyword("NATURAL LEFT OUTER");
5583                            self.write_keyword(directed_str);
5584                            self.write_keyword(" JOIN");
5585                        }
5586                    } else {
5587                        if directed_str.is_empty() {
5588                            self.write_keyword("NATURAL LEFT JOIN");
5589                        } else {
5590                            self.write_keyword("NATURAL LEFT");
5591                            self.write_keyword(directed_str);
5592                            self.write_keyword(" JOIN");
5593                        }
5594                    }
5595                }
5596                JoinKind::NaturalRight => {
5597                    if join.use_outer_keyword {
5598                        if directed_str.is_empty() {
5599                            self.write_keyword("NATURAL RIGHT OUTER JOIN");
5600                        } else {
5601                            self.write_keyword("NATURAL RIGHT OUTER");
5602                            self.write_keyword(directed_str);
5603                            self.write_keyword(" JOIN");
5604                        }
5605                    } else {
5606                        if directed_str.is_empty() {
5607                            self.write_keyword("NATURAL RIGHT JOIN");
5608                        } else {
5609                            self.write_keyword("NATURAL RIGHT");
5610                            self.write_keyword(directed_str);
5611                            self.write_keyword(" JOIN");
5612                        }
5613                    }
5614                }
5615                JoinKind::NaturalFull => {
5616                    if join.use_outer_keyword {
5617                        if directed_str.is_empty() {
5618                            self.write_keyword("NATURAL FULL OUTER JOIN");
5619                        } else {
5620                            self.write_keyword("NATURAL FULL OUTER");
5621                            self.write_keyword(directed_str);
5622                            self.write_keyword(" JOIN");
5623                        }
5624                    } else {
5625                        if directed_str.is_empty() {
5626                            self.write_keyword("NATURAL FULL JOIN");
5627                        } else {
5628                            self.write_keyword("NATURAL FULL");
5629                            self.write_keyword(directed_str);
5630                            self.write_keyword(" JOIN");
5631                        }
5632                    }
5633                }
5634                JoinKind::Semi => self.write_keyword("SEMI JOIN"),
5635                JoinKind::Anti => self.write_keyword("ANTI JOIN"),
5636                JoinKind::LeftSemi => self.write_keyword("LEFT SEMI JOIN"),
5637                JoinKind::LeftAnti => self.write_keyword("LEFT ANTI JOIN"),
5638                JoinKind::RightSemi => self.write_keyword("RIGHT SEMI JOIN"),
5639                JoinKind::RightAnti => self.write_keyword("RIGHT ANTI JOIN"),
5640                JoinKind::CrossApply => {
5641                    // CROSS APPLY -> INNER JOIN LATERAL for non-TSQL dialects
5642                    if matches!(self.config.dialect, Some(DialectType::TSQL) | None) {
5643                        self.write_keyword("CROSS APPLY");
5644                    } else {
5645                        self.write_keyword("INNER JOIN LATERAL");
5646                    }
5647                }
5648                JoinKind::OuterApply => {
5649                    // OUTER APPLY -> LEFT JOIN LATERAL for non-TSQL dialects
5650                    if matches!(self.config.dialect, Some(DialectType::TSQL) | None) {
5651                        self.write_keyword("OUTER APPLY");
5652                    } else {
5653                        self.write_keyword("LEFT JOIN LATERAL");
5654                    }
5655                }
5656                JoinKind::AsOf => self.write_keyword("ASOF JOIN"),
5657                JoinKind::AsOfLeft => {
5658                    if join.use_outer_keyword {
5659                        self.write_keyword("ASOF LEFT OUTER JOIN");
5660                    } else {
5661                        self.write_keyword("ASOF LEFT JOIN");
5662                    }
5663                }
5664                JoinKind::AsOfRight => {
5665                    if join.use_outer_keyword {
5666                        self.write_keyword("ASOF RIGHT OUTER JOIN");
5667                    } else {
5668                        self.write_keyword("ASOF RIGHT JOIN");
5669                    }
5670                }
5671                JoinKind::Lateral => self.write_keyword("LATERAL JOIN"),
5672                JoinKind::LeftLateral => {
5673                    if join.use_outer_keyword {
5674                        self.write_keyword("LEFT OUTER LATERAL JOIN");
5675                    } else {
5676                        self.write_keyword("LEFT LATERAL JOIN");
5677                    }
5678                }
5679                JoinKind::Straight => self.write_keyword("STRAIGHT_JOIN"),
5680                JoinKind::Implicit => {
5681                    // BigQuery, Hive, Spark, and Databricks prefer explicit CROSS JOIN over comma syntax
5682                    // But only when source is the same dialect (identity) or source is another CROSS JOIN dialect
5683                    // When source is Generic, keep commas (Python sqlglot: parser marks joins, not generator)
5684                    use crate::dialects::DialectType;
5685                    let is_cj_dialect = matches!(
5686                        self.config.dialect,
5687                        Some(DialectType::BigQuery)
5688                            | Some(DialectType::Hive)
5689                            | Some(DialectType::Spark)
5690                            | Some(DialectType::Databricks)
5691                    );
5692                    let source_is_same = self.config.source_dialect.is_some()
5693                        && self.config.source_dialect == self.config.dialect;
5694                    let source_is_cj = matches!(
5695                        self.config.source_dialect,
5696                        Some(DialectType::BigQuery)
5697                            | Some(DialectType::Hive)
5698                            | Some(DialectType::Spark)
5699                            | Some(DialectType::Databricks)
5700                    );
5701                    if is_cj_dialect
5702                        && (source_is_same || source_is_cj || self.config.source_dialect.is_none())
5703                    {
5704                        self.write_keyword("CROSS JOIN");
5705                    } else {
5706                        // Implicit join uses comma: FROM a, b
5707                        // We already wrote a space before the match, so replace with comma
5708                        // by removing trailing space and writing ", "
5709                        self.output.truncate(self.output.trim_end().len());
5710                        self.write(",");
5711                    }
5712                }
5713                JoinKind::Array => self.write_keyword("ARRAY JOIN"),
5714                JoinKind::LeftArray => self.write_keyword("LEFT ARRAY JOIN"),
5715                JoinKind::Paste => self.write_keyword("PASTE JOIN"),
5716                JoinKind::Positional => self.write_keyword("POSITIONAL JOIN"),
5717            }
5718        }
5719
5720        // ARRAY JOIN items need comma-separated output (Tuple holds multiple items)
5721        if matches!(join.kind, JoinKind::Array | JoinKind::LeftArray) {
5722            self.write_space();
5723            match &join.this {
5724                Expression::Tuple(t) => {
5725                    for (i, item) in t.expressions.iter().enumerate() {
5726                        if i > 0 {
5727                            self.write(", ");
5728                        }
5729                        self.generate_expression(item)?;
5730                    }
5731                }
5732                other => {
5733                    self.generate_expression(other)?;
5734                }
5735            }
5736        } else {
5737            self.write_space();
5738            self.generate_expression(&join.this)?;
5739        }
5740
5741        // Only output MATCH_CONDITION/ON/USING inline if the condition wasn't deferred
5742        if !join.deferred_condition {
5743            // Output MATCH_CONDITION first (Snowflake ASOF JOIN)
5744            if let Some(match_cond) = &join.match_condition {
5745                self.write_space();
5746                self.write_keyword("MATCH_CONDITION");
5747                self.write(" (");
5748                self.generate_expression(match_cond)?;
5749                self.write(")");
5750            }
5751
5752            if let Some(on) = &join.on {
5753                if self.config.pretty {
5754                    self.write_newline();
5755                    self.indent_level += 1;
5756                    self.write_indent();
5757                    self.write_keyword("ON");
5758                    self.write_space();
5759                    self.generate_join_on_condition(on)?;
5760                    self.indent_level -= 1;
5761                } else {
5762                    self.write_space();
5763                    self.write_keyword("ON");
5764                    self.write_space();
5765                    self.generate_expression(on)?;
5766                }
5767            }
5768
5769            if !join.using.is_empty() {
5770                if self.config.pretty {
5771                    self.write_newline();
5772                    self.indent_level += 1;
5773                    self.write_indent();
5774                    self.write_keyword("USING");
5775                    self.write(" (");
5776                    for (i, col) in join.using.iter().enumerate() {
5777                        if i > 0 {
5778                            self.write(", ");
5779                        }
5780                        self.generate_identifier(col)?;
5781                    }
5782                    self.write(")");
5783                    self.indent_level -= 1;
5784                } else {
5785                    self.write_space();
5786                    self.write_keyword("USING");
5787                    self.write(" (");
5788                    for (i, col) in join.using.iter().enumerate() {
5789                        if i > 0 {
5790                            self.write(", ");
5791                        }
5792                        self.generate_identifier(col)?;
5793                    }
5794                    self.write(")");
5795                }
5796            }
5797        }
5798
5799        // Generate PIVOT/UNPIVOT expressions that follow this join
5800        for pivot in &join.pivots {
5801            self.write_space();
5802            self.generate_expression(pivot)?;
5803        }
5804
5805        Ok(())
5806    }
5807
5808    /// Generate just the ON/USING/MATCH_CONDITION for a join (used for deferred conditions)
5809    fn generate_join_condition(&mut self, join: &Join) -> Result<()> {
5810        // Generate MATCH_CONDITION first (Snowflake ASOF JOIN)
5811        if let Some(match_cond) = &join.match_condition {
5812            self.write_space();
5813            self.write_keyword("MATCH_CONDITION");
5814            self.write(" (");
5815            self.generate_expression(match_cond)?;
5816            self.write(")");
5817        }
5818
5819        if let Some(on) = &join.on {
5820            if self.config.pretty {
5821                self.write_newline();
5822                self.indent_level += 1;
5823                self.write_indent();
5824                self.write_keyword("ON");
5825                self.write_space();
5826                // In pretty mode, split AND conditions onto separate lines
5827                self.generate_join_on_condition(on)?;
5828                self.indent_level -= 1;
5829            } else {
5830                self.write_space();
5831                self.write_keyword("ON");
5832                self.write_space();
5833                self.generate_expression(on)?;
5834            }
5835        }
5836
5837        if !join.using.is_empty() {
5838            if self.config.pretty {
5839                self.write_newline();
5840                self.indent_level += 1;
5841                self.write_indent();
5842                self.write_keyword("USING");
5843                self.write(" (");
5844                for (i, col) in join.using.iter().enumerate() {
5845                    if i > 0 {
5846                        self.write(", ");
5847                    }
5848                    self.generate_identifier(col)?;
5849                }
5850                self.write(")");
5851                self.indent_level -= 1;
5852            } else {
5853                self.write_space();
5854                self.write_keyword("USING");
5855                self.write(" (");
5856                for (i, col) in join.using.iter().enumerate() {
5857                    if i > 0 {
5858                        self.write(", ");
5859                    }
5860                    self.generate_identifier(col)?;
5861                }
5862                self.write(")");
5863            }
5864        }
5865
5866        // Generate PIVOT/UNPIVOT expressions that follow this join (for deferred conditions)
5867        for pivot in &join.pivots {
5868            self.write_space();
5869            self.generate_expression(pivot)?;
5870        }
5871
5872        Ok(())
5873    }
5874
5875    /// Generate JOIN ON condition with AND clauses on separate lines in pretty mode
5876    fn generate_join_on_condition(&mut self, expr: &Expression) -> Result<()> {
5877        if let Expression::And(and_op) = expr {
5878            if let Some(conditions) = self.flatten_connector_terms(and_op, ConnectorOperator::And) {
5879                self.generate_expression(conditions[0])?;
5880                for condition in conditions.iter().skip(1) {
5881                    self.write_newline();
5882                    self.write_indent();
5883                    self.write_keyword("AND");
5884                    self.write_space();
5885                    self.generate_expression(condition)?;
5886                }
5887                return Ok(());
5888            }
5889        }
5890
5891        self.generate_expression(expr)
5892    }
5893
5894    fn generate_joined_table(&mut self, jt: &JoinedTable) -> Result<()> {
5895        // Parenthesized join: (tbl1 CROSS JOIN tbl2)
5896        self.write("(");
5897        self.generate_expression(&jt.left)?;
5898
5899        // Generate all joins
5900        for join in &jt.joins {
5901            self.generate_join(join)?;
5902        }
5903
5904        // Generate LATERAL VIEW clauses (Hive/Spark)
5905        for (lv_idx, lv) in jt.lateral_views.iter().enumerate() {
5906            self.generate_lateral_view(lv, lv_idx)?;
5907        }
5908
5909        self.write(")");
5910
5911        // Alias
5912        if let Some(alias) = &jt.alias {
5913            self.write_space();
5914            self.write_keyword("AS");
5915            self.write_space();
5916            self.generate_identifier(alias)?;
5917        }
5918
5919        Ok(())
5920    }
5921
5922    fn generate_lateral_view(&mut self, lv: &LateralView, lv_index: usize) -> Result<()> {
5923        use crate::dialects::DialectType;
5924
5925        if self.config.pretty {
5926            self.write_newline();
5927            self.write_indent();
5928        } else {
5929            self.write_space();
5930        }
5931
5932        // For Hive/Spark/Databricks (or no dialect specified), output native LATERAL VIEW syntax
5933        // For PostgreSQL and other specific dialects, convert to CROSS JOIN (LATERAL or UNNEST)
5934        let use_lateral_join = matches!(
5935            self.config.dialect,
5936            Some(DialectType::PostgreSQL)
5937                | Some(DialectType::DuckDB)
5938                | Some(DialectType::Snowflake)
5939                | Some(DialectType::TSQL)
5940                | Some(DialectType::Presto)
5941                | Some(DialectType::Trino)
5942                | Some(DialectType::Athena)
5943        );
5944
5945        // Check if target dialect should use UNNEST instead of EXPLODE
5946        let use_unnest = matches!(
5947            self.config.dialect,
5948            Some(DialectType::DuckDB)
5949                | Some(DialectType::Presto)
5950                | Some(DialectType::Trino)
5951                | Some(DialectType::Athena)
5952        );
5953
5954        // Check if we need POSEXPLODE -> UNNEST WITH ORDINALITY
5955        let (is_posexplode, is_inline, func_args) = match &lv.this {
5956            Expression::Explode(uf) => {
5957                // Expression::Explode is the dedicated EXPLODE expression type
5958                (false, false, vec![uf.this.clone()])
5959            }
5960            Expression::Unnest(uf) => {
5961                let mut args = vec![uf.this.clone()];
5962                args.extend(uf.expressions.clone());
5963                (false, false, args)
5964            }
5965            Expression::Function(func) => {
5966                if func.name.eq_ignore_ascii_case("POSEXPLODE")
5967                    || func.name.eq_ignore_ascii_case("POSEXPLODE_OUTER")
5968                {
5969                    (true, false, func.args.clone())
5970                } else if func.name.eq_ignore_ascii_case("INLINE") {
5971                    (false, true, func.args.clone())
5972                } else if func.name.eq_ignore_ascii_case("EXPLODE")
5973                    || func.name.eq_ignore_ascii_case("EXPLODE_OUTER")
5974                {
5975                    (false, false, func.args.clone())
5976                } else {
5977                    (false, false, vec![])
5978                }
5979            }
5980            _ => (false, false, vec![]),
5981        };
5982
5983        if use_lateral_join {
5984            // Convert to CROSS JOIN for PostgreSQL-like dialects
5985            if lv.outer {
5986                self.write_keyword("LEFT JOIN LATERAL");
5987            } else {
5988                self.write_keyword("CROSS JOIN");
5989            }
5990            self.write_space();
5991
5992            if use_unnest && !func_args.is_empty() {
5993                // Convert EXPLODE(y) -> UNNEST(y), POSEXPLODE(y) -> UNNEST(y)
5994                // For DuckDB, also convert ARRAY(y) -> [y]
5995                let unnest_args = if matches!(self.config.dialect, Some(DialectType::DuckDB)) {
5996                    // DuckDB: ARRAY(y) -> [y]
5997                    func_args
5998                        .iter()
5999                        .map(|a| {
6000                            if let Expression::Function(ref f) = a {
6001                                if f.name.eq_ignore_ascii_case("ARRAY") && f.args.len() == 1 {
6002                                    return Expression::ArrayFunc(Box::new(
6003                                        crate::expressions::ArrayConstructor {
6004                                            expressions: f.args.clone(),
6005                                            bracket_notation: true,
6006                                            use_list_keyword: false,
6007                                        },
6008                                    ));
6009                                }
6010                            }
6011                            a.clone()
6012                        })
6013                        .collect::<Vec<_>>()
6014                } else if matches!(
6015                    self.config.dialect,
6016                    Some(DialectType::Presto)
6017                        | Some(DialectType::Trino)
6018                        | Some(DialectType::Athena)
6019                ) {
6020                    // Presto: ARRAY(y) -> ARRAY[y]
6021                    func_args
6022                        .iter()
6023                        .map(|a| {
6024                            if let Expression::Function(ref f) = a {
6025                                if f.name.eq_ignore_ascii_case("ARRAY") && f.args.len() >= 1 {
6026                                    return Expression::ArrayFunc(Box::new(
6027                                        crate::expressions::ArrayConstructor {
6028                                            expressions: f.args.clone(),
6029                                            bracket_notation: true,
6030                                            use_list_keyword: false,
6031                                        },
6032                                    ));
6033                                }
6034                            }
6035                            a.clone()
6036                        })
6037                        .collect::<Vec<_>>()
6038                } else {
6039                    func_args
6040                };
6041
6042                // POSEXPLODE -> LATERAL (SELECT pos - 1 AS pos, col FROM UNNEST(y) WITH ORDINALITY AS t(col, pos))
6043                if is_posexplode {
6044                    self.write_keyword("LATERAL");
6045                    self.write(" (");
6046                    self.write_keyword("SELECT");
6047                    self.write_space();
6048
6049                    // Build the outer SELECT list: pos - 1 AS pos, then data columns
6050                    // column_aliases[0] is the position column, rest are data columns
6051                    let pos_alias = if !lv.column_aliases.is_empty() {
6052                        lv.column_aliases[0].clone()
6053                    } else {
6054                        Identifier::new("pos")
6055                    };
6056                    let data_aliases: Vec<Identifier> = if lv.column_aliases.len() > 1 {
6057                        lv.column_aliases[1..].to_vec()
6058                    } else {
6059                        vec![Identifier::new("col")]
6060                    };
6061
6062                    // pos - 1 AS pos
6063                    self.generate_identifier(&pos_alias)?;
6064                    self.write(" - 1");
6065                    self.write_space();
6066                    self.write_keyword("AS");
6067                    self.write_space();
6068                    self.generate_identifier(&pos_alias)?;
6069
6070                    // , col [, key, value ...]
6071                    for data_col in &data_aliases {
6072                        self.write(", ");
6073                        self.generate_identifier(data_col)?;
6074                    }
6075
6076                    self.write_space();
6077                    self.write_keyword("FROM");
6078                    self.write_space();
6079                    self.write_keyword("UNNEST");
6080                    self.write("(");
6081                    for (i, arg) in unnest_args.iter().enumerate() {
6082                        if i > 0 {
6083                            self.write(", ");
6084                        }
6085                        self.generate_expression(arg)?;
6086                    }
6087                    self.write(")");
6088                    self.write_space();
6089                    self.write_keyword("WITH ORDINALITY");
6090                    self.write_space();
6091                    self.write_keyword("AS");
6092                    self.write_space();
6093
6094                    // Inner alias: t(data_cols..., pos) - data columns first, pos last
6095                    let table_alias_ident = lv
6096                        .table_alias
6097                        .clone()
6098                        .unwrap_or_else(|| Identifier::new("t"));
6099                    self.generate_identifier(&table_alias_ident)?;
6100                    self.write("(");
6101                    for (i, data_col) in data_aliases.iter().enumerate() {
6102                        if i > 0 {
6103                            self.write(", ");
6104                        }
6105                        self.generate_identifier(data_col)?;
6106                    }
6107                    self.write(", ");
6108                    self.generate_identifier(&pos_alias)?;
6109                    self.write("))");
6110                } else if is_inline && matches!(self.config.dialect, Some(DialectType::DuckDB)) {
6111                    // INLINE -> LATERAL (SELECT UNNEST(arg, max_depth => 2)) AS alias
6112                    self.write_keyword("LATERAL");
6113                    self.write(" (");
6114                    self.write_keyword("SELECT");
6115                    self.write_space();
6116                    self.write_keyword("UNNEST");
6117                    self.write("(");
6118                    for (i, arg) in unnest_args.iter().enumerate() {
6119                        if i > 0 {
6120                            self.write(", ");
6121                        }
6122                        self.generate_expression(arg)?;
6123                    }
6124                    self.write(", ");
6125                    self.write_keyword("max_depth");
6126                    self.write(" => 2))");
6127
6128                    // Add table and column aliases
6129                    if let Some(alias) = &lv.table_alias {
6130                        self.write_space();
6131                        self.write_keyword("AS");
6132                        self.write_space();
6133                        self.generate_identifier(alias)?;
6134                        if !lv.column_aliases.is_empty() {
6135                            self.write("(");
6136                            for (i, col) in lv.column_aliases.iter().enumerate() {
6137                                if i > 0 {
6138                                    self.write(", ");
6139                                }
6140                                self.generate_identifier(col)?;
6141                            }
6142                            self.write(")");
6143                        }
6144                    } else if !lv.column_aliases.is_empty() {
6145                        // Auto-generate alias like _u_N
6146                        self.write_space();
6147                        self.write_keyword("AS");
6148                        self.write_space();
6149                        self.write(&format!("_u_{}", lv_index));
6150                        self.write("(");
6151                        for (i, col) in lv.column_aliases.iter().enumerate() {
6152                            if i > 0 {
6153                                self.write(", ");
6154                            }
6155                            self.generate_identifier(col)?;
6156                        }
6157                        self.write(")");
6158                    }
6159                } else {
6160                    self.write_keyword("UNNEST");
6161                    self.write("(");
6162                    for (i, arg) in unnest_args.iter().enumerate() {
6163                        if i > 0 {
6164                            self.write(", ");
6165                        }
6166                        self.generate_expression(arg)?;
6167                    }
6168                    self.write(")");
6169
6170                    // Add table and column aliases for non-POSEXPLODE
6171                    if let Some(alias) = &lv.table_alias {
6172                        self.write_space();
6173                        self.write_keyword("AS");
6174                        self.write_space();
6175                        self.generate_identifier(alias)?;
6176                        if !lv.column_aliases.is_empty() {
6177                            self.write("(");
6178                            for (i, col) in lv.column_aliases.iter().enumerate() {
6179                                if i > 0 {
6180                                    self.write(", ");
6181                                }
6182                                self.generate_identifier(col)?;
6183                            }
6184                            self.write(")");
6185                        }
6186                    } else if !lv.column_aliases.is_empty() {
6187                        self.write_space();
6188                        self.write_keyword("AS");
6189                        self.write(" t(");
6190                        for (i, col) in lv.column_aliases.iter().enumerate() {
6191                            if i > 0 {
6192                                self.write(", ");
6193                            }
6194                            self.generate_identifier(col)?;
6195                        }
6196                        self.write(")");
6197                    }
6198                }
6199            } else {
6200                // Not EXPLODE/POSEXPLODE or not using UNNEST, use LATERAL
6201                if !lv.outer {
6202                    self.write_keyword("LATERAL");
6203                    self.write_space();
6204                }
6205                self.generate_expression(&lv.this)?;
6206
6207                // Add table and column aliases
6208                if let Some(alias) = &lv.table_alias {
6209                    self.write_space();
6210                    self.write_keyword("AS");
6211                    self.write_space();
6212                    self.generate_identifier(alias)?;
6213                    if !lv.column_aliases.is_empty() {
6214                        self.write("(");
6215                        for (i, col) in lv.column_aliases.iter().enumerate() {
6216                            if i > 0 {
6217                                self.write(", ");
6218                            }
6219                            self.generate_identifier(col)?;
6220                        }
6221                        self.write(")");
6222                    }
6223                } else if !lv.column_aliases.is_empty() {
6224                    self.write_space();
6225                    self.write_keyword("AS");
6226                    self.write(" t(");
6227                    for (i, col) in lv.column_aliases.iter().enumerate() {
6228                        if i > 0 {
6229                            self.write(", ");
6230                        }
6231                        self.generate_identifier(col)?;
6232                    }
6233                    self.write(")");
6234                }
6235            }
6236
6237            // For LEFT JOIN LATERAL, need ON TRUE
6238            if lv.outer {
6239                self.write_space();
6240                self.write_keyword("ON TRUE");
6241            }
6242        } else {
6243            // Output native LATERAL VIEW syntax (Hive/Spark/Databricks or default)
6244            self.write_keyword("LATERAL VIEW");
6245            if lv.outer {
6246                self.write_space();
6247                self.write_keyword("OUTER");
6248            }
6249            if self.config.pretty {
6250                self.write_newline();
6251                self.write_indent();
6252            } else {
6253                self.write_space();
6254            }
6255            self.generate_expression(&lv.this)?;
6256
6257            // Table alias
6258            if let Some(alias) = &lv.table_alias {
6259                self.write_space();
6260                self.generate_identifier(alias)?;
6261            }
6262
6263            // Column aliases
6264            if !lv.column_aliases.is_empty() {
6265                self.write_space();
6266                self.write_keyword("AS");
6267                self.write_space();
6268                for (i, col) in lv.column_aliases.iter().enumerate() {
6269                    if i > 0 {
6270                        self.write(", ");
6271                    }
6272                    self.generate_identifier(col)?;
6273                }
6274            }
6275        }
6276
6277        Ok(())
6278    }
6279
6280    fn generate_union(&mut self, outermost: &Union) -> Result<()> {
6281        // Collect the left-recursive chain of Union nodes iteratively.
6282        // This avoids stack overflow for deeply nested chains like
6283        // SELECT 1 UNION ALL SELECT 2 UNION ALL ... UNION ALL SELECT N
6284        // where the parser builds: Union(Union(Union(A, B), C), D)
6285        let mut chain: Vec<&Union> = vec![outermost];
6286        let mut leftmost: &Expression = &outermost.left;
6287        while let Expression::Union(inner) = leftmost {
6288            chain.push(inner);
6289            leftmost = &inner.left;
6290        }
6291        // chain[0] = outermost, chain[last] = innermost
6292        // leftmost = innermost.left (a non-Union expression, typically Select)
6293
6294        // WITH clause (only on outermost)
6295        if let Some(with) = &outermost.with {
6296            self.generate_with(with)?;
6297            self.write_space();
6298        }
6299
6300        // Generate the base (leftmost) expression
6301        self.generate_expression(leftmost)?;
6302
6303        // Generate each union step from innermost to outermost
6304        for union in chain.iter().rev() {
6305            self.generate_union_step(union)?;
6306        }
6307        Ok(())
6308    }
6309
6310    /// Generate a single UNION step: keyword, right expression, and trailing modifiers.
6311    fn generate_union_step(&mut self, union: &Union) -> Result<()> {
6312        if self.config.pretty {
6313            self.write_newline();
6314            self.write_indent();
6315        } else {
6316            self.write_space();
6317        }
6318
6319        // BigQuery set operation modifiers: [side] [kind] UNION
6320        if let Some(side) = &union.side {
6321            self.write_keyword(side);
6322            self.write_space();
6323        }
6324        if let Some(kind) = &union.kind {
6325            self.write_keyword(kind);
6326            self.write_space();
6327        }
6328
6329        self.write_keyword("UNION");
6330        if union.all {
6331            self.write_space();
6332            self.write_keyword("ALL");
6333        } else if union.distinct {
6334            self.write_space();
6335            self.write_keyword("DISTINCT");
6336        }
6337
6338        // BigQuery: CORRESPONDING/STRICT CORRESPONDING -> BY NAME, BY (cols) -> ON (cols)
6339        // DuckDB: BY NAME
6340        if union.corresponding || union.by_name {
6341            self.write_space();
6342            self.write_keyword("BY NAME");
6343        }
6344        if !union.on_columns.is_empty() {
6345            self.write_space();
6346            self.write_keyword("ON");
6347            self.write(" (");
6348            for (i, col) in union.on_columns.iter().enumerate() {
6349                if i > 0 {
6350                    self.write(", ");
6351                }
6352                self.generate_expression(col)?;
6353            }
6354            self.write(")");
6355        }
6356
6357        if self.config.pretty {
6358            self.write_newline();
6359            self.write_indent();
6360        } else {
6361            self.write_space();
6362        }
6363        self.generate_expression(&union.right)?;
6364        // ORDER BY, LIMIT, OFFSET for the set operation
6365        if let Some(order_by) = &union.order_by {
6366            if self.config.pretty {
6367                self.write_newline();
6368            } else {
6369                self.write_space();
6370            }
6371            self.write_keyword("ORDER BY");
6372            self.write_space();
6373            for (i, ordered) in order_by.expressions.iter().enumerate() {
6374                if i > 0 {
6375                    self.write(", ");
6376                }
6377                self.generate_ordered(ordered)?;
6378            }
6379        }
6380        if let Some(limit) = &union.limit {
6381            if self.config.pretty {
6382                self.write_newline();
6383            } else {
6384                self.write_space();
6385            }
6386            self.write_keyword("LIMIT");
6387            self.write_space();
6388            self.generate_expression(limit)?;
6389        }
6390        if let Some(offset) = &union.offset {
6391            if self.config.pretty {
6392                self.write_newline();
6393            } else {
6394                self.write_space();
6395            }
6396            self.write_keyword("OFFSET");
6397            self.write_space();
6398            self.generate_expression(offset)?;
6399        }
6400        // DISTRIBUTE BY (Hive/Spark)
6401        if let Some(distribute_by) = &union.distribute_by {
6402            self.write_space();
6403            self.write_keyword("DISTRIBUTE BY");
6404            self.write_space();
6405            for (i, expr) in distribute_by.expressions.iter().enumerate() {
6406                if i > 0 {
6407                    self.write(", ");
6408                }
6409                self.generate_expression(expr)?;
6410            }
6411        }
6412        // SORT BY (Hive/Spark)
6413        if let Some(sort_by) = &union.sort_by {
6414            self.write_space();
6415            self.write_keyword("SORT BY");
6416            self.write_space();
6417            for (i, ord) in sort_by.expressions.iter().enumerate() {
6418                if i > 0 {
6419                    self.write(", ");
6420                }
6421                self.generate_ordered(ord)?;
6422            }
6423        }
6424        // CLUSTER BY (Hive/Spark)
6425        if let Some(cluster_by) = &union.cluster_by {
6426            self.write_space();
6427            self.write_keyword("CLUSTER BY");
6428            self.write_space();
6429            for (i, ord) in cluster_by.expressions.iter().enumerate() {
6430                if i > 0 {
6431                    self.write(", ");
6432                }
6433                self.generate_ordered(ord)?;
6434            }
6435        }
6436        Ok(())
6437    }
6438
6439    fn generate_intersect(&mut self, outermost: &Intersect) -> Result<()> {
6440        // Collect the left-recursive chain iteratively to avoid stack overflow
6441        let mut chain: Vec<&Intersect> = vec![outermost];
6442        let mut leftmost: &Expression = &outermost.left;
6443        while let Expression::Intersect(inner) = leftmost {
6444            chain.push(inner);
6445            leftmost = &inner.left;
6446        }
6447
6448        if let Some(with) = &outermost.with {
6449            self.generate_with(with)?;
6450            self.write_space();
6451        }
6452
6453        self.generate_expression(leftmost)?;
6454
6455        for intersect in chain.iter().rev() {
6456            self.generate_intersect_step(intersect)?;
6457        }
6458        Ok(())
6459    }
6460
6461    /// Generate a single INTERSECT step: keyword, right expression, and trailing modifiers.
6462    fn generate_intersect_step(&mut self, intersect: &Intersect) -> Result<()> {
6463        if self.config.pretty {
6464            self.write_newline();
6465            self.write_indent();
6466        } else {
6467            self.write_space();
6468        }
6469
6470        // BigQuery set operation modifiers: [side] [kind] INTERSECT
6471        if let Some(side) = &intersect.side {
6472            self.write_keyword(side);
6473            self.write_space();
6474        }
6475        if let Some(kind) = &intersect.kind {
6476            self.write_keyword(kind);
6477            self.write_space();
6478        }
6479
6480        self.write_keyword("INTERSECT");
6481        if intersect.all {
6482            self.write_space();
6483            self.write_keyword("ALL");
6484        } else if intersect.distinct {
6485            self.write_space();
6486            self.write_keyword("DISTINCT");
6487        }
6488
6489        // BigQuery: CORRESPONDING/STRICT CORRESPONDING -> BY NAME, BY (cols) -> ON (cols)
6490        // DuckDB: BY NAME
6491        if intersect.corresponding || intersect.by_name {
6492            self.write_space();
6493            self.write_keyword("BY NAME");
6494        }
6495        if !intersect.on_columns.is_empty() {
6496            self.write_space();
6497            self.write_keyword("ON");
6498            self.write(" (");
6499            for (i, col) in intersect.on_columns.iter().enumerate() {
6500                if i > 0 {
6501                    self.write(", ");
6502                }
6503                self.generate_expression(col)?;
6504            }
6505            self.write(")");
6506        }
6507
6508        if self.config.pretty {
6509            self.write_newline();
6510            self.write_indent();
6511        } else {
6512            self.write_space();
6513        }
6514        self.generate_expression(&intersect.right)?;
6515        // ORDER BY, LIMIT, OFFSET for the set operation
6516        if let Some(order_by) = &intersect.order_by {
6517            if self.config.pretty {
6518                self.write_newline();
6519            } else {
6520                self.write_space();
6521            }
6522            self.write_keyword("ORDER BY");
6523            self.write_space();
6524            for (i, ordered) in order_by.expressions.iter().enumerate() {
6525                if i > 0 {
6526                    self.write(", ");
6527                }
6528                self.generate_ordered(ordered)?;
6529            }
6530        }
6531        if let Some(limit) = &intersect.limit {
6532            if self.config.pretty {
6533                self.write_newline();
6534            } else {
6535                self.write_space();
6536            }
6537            self.write_keyword("LIMIT");
6538            self.write_space();
6539            self.generate_expression(limit)?;
6540        }
6541        if let Some(offset) = &intersect.offset {
6542            if self.config.pretty {
6543                self.write_newline();
6544            } else {
6545                self.write_space();
6546            }
6547            self.write_keyword("OFFSET");
6548            self.write_space();
6549            self.generate_expression(offset)?;
6550        }
6551        // DISTRIBUTE BY (Hive/Spark)
6552        if let Some(distribute_by) = &intersect.distribute_by {
6553            self.write_space();
6554            self.write_keyword("DISTRIBUTE BY");
6555            self.write_space();
6556            for (i, expr) in distribute_by.expressions.iter().enumerate() {
6557                if i > 0 {
6558                    self.write(", ");
6559                }
6560                self.generate_expression(expr)?;
6561            }
6562        }
6563        // SORT BY (Hive/Spark)
6564        if let Some(sort_by) = &intersect.sort_by {
6565            self.write_space();
6566            self.write_keyword("SORT BY");
6567            self.write_space();
6568            for (i, ord) in sort_by.expressions.iter().enumerate() {
6569                if i > 0 {
6570                    self.write(", ");
6571                }
6572                self.generate_ordered(ord)?;
6573            }
6574        }
6575        // CLUSTER BY (Hive/Spark)
6576        if let Some(cluster_by) = &intersect.cluster_by {
6577            self.write_space();
6578            self.write_keyword("CLUSTER BY");
6579            self.write_space();
6580            for (i, ord) in cluster_by.expressions.iter().enumerate() {
6581                if i > 0 {
6582                    self.write(", ");
6583                }
6584                self.generate_ordered(ord)?;
6585            }
6586        }
6587        Ok(())
6588    }
6589
6590    fn generate_except(&mut self, outermost: &Except) -> Result<()> {
6591        // Collect the left-recursive chain iteratively to avoid stack overflow
6592        let mut chain: Vec<&Except> = vec![outermost];
6593        let mut leftmost: &Expression = &outermost.left;
6594        while let Expression::Except(inner) = leftmost {
6595            chain.push(inner);
6596            leftmost = &inner.left;
6597        }
6598
6599        if let Some(with) = &outermost.with {
6600            self.generate_with(with)?;
6601            self.write_space();
6602        }
6603
6604        self.generate_expression(leftmost)?;
6605
6606        for except in chain.iter().rev() {
6607            self.generate_except_step(except)?;
6608        }
6609        Ok(())
6610    }
6611
6612    /// Generate a single EXCEPT step: keyword, right expression, and trailing modifiers.
6613    fn generate_except_step(&mut self, except: &Except) -> Result<()> {
6614        use crate::dialects::DialectType;
6615
6616        if self.config.pretty {
6617            self.write_newline();
6618            self.write_indent();
6619        } else {
6620            self.write_space();
6621        }
6622
6623        // BigQuery set operation modifiers: [side] [kind] EXCEPT
6624        if let Some(side) = &except.side {
6625            self.write_keyword(side);
6626            self.write_space();
6627        }
6628        if let Some(kind) = &except.kind {
6629            self.write_keyword(kind);
6630            self.write_space();
6631        }
6632
6633        // Oracle uses MINUS instead of EXCEPT (but not for EXCEPT ALL)
6634        match self.config.dialect {
6635            Some(DialectType::Oracle) if !except.all => {
6636                self.write_keyword("MINUS");
6637            }
6638            Some(DialectType::ClickHouse) => {
6639                // ClickHouse: drop ALL from EXCEPT ALL
6640                self.write_keyword("EXCEPT");
6641                if except.distinct {
6642                    self.write_space();
6643                    self.write_keyword("DISTINCT");
6644                }
6645            }
6646            Some(DialectType::BigQuery) => {
6647                // BigQuery: bare EXCEPT defaults to EXCEPT DISTINCT
6648                self.write_keyword("EXCEPT");
6649                if except.all {
6650                    self.write_space();
6651                    self.write_keyword("ALL");
6652                } else {
6653                    self.write_space();
6654                    self.write_keyword("DISTINCT");
6655                }
6656            }
6657            _ => {
6658                self.write_keyword("EXCEPT");
6659                if except.all {
6660                    self.write_space();
6661                    self.write_keyword("ALL");
6662                } else if except.distinct {
6663                    self.write_space();
6664                    self.write_keyword("DISTINCT");
6665                }
6666            }
6667        }
6668
6669        // BigQuery: CORRESPONDING/STRICT CORRESPONDING -> BY NAME, BY (cols) -> ON (cols)
6670        // DuckDB: BY NAME
6671        if except.corresponding || except.by_name {
6672            self.write_space();
6673            self.write_keyword("BY NAME");
6674        }
6675        if !except.on_columns.is_empty() {
6676            self.write_space();
6677            self.write_keyword("ON");
6678            self.write(" (");
6679            for (i, col) in except.on_columns.iter().enumerate() {
6680                if i > 0 {
6681                    self.write(", ");
6682                }
6683                self.generate_expression(col)?;
6684            }
6685            self.write(")");
6686        }
6687
6688        if self.config.pretty {
6689            self.write_newline();
6690            self.write_indent();
6691        } else {
6692            self.write_space();
6693        }
6694        self.generate_expression(&except.right)?;
6695        // ORDER BY, LIMIT, OFFSET for the set operation
6696        if let Some(order_by) = &except.order_by {
6697            if self.config.pretty {
6698                self.write_newline();
6699            } else {
6700                self.write_space();
6701            }
6702            self.write_keyword("ORDER BY");
6703            self.write_space();
6704            for (i, ordered) in order_by.expressions.iter().enumerate() {
6705                if i > 0 {
6706                    self.write(", ");
6707                }
6708                self.generate_ordered(ordered)?;
6709            }
6710        }
6711        if let Some(limit) = &except.limit {
6712            if self.config.pretty {
6713                self.write_newline();
6714            } else {
6715                self.write_space();
6716            }
6717            self.write_keyword("LIMIT");
6718            self.write_space();
6719            self.generate_expression(limit)?;
6720        }
6721        if let Some(offset) = &except.offset {
6722            if self.config.pretty {
6723                self.write_newline();
6724            } else {
6725                self.write_space();
6726            }
6727            self.write_keyword("OFFSET");
6728            self.write_space();
6729            self.generate_expression(offset)?;
6730        }
6731        // DISTRIBUTE BY (Hive/Spark)
6732        if let Some(distribute_by) = &except.distribute_by {
6733            self.write_space();
6734            self.write_keyword("DISTRIBUTE BY");
6735            self.write_space();
6736            for (i, expr) in distribute_by.expressions.iter().enumerate() {
6737                if i > 0 {
6738                    self.write(", ");
6739                }
6740                self.generate_expression(expr)?;
6741            }
6742        }
6743        // SORT BY (Hive/Spark)
6744        if let Some(sort_by) = &except.sort_by {
6745            self.write_space();
6746            self.write_keyword("SORT BY");
6747            self.write_space();
6748            for (i, ord) in sort_by.expressions.iter().enumerate() {
6749                if i > 0 {
6750                    self.write(", ");
6751                }
6752                self.generate_ordered(ord)?;
6753            }
6754        }
6755        // CLUSTER BY (Hive/Spark)
6756        if let Some(cluster_by) = &except.cluster_by {
6757            self.write_space();
6758            self.write_keyword("CLUSTER BY");
6759            self.write_space();
6760            for (i, ord) in cluster_by.expressions.iter().enumerate() {
6761                if i > 0 {
6762                    self.write(", ");
6763                }
6764                self.generate_ordered(ord)?;
6765            }
6766        }
6767        Ok(())
6768    }
6769
6770    fn generate_insert(&mut self, insert: &Insert) -> Result<()> {
6771        // For TSQL/Fabric/Spark/Hive/Databricks, CTEs must be prepended before INSERT
6772        let prepend_query_cte = if insert.with.is_none() {
6773            use crate::dialects::DialectType;
6774            let should_prepend = matches!(
6775                self.config.dialect,
6776                Some(DialectType::TSQL)
6777                    | Some(DialectType::Fabric)
6778                    | Some(DialectType::Spark)
6779                    | Some(DialectType::Databricks)
6780                    | Some(DialectType::Hive)
6781            );
6782            if should_prepend {
6783                if let Some(Expression::Select(select)) = &insert.query {
6784                    select.with.clone()
6785                } else {
6786                    None
6787                }
6788            } else {
6789                None
6790            }
6791        } else {
6792            None
6793        };
6794
6795        // Output WITH clause if on INSERT (e.g., WITH ... INSERT INTO ...)
6796        if let Some(with) = &insert.with {
6797            self.generate_with(with)?;
6798            self.write_space();
6799        } else if let Some(with) = &prepend_query_cte {
6800            self.generate_with(with)?;
6801            self.write_space();
6802        }
6803
6804        // Output leading comments before INSERT
6805        for comment in &insert.leading_comments {
6806            self.write_formatted_comment(comment);
6807            self.write(" ");
6808        }
6809
6810        // Handle directory insert (INSERT OVERWRITE DIRECTORY)
6811        if let Some(dir) = &insert.directory {
6812            self.write_keyword("INSERT OVERWRITE");
6813            if dir.local {
6814                self.write_space();
6815                self.write_keyword("LOCAL");
6816            }
6817            self.write_space();
6818            self.write_keyword("DIRECTORY");
6819            self.write_space();
6820            self.write("'");
6821            self.write(&dir.path);
6822            self.write("'");
6823
6824            // ROW FORMAT clause
6825            if let Some(row_format) = &dir.row_format {
6826                self.write_space();
6827                self.write_keyword("ROW FORMAT");
6828                if row_format.delimited {
6829                    self.write_space();
6830                    self.write_keyword("DELIMITED");
6831                }
6832                if let Some(val) = &row_format.fields_terminated_by {
6833                    self.write_space();
6834                    self.write_keyword("FIELDS TERMINATED BY");
6835                    self.write_space();
6836                    self.write("'");
6837                    self.write(val);
6838                    self.write("'");
6839                }
6840                if let Some(val) = &row_format.collection_items_terminated_by {
6841                    self.write_space();
6842                    self.write_keyword("COLLECTION ITEMS TERMINATED BY");
6843                    self.write_space();
6844                    self.write("'");
6845                    self.write(val);
6846                    self.write("'");
6847                }
6848                if let Some(val) = &row_format.map_keys_terminated_by {
6849                    self.write_space();
6850                    self.write_keyword("MAP KEYS TERMINATED BY");
6851                    self.write_space();
6852                    self.write("'");
6853                    self.write(val);
6854                    self.write("'");
6855                }
6856                if let Some(val) = &row_format.lines_terminated_by {
6857                    self.write_space();
6858                    self.write_keyword("LINES TERMINATED BY");
6859                    self.write_space();
6860                    self.write("'");
6861                    self.write(val);
6862                    self.write("'");
6863                }
6864                if let Some(val) = &row_format.null_defined_as {
6865                    self.write_space();
6866                    self.write_keyword("NULL DEFINED AS");
6867                    self.write_space();
6868                    self.write("'");
6869                    self.write(val);
6870                    self.write("'");
6871                }
6872            }
6873
6874            // STORED AS clause
6875            if let Some(format) = &dir.stored_as {
6876                self.write_space();
6877                self.write_keyword("STORED AS");
6878                self.write_space();
6879                self.write_keyword(format);
6880            }
6881
6882            // Query (SELECT statement)
6883            if let Some(query) = &insert.query {
6884                self.write_space();
6885                self.generate_expression(query)?;
6886            }
6887
6888            return Ok(());
6889        }
6890
6891        if insert.is_replace {
6892            // MySQL/SQLite REPLACE INTO statement
6893            self.write_keyword("REPLACE INTO");
6894        } else if insert.overwrite {
6895            // Use dialect-specific INSERT OVERWRITE format
6896            self.write_keyword("INSERT");
6897            // Output hint if present (Oracle: INSERT /*+ APPEND */ INTO)
6898            if let Some(ref hint) = insert.hint {
6899                self.generate_hint(hint)?;
6900            }
6901            self.write(&self.config.insert_overwrite.to_ascii_uppercase());
6902        } else if let Some(ref action) = insert.conflict_action {
6903            // SQLite conflict action: INSERT OR ABORT|FAIL|IGNORE|REPLACE|ROLLBACK INTO
6904            self.write_keyword("INSERT OR");
6905            self.write_space();
6906            self.write_keyword(action);
6907            self.write_space();
6908            self.write_keyword("INTO");
6909        } else if insert.ignore {
6910            // MySQL INSERT IGNORE syntax
6911            self.write_keyword("INSERT IGNORE INTO");
6912        } else {
6913            self.write_keyword("INSERT");
6914            // Output hint if present (Oracle: INSERT /*+ APPEND */ INTO)
6915            if let Some(ref hint) = insert.hint {
6916                self.generate_hint(hint)?;
6917            }
6918            self.write_space();
6919            self.write_keyword("INTO");
6920        }
6921        // ClickHouse: INSERT INTO FUNCTION func_name(args...)
6922        if let Some(ref func) = insert.function_target {
6923            self.write_space();
6924            self.write_keyword("FUNCTION");
6925            self.write_space();
6926            self.generate_expression(func)?;
6927        } else {
6928            self.write_space();
6929            self.generate_table(&insert.table)?;
6930        }
6931
6932        // Table alias (PostgreSQL: INSERT INTO table AS t(...), Oracle: INSERT INTO table t ...)
6933        if let Some(ref alias) = insert.alias {
6934            self.write_space();
6935            if insert.alias_explicit_as {
6936                self.write_keyword("AS");
6937                self.write_space();
6938            }
6939            self.generate_identifier(alias)?;
6940        }
6941
6942        // IF EXISTS clause (Hive)
6943        if insert.if_exists {
6944            self.write_space();
6945            self.write_keyword("IF EXISTS");
6946        }
6947
6948        // REPLACE WHERE clause (Databricks)
6949        if let Some(ref replace_where) = insert.replace_where {
6950            if self.config.pretty {
6951                self.write_newline();
6952                self.write_indent();
6953            } else {
6954                self.write_space();
6955            }
6956            self.write_keyword("REPLACE WHERE");
6957            self.write_space();
6958            self.generate_expression(replace_where)?;
6959        }
6960
6961        // Generate PARTITION clause if present
6962        if !insert.partition.is_empty() {
6963            self.write_space();
6964            self.write_keyword("PARTITION");
6965            self.write("(");
6966            for (i, (col, val)) in insert.partition.iter().enumerate() {
6967                if i > 0 {
6968                    self.write(", ");
6969                }
6970                self.generate_identifier(col)?;
6971                if let Some(v) = val {
6972                    self.write(" = ");
6973                    self.generate_expression(v)?;
6974                }
6975            }
6976            self.write(")");
6977        }
6978
6979        // ClickHouse: PARTITION BY expr
6980        if let Some(ref partition_by) = insert.partition_by {
6981            self.write_space();
6982            self.write_keyword("PARTITION BY");
6983            self.write_space();
6984            self.generate_expression(partition_by)?;
6985        }
6986
6987        // ClickHouse: SETTINGS key = val, ...
6988        if !insert.settings.is_empty() {
6989            self.write_space();
6990            self.write_keyword("SETTINGS");
6991            self.write_space();
6992            for (i, setting) in insert.settings.iter().enumerate() {
6993                if i > 0 {
6994                    self.write(", ");
6995                }
6996                self.generate_expression(setting)?;
6997            }
6998        }
6999
7000        if !insert.columns.is_empty() {
7001            if insert.alias.is_some() && insert.alias_explicit_as {
7002                // No space when explicit AS alias is present: INSERT INTO table AS t(a, b, c)
7003                self.write("(");
7004            } else {
7005                // Space for implicit alias or no alias: INSERT INTO dest d (i, value)
7006                self.write(" (");
7007            }
7008            for (i, col) in insert.columns.iter().enumerate() {
7009                if i > 0 {
7010                    self.write(", ");
7011                }
7012                self.generate_identifier(col)?;
7013            }
7014            self.write(")");
7015        }
7016
7017        // OUTPUT clause (TSQL)
7018        if let Some(ref output) = insert.output {
7019            self.generate_output_clause(output)?;
7020        }
7021
7022        // BY NAME modifier (DuckDB)
7023        if insert.by_name {
7024            self.write_space();
7025            self.write_keyword("BY NAME");
7026        }
7027
7028        if insert.default_values {
7029            self.write_space();
7030            self.write_keyword("DEFAULT VALUES");
7031        } else if let Some(query) = &insert.query {
7032            if self.config.pretty {
7033                self.write_newline();
7034            } else {
7035                self.write_space();
7036            }
7037            // If we prepended CTEs from nested SELECT (TSQL), strip the WITH from SELECT
7038            if prepend_query_cte.is_some() {
7039                if let Expression::Select(select) = query {
7040                    let mut select_no_with = select.clone();
7041                    select_no_with.with = None;
7042                    self.generate_select(&select_no_with)?;
7043                } else {
7044                    self.generate_expression(query)?;
7045                }
7046            } else {
7047                self.generate_expression(query)?;
7048            }
7049        } else if !insert.values.is_empty() {
7050            if self.config.pretty {
7051                // Pretty printing: VALUES on new line, each tuple indented
7052                self.write_newline();
7053                self.write_keyword("VALUES");
7054                self.write_newline();
7055                self.indent_level += 1;
7056                for (i, row) in insert.values.iter().enumerate() {
7057                    if i > 0 {
7058                        self.write(",");
7059                        self.write_newline();
7060                    }
7061                    self.write_indent();
7062                    self.write("(");
7063                    for (j, val) in row.iter().enumerate() {
7064                        if j > 0 {
7065                            self.write(", ");
7066                        }
7067                        self.generate_expression(val)?;
7068                    }
7069                    self.write(")");
7070                }
7071                self.indent_level -= 1;
7072            } else {
7073                // Non-pretty: single line
7074                self.write_space();
7075                self.write_keyword("VALUES");
7076                for (i, row) in insert.values.iter().enumerate() {
7077                    if i > 0 {
7078                        self.write(",");
7079                    }
7080                    self.write(" (");
7081                    for (j, val) in row.iter().enumerate() {
7082                        if j > 0 {
7083                            self.write(", ");
7084                        }
7085                        self.generate_expression(val)?;
7086                    }
7087                    self.write(")");
7088                }
7089            }
7090        }
7091
7092        // Source table (Hive/Spark): INSERT OVERWRITE TABLE target TABLE source
7093        if let Some(ref source) = insert.source {
7094            self.write_space();
7095            self.write_keyword("TABLE");
7096            self.write_space();
7097            self.generate_expression(source)?;
7098        }
7099
7100        // Source alias (MySQL: VALUES (...) AS new_data)
7101        if let Some(alias) = &insert.source_alias {
7102            self.write_space();
7103            self.write_keyword("AS");
7104            self.write_space();
7105            self.generate_identifier(alias)?;
7106        }
7107
7108        // ON CONFLICT clause (Materialize doesn't support ON CONFLICT)
7109        if let Some(on_conflict) = &insert.on_conflict {
7110            if !matches!(self.config.dialect, Some(DialectType::Materialize)) {
7111                self.write_space();
7112                self.generate_expression(on_conflict)?;
7113            }
7114        }
7115
7116        // RETURNING clause
7117        if !insert.returning.is_empty() {
7118            self.write_space();
7119            self.write_keyword("RETURNING");
7120            self.write_space();
7121            for (i, expr) in insert.returning.iter().enumerate() {
7122                if i > 0 {
7123                    self.write(", ");
7124                }
7125                self.generate_expression(expr)?;
7126            }
7127        }
7128
7129        Ok(())
7130    }
7131
7132    fn generate_update(&mut self, update: &Update) -> Result<()> {
7133        // Output leading comments before UPDATE
7134        for comment in &update.leading_comments {
7135            self.write_formatted_comment(comment);
7136            self.write(" ");
7137        }
7138
7139        // WITH clause (CTEs)
7140        if let Some(ref with) = update.with {
7141            self.generate_with(with)?;
7142            self.write_space();
7143        }
7144
7145        self.write_keyword("UPDATE");
7146        self.write_space();
7147        self.generate_table(&update.table)?;
7148
7149        let mysql_like_update_from = matches!(
7150            self.config.dialect,
7151            Some(DialectType::MySQL) | Some(DialectType::SingleStore)
7152        ) && update.from_clause.is_some();
7153
7154        let mut set_pairs = update.set.clone();
7155
7156        // MySQL-style UPDATE doesn't support FROM after SET. Convert FROM tables to JOIN ... ON TRUE.
7157        let mut pre_set_joins = update.table_joins.clone();
7158        if mysql_like_update_from {
7159            let target_name = update
7160                .table
7161                .alias
7162                .as_ref()
7163                .map(|a| a.name.clone())
7164                .unwrap_or_else(|| update.table.name.name.clone());
7165
7166            for (col, _) in &mut set_pairs {
7167                if !col.name.contains('.') {
7168                    col.name = format!("{}.{}", target_name, col.name);
7169                }
7170            }
7171
7172            if let Some(from_clause) = &update.from_clause {
7173                for table_expr in &from_clause.expressions {
7174                    pre_set_joins.push(crate::expressions::Join {
7175                        this: table_expr.clone(),
7176                        on: Some(Expression::Boolean(crate::expressions::BooleanLiteral {
7177                            value: true,
7178                        })),
7179                        using: Vec::new(),
7180                        kind: crate::expressions::JoinKind::Inner,
7181                        use_inner_keyword: false,
7182                        use_outer_keyword: false,
7183                        deferred_condition: false,
7184                        join_hint: None,
7185                        match_condition: None,
7186                        pivots: Vec::new(),
7187                        comments: Vec::new(),
7188                        nesting_group: 0,
7189                        directed: false,
7190                    });
7191                }
7192            }
7193            for join in &update.from_joins {
7194                let mut join = join.clone();
7195                if join.on.is_none() && join.using.is_empty() {
7196                    join.on = Some(Expression::Boolean(crate::expressions::BooleanLiteral {
7197                        value: true,
7198                    }));
7199                }
7200                pre_set_joins.push(join);
7201            }
7202        }
7203
7204        // Extra tables for multi-table UPDATE (MySQL syntax)
7205        for extra_table in &update.extra_tables {
7206            self.write(", ");
7207            self.generate_table(extra_table)?;
7208        }
7209
7210        // JOINs attached to the table list (MySQL multi-table syntax)
7211        for join in &pre_set_joins {
7212            // generate_join already adds a leading space
7213            self.generate_join(join)?;
7214        }
7215
7216        // Teradata: FROM clause comes before SET
7217        let teradata_from_before_set = matches!(self.config.dialect, Some(DialectType::Teradata));
7218        if teradata_from_before_set && !mysql_like_update_from {
7219            if let Some(ref from_clause) = update.from_clause {
7220                self.write_space();
7221                self.write_keyword("FROM");
7222                self.write_space();
7223                for (i, table_expr) in from_clause.expressions.iter().enumerate() {
7224                    if i > 0 {
7225                        self.write(", ");
7226                    }
7227                    self.generate_expression(table_expr)?;
7228                }
7229            }
7230            for join in &update.from_joins {
7231                self.generate_join(join)?;
7232            }
7233        }
7234
7235        self.write_space();
7236        self.write_keyword("SET");
7237        self.write_space();
7238
7239        for (i, (col, val)) in set_pairs.iter().enumerate() {
7240            if i > 0 {
7241                self.write(", ");
7242            }
7243            self.generate_identifier(col)?;
7244            self.write(" = ");
7245            self.generate_expression(val)?;
7246        }
7247
7248        // OUTPUT clause (TSQL)
7249        if let Some(ref output) = update.output {
7250            self.generate_output_clause(output)?;
7251        }
7252
7253        // FROM clause (after SET for non-Teradata, non-MySQL dialects)
7254        if !mysql_like_update_from && !teradata_from_before_set {
7255            if let Some(ref from_clause) = update.from_clause {
7256                self.write_space();
7257                self.write_keyword("FROM");
7258                self.write_space();
7259                // Generate each table in the FROM clause
7260                for (i, table_expr) in from_clause.expressions.iter().enumerate() {
7261                    if i > 0 {
7262                        self.write(", ");
7263                    }
7264                    self.generate_expression(table_expr)?;
7265                }
7266            }
7267        }
7268
7269        if !mysql_like_update_from && !teradata_from_before_set {
7270            // JOINs after FROM clause (PostgreSQL, Snowflake, SQL Server syntax)
7271            for join in &update.from_joins {
7272                self.generate_join(join)?;
7273            }
7274        }
7275
7276        if let Some(where_clause) = &update.where_clause {
7277            self.write_space();
7278            self.write_keyword("WHERE");
7279            self.write_space();
7280            self.generate_expression(&where_clause.this)?;
7281        }
7282
7283        // RETURNING clause
7284        if !update.returning.is_empty() {
7285            self.write_space();
7286            self.write_keyword("RETURNING");
7287            self.write_space();
7288            for (i, expr) in update.returning.iter().enumerate() {
7289                if i > 0 {
7290                    self.write(", ");
7291                }
7292                self.generate_expression(expr)?;
7293            }
7294        }
7295
7296        // ORDER BY clause (MySQL)
7297        if let Some(ref order_by) = update.order_by {
7298            self.write_space();
7299            self.generate_order_by(order_by)?;
7300        }
7301
7302        // LIMIT clause (MySQL)
7303        if let Some(ref limit) = update.limit {
7304            self.write_space();
7305            self.write_keyword("LIMIT");
7306            self.write_space();
7307            self.generate_expression(limit)?;
7308        }
7309
7310        Ok(())
7311    }
7312
7313    fn generate_delete(&mut self, delete: &Delete) -> Result<()> {
7314        // Output WITH clause if present
7315        if let Some(with) = &delete.with {
7316            self.generate_with(with)?;
7317            self.write_space();
7318        }
7319
7320        // Output leading comments before DELETE
7321        for comment in &delete.leading_comments {
7322            self.write_formatted_comment(comment);
7323            self.write(" ");
7324        }
7325
7326        // MySQL multi-table DELETE or TSQL DELETE with OUTPUT before FROM
7327        if !delete.tables.is_empty() && !delete.tables_from_using {
7328            // DELETE t1[, t2] [OUTPUT ...] FROM ... syntax (tables before FROM)
7329            self.write_keyword("DELETE");
7330            self.write_space();
7331            for (i, tbl) in delete.tables.iter().enumerate() {
7332                if i > 0 {
7333                    self.write(", ");
7334                }
7335                self.generate_table(tbl)?;
7336            }
7337            // TSQL: OUTPUT clause between target table and FROM
7338            if let Some(ref output) = delete.output {
7339                self.generate_output_clause(output)?;
7340            }
7341            self.write_space();
7342            self.write_keyword("FROM");
7343            self.write_space();
7344            self.generate_table(&delete.table)?;
7345        } else if !delete.tables.is_empty() && delete.tables_from_using {
7346            // DELETE FROM t1, t2 USING ... syntax (tables after FROM)
7347            self.write_keyword("DELETE FROM");
7348            self.write_space();
7349            for (i, tbl) in delete.tables.iter().enumerate() {
7350                if i > 0 {
7351                    self.write(", ");
7352                }
7353                self.generate_table(tbl)?;
7354            }
7355        } else if delete.no_from && matches!(self.config.dialect, Some(DialectType::BigQuery)) {
7356            // BigQuery-style DELETE without FROM keyword
7357            self.write_keyword("DELETE");
7358            self.write_space();
7359            self.generate_table(&delete.table)?;
7360        } else {
7361            self.write_keyword("DELETE FROM");
7362            self.write_space();
7363            self.generate_table(&delete.table)?;
7364        }
7365
7366        // ClickHouse: ON CLUSTER clause
7367        if let Some(ref on_cluster) = delete.on_cluster {
7368            self.write_space();
7369            self.generate_on_cluster(on_cluster)?;
7370        }
7371
7372        // FORCE INDEX hint (MySQL)
7373        if let Some(ref idx) = delete.force_index {
7374            self.write_space();
7375            self.write_keyword("FORCE INDEX");
7376            self.write(" (");
7377            self.write(idx);
7378            self.write(")");
7379        }
7380
7381        // Optional alias
7382        if let Some(ref alias) = delete.alias {
7383            self.write_space();
7384            if delete.alias_explicit_as
7385                || matches!(self.config.dialect, Some(DialectType::BigQuery))
7386            {
7387                self.write_keyword("AS");
7388                self.write_space();
7389            }
7390            self.generate_identifier(alias)?;
7391        }
7392
7393        // JOINs (MySQL multi-table) - when NOT tables_from_using, JOINs come before USING
7394        if !delete.tables_from_using {
7395            for join in &delete.joins {
7396                self.generate_join(join)?;
7397            }
7398        }
7399
7400        // USING clause (PostgreSQL/DuckDB/MySQL)
7401        if !delete.using.is_empty() {
7402            self.write_space();
7403            self.write_keyword("USING");
7404            for (i, table) in delete.using.iter().enumerate() {
7405                if i > 0 {
7406                    self.write(",");
7407                }
7408                self.write_space();
7409                // Check if the table has subquery hints (DuckDB USING with subquery)
7410                if !table.hints.is_empty() && table.name.is_empty() {
7411                    // Subquery in USING: (VALUES ...) AS alias(cols)
7412                    self.generate_expression(&table.hints[0])?;
7413                    if let Some(ref alias) = table.alias {
7414                        self.write_space();
7415                        if table.alias_explicit_as {
7416                            self.write_keyword("AS");
7417                            self.write_space();
7418                        }
7419                        self.generate_identifier(alias)?;
7420                        if !table.column_aliases.is_empty() {
7421                            self.write("(");
7422                            for (j, col_alias) in table.column_aliases.iter().enumerate() {
7423                                if j > 0 {
7424                                    self.write(", ");
7425                                }
7426                                self.generate_identifier(col_alias)?;
7427                            }
7428                            self.write(")");
7429                        }
7430                    }
7431                } else {
7432                    self.generate_table(table)?;
7433                }
7434            }
7435        }
7436
7437        // JOINs (MySQL multi-table) - when tables_from_using, JOINs come after USING
7438        if delete.tables_from_using {
7439            for join in &delete.joins {
7440                self.generate_join(join)?;
7441            }
7442        }
7443
7444        // OUTPUT clause (TSQL) - only if not already emitted in the early position
7445        let output_already_emitted =
7446            !delete.tables.is_empty() && !delete.tables_from_using && delete.output.is_some();
7447        if !output_already_emitted {
7448            if let Some(ref output) = delete.output {
7449                self.generate_output_clause(output)?;
7450            }
7451        }
7452
7453        if let Some(where_clause) = &delete.where_clause {
7454            self.write_space();
7455            self.write_keyword("WHERE");
7456            self.write_space();
7457            self.generate_expression(&where_clause.this)?;
7458        }
7459
7460        // ORDER BY clause (MySQL)
7461        if let Some(ref order_by) = delete.order_by {
7462            self.write_space();
7463            self.generate_order_by(order_by)?;
7464        }
7465
7466        // LIMIT clause (MySQL)
7467        if let Some(ref limit) = delete.limit {
7468            self.write_space();
7469            self.write_keyword("LIMIT");
7470            self.write_space();
7471            self.generate_expression(limit)?;
7472        }
7473
7474        // RETURNING clause (PostgreSQL)
7475        if !delete.returning.is_empty() {
7476            self.write_space();
7477            self.write_keyword("RETURNING");
7478            self.write_space();
7479            for (i, expr) in delete.returning.iter().enumerate() {
7480                if i > 0 {
7481                    self.write(", ");
7482                }
7483                self.generate_expression(expr)?;
7484            }
7485        }
7486
7487        Ok(())
7488    }
7489
7490    // ==================== DDL Generation ====================
7491
7492    fn generate_create_table(&mut self, ct: &CreateTable) -> Result<()> {
7493        // Athena: Determine if this is Hive-style DDL or Trino-style DML
7494        // CREATE TABLE AS SELECT uses Trino (double quotes)
7495        // CREATE TABLE (without AS SELECT) and CREATE EXTERNAL TABLE use Hive (backticks)
7496        let saved_athena_hive_context = self.athena_hive_context;
7497        let is_clickhouse = matches!(self.config.dialect, Some(DialectType::ClickHouse));
7498        if matches!(
7499            self.config.dialect,
7500            Some(crate::dialects::DialectType::Athena)
7501        ) {
7502            // Use Hive context if:
7503            // 1. It's an EXTERNAL table, OR
7504            // 2. There's no AS SELECT clause
7505            let is_external = ct
7506                .table_modifier
7507                .as_ref()
7508                .map(|m| m.eq_ignore_ascii_case("EXTERNAL"))
7509                .unwrap_or(false);
7510            let has_as_select = ct.as_select.is_some();
7511            self.athena_hive_context = is_external || !has_as_select;
7512        }
7513
7514        // TSQL: Convert CREATE TABLE AS SELECT to SELECT * INTO table FROM (subquery) AS temp
7515        if matches!(
7516            self.config.dialect,
7517            Some(crate::dialects::DialectType::TSQL)
7518        ) {
7519            if let Some(ref query) = ct.as_select {
7520                // Output WITH CTE clause if present
7521                if let Some(with_cte) = &ct.with_cte {
7522                    self.generate_with(with_cte)?;
7523                    self.write_space();
7524                }
7525
7526                // Generate: SELECT * INTO [table] FROM (subquery) AS temp
7527                self.write_keyword("SELECT");
7528                self.write(" * ");
7529                self.write_keyword("INTO");
7530                self.write_space();
7531
7532                // If temporary, prefix with # for TSQL temp table
7533                if ct.temporary {
7534                    self.write("#");
7535                }
7536                self.generate_table(&ct.name)?;
7537
7538                self.write_space();
7539                self.write_keyword("FROM");
7540                self.write(" (");
7541                // For TSQL, add aliases to select columns to preserve column names
7542                let aliased_query = Self::add_column_aliases_to_query(query.clone());
7543                self.generate_expression(&aliased_query)?;
7544                self.write(") ");
7545                self.write_keyword("AS");
7546                self.write(" temp");
7547                return Ok(());
7548            }
7549        }
7550
7551        // Output WITH CTE clause if present
7552        if let Some(with_cte) = &ct.with_cte {
7553            self.generate_with(with_cte)?;
7554            self.write_space();
7555        }
7556
7557        // Output leading comments before CREATE
7558        for comment in &ct.leading_comments {
7559            self.write_formatted_comment(comment);
7560            self.write(" ");
7561        }
7562        self.write_keyword("CREATE");
7563
7564        if ct.or_replace {
7565            self.write_space();
7566            self.write_keyword("OR REPLACE");
7567        }
7568
7569        if ct.temporary {
7570            self.write_space();
7571            // Oracle uses GLOBAL TEMPORARY TABLE syntax
7572            if matches!(self.config.dialect, Some(DialectType::Oracle)) {
7573                self.write_keyword("GLOBAL TEMPORARY");
7574            } else {
7575                self.write_keyword("TEMPORARY");
7576            }
7577        }
7578
7579        // Table modifier: DYNAMIC, ICEBERG, EXTERNAL, HYBRID, TRANSIENT
7580        let is_dictionary = ct
7581            .table_modifier
7582            .as_ref()
7583            .map(|m| m.eq_ignore_ascii_case("DICTIONARY"))
7584            .unwrap_or(false);
7585        if let Some(ref modifier) = ct.table_modifier {
7586            // TRANSIENT is Snowflake-specific - skip for other dialects
7587            let skip_transient = modifier.eq_ignore_ascii_case("TRANSIENT")
7588                && !matches!(self.config.dialect, Some(DialectType::Snowflake) | None);
7589            // Teradata-specific modifiers: VOLATILE, SET, MULTISET, SET TABLE combinations
7590            let is_teradata_modifier = modifier.eq_ignore_ascii_case("VOLATILE")
7591                || modifier.eq_ignore_ascii_case("SET")
7592                || modifier.eq_ignore_ascii_case("MULTISET")
7593                || modifier.to_ascii_uppercase().contains("VOLATILE")
7594                || modifier.to_ascii_uppercase().starts_with("SET ")
7595                || modifier.to_ascii_uppercase().starts_with("MULTISET ");
7596            let skip_teradata =
7597                is_teradata_modifier && !matches!(self.config.dialect, Some(DialectType::Teradata));
7598            if !skip_transient && !skip_teradata {
7599                self.write_space();
7600                self.write_keyword(modifier);
7601            }
7602        }
7603
7604        if !is_dictionary {
7605            self.write_space();
7606            self.write_keyword("TABLE");
7607        }
7608
7609        if ct.if_not_exists {
7610            self.write_space();
7611            self.write_keyword("IF NOT EXISTS");
7612        }
7613
7614        self.write_space();
7615        self.generate_table(&ct.name)?;
7616
7617        // ClickHouse: UUID 'xxx' clause after table name
7618        if let Some(ref uuid) = ct.uuid {
7619            self.write_space();
7620            self.write_keyword("UUID");
7621            self.write(" '");
7622            self.write(uuid);
7623            self.write("'");
7624        }
7625
7626        // ClickHouse: ON CLUSTER clause
7627        if let Some(ref on_cluster) = ct.on_cluster {
7628            self.write_space();
7629            self.generate_on_cluster(on_cluster)?;
7630        }
7631
7632        // Teradata: options after table name before column list (comma-separated)
7633        if matches!(
7634            self.config.dialect,
7635            Some(crate::dialects::DialectType::Teradata)
7636        ) && !ct.teradata_post_name_options.is_empty()
7637        {
7638            for opt in &ct.teradata_post_name_options {
7639                self.write(", ");
7640                self.write(opt);
7641            }
7642        }
7643
7644        // Snowflake: COPY GRANTS clause
7645        if ct.copy_grants {
7646            self.write_space();
7647            self.write_keyword("COPY GRANTS");
7648        }
7649
7650        // Snowflake: USING TEMPLATE clause (before columns or AS SELECT)
7651        if let Some(ref using_template) = ct.using_template {
7652            self.write_space();
7653            self.write_keyword("USING TEMPLATE");
7654            self.write_space();
7655            self.generate_expression(using_template)?;
7656            return Ok(());
7657        }
7658
7659        // Handle [SHALLOW | DEEP] CLONE/COPY source_table [AT(...) | BEFORE(...)]
7660        if let Some(ref clone_source) = ct.clone_source {
7661            self.write_space();
7662            if ct.is_copy && self.config.supports_table_copy {
7663                // BigQuery uses COPY
7664                self.write_keyword("COPY");
7665            } else if ct.shallow_clone {
7666                self.write_keyword("SHALLOW CLONE");
7667            } else {
7668                self.write_keyword("CLONE");
7669            }
7670            self.write_space();
7671            self.generate_table(clone_source)?;
7672            // Generate AT/BEFORE time travel clause (stored as Raw expression)
7673            if let Some(ref at_clause) = ct.clone_at_clause {
7674                self.write_space();
7675                self.generate_expression(at_clause)?;
7676            }
7677            return Ok(());
7678        }
7679
7680        // Handle PARTITION OF property
7681        // Output order: PARTITION OF <table> (<columns/constraints>) FOR VALUES ...
7682        // Columns/constraints must appear BETWEEN the table name and the partition bound spec
7683        if let Some(ref partition_of) = ct.partition_of {
7684            self.write_space();
7685
7686            // Extract the PartitionedOfProperty parts to generate them separately
7687            if let Expression::PartitionedOfProperty(ref pop) = partition_of {
7688                // Output: PARTITION OF <table>
7689                self.write_keyword("PARTITION OF");
7690                self.write_space();
7691                self.generate_expression(&pop.this)?;
7692
7693                // Output columns/constraints if present (e.g., (unitsales DEFAULT 0) or (CONSTRAINT ...))
7694                if !ct.columns.is_empty() || !ct.constraints.is_empty() {
7695                    self.write(" (");
7696                    let mut first = true;
7697                    for col in &ct.columns {
7698                        if !first {
7699                            self.write(", ");
7700                        }
7701                        first = false;
7702                        self.generate_column_def(col)?;
7703                    }
7704                    for constraint in &ct.constraints {
7705                        if !first {
7706                            self.write(", ");
7707                        }
7708                        first = false;
7709                        self.generate_table_constraint(constraint)?;
7710                    }
7711                    self.write(")");
7712                }
7713
7714                // Output partition bound spec: FOR VALUES ... or DEFAULT
7715                if let Expression::PartitionBoundSpec(_) = pop.expression.as_ref() {
7716                    self.write_space();
7717                    self.write_keyword("FOR VALUES");
7718                    self.write_space();
7719                    self.generate_expression(&pop.expression)?;
7720                } else {
7721                    self.write_space();
7722                    self.write_keyword("DEFAULT");
7723                }
7724            } else {
7725                // Fallback: generate the whole expression if it's not a PartitionedOfProperty
7726                self.generate_expression(partition_of)?;
7727
7728                // Output columns/constraints if present
7729                if !ct.columns.is_empty() || !ct.constraints.is_empty() {
7730                    self.write(" (");
7731                    let mut first = true;
7732                    for col in &ct.columns {
7733                        if !first {
7734                            self.write(", ");
7735                        }
7736                        first = false;
7737                        self.generate_column_def(col)?;
7738                    }
7739                    for constraint in &ct.constraints {
7740                        if !first {
7741                            self.write(", ");
7742                        }
7743                        first = false;
7744                        self.generate_table_constraint(constraint)?;
7745                    }
7746                    self.write(")");
7747                }
7748            }
7749
7750            // Output table properties (e.g., PARTITION BY RANGE(population))
7751            for prop in &ct.properties {
7752                self.write_space();
7753                self.generate_expression(prop)?;
7754            }
7755
7756            return Ok(());
7757        }
7758
7759        // SQLite: Inline single-column PRIMARY KEY constraints into column definition
7760        // This matches Python sqlglot's behavior for SQLite dialect
7761        self.sqlite_inline_pk_columns.clear();
7762        if matches!(
7763            self.config.dialect,
7764            Some(crate::dialects::DialectType::SQLite)
7765        ) {
7766            for constraint in &ct.constraints {
7767                if let TableConstraint::PrimaryKey { columns, name, .. } = constraint {
7768                    // Only inline if: single column, no constraint name, and column exists in table
7769                    if columns.len() == 1 && name.is_none() {
7770                        let pk_col_name = columns[0].name.to_ascii_lowercase();
7771                        // Check if this column exists in the table
7772                        if ct
7773                            .columns
7774                            .iter()
7775                            .any(|c| c.name.name.to_ascii_lowercase() == pk_col_name)
7776                        {
7777                            self.sqlite_inline_pk_columns.insert(pk_col_name);
7778                        }
7779                    }
7780                }
7781            }
7782        }
7783
7784        // Output columns if present (even for CTAS with columns)
7785        if !ct.columns.is_empty() {
7786            if self.config.pretty {
7787                // Pretty print: each column on new line
7788                self.write(" (");
7789                self.write_newline();
7790                self.indent_level += 1;
7791                for (i, col) in ct.columns.iter().enumerate() {
7792                    if i > 0 {
7793                        self.write(",");
7794                        self.write_newline();
7795                    }
7796                    self.write_indent();
7797                    self.generate_column_def(col)?;
7798                }
7799                // Table constraints (skip inlined PRIMARY KEY for SQLite)
7800                for constraint in &ct.constraints {
7801                    // Skip single-column PRIMARY KEY that was inlined for SQLite
7802                    if let TableConstraint::PrimaryKey { columns, name, .. } = constraint {
7803                        if columns.len() == 1
7804                            && name.is_none()
7805                            && self
7806                                .sqlite_inline_pk_columns
7807                                .contains(&columns[0].name.to_ascii_lowercase())
7808                        {
7809                            continue;
7810                        }
7811                    }
7812                    self.write(",");
7813                    self.write_newline();
7814                    self.write_indent();
7815                    self.generate_table_constraint(constraint)?;
7816                }
7817                self.indent_level -= 1;
7818                self.write_newline();
7819                self.write(")");
7820            } else {
7821                self.write(" (");
7822                for (i, col) in ct.columns.iter().enumerate() {
7823                    if i > 0 {
7824                        self.write(", ");
7825                    }
7826                    self.generate_column_def(col)?;
7827                }
7828                // Table constraints (skip inlined PRIMARY KEY for SQLite)
7829                let mut first_constraint = true;
7830                for constraint in &ct.constraints {
7831                    // Skip single-column PRIMARY KEY that was inlined for SQLite
7832                    if let TableConstraint::PrimaryKey { columns, name, .. } = constraint {
7833                        if columns.len() == 1
7834                            && name.is_none()
7835                            && self
7836                                .sqlite_inline_pk_columns
7837                                .contains(&columns[0].name.to_ascii_lowercase())
7838                        {
7839                            continue;
7840                        }
7841                    }
7842                    if first_constraint {
7843                        self.write(", ");
7844                        first_constraint = false;
7845                    } else {
7846                        self.write(", ");
7847                    }
7848                    self.generate_table_constraint(constraint)?;
7849                }
7850                self.write(")");
7851            }
7852        } else if !ct.constraints.is_empty() {
7853            // No columns but constraints exist (e.g., CREATE TABLE A LIKE B or CREATE TABLE A TAG (...))
7854            let has_like_only = ct
7855                .constraints
7856                .iter()
7857                .all(|c| matches!(c, TableConstraint::Like { .. }));
7858            let has_tags_only = ct
7859                .constraints
7860                .iter()
7861                .all(|c| matches!(c, TableConstraint::Tags(_)));
7862            // PostgreSQL: CREATE TABLE A (LIKE B INCLUDING ALL) (with parens)
7863            // Most dialects: CREATE TABLE A LIKE B (no parens)
7864            // Snowflake: CREATE TABLE A TAG (...) (no outer parens, but TAG has its own)
7865            let is_pg_like = matches!(
7866                self.config.dialect,
7867                Some(crate::dialects::DialectType::PostgreSQL)
7868                    | Some(crate::dialects::DialectType::CockroachDB)
7869                    | Some(crate::dialects::DialectType::Materialize)
7870                    | Some(crate::dialects::DialectType::RisingWave)
7871                    | Some(crate::dialects::DialectType::Redshift)
7872                    | Some(crate::dialects::DialectType::Presto)
7873                    | Some(crate::dialects::DialectType::Trino)
7874                    | Some(crate::dialects::DialectType::Athena)
7875            );
7876            let use_parens = if has_like_only {
7877                is_pg_like
7878            } else {
7879                !has_tags_only
7880            };
7881            if self.config.pretty && use_parens {
7882                self.write(" (");
7883                self.write_newline();
7884                self.indent_level += 1;
7885                for (i, constraint) in ct.constraints.iter().enumerate() {
7886                    if i > 0 {
7887                        self.write(",");
7888                        self.write_newline();
7889                    }
7890                    self.write_indent();
7891                    self.generate_table_constraint(constraint)?;
7892                }
7893                self.indent_level -= 1;
7894                self.write_newline();
7895                self.write(")");
7896            } else {
7897                if use_parens {
7898                    self.write(" (");
7899                } else {
7900                    self.write_space();
7901                }
7902                for (i, constraint) in ct.constraints.iter().enumerate() {
7903                    if i > 0 {
7904                        self.write(", ");
7905                    }
7906                    self.generate_table_constraint(constraint)?;
7907                }
7908                if use_parens {
7909                    self.write(")");
7910                }
7911            }
7912        }
7913
7914        // TSQL ON filegroup or ON filegroup (partition_column) clause
7915        if let Some(ref on_prop) = ct.on_property {
7916            self.write(" ");
7917            self.write_keyword("ON");
7918            self.write(" ");
7919            self.generate_expression(&on_prop.this)?;
7920        }
7921
7922        // Output SchemaCommentProperty BEFORE WITH properties (Presto/Hive/Spark style)
7923        // For ClickHouse, SchemaCommentProperty goes after AS SELECT, handled later
7924        if !is_clickhouse {
7925            for prop in &ct.properties {
7926                if let Expression::SchemaCommentProperty(_) = prop {
7927                    if self.config.pretty {
7928                        self.write_newline();
7929                    } else {
7930                        self.write_space();
7931                    }
7932                    self.generate_expression(prop)?;
7933                }
7934            }
7935        }
7936
7937        // WITH properties (output after columns if columns exist, otherwise before AS)
7938        if !ct.with_properties.is_empty() {
7939            // Snowflake ICEBERG/DYNAMIC TABLE: output properties inline (space-separated, no WITH wrapper)
7940            let is_snowflake_special_table = matches!(
7941                self.config.dialect,
7942                Some(crate::dialects::DialectType::Snowflake)
7943            ) && (ct.table_modifier.as_deref() == Some("ICEBERG")
7944                || ct.table_modifier.as_deref() == Some("DYNAMIC"));
7945            if is_snowflake_special_table {
7946                for (key, value) in &ct.with_properties {
7947                    self.write_space();
7948                    self.write(key);
7949                    self.write("=");
7950                    self.write(value);
7951                }
7952            } else if self.config.pretty {
7953                self.write_newline();
7954                self.write_keyword("WITH");
7955                self.write(" (");
7956                self.write_newline();
7957                self.indent_level += 1;
7958                for (i, (key, value)) in ct.with_properties.iter().enumerate() {
7959                    if i > 0 {
7960                        self.write(",");
7961                        self.write_newline();
7962                    }
7963                    self.write_indent();
7964                    self.write(key);
7965                    self.write("=");
7966                    self.write(value);
7967                }
7968                self.indent_level -= 1;
7969                self.write_newline();
7970                self.write(")");
7971            } else {
7972                self.write_space();
7973                self.write_keyword("WITH");
7974                self.write(" (");
7975                for (i, (key, value)) in ct.with_properties.iter().enumerate() {
7976                    if i > 0 {
7977                        self.write(", ");
7978                    }
7979                    self.write(key);
7980                    self.write("=");
7981                    self.write(value);
7982                }
7983                self.write(")");
7984            }
7985        }
7986
7987        let (pre_as_properties, post_as_properties): (Vec<&Expression>, Vec<&Expression>) =
7988            if is_clickhouse && ct.as_select.is_some() {
7989                let mut pre = Vec::new();
7990                let mut post = Vec::new();
7991                for prop in &ct.properties {
7992                    if matches!(prop, Expression::SchemaCommentProperty(_)) {
7993                        post.push(prop);
7994                    } else {
7995                        pre.push(prop);
7996                    }
7997                }
7998                (pre, post)
7999            } else {
8000                (ct.properties.iter().collect(), Vec::new())
8001            };
8002
8003        // Table properties like DEFAULT COLLATE (BigQuery), OPTIONS (...), TBLPROPERTIES (...), or PROPERTIES (...)
8004        for prop in pre_as_properties {
8005            // SchemaCommentProperty was already output before WITH properties (except for ClickHouse)
8006            if !is_clickhouse && matches!(prop, Expression::SchemaCommentProperty(_)) {
8007                continue;
8008            }
8009            if self.config.pretty {
8010                self.write_newline();
8011            } else {
8012                self.write_space();
8013            }
8014            // BigQuery: Properties containing OPTIONS should be wrapped with OPTIONS (...)
8015            // Hive: Properties should be wrapped with TBLPROPERTIES (...)
8016            // Doris/StarRocks: Properties should be wrapped with PROPERTIES (...)
8017            if let Expression::Properties(props) = prop {
8018                let is_hive_dialect = matches!(
8019                    self.config.dialect,
8020                    Some(crate::dialects::DialectType::Hive)
8021                        | Some(crate::dialects::DialectType::Spark)
8022                        | Some(crate::dialects::DialectType::Databricks)
8023                        | Some(crate::dialects::DialectType::Athena)
8024                );
8025                let is_doris_starrocks = matches!(
8026                    self.config.dialect,
8027                    Some(crate::dialects::DialectType::Doris)
8028                        | Some(crate::dialects::DialectType::StarRocks)
8029                );
8030                if is_hive_dialect {
8031                    self.generate_tblproperties_clause(&props.expressions)?;
8032                } else if is_doris_starrocks {
8033                    self.generate_properties_clause(&props.expressions)?;
8034                } else {
8035                    self.generate_options_clause(&props.expressions)?;
8036                }
8037            } else {
8038                self.generate_expression(prop)?;
8039            }
8040        }
8041
8042        // Post-table properties like TSQL WITH(SYSTEM_VERSIONING=ON(...)) or Doris PROPERTIES
8043        for prop in &ct.post_table_properties {
8044            if let Expression::WithSystemVersioningProperty(ref svp) = prop {
8045                self.write(" WITH(");
8046                self.generate_system_versioning_content(svp)?;
8047                self.write(")");
8048            } else if let Expression::Properties(props) = prop {
8049                // Doris/StarRocks: PROPERTIES ('key'='value', ...) in post_table_properties
8050                let is_doris_starrocks = matches!(
8051                    self.config.dialect,
8052                    Some(crate::dialects::DialectType::Doris)
8053                        | Some(crate::dialects::DialectType::StarRocks)
8054                );
8055                self.write_space();
8056                if is_doris_starrocks {
8057                    self.generate_properties_clause(&props.expressions)?;
8058                } else {
8059                    self.generate_options_clause(&props.expressions)?;
8060                }
8061            } else {
8062                self.write_space();
8063                self.generate_expression(prop)?;
8064            }
8065        }
8066
8067        // StarRocks ROLLUP property: ROLLUP (r1(col1, col2), r2(col1))
8068        // Only output for StarRocks target
8069        if let Some(ref rollup) = ct.rollup {
8070            if matches!(self.config.dialect, Some(DialectType::StarRocks)) {
8071                self.write_space();
8072                self.generate_rollup_property(rollup)?;
8073            }
8074        }
8075
8076        // MySQL table options (ENGINE=val, AUTO_INCREMENT=val, etc.)
8077        // Only output for MySQL-compatible dialects; strip for others during transpilation
8078        // COMMENT is also used by Hive/Spark so we selectively preserve it
8079        let is_mysql_compatible = matches!(
8080            self.config.dialect,
8081            Some(DialectType::MySQL)
8082                | Some(DialectType::SingleStore)
8083                | Some(DialectType::Doris)
8084                | Some(DialectType::StarRocks)
8085                | None
8086        );
8087        let is_hive_compatible = matches!(
8088            self.config.dialect,
8089            Some(DialectType::Hive)
8090                | Some(DialectType::Spark)
8091                | Some(DialectType::Databricks)
8092                | Some(DialectType::Athena)
8093        );
8094        let mysql_pretty_options =
8095            self.config.pretty && matches!(self.config.dialect, Some(DialectType::MySQL));
8096        for (key, value) in &ct.mysql_table_options {
8097            // Skip non-MySQL-specific options for non-MySQL targets
8098            let should_output = if is_mysql_compatible {
8099                true
8100            } else if is_hive_compatible && key == "COMMENT" {
8101                true // COMMENT is valid in Hive/Spark table definitions
8102            } else {
8103                false
8104            };
8105            if should_output {
8106                if mysql_pretty_options {
8107                    self.write_newline();
8108                    self.write_indent();
8109                } else {
8110                    self.write_space();
8111                }
8112                self.write_keyword(key);
8113                // StarRocks/Doris: COMMENT 'value' (no =), others: COMMENT='value'
8114                if key == "COMMENT" && !self.config.schema_comment_with_eq {
8115                    self.write_space();
8116                } else {
8117                    self.write("=");
8118                }
8119                self.write(value);
8120            }
8121        }
8122
8123        // Spark/Databricks: USING PARQUET for temporary tables that don't already have a storage format
8124        if ct.temporary
8125            && matches!(
8126                self.config.dialect,
8127                Some(DialectType::Spark) | Some(DialectType::Databricks)
8128            )
8129            && ct.as_select.is_none()
8130        {
8131            self.write_space();
8132            self.write_keyword("USING PARQUET");
8133        }
8134
8135        // PostgreSQL INHERITS clause
8136        if !ct.inherits.is_empty() {
8137            self.write_space();
8138            self.write_keyword("INHERITS");
8139            self.write(" (");
8140            for (i, parent) in ct.inherits.iter().enumerate() {
8141                if i > 0 {
8142                    self.write(", ");
8143                }
8144                self.generate_table(parent)?;
8145            }
8146            self.write(")");
8147        }
8148
8149        // CREATE TABLE AS SELECT
8150        if let Some(ref query) = ct.as_select {
8151            self.write_space();
8152            self.write_keyword("AS");
8153            self.write_space();
8154            if ct.as_select_parenthesized {
8155                self.write("(");
8156            }
8157            self.generate_expression(query)?;
8158            if ct.as_select_parenthesized {
8159                self.write(")");
8160            }
8161
8162            // Teradata: WITH DATA / WITH NO DATA
8163            if let Some(with_data) = ct.with_data {
8164                self.write_space();
8165                self.write_keyword("WITH");
8166                if !with_data {
8167                    self.write_space();
8168                    self.write_keyword("NO");
8169                }
8170                self.write_space();
8171                self.write_keyword("DATA");
8172            }
8173
8174            // Teradata: AND STATISTICS / AND NO STATISTICS
8175            if let Some(with_statistics) = ct.with_statistics {
8176                self.write_space();
8177                self.write_keyword("AND");
8178                if !with_statistics {
8179                    self.write_space();
8180                    self.write_keyword("NO");
8181                }
8182                self.write_space();
8183                self.write_keyword("STATISTICS");
8184            }
8185
8186            // Teradata: Index specifications
8187            for index in &ct.teradata_indexes {
8188                self.write_space();
8189                match index.kind {
8190                    TeradataIndexKind::NoPrimary => {
8191                        self.write_keyword("NO PRIMARY INDEX");
8192                    }
8193                    TeradataIndexKind::Primary => {
8194                        self.write_keyword("PRIMARY INDEX");
8195                    }
8196                    TeradataIndexKind::PrimaryAmp => {
8197                        self.write_keyword("PRIMARY AMP INDEX");
8198                    }
8199                    TeradataIndexKind::Unique => {
8200                        self.write_keyword("UNIQUE INDEX");
8201                    }
8202                    TeradataIndexKind::UniquePrimary => {
8203                        self.write_keyword("UNIQUE PRIMARY INDEX");
8204                    }
8205                    TeradataIndexKind::Secondary => {
8206                        self.write_keyword("INDEX");
8207                    }
8208                }
8209                // Output index name if present
8210                if let Some(ref name) = index.name {
8211                    self.write_space();
8212                    self.write(name);
8213                }
8214                // Output columns if present
8215                if !index.columns.is_empty() {
8216                    self.write(" (");
8217                    for (i, col) in index.columns.iter().enumerate() {
8218                        if i > 0 {
8219                            self.write(", ");
8220                        }
8221                        self.write(col);
8222                    }
8223                    self.write(")");
8224                }
8225            }
8226
8227            // Teradata: ON COMMIT behavior for volatile tables
8228            if let Some(ref on_commit) = ct.on_commit {
8229                self.write_space();
8230                self.write_keyword("ON COMMIT");
8231                self.write_space();
8232                match on_commit {
8233                    OnCommit::PreserveRows => self.write_keyword("PRESERVE ROWS"),
8234                    OnCommit::DeleteRows => self.write_keyword("DELETE ROWS"),
8235                }
8236            }
8237
8238            if !post_as_properties.is_empty() {
8239                for prop in post_as_properties {
8240                    self.write_space();
8241                    self.generate_expression(prop)?;
8242                }
8243            }
8244
8245            // Restore Athena Hive context before early return
8246            self.athena_hive_context = saved_athena_hive_context;
8247            return Ok(());
8248        }
8249
8250        // ON COMMIT behavior (for non-CTAS tables)
8251        if let Some(ref on_commit) = ct.on_commit {
8252            self.write_space();
8253            self.write_keyword("ON COMMIT");
8254            self.write_space();
8255            match on_commit {
8256                OnCommit::PreserveRows => self.write_keyword("PRESERVE ROWS"),
8257                OnCommit::DeleteRows => self.write_keyword("DELETE ROWS"),
8258            }
8259        }
8260
8261        // Restore Athena Hive context
8262        self.athena_hive_context = saved_athena_hive_context;
8263
8264        Ok(())
8265    }
8266
8267    /// Generate column definition as an expression (for ROWS FROM alias columns, XMLTABLE/JSON_TABLE)
8268    /// Outputs: "col_name" TYPE [PATH 'xpath'] (not the full CREATE TABLE column definition)
8269    fn generate_column_def_expr(&mut self, col: &ColumnDef) -> Result<()> {
8270        // Output column name
8271        self.generate_identifier(&col.name)?;
8272        // Output data type if known
8273        if !matches!(col.data_type, DataType::Unknown) {
8274            self.write_space();
8275            self.generate_data_type(&col.data_type)?;
8276        }
8277        // Output PATH constraint if present (for XMLTABLE/JSON_TABLE columns)
8278        for constraint in &col.constraints {
8279            if let ColumnConstraint::Path(path_expr) = constraint {
8280                self.write_space();
8281                self.write_keyword("PATH");
8282                self.write_space();
8283                self.generate_expression(path_expr)?;
8284            }
8285        }
8286        Ok(())
8287    }
8288
8289    fn generate_column_def(&mut self, col: &ColumnDef) -> Result<()> {
8290        // Check if this is a TSQL computed column (no data type)
8291        let has_computed_no_type = matches!(&col.data_type, DataType::Custom { name } if name.is_empty())
8292            && col
8293                .constraints
8294                .iter()
8295                .any(|c| matches!(c, ColumnConstraint::ComputedColumn(_)));
8296        // Some dialects (notably TSQL/Fabric) do not include an explicit type for computed columns.
8297        let omit_computed_type = !self.config.computed_column_with_type
8298            && col
8299                .constraints
8300                .iter()
8301                .any(|c| matches!(c, ColumnConstraint::ComputedColumn(_)));
8302
8303        // Check if this is a partition column spec (no data type, type is Unknown)
8304        // This is used in PostgreSQL PARTITION OF syntax where columns only have constraints
8305        let is_partition_column_spec = matches!(col.data_type, DataType::Unknown);
8306
8307        // Check if this is a DYNAMIC TABLE column (no data type, empty Custom name, no constraints)
8308        // Also check the no_type flag for SQLite columns without types
8309        let has_no_type = col.no_type
8310            || (matches!(&col.data_type, DataType::Custom { name } if name.is_empty())
8311                && col.constraints.is_empty());
8312
8313        self.generate_identifier(&col.name)?;
8314
8315        // Check for SERIAL/BIGSERIAL/SMALLSERIAL expansion for Materialize and PostgreSQL
8316        let serial_expansion = if matches!(
8317            self.config.dialect,
8318            Some(DialectType::Materialize) | Some(DialectType::PostgreSQL)
8319        ) {
8320            if let DataType::Custom { ref name } = col.data_type {
8321                if name.eq_ignore_ascii_case("SERIAL") {
8322                    Some("INT")
8323                } else if name.eq_ignore_ascii_case("BIGSERIAL") {
8324                    Some("BIGINT")
8325                } else if name.eq_ignore_ascii_case("SMALLSERIAL") {
8326                    Some("SMALLINT")
8327                } else {
8328                    None
8329                }
8330            } else {
8331                None
8332            }
8333        } else {
8334            None
8335        };
8336
8337        if !has_computed_no_type && !omit_computed_type && !is_partition_column_spec && !has_no_type
8338        {
8339            self.write_space();
8340            // ClickHouse CREATE TABLE column types: suppress automatic Nullable wrapping
8341            // since ClickHouse uses explicit Nullable() in its type system.
8342            let saved_nullable_depth = self.clickhouse_nullable_depth;
8343            if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
8344                self.clickhouse_nullable_depth = -1;
8345            }
8346            if let Some(int_type) = serial_expansion {
8347                // SERIAL -> INT (+ constraints added below)
8348                self.write_keyword(int_type);
8349            } else if col.unsigned && matches!(self.config.dialect, Some(DialectType::DuckDB)) {
8350                // For DuckDB: convert unsigned integer types to their unsigned equivalents
8351                let unsigned_type = match &col.data_type {
8352                    DataType::Int { .. } => Some("UINTEGER"),
8353                    DataType::BigInt { .. } => Some("UBIGINT"),
8354                    DataType::SmallInt { .. } => Some("USMALLINT"),
8355                    DataType::TinyInt { .. } => Some("UTINYINT"),
8356                    _ => None,
8357                };
8358                if let Some(utype) = unsigned_type {
8359                    self.write_keyword(utype);
8360                } else {
8361                    self.generate_data_type(&col.data_type)?;
8362                }
8363            } else {
8364                self.generate_data_type(&col.data_type)?;
8365            }
8366            self.clickhouse_nullable_depth = saved_nullable_depth;
8367        }
8368
8369        // MySQL type modifiers (must come right after data type)
8370        // Skip UNSIGNED for DuckDB (already mapped to unsigned type above)
8371        if col.unsigned && !matches!(self.config.dialect, Some(DialectType::DuckDB)) {
8372            self.write_space();
8373            self.write_keyword("UNSIGNED");
8374        }
8375        if col.zerofill {
8376            self.write_space();
8377            self.write_keyword("ZEROFILL");
8378        }
8379
8380        // Teradata column attributes (must come right after data type, in specific order)
8381        // ORDER: CHARACTER SET, UPPERCASE, CASESPECIFIC, FORMAT, TITLE, INLINE LENGTH, COMPRESS
8382
8383        if let Some(ref charset) = col.character_set {
8384            self.write_space();
8385            self.write_keyword("CHARACTER SET");
8386            self.write_space();
8387            self.write(charset);
8388        }
8389
8390        if col.uppercase {
8391            self.write_space();
8392            self.write_keyword("UPPERCASE");
8393        }
8394
8395        if let Some(casespecific) = col.casespecific {
8396            self.write_space();
8397            if casespecific {
8398                self.write_keyword("CASESPECIFIC");
8399            } else {
8400                self.write_keyword("NOT CASESPECIFIC");
8401            }
8402        }
8403
8404        if let Some(ref format) = col.format {
8405            self.write_space();
8406            self.write_keyword("FORMAT");
8407            self.write(" '");
8408            self.write(format);
8409            self.write("'");
8410        }
8411
8412        if let Some(ref title) = col.title {
8413            self.write_space();
8414            self.write_keyword("TITLE");
8415            self.write(" '");
8416            self.write(title);
8417            self.write("'");
8418        }
8419
8420        if let Some(length) = col.inline_length {
8421            self.write_space();
8422            self.write_keyword("INLINE LENGTH");
8423            self.write(" ");
8424            self.write(&length.to_string());
8425        }
8426
8427        if let Some(ref compress) = col.compress {
8428            self.write_space();
8429            self.write_keyword("COMPRESS");
8430            if !compress.is_empty() {
8431                // Single string literal: output without parentheses (Teradata syntax)
8432                if compress.len() == 1 {
8433                    if let Expression::Literal(lit) = &compress[0] {
8434                        if let Literal::String(_) = lit.as_ref() {
8435                            self.write_space();
8436                            self.generate_expression(&compress[0])?;
8437                        }
8438                    } else {
8439                        self.write(" (");
8440                        self.generate_expression(&compress[0])?;
8441                        self.write(")");
8442                    }
8443                } else {
8444                    self.write(" (");
8445                    for (i, val) in compress.iter().enumerate() {
8446                        if i > 0 {
8447                            self.write(", ");
8448                        }
8449                        self.generate_expression(val)?;
8450                    }
8451                    self.write(")");
8452                }
8453            }
8454        }
8455
8456        // Column constraints - output in original order if constraint_order is populated
8457        // Otherwise fall back to legacy fixed order for backward compatibility
8458        if !col.constraint_order.is_empty() {
8459            // Use constraint_order for original ordering
8460            // Track indices for constraints stored in the constraints Vec
8461            let mut references_idx = 0;
8462            let mut check_idx = 0;
8463            let mut generated_idx = 0;
8464            let mut collate_idx = 0;
8465            let mut comment_idx = 0;
8466            // The preprocessing in dialects/mod.rs now handles the correct ordering of
8467            // NOT NULL relative to IDENTITY for PostgreSQL, so no deferral needed here.
8468            let defer_not_null_after_identity = false;
8469            let mut pending_not_null_after_identity = false;
8470
8471            for constraint_type in &col.constraint_order {
8472                match constraint_type {
8473                    ConstraintType::PrimaryKey => {
8474                        // Materialize doesn't support PRIMARY KEY column constraints
8475                        if col.primary_key
8476                            && !matches!(self.config.dialect, Some(DialectType::Materialize))
8477                        {
8478                            if let Some(ref cname) = col.primary_key_constraint_name {
8479                                self.write_space();
8480                                self.write_keyword("CONSTRAINT");
8481                                self.write_space();
8482                                self.write(cname);
8483                            }
8484                            self.write_space();
8485                            self.write_keyword("PRIMARY KEY");
8486                            if let Some(ref order) = col.primary_key_order {
8487                                self.write_space();
8488                                match order {
8489                                    SortOrder::Asc => self.write_keyword("ASC"),
8490                                    SortOrder::Desc => self.write_keyword("DESC"),
8491                                }
8492                            }
8493                        }
8494                    }
8495                    ConstraintType::Unique => {
8496                        if col.unique {
8497                            if let Some(ref cname) = col.unique_constraint_name {
8498                                self.write_space();
8499                                self.write_keyword("CONSTRAINT");
8500                                self.write_space();
8501                                self.write(cname);
8502                            }
8503                            self.write_space();
8504                            self.write_keyword("UNIQUE");
8505                            // PostgreSQL 15+: NULLS NOT DISTINCT
8506                            if col.unique_nulls_not_distinct {
8507                                self.write(" NULLS NOT DISTINCT");
8508                            }
8509                        }
8510                    }
8511                    ConstraintType::NotNull => {
8512                        if col.nullable == Some(false) {
8513                            if defer_not_null_after_identity {
8514                                pending_not_null_after_identity = true;
8515                                continue;
8516                            }
8517                            if let Some(ref cname) = col.not_null_constraint_name {
8518                                self.write_space();
8519                                self.write_keyword("CONSTRAINT");
8520                                self.write_space();
8521                                self.write(cname);
8522                            }
8523                            self.write_space();
8524                            self.write_keyword("NOT NULL");
8525                        }
8526                    }
8527                    ConstraintType::Null => {
8528                        if col.nullable == Some(true) {
8529                            self.write_space();
8530                            self.write_keyword("NULL");
8531                        }
8532                    }
8533                    ConstraintType::Default => {
8534                        if let Some(ref default) = col.default {
8535                            self.write_space();
8536                            self.write_keyword("DEFAULT");
8537                            self.write_space();
8538                            self.generate_expression(default)?;
8539                        }
8540                    }
8541                    ConstraintType::AutoIncrement => {
8542                        if col.auto_increment {
8543                            // DuckDB doesn't support AUTO_INCREMENT - skip entirely
8544                            if matches!(
8545                                self.config.dialect,
8546                                Some(crate::dialects::DialectType::DuckDB)
8547                            ) {
8548                                // Skip - DuckDB uses sequences or rowid instead
8549                            } else if matches!(
8550                                self.config.dialect,
8551                                Some(crate::dialects::DialectType::Materialize)
8552                            ) {
8553                                // Materialize strips AUTO_INCREMENT but adds NOT NULL
8554                                if !matches!(col.nullable, Some(false)) {
8555                                    self.write_space();
8556                                    self.write_keyword("NOT NULL");
8557                                }
8558                            } else if matches!(
8559                                self.config.dialect,
8560                                Some(crate::dialects::DialectType::PostgreSQL)
8561                            ) {
8562                                // PostgreSQL: AUTO_INCREMENT -> GENERATED BY DEFAULT AS IDENTITY
8563                                self.write_space();
8564                                self.generate_auto_increment_keyword(col)?;
8565                            } else {
8566                                self.write_space();
8567                                self.generate_auto_increment_keyword(col)?;
8568                                if pending_not_null_after_identity {
8569                                    self.write_space();
8570                                    self.write_keyword("NOT NULL");
8571                                    pending_not_null_after_identity = false;
8572                                }
8573                            }
8574                        } // close else for DuckDB skip
8575                    }
8576                    ConstraintType::References => {
8577                        // Find next References constraint
8578                        while references_idx < col.constraints.len() {
8579                            if let ColumnConstraint::References(fk_ref) =
8580                                &col.constraints[references_idx]
8581                            {
8582                                // CONSTRAINT name if present
8583                                if let Some(ref name) = fk_ref.constraint_name {
8584                                    self.write_space();
8585                                    self.write_keyword("CONSTRAINT");
8586                                    self.write_space();
8587                                    self.write(name);
8588                                }
8589                                self.write_space();
8590                                if fk_ref.has_foreign_key_keywords {
8591                                    self.write_keyword("FOREIGN KEY");
8592                                    self.write_space();
8593                                }
8594                                self.write_keyword("REFERENCES");
8595                                self.write_space();
8596                                self.generate_table(&fk_ref.table)?;
8597                                if !fk_ref.columns.is_empty() {
8598                                    self.write(" (");
8599                                    for (i, c) in fk_ref.columns.iter().enumerate() {
8600                                        if i > 0 {
8601                                            self.write(", ");
8602                                        }
8603                                        self.generate_identifier(c)?;
8604                                    }
8605                                    self.write(")");
8606                                }
8607                                self.generate_referential_actions(fk_ref)?;
8608                                references_idx += 1;
8609                                break;
8610                            }
8611                            references_idx += 1;
8612                        }
8613                    }
8614                    ConstraintType::Check => {
8615                        // Find next Check constraint
8616                        while check_idx < col.constraints.len() {
8617                            if let ColumnConstraint::Check(expr) = &col.constraints[check_idx] {
8618                                // Output CONSTRAINT name if present (only for first CHECK)
8619                                if check_idx == 0 {
8620                                    if let Some(ref cname) = col.check_constraint_name {
8621                                        self.write_space();
8622                                        self.write_keyword("CONSTRAINT");
8623                                        self.write_space();
8624                                        self.write(cname);
8625                                    }
8626                                }
8627                                self.write_space();
8628                                self.write_keyword("CHECK");
8629                                self.write(" (");
8630                                self.generate_expression(expr)?;
8631                                self.write(")");
8632                                check_idx += 1;
8633                                break;
8634                            }
8635                            check_idx += 1;
8636                        }
8637                    }
8638                    ConstraintType::GeneratedAsIdentity => {
8639                        // Find next GeneratedAsIdentity constraint
8640                        while generated_idx < col.constraints.len() {
8641                            if let ColumnConstraint::GeneratedAsIdentity(gen) =
8642                                &col.constraints[generated_idx]
8643                            {
8644                                self.write_space();
8645                                // Redshift uses IDENTITY(start, increment) syntax
8646                                if matches!(
8647                                    self.config.dialect,
8648                                    Some(crate::dialects::DialectType::Redshift)
8649                                ) {
8650                                    self.write_keyword("IDENTITY");
8651                                    self.write("(");
8652                                    if let Some(ref start) = gen.start {
8653                                        self.generate_expression(start)?;
8654                                    } else {
8655                                        self.write("0");
8656                                    }
8657                                    self.write(", ");
8658                                    if let Some(ref incr) = gen.increment {
8659                                        self.generate_expression(incr)?;
8660                                    } else {
8661                                        self.write("1");
8662                                    }
8663                                    self.write(")");
8664                                } else {
8665                                    self.write_keyword("GENERATED");
8666                                    if gen.always {
8667                                        self.write_space();
8668                                        self.write_keyword("ALWAYS");
8669                                    } else {
8670                                        self.write_space();
8671                                        self.write_keyword("BY DEFAULT");
8672                                        if gen.on_null {
8673                                            self.write_space();
8674                                            self.write_keyword("ON NULL");
8675                                        }
8676                                    }
8677                                    self.write_space();
8678                                    self.write_keyword("AS IDENTITY");
8679
8680                                    let has_options = gen.start.is_some()
8681                                        || gen.increment.is_some()
8682                                        || gen.minvalue.is_some()
8683                                        || gen.maxvalue.is_some()
8684                                        || gen.cycle.is_some();
8685                                    if has_options {
8686                                        self.write(" (");
8687                                        let mut first = true;
8688                                        if let Some(ref start) = gen.start {
8689                                            if !first {
8690                                                self.write(" ");
8691                                            }
8692                                            first = false;
8693                                            self.write_keyword("START WITH");
8694                                            self.write_space();
8695                                            self.generate_expression(start)?;
8696                                        }
8697                                        if let Some(ref incr) = gen.increment {
8698                                            if !first {
8699                                                self.write(" ");
8700                                            }
8701                                            first = false;
8702                                            self.write_keyword("INCREMENT BY");
8703                                            self.write_space();
8704                                            self.generate_expression(incr)?;
8705                                        }
8706                                        if let Some(ref minv) = gen.minvalue {
8707                                            if !first {
8708                                                self.write(" ");
8709                                            }
8710                                            first = false;
8711                                            self.write_keyword("MINVALUE");
8712                                            self.write_space();
8713                                            self.generate_expression(minv)?;
8714                                        }
8715                                        if let Some(ref maxv) = gen.maxvalue {
8716                                            if !first {
8717                                                self.write(" ");
8718                                            }
8719                                            first = false;
8720                                            self.write_keyword("MAXVALUE");
8721                                            self.write_space();
8722                                            self.generate_expression(maxv)?;
8723                                        }
8724                                        if let Some(cycle) = gen.cycle {
8725                                            if !first {
8726                                                self.write(" ");
8727                                            }
8728                                            if cycle {
8729                                                self.write_keyword("CYCLE");
8730                                            } else {
8731                                                self.write_keyword("NO CYCLE");
8732                                            }
8733                                        }
8734                                        self.write(")");
8735                                    }
8736                                }
8737                                generated_idx += 1;
8738                                break;
8739                            }
8740                            generated_idx += 1;
8741                        }
8742                    }
8743                    ConstraintType::Collate => {
8744                        // Find next Collate constraint
8745                        while collate_idx < col.constraints.len() {
8746                            if let ColumnConstraint::Collate(collation) =
8747                                &col.constraints[collate_idx]
8748                            {
8749                                self.write_space();
8750                                self.write_keyword("COLLATE");
8751                                self.write_space();
8752                                self.generate_identifier(collation)?;
8753                                collate_idx += 1;
8754                                break;
8755                            }
8756                            collate_idx += 1;
8757                        }
8758                    }
8759                    ConstraintType::Comment => {
8760                        // Find next Comment constraint
8761                        while comment_idx < col.constraints.len() {
8762                            if let ColumnConstraint::Comment(comment) =
8763                                &col.constraints[comment_idx]
8764                            {
8765                                self.write_space();
8766                                self.write_keyword("COMMENT");
8767                                self.write_space();
8768                                self.generate_string_literal(comment)?;
8769                                comment_idx += 1;
8770                                break;
8771                            }
8772                            comment_idx += 1;
8773                        }
8774                    }
8775                    ConstraintType::Tags => {
8776                        // Find next Tags constraint (Snowflake)
8777                        for constraint in &col.constraints {
8778                            if let ColumnConstraint::Tags(tags) = constraint {
8779                                self.write_space();
8780                                self.write_keyword("TAG");
8781                                self.write(" (");
8782                                for (i, expr) in tags.expressions.iter().enumerate() {
8783                                    if i > 0 {
8784                                        self.write(", ");
8785                                    }
8786                                    self.generate_expression(expr)?;
8787                                }
8788                                self.write(")");
8789                                break;
8790                            }
8791                        }
8792                    }
8793                    ConstraintType::ComputedColumn => {
8794                        // Find next ComputedColumn constraint
8795                        for constraint in &col.constraints {
8796                            if let ColumnConstraint::ComputedColumn(cc) = constraint {
8797                                self.write_space();
8798                                self.generate_computed_column_inline(cc)?;
8799                                break;
8800                            }
8801                        }
8802                    }
8803                    ConstraintType::GeneratedAsRow => {
8804                        // Find next GeneratedAsRow constraint
8805                        for constraint in &col.constraints {
8806                            if let ColumnConstraint::GeneratedAsRow(gar) = constraint {
8807                                self.write_space();
8808                                self.generate_generated_as_row_inline(gar)?;
8809                                break;
8810                            }
8811                        }
8812                    }
8813                    ConstraintType::OnUpdate => {
8814                        if let Some(ref expr) = col.on_update {
8815                            self.write_space();
8816                            self.write_keyword("ON UPDATE");
8817                            self.write_space();
8818                            self.generate_expression(expr)?;
8819                        }
8820                    }
8821                    ConstraintType::Encode => {
8822                        if let Some(ref encoding) = col.encoding {
8823                            self.write_space();
8824                            self.write_keyword("ENCODE");
8825                            self.write_space();
8826                            self.write(encoding);
8827                        }
8828                    }
8829                    ConstraintType::Path => {
8830                        // Find next Path constraint
8831                        for constraint in &col.constraints {
8832                            if let ColumnConstraint::Path(path_expr) = constraint {
8833                                self.write_space();
8834                                self.write_keyword("PATH");
8835                                self.write_space();
8836                                self.generate_expression(path_expr)?;
8837                                break;
8838                            }
8839                        }
8840                    }
8841                }
8842            }
8843            if pending_not_null_after_identity {
8844                self.write_space();
8845                self.write_keyword("NOT NULL");
8846            }
8847        } else {
8848            // Legacy fixed order for backward compatibility
8849            if col.primary_key {
8850                self.write_space();
8851                self.write_keyword("PRIMARY KEY");
8852                if let Some(ref order) = col.primary_key_order {
8853                    self.write_space();
8854                    match order {
8855                        SortOrder::Asc => self.write_keyword("ASC"),
8856                        SortOrder::Desc => self.write_keyword("DESC"),
8857                    }
8858                }
8859            }
8860
8861            if col.unique {
8862                self.write_space();
8863                self.write_keyword("UNIQUE");
8864                // PostgreSQL 15+: NULLS NOT DISTINCT
8865                if col.unique_nulls_not_distinct {
8866                    self.write(" NULLS NOT DISTINCT");
8867                }
8868            }
8869
8870            match col.nullable {
8871                Some(false) => {
8872                    self.write_space();
8873                    self.write_keyword("NOT NULL");
8874                }
8875                Some(true) => {
8876                    self.write_space();
8877                    self.write_keyword("NULL");
8878                }
8879                None => {}
8880            }
8881
8882            if let Some(ref default) = col.default {
8883                self.write_space();
8884                self.write_keyword("DEFAULT");
8885                self.write_space();
8886                self.generate_expression(default)?;
8887            }
8888
8889            if col.auto_increment {
8890                self.write_space();
8891                self.generate_auto_increment_keyword(col)?;
8892            }
8893
8894            // Column-level constraints from Vec
8895            for constraint in &col.constraints {
8896                match constraint {
8897                    ColumnConstraint::References(fk_ref) => {
8898                        self.write_space();
8899                        if fk_ref.has_foreign_key_keywords {
8900                            self.write_keyword("FOREIGN KEY");
8901                            self.write_space();
8902                        }
8903                        self.write_keyword("REFERENCES");
8904                        self.write_space();
8905                        self.generate_table(&fk_ref.table)?;
8906                        if !fk_ref.columns.is_empty() {
8907                            self.write(" (");
8908                            for (i, c) in fk_ref.columns.iter().enumerate() {
8909                                if i > 0 {
8910                                    self.write(", ");
8911                                }
8912                                self.generate_identifier(c)?;
8913                            }
8914                            self.write(")");
8915                        }
8916                        self.generate_referential_actions(fk_ref)?;
8917                    }
8918                    ColumnConstraint::Check(expr) => {
8919                        self.write_space();
8920                        self.write_keyword("CHECK");
8921                        self.write(" (");
8922                        self.generate_expression(expr)?;
8923                        self.write(")");
8924                    }
8925                    ColumnConstraint::GeneratedAsIdentity(gen) => {
8926                        self.write_space();
8927                        // Redshift uses IDENTITY(start, increment) syntax
8928                        if matches!(
8929                            self.config.dialect,
8930                            Some(crate::dialects::DialectType::Redshift)
8931                        ) {
8932                            self.write_keyword("IDENTITY");
8933                            self.write("(");
8934                            if let Some(ref start) = gen.start {
8935                                self.generate_expression(start)?;
8936                            } else {
8937                                self.write("0");
8938                            }
8939                            self.write(", ");
8940                            if let Some(ref incr) = gen.increment {
8941                                self.generate_expression(incr)?;
8942                            } else {
8943                                self.write("1");
8944                            }
8945                            self.write(")");
8946                        } else {
8947                            self.write_keyword("GENERATED");
8948                            if gen.always {
8949                                self.write_space();
8950                                self.write_keyword("ALWAYS");
8951                            } else {
8952                                self.write_space();
8953                                self.write_keyword("BY DEFAULT");
8954                                if gen.on_null {
8955                                    self.write_space();
8956                                    self.write_keyword("ON NULL");
8957                                }
8958                            }
8959                            self.write_space();
8960                            self.write_keyword("AS IDENTITY");
8961
8962                            let has_options = gen.start.is_some()
8963                                || gen.increment.is_some()
8964                                || gen.minvalue.is_some()
8965                                || gen.maxvalue.is_some()
8966                                || gen.cycle.is_some();
8967                            if has_options {
8968                                self.write(" (");
8969                                let mut first = true;
8970                                if let Some(ref start) = gen.start {
8971                                    if !first {
8972                                        self.write(" ");
8973                                    }
8974                                    first = false;
8975                                    self.write_keyword("START WITH");
8976                                    self.write_space();
8977                                    self.generate_expression(start)?;
8978                                }
8979                                if let Some(ref incr) = gen.increment {
8980                                    if !first {
8981                                        self.write(" ");
8982                                    }
8983                                    first = false;
8984                                    self.write_keyword("INCREMENT BY");
8985                                    self.write_space();
8986                                    self.generate_expression(incr)?;
8987                                }
8988                                if let Some(ref minv) = gen.minvalue {
8989                                    if !first {
8990                                        self.write(" ");
8991                                    }
8992                                    first = false;
8993                                    self.write_keyword("MINVALUE");
8994                                    self.write_space();
8995                                    self.generate_expression(minv)?;
8996                                }
8997                                if let Some(ref maxv) = gen.maxvalue {
8998                                    if !first {
8999                                        self.write(" ");
9000                                    }
9001                                    first = false;
9002                                    self.write_keyword("MAXVALUE");
9003                                    self.write_space();
9004                                    self.generate_expression(maxv)?;
9005                                }
9006                                if let Some(cycle) = gen.cycle {
9007                                    if !first {
9008                                        self.write(" ");
9009                                    }
9010                                    if cycle {
9011                                        self.write_keyword("CYCLE");
9012                                    } else {
9013                                        self.write_keyword("NO CYCLE");
9014                                    }
9015                                }
9016                                self.write(")");
9017                            }
9018                        }
9019                    }
9020                    ColumnConstraint::Collate(collation) => {
9021                        self.write_space();
9022                        self.write_keyword("COLLATE");
9023                        self.write_space();
9024                        self.generate_identifier(collation)?;
9025                    }
9026                    ColumnConstraint::Comment(comment) => {
9027                        self.write_space();
9028                        self.write_keyword("COMMENT");
9029                        self.write_space();
9030                        self.generate_string_literal(comment)?;
9031                    }
9032                    ColumnConstraint::Path(path_expr) => {
9033                        self.write_space();
9034                        self.write_keyword("PATH");
9035                        self.write_space();
9036                        self.generate_expression(path_expr)?;
9037                    }
9038                    _ => {} // Other constraints handled above
9039                }
9040            }
9041
9042            // Redshift: ENCODE encoding_type (legacy path)
9043            if let Some(ref encoding) = col.encoding {
9044                self.write_space();
9045                self.write_keyword("ENCODE");
9046                self.write_space();
9047                self.write(encoding);
9048            }
9049        }
9050
9051        // ClickHouse: CODEC(...)
9052        if let Some(ref codec) = col.codec {
9053            self.write_space();
9054            self.write_keyword("CODEC");
9055            self.write("(");
9056            self.write(codec);
9057            self.write(")");
9058        }
9059
9060        // ClickHouse: EPHEMERAL [expr]
9061        if let Some(ref ephemeral) = col.ephemeral {
9062            self.write_space();
9063            self.write_keyword("EPHEMERAL");
9064            if let Some(ref expr) = ephemeral {
9065                self.write_space();
9066                self.generate_expression(expr)?;
9067            }
9068        }
9069
9070        // ClickHouse: MATERIALIZED expr
9071        if let Some(ref mat_expr) = col.materialized_expr {
9072            self.write_space();
9073            self.write_keyword("MATERIALIZED");
9074            self.write_space();
9075            self.generate_expression(mat_expr)?;
9076        }
9077
9078        // ClickHouse: ALIAS expr
9079        if let Some(ref alias_expr) = col.alias_expr {
9080            self.write_space();
9081            self.write_keyword("ALIAS");
9082            self.write_space();
9083            self.generate_expression(alias_expr)?;
9084        }
9085
9086        // ClickHouse: TTL expr
9087        if let Some(ref ttl_expr) = col.ttl_expr {
9088            self.write_space();
9089            self.write_keyword("TTL");
9090            self.write_space();
9091            self.generate_expression(ttl_expr)?;
9092        }
9093
9094        // TSQL: NOT FOR REPLICATION
9095        if col.not_for_replication
9096            && matches!(
9097                self.config.dialect,
9098                Some(crate::dialects::DialectType::TSQL)
9099                    | Some(crate::dialects::DialectType::Fabric)
9100            )
9101        {
9102            self.write_space();
9103            self.write_keyword("NOT FOR REPLICATION");
9104        }
9105
9106        // BigQuery: OPTIONS (key=value, ...) on column - comes after all constraints
9107        if !col.options.is_empty() {
9108            self.write_space();
9109            self.generate_options_clause(&col.options)?;
9110        }
9111
9112        // SQLite: Inline PRIMARY KEY from table constraint
9113        // This comes at the end, after all existing column constraints
9114        if !col.primary_key
9115            && self
9116                .sqlite_inline_pk_columns
9117                .contains(&col.name.name.to_ascii_lowercase())
9118        {
9119            self.write_space();
9120            self.write_keyword("PRIMARY KEY");
9121        }
9122
9123        // SERIAL expansion: add GENERATED BY DEFAULT AS IDENTITY NOT NULL for PostgreSQL,
9124        // just NOT NULL for Materialize (which strips GENERATED AS IDENTITY)
9125        if serial_expansion.is_some() {
9126            if matches!(self.config.dialect, Some(DialectType::PostgreSQL)) {
9127                self.write_space();
9128                self.write_keyword("GENERATED BY DEFAULT AS IDENTITY NOT NULL");
9129            } else if matches!(self.config.dialect, Some(DialectType::Materialize)) {
9130                self.write_space();
9131                self.write_keyword("NOT NULL");
9132            }
9133        }
9134
9135        Ok(())
9136    }
9137
9138    fn generate_table_constraint(&mut self, constraint: &TableConstraint) -> Result<()> {
9139        match constraint {
9140            TableConstraint::PrimaryKey {
9141                name,
9142                columns,
9143                include_columns,
9144                modifiers,
9145                has_constraint_keyword,
9146            } => {
9147                if let Some(ref n) = name {
9148                    if *has_constraint_keyword {
9149                        self.write_keyword("CONSTRAINT");
9150                        self.write_space();
9151                        self.generate_identifier(n)?;
9152                        self.write_space();
9153                    }
9154                }
9155                self.write_keyword("PRIMARY KEY");
9156                // TSQL CLUSTERED/NONCLUSTERED modifier (before columns)
9157                if let Some(ref clustered) = modifiers.clustered {
9158                    self.write_space();
9159                    self.write_keyword(clustered);
9160                }
9161                // MySQL format: PRIMARY KEY name (cols) when no CONSTRAINT keyword
9162                if let Some(ref n) = name {
9163                    if !*has_constraint_keyword {
9164                        self.write_space();
9165                        self.generate_identifier(n)?;
9166                    }
9167                }
9168                self.write(" (");
9169                for (i, col) in columns.iter().enumerate() {
9170                    if i > 0 {
9171                        self.write(", ");
9172                    }
9173                    self.generate_identifier(col)?;
9174                }
9175                self.write(")");
9176                if !include_columns.is_empty() {
9177                    self.write_space();
9178                    self.write_keyword("INCLUDE");
9179                    self.write(" (");
9180                    for (i, col) in include_columns.iter().enumerate() {
9181                        if i > 0 {
9182                            self.write(", ");
9183                        }
9184                        self.generate_identifier(col)?;
9185                    }
9186                    self.write(")");
9187                }
9188                self.generate_constraint_modifiers(modifiers);
9189            }
9190            TableConstraint::Unique {
9191                name,
9192                columns,
9193                columns_parenthesized,
9194                modifiers,
9195                has_constraint_keyword,
9196                nulls_not_distinct,
9197            } => {
9198                if let Some(ref n) = name {
9199                    if *has_constraint_keyword {
9200                        self.write_keyword("CONSTRAINT");
9201                        self.write_space();
9202                        self.generate_identifier(n)?;
9203                        self.write_space();
9204                    }
9205                }
9206                self.write_keyword("UNIQUE");
9207                // TSQL CLUSTERED/NONCLUSTERED modifier (before columns)
9208                if let Some(ref clustered) = modifiers.clustered {
9209                    self.write_space();
9210                    self.write_keyword(clustered);
9211                }
9212                // PostgreSQL 15+: NULLS NOT DISTINCT
9213                if *nulls_not_distinct {
9214                    self.write(" NULLS NOT DISTINCT");
9215                }
9216                // MySQL format: UNIQUE name (cols) when no CONSTRAINT keyword
9217                if let Some(ref n) = name {
9218                    if !*has_constraint_keyword {
9219                        self.write_space();
9220                        self.generate_identifier(n)?;
9221                    }
9222                }
9223                if *columns_parenthesized {
9224                    self.write(" (");
9225                    for (i, col) in columns.iter().enumerate() {
9226                        if i > 0 {
9227                            self.write(", ");
9228                        }
9229                        self.generate_identifier(col)?;
9230                    }
9231                    self.write(")");
9232                } else {
9233                    // UNIQUE without parentheses (e.g., UNIQUE idx_name)
9234                    for col in columns.iter() {
9235                        self.write_space();
9236                        self.generate_identifier(col)?;
9237                    }
9238                }
9239                self.generate_constraint_modifiers(modifiers);
9240            }
9241            TableConstraint::ForeignKey {
9242                name,
9243                columns,
9244                references,
9245                on_delete,
9246                on_update,
9247                modifiers,
9248            } => {
9249                if let Some(ref n) = name {
9250                    self.write_keyword("CONSTRAINT");
9251                    self.write_space();
9252                    self.generate_identifier(n)?;
9253                    self.write_space();
9254                }
9255                self.write_keyword("FOREIGN KEY");
9256                self.write(" (");
9257                for (i, col) in columns.iter().enumerate() {
9258                    if i > 0 {
9259                        self.write(", ");
9260                    }
9261                    self.generate_identifier(col)?;
9262                }
9263                self.write(")");
9264                if let Some(ref refs) = references {
9265                    self.write(" ");
9266                    self.write_keyword("REFERENCES");
9267                    self.write_space();
9268                    self.generate_table(&refs.table)?;
9269                    if !refs.columns.is_empty() {
9270                        if self.config.pretty {
9271                            self.write(" (");
9272                            self.write_newline();
9273                            self.indent_level += 1;
9274                            for (i, col) in refs.columns.iter().enumerate() {
9275                                if i > 0 {
9276                                    self.write(",");
9277                                    self.write_newline();
9278                                }
9279                                self.write_indent();
9280                                self.generate_identifier(col)?;
9281                            }
9282                            self.indent_level -= 1;
9283                            self.write_newline();
9284                            self.write_indent();
9285                            self.write(")");
9286                        } else {
9287                            self.write(" (");
9288                            for (i, col) in refs.columns.iter().enumerate() {
9289                                if i > 0 {
9290                                    self.write(", ");
9291                                }
9292                                self.generate_identifier(col)?;
9293                            }
9294                            self.write(")");
9295                        }
9296                    }
9297                    self.generate_referential_actions(refs)?;
9298                } else {
9299                    // No REFERENCES - output ON DELETE/ON UPDATE directly
9300                    if let Some(ref action) = on_delete {
9301                        self.write_space();
9302                        self.write_keyword("ON DELETE");
9303                        self.write_space();
9304                        self.generate_referential_action(action);
9305                    }
9306                    if let Some(ref action) = on_update {
9307                        self.write_space();
9308                        self.write_keyword("ON UPDATE");
9309                        self.write_space();
9310                        self.generate_referential_action(action);
9311                    }
9312                }
9313                self.generate_constraint_modifiers(modifiers);
9314            }
9315            TableConstraint::Check {
9316                name,
9317                expression,
9318                modifiers,
9319            } => {
9320                if let Some(ref n) = name {
9321                    self.write_keyword("CONSTRAINT");
9322                    self.write_space();
9323                    self.generate_identifier(n)?;
9324                    self.write_space();
9325                }
9326                self.write_keyword("CHECK");
9327                self.write(" (");
9328                self.generate_expression(expression)?;
9329                self.write(")");
9330                self.generate_constraint_modifiers(modifiers);
9331            }
9332            TableConstraint::Assume { name, expression } => {
9333                if let Some(ref n) = name {
9334                    self.write_keyword("CONSTRAINT");
9335                    self.write_space();
9336                    self.generate_identifier(n)?;
9337                    self.write_space();
9338                }
9339                self.write_keyword("ASSUME");
9340                self.write(" (");
9341                self.generate_expression(expression)?;
9342                self.write(")");
9343            }
9344            TableConstraint::Index {
9345                name,
9346                columns,
9347                kind,
9348                modifiers,
9349                use_key_keyword,
9350                expression,
9351                index_type,
9352                granularity,
9353            } => {
9354                // ClickHouse-style INDEX: INDEX name expr TYPE type_func GRANULARITY n
9355                if expression.is_some() {
9356                    self.write_keyword("INDEX");
9357                    if let Some(ref n) = name {
9358                        self.write_space();
9359                        self.generate_identifier(n)?;
9360                    }
9361                    if let Some(ref expr) = expression {
9362                        self.write_space();
9363                        self.generate_expression(expr)?;
9364                    }
9365                    if let Some(ref idx_type) = index_type {
9366                        self.write_space();
9367                        self.write_keyword("TYPE");
9368                        self.write_space();
9369                        self.generate_expression(idx_type)?;
9370                    }
9371                    if let Some(ref gran) = granularity {
9372                        self.write_space();
9373                        self.write_keyword("GRANULARITY");
9374                        self.write_space();
9375                        self.generate_expression(gran)?;
9376                    }
9377                } else {
9378                    // Standard INDEX syntax
9379                    // Determine the index keyword to use
9380                    // MySQL normalizes KEY to INDEX
9381                    use crate::dialects::DialectType;
9382                    let index_keyword = if *use_key_keyword
9383                        && !matches!(self.config.dialect, Some(DialectType::MySQL))
9384                    {
9385                        "KEY"
9386                    } else {
9387                        "INDEX"
9388                    };
9389
9390                    // Output kind (UNIQUE, FULLTEXT, SPATIAL) if present
9391                    if let Some(ref k) = kind {
9392                        self.write_keyword(k);
9393                        // For UNIQUE, don't add INDEX/KEY keyword
9394                        if k != "UNIQUE" {
9395                            self.write_space();
9396                            self.write_keyword(index_keyword);
9397                        }
9398                    } else {
9399                        self.write_keyword(index_keyword);
9400                    }
9401
9402                    // Output USING before name if using_before_columns is true and there's no name
9403                    if modifiers.using_before_columns && name.is_none() {
9404                        if let Some(ref using) = modifiers.using {
9405                            self.write_space();
9406                            self.write_keyword("USING");
9407                            self.write_space();
9408                            self.write_keyword(using);
9409                        }
9410                    }
9411
9412                    // Output index name if present
9413                    if let Some(ref n) = name {
9414                        self.write_space();
9415                        self.generate_identifier(n)?;
9416                    }
9417
9418                    // Output USING after name but before columns if using_before_columns and there's a name
9419                    if modifiers.using_before_columns && name.is_some() {
9420                        if let Some(ref using) = modifiers.using {
9421                            self.write_space();
9422                            self.write_keyword("USING");
9423                            self.write_space();
9424                            self.write_keyword(using);
9425                        }
9426                    }
9427
9428                    // Output columns
9429                    self.write(" (");
9430                    for (i, col) in columns.iter().enumerate() {
9431                        if i > 0 {
9432                            self.write(", ");
9433                        }
9434                        self.generate_identifier(col)?;
9435                    }
9436                    self.write(")");
9437
9438                    // Output USING after columns if not using_before_columns
9439                    if !modifiers.using_before_columns {
9440                        if let Some(ref using) = modifiers.using {
9441                            self.write_space();
9442                            self.write_keyword("USING");
9443                            self.write_space();
9444                            self.write_keyword(using);
9445                        }
9446                    }
9447
9448                    // Output other constraint modifiers (but skip USING since we already handled it)
9449                    self.generate_constraint_modifiers_without_using(modifiers);
9450                }
9451            }
9452            TableConstraint::Projection { name, expression } => {
9453                // ClickHouse: PROJECTION name (SELECT ...)
9454                self.write_keyword("PROJECTION");
9455                self.write_space();
9456                self.generate_identifier(name)?;
9457                self.write(" (");
9458                self.generate_expression(expression)?;
9459                self.write(")");
9460            }
9461            TableConstraint::Like { source, options } => {
9462                self.write_keyword("LIKE");
9463                self.write_space();
9464                self.generate_table(source)?;
9465                for (action, prop) in options {
9466                    self.write_space();
9467                    match action {
9468                        LikeOptionAction::Including => self.write_keyword("INCLUDING"),
9469                        LikeOptionAction::Excluding => self.write_keyword("EXCLUDING"),
9470                    }
9471                    self.write_space();
9472                    self.write_keyword(prop);
9473                }
9474            }
9475            TableConstraint::PeriodForSystemTime { start_col, end_col } => {
9476                self.write_keyword("PERIOD FOR SYSTEM_TIME");
9477                self.write(" (");
9478                self.generate_identifier(start_col)?;
9479                self.write(", ");
9480                self.generate_identifier(end_col)?;
9481                self.write(")");
9482            }
9483            TableConstraint::Exclude {
9484                name,
9485                using,
9486                elements,
9487                include_columns,
9488                where_clause,
9489                with_params,
9490                using_index_tablespace,
9491                modifiers: _,
9492            } => {
9493                if let Some(ref n) = name {
9494                    self.write_keyword("CONSTRAINT");
9495                    self.write_space();
9496                    self.generate_identifier(n)?;
9497                    self.write_space();
9498                }
9499                self.write_keyword("EXCLUDE");
9500                if let Some(ref method) = using {
9501                    self.write_space();
9502                    self.write_keyword("USING");
9503                    self.write_space();
9504                    self.write(method);
9505                    self.write("(");
9506                } else {
9507                    self.write(" (");
9508                }
9509                for (i, elem) in elements.iter().enumerate() {
9510                    if i > 0 {
9511                        self.write(", ");
9512                    }
9513                    self.write(&elem.expression);
9514                    self.write_space();
9515                    self.write_keyword("WITH");
9516                    self.write_space();
9517                    self.write(&elem.operator);
9518                }
9519                self.write(")");
9520                if !include_columns.is_empty() {
9521                    self.write_space();
9522                    self.write_keyword("INCLUDE");
9523                    self.write(" (");
9524                    for (i, col) in include_columns.iter().enumerate() {
9525                        if i > 0 {
9526                            self.write(", ");
9527                        }
9528                        self.generate_identifier(col)?;
9529                    }
9530                    self.write(")");
9531                }
9532                if !with_params.is_empty() {
9533                    self.write_space();
9534                    self.write_keyword("WITH");
9535                    self.write(" (");
9536                    for (i, (key, val)) in with_params.iter().enumerate() {
9537                        if i > 0 {
9538                            self.write(", ");
9539                        }
9540                        self.write(key);
9541                        self.write("=");
9542                        self.write(val);
9543                    }
9544                    self.write(")");
9545                }
9546                if let Some(ref tablespace) = using_index_tablespace {
9547                    self.write_space();
9548                    self.write_keyword("USING INDEX TABLESPACE");
9549                    self.write_space();
9550                    self.write(tablespace);
9551                }
9552                if let Some(ref where_expr) = where_clause {
9553                    self.write_space();
9554                    self.write_keyword("WHERE");
9555                    self.write(" (");
9556                    self.generate_expression(where_expr)?;
9557                    self.write(")");
9558                }
9559            }
9560            TableConstraint::Tags(tags) => {
9561                self.write_keyword("TAG");
9562                self.write(" (");
9563                for (i, expr) in tags.expressions.iter().enumerate() {
9564                    if i > 0 {
9565                        self.write(", ");
9566                    }
9567                    self.generate_expression(expr)?;
9568                }
9569                self.write(")");
9570            }
9571            TableConstraint::InitiallyDeferred { deferred } => {
9572                self.write_keyword("INITIALLY");
9573                self.write_space();
9574                if *deferred {
9575                    self.write_keyword("DEFERRED");
9576                } else {
9577                    self.write_keyword("IMMEDIATE");
9578                }
9579            }
9580        }
9581        Ok(())
9582    }
9583
9584    fn generate_constraint_modifiers(&mut self, modifiers: &ConstraintModifiers) {
9585        // Output USING BTREE/HASH (MySQL) - comes first
9586        if let Some(using) = &modifiers.using {
9587            self.write_space();
9588            self.write_keyword("USING");
9589            self.write_space();
9590            self.write_keyword(using);
9591        }
9592        // Output ENFORCED/NOT ENFORCED
9593        if let Some(enforced) = modifiers.enforced {
9594            self.write_space();
9595            if enforced {
9596                self.write_keyword("ENFORCED");
9597            } else {
9598                self.write_keyword("NOT ENFORCED");
9599            }
9600        }
9601        // Output DEFERRABLE/NOT DEFERRABLE
9602        if let Some(deferrable) = modifiers.deferrable {
9603            self.write_space();
9604            if deferrable {
9605                self.write_keyword("DEFERRABLE");
9606            } else {
9607                self.write_keyword("NOT DEFERRABLE");
9608            }
9609        }
9610        // Output INITIALLY DEFERRED/INITIALLY IMMEDIATE
9611        if let Some(initially_deferred) = modifiers.initially_deferred {
9612            self.write_space();
9613            if initially_deferred {
9614                self.write_keyword("INITIALLY DEFERRED");
9615            } else {
9616                self.write_keyword("INITIALLY IMMEDIATE");
9617            }
9618        }
9619        // Output NORELY
9620        if modifiers.norely {
9621            self.write_space();
9622            self.write_keyword("NORELY");
9623        }
9624        // Output RELY
9625        if modifiers.rely {
9626            self.write_space();
9627            self.write_keyword("RELY");
9628        }
9629        // Output NOT VALID (PostgreSQL)
9630        if modifiers.not_valid {
9631            self.write_space();
9632            self.write_keyword("NOT VALID");
9633        }
9634        // Output ON CONFLICT (SQLite)
9635        if let Some(on_conflict) = &modifiers.on_conflict {
9636            self.write_space();
9637            self.write_keyword("ON CONFLICT");
9638            self.write_space();
9639            self.write_keyword(on_conflict);
9640        }
9641        // Output TSQL WITH options (PAD_INDEX=ON, STATISTICS_NORECOMPUTE=OFF, ...)
9642        if !modifiers.with_options.is_empty() {
9643            self.write_space();
9644            self.write_keyword("WITH");
9645            self.write(" (");
9646            for (i, (key, value)) in modifiers.with_options.iter().enumerate() {
9647                if i > 0 {
9648                    self.write(", ");
9649                }
9650                self.write(key);
9651                self.write("=");
9652                self.write(value);
9653            }
9654            self.write(")");
9655        }
9656        // Output TSQL ON filegroup
9657        if let Some(ref fg) = modifiers.on_filegroup {
9658            self.write_space();
9659            self.write_keyword("ON");
9660            self.write_space();
9661            let _ = self.generate_identifier(fg);
9662        }
9663    }
9664
9665    /// Generate constraint modifiers without USING (for Index constraints where USING is handled separately)
9666    fn generate_constraint_modifiers_without_using(&mut self, modifiers: &ConstraintModifiers) {
9667        // Output ENFORCED/NOT ENFORCED
9668        if let Some(enforced) = modifiers.enforced {
9669            self.write_space();
9670            if enforced {
9671                self.write_keyword("ENFORCED");
9672            } else {
9673                self.write_keyword("NOT ENFORCED");
9674            }
9675        }
9676        // Output DEFERRABLE/NOT DEFERRABLE
9677        if let Some(deferrable) = modifiers.deferrable {
9678            self.write_space();
9679            if deferrable {
9680                self.write_keyword("DEFERRABLE");
9681            } else {
9682                self.write_keyword("NOT DEFERRABLE");
9683            }
9684        }
9685        // Output INITIALLY DEFERRED/INITIALLY IMMEDIATE
9686        if let Some(initially_deferred) = modifiers.initially_deferred {
9687            self.write_space();
9688            if initially_deferred {
9689                self.write_keyword("INITIALLY DEFERRED");
9690            } else {
9691                self.write_keyword("INITIALLY IMMEDIATE");
9692            }
9693        }
9694        // Output NORELY
9695        if modifiers.norely {
9696            self.write_space();
9697            self.write_keyword("NORELY");
9698        }
9699        // Output RELY
9700        if modifiers.rely {
9701            self.write_space();
9702            self.write_keyword("RELY");
9703        }
9704        // Output NOT VALID (PostgreSQL)
9705        if modifiers.not_valid {
9706            self.write_space();
9707            self.write_keyword("NOT VALID");
9708        }
9709        // Output ON CONFLICT (SQLite)
9710        if let Some(on_conflict) = &modifiers.on_conflict {
9711            self.write_space();
9712            self.write_keyword("ON CONFLICT");
9713            self.write_space();
9714            self.write_keyword(on_conflict);
9715        }
9716        // Output MySQL index-specific modifiers
9717        self.generate_index_specific_modifiers(modifiers);
9718    }
9719
9720    /// Generate MySQL index-specific modifiers (COMMENT, VISIBLE, ENGINE_ATTRIBUTE, WITH PARSER)
9721    fn generate_index_specific_modifiers(&mut self, modifiers: &ConstraintModifiers) {
9722        if let Some(ref comment) = modifiers.comment {
9723            self.write_space();
9724            self.write_keyword("COMMENT");
9725            self.write(" '");
9726            self.write(comment);
9727            self.write("'");
9728        }
9729        if let Some(visible) = modifiers.visible {
9730            self.write_space();
9731            if visible {
9732                self.write_keyword("VISIBLE");
9733            } else {
9734                self.write_keyword("INVISIBLE");
9735            }
9736        }
9737        if let Some(ref attr) = modifiers.engine_attribute {
9738            self.write_space();
9739            self.write_keyword("ENGINE_ATTRIBUTE");
9740            self.write(" = '");
9741            self.write(attr);
9742            self.write("'");
9743        }
9744        if let Some(ref parser) = modifiers.with_parser {
9745            self.write_space();
9746            self.write_keyword("WITH PARSER");
9747            self.write_space();
9748            self.write(parser);
9749        }
9750    }
9751
9752    fn generate_referential_actions(&mut self, fk_ref: &ForeignKeyRef) -> Result<()> {
9753        // MATCH clause before ON DELETE/ON UPDATE (default position, e.g. PostgreSQL)
9754        if !fk_ref.match_after_actions {
9755            if let Some(ref match_type) = fk_ref.match_type {
9756                self.write_space();
9757                self.write_keyword("MATCH");
9758                self.write_space();
9759                match match_type {
9760                    MatchType::Full => self.write_keyword("FULL"),
9761                    MatchType::Partial => self.write_keyword("PARTIAL"),
9762                    MatchType::Simple => self.write_keyword("SIMPLE"),
9763                }
9764            }
9765        }
9766
9767        // Output ON UPDATE and ON DELETE in the original order
9768        if fk_ref.on_update_first {
9769            if let Some(ref action) = fk_ref.on_update {
9770                self.write_space();
9771                self.write_keyword("ON UPDATE");
9772                self.write_space();
9773                self.generate_referential_action(action);
9774            }
9775            if let Some(ref action) = fk_ref.on_delete {
9776                self.write_space();
9777                self.write_keyword("ON DELETE");
9778                self.write_space();
9779                self.generate_referential_action(action);
9780            }
9781        } else {
9782            if let Some(ref action) = fk_ref.on_delete {
9783                self.write_space();
9784                self.write_keyword("ON DELETE");
9785                self.write_space();
9786                self.generate_referential_action(action);
9787            }
9788            if let Some(ref action) = fk_ref.on_update {
9789                self.write_space();
9790                self.write_keyword("ON UPDATE");
9791                self.write_space();
9792                self.generate_referential_action(action);
9793            }
9794        }
9795
9796        // MATCH clause after ON DELETE/ON UPDATE (when original SQL had it after)
9797        if fk_ref.match_after_actions {
9798            if let Some(ref match_type) = fk_ref.match_type {
9799                self.write_space();
9800                self.write_keyword("MATCH");
9801                self.write_space();
9802                match match_type {
9803                    MatchType::Full => self.write_keyword("FULL"),
9804                    MatchType::Partial => self.write_keyword("PARTIAL"),
9805                    MatchType::Simple => self.write_keyword("SIMPLE"),
9806                }
9807            }
9808        }
9809
9810        // DEFERRABLE / NOT DEFERRABLE
9811        if let Some(deferrable) = fk_ref.deferrable {
9812            self.write_space();
9813            if deferrable {
9814                self.write_keyword("DEFERRABLE");
9815            } else {
9816                self.write_keyword("NOT DEFERRABLE");
9817            }
9818        }
9819
9820        Ok(())
9821    }
9822
9823    fn generate_referential_action(&mut self, action: &ReferentialAction) {
9824        match action {
9825            ReferentialAction::Cascade => self.write_keyword("CASCADE"),
9826            ReferentialAction::SetNull => self.write_keyword("SET NULL"),
9827            ReferentialAction::SetDefault => self.write_keyword("SET DEFAULT"),
9828            ReferentialAction::Restrict => self.write_keyword("RESTRICT"),
9829            ReferentialAction::NoAction => self.write_keyword("NO ACTION"),
9830        }
9831    }
9832
9833    fn generate_drop_table(&mut self, dt: &DropTable) -> Result<()> {
9834        // TSQL: IF NOT OBJECT_ID(...) IS NULL BEGIN DROP TABLE ...; END
9835        if let Some(ref object_id_args) = dt.object_id_args {
9836            if matches!(
9837                self.config.dialect,
9838                Some(crate::dialects::DialectType::TSQL)
9839                    | Some(crate::dialects::DialectType::Fabric)
9840            ) {
9841                self.write_keyword("IF NOT OBJECT_ID");
9842                self.write("(");
9843                self.write(object_id_args);
9844                self.write(")");
9845                self.write_space();
9846                self.write_keyword("IS NULL BEGIN DROP TABLE");
9847                self.write_space();
9848                for (i, table) in dt.names.iter().enumerate() {
9849                    if i > 0 {
9850                        self.write(", ");
9851                    }
9852                    self.generate_table(table)?;
9853                }
9854                self.write("; ");
9855                self.write_keyword("END");
9856                return Ok(());
9857            }
9858        }
9859
9860        // Athena: DROP TABLE uses Hive engine (backticks)
9861        let saved_athena_hive_context = self.athena_hive_context;
9862        if matches!(
9863            self.config.dialect,
9864            Some(crate::dialects::DialectType::Athena)
9865        ) {
9866            self.athena_hive_context = true;
9867        }
9868
9869        // Output leading comments (e.g., "-- comment\nDROP TABLE ...")
9870        for comment in &dt.leading_comments {
9871            self.write_formatted_comment(comment);
9872            self.write_space();
9873        }
9874        if dt.iceberg {
9875            self.write_keyword("DROP ICEBERG TABLE");
9876        } else {
9877            self.write_keyword("DROP TABLE");
9878        }
9879
9880        if dt.if_exists {
9881            self.write_space();
9882            self.write_keyword("IF EXISTS");
9883        }
9884
9885        self.write_space();
9886        for (i, table) in dt.names.iter().enumerate() {
9887            if i > 0 {
9888                self.write(", ");
9889            }
9890            self.generate_table(table)?;
9891        }
9892
9893        if dt.cascade_constraints {
9894            self.write_space();
9895            self.write_keyword("CASCADE CONSTRAINTS");
9896        } else if dt.cascade {
9897            self.write_space();
9898            self.write_keyword("CASCADE");
9899        }
9900
9901        if dt.restrict {
9902            self.write_space();
9903            self.write_keyword("RESTRICT");
9904        }
9905
9906        if dt.purge {
9907            self.write_space();
9908            self.write_keyword("PURGE");
9909        }
9910
9911        if dt.sync {
9912            self.write_space();
9913            self.write_keyword("SYNC");
9914        }
9915
9916        // Restore Athena Hive context
9917        self.athena_hive_context = saved_athena_hive_context;
9918
9919        Ok(())
9920    }
9921
9922    fn generate_alter_table(&mut self, at: &AlterTable) -> Result<()> {
9923        // Athena: ALTER TABLE uses Hive engine (backticks)
9924        let saved_athena_hive_context = self.athena_hive_context;
9925        if matches!(
9926            self.config.dialect,
9927            Some(crate::dialects::DialectType::Athena)
9928        ) {
9929            self.athena_hive_context = true;
9930        }
9931
9932        self.write_keyword("ALTER");
9933        // Write table modifier (e.g., ICEBERG) unless target is DuckDB
9934        if let Some(ref modifier) = at.table_modifier {
9935            if !matches!(
9936                self.config.dialect,
9937                Some(crate::dialects::DialectType::DuckDB)
9938            ) {
9939                self.write_space();
9940                self.write_keyword(modifier);
9941            }
9942        }
9943        self.write(" ");
9944        self.write_keyword("TABLE");
9945        if at.if_exists {
9946            self.write_space();
9947            self.write_keyword("IF EXISTS");
9948        }
9949        self.write_space();
9950        self.generate_table(&at.name)?;
9951
9952        // ClickHouse: ON CLUSTER clause
9953        if let Some(ref on_cluster) = at.on_cluster {
9954            self.write_space();
9955            self.generate_on_cluster(on_cluster)?;
9956        }
9957
9958        // Hive: PARTITION(key=value, ...) clause
9959        if let Some(ref partition) = at.partition {
9960            self.write_space();
9961            self.write_keyword("PARTITION");
9962            self.write("(");
9963            for (i, (key, value)) in partition.iter().enumerate() {
9964                if i > 0 {
9965                    self.write(", ");
9966                }
9967                self.generate_identifier(key)?;
9968                self.write(" = ");
9969                self.generate_expression(value)?;
9970            }
9971            self.write(")");
9972        }
9973
9974        // TSQL: WITH CHECK / WITH NOCHECK modifier
9975        if let Some(ref with_check) = at.with_check {
9976            self.write_space();
9977            self.write_keyword(with_check);
9978        }
9979
9980        if self.config.pretty {
9981            // In pretty mode, format actions with newlines and indentation
9982            self.write_newline();
9983            self.indent_level += 1;
9984            for (i, action) in at.actions.iter().enumerate() {
9985                // Check if this is a continuation of previous ADD COLUMN or ADD CONSTRAINT
9986                let is_continuation = i > 0
9987                    && matches!(
9988                        (&at.actions[i - 1], action),
9989                        (
9990                            AlterTableAction::AddColumn { .. },
9991                            AlterTableAction::AddColumn { .. }
9992                        ) | (
9993                            AlterTableAction::AddConstraint(_),
9994                            AlterTableAction::AddConstraint(_)
9995                        )
9996                    );
9997                if i > 0 {
9998                    self.write(",");
9999                    self.write_newline();
10000                }
10001                self.write_indent();
10002                self.generate_alter_action_with_continuation(action, is_continuation)?;
10003            }
10004            self.indent_level -= 1;
10005        } else {
10006            for (i, action) in at.actions.iter().enumerate() {
10007                // Check if this is a continuation of previous ADD COLUMN or ADD CONSTRAINT
10008                let is_continuation = i > 0
10009                    && matches!(
10010                        (&at.actions[i - 1], action),
10011                        (
10012                            AlterTableAction::AddColumn { .. },
10013                            AlterTableAction::AddColumn { .. }
10014                        ) | (
10015                            AlterTableAction::AddConstraint(_),
10016                            AlterTableAction::AddConstraint(_)
10017                        )
10018                    );
10019                if i > 0 {
10020                    self.write(",");
10021                }
10022                self.write_space();
10023                self.generate_alter_action_with_continuation(action, is_continuation)?;
10024            }
10025        }
10026
10027        // MySQL ALTER TABLE trailing options
10028        if let Some(ref algorithm) = at.algorithm {
10029            self.write(", ");
10030            self.write_keyword("ALGORITHM");
10031            self.write("=");
10032            self.write_keyword(algorithm);
10033        }
10034        if let Some(ref lock) = at.lock {
10035            self.write(", ");
10036            self.write_keyword("LOCK");
10037            self.write("=");
10038            self.write_keyword(lock);
10039        }
10040
10041        // Restore Athena Hive context
10042        self.athena_hive_context = saved_athena_hive_context;
10043
10044        Ok(())
10045    }
10046
10047    fn generate_alter_action_with_continuation(
10048        &mut self,
10049        action: &AlterTableAction,
10050        is_continuation: bool,
10051    ) -> Result<()> {
10052        match action {
10053            AlterTableAction::AddColumn {
10054                column,
10055                if_not_exists,
10056                position,
10057            } => {
10058                use crate::dialects::DialectType;
10059                // For Snowflake: consecutive ADD COLUMN actions are combined with commas
10060                // e.g., "ADD col1, col2" instead of "ADD col1, ADD col2"
10061                // For other dialects, repeat ADD COLUMN for each
10062                let is_snowflake = matches!(self.config.dialect, Some(DialectType::Snowflake));
10063                let is_tsql_like = matches!(
10064                    self.config.dialect,
10065                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
10066                );
10067                // Athena uses "ADD COLUMNS (col_def)" instead of "ADD COLUMN col_def"
10068                let is_athena = matches!(self.config.dialect, Some(DialectType::Athena));
10069
10070                if is_continuation && (is_snowflake || is_tsql_like) {
10071                    // Don't write ADD keyword for continuation in Snowflake/TSQL
10072                } else if is_snowflake {
10073                    self.write_keyword("ADD");
10074                    self.write_space();
10075                } else if is_athena {
10076                    // Athena uses ADD COLUMNS (col_def) syntax
10077                    self.write_keyword("ADD COLUMNS");
10078                    self.write(" (");
10079                } else if self.config.alter_table_include_column_keyword {
10080                    self.write_keyword("ADD COLUMN");
10081                    self.write_space();
10082                } else {
10083                    // Dialects like Oracle and TSQL don't use COLUMN keyword
10084                    self.write_keyword("ADD");
10085                    self.write_space();
10086                }
10087
10088                if *if_not_exists {
10089                    self.write_keyword("IF NOT EXISTS");
10090                    self.write_space();
10091                }
10092                self.generate_column_def(column)?;
10093
10094                // Close parenthesis for Athena
10095                if is_athena {
10096                    self.write(")");
10097                }
10098
10099                // Column position (FIRST or AFTER)
10100                if let Some(pos) = position {
10101                    self.write_space();
10102                    match pos {
10103                        ColumnPosition::First => self.write_keyword("FIRST"),
10104                        ColumnPosition::After(col_name) => {
10105                            self.write_keyword("AFTER");
10106                            self.write_space();
10107                            self.generate_identifier(col_name)?;
10108                        }
10109                    }
10110                }
10111            }
10112            AlterTableAction::DropColumn {
10113                name,
10114                if_exists,
10115                cascade,
10116            } => {
10117                self.write_keyword("DROP COLUMN");
10118                if *if_exists {
10119                    self.write_space();
10120                    self.write_keyword("IF EXISTS");
10121                }
10122                self.write_space();
10123                self.generate_identifier(name)?;
10124                if *cascade {
10125                    self.write_space();
10126                    self.write_keyword("CASCADE");
10127                }
10128            }
10129            AlterTableAction::DropColumns { names } => {
10130                self.write_keyword("DROP COLUMNS");
10131                self.write(" (");
10132                for (i, name) in names.iter().enumerate() {
10133                    if i > 0 {
10134                        self.write(", ");
10135                    }
10136                    self.generate_identifier(name)?;
10137                }
10138                self.write(")");
10139            }
10140            AlterTableAction::RenameColumn {
10141                old_name,
10142                new_name,
10143                if_exists,
10144            } => {
10145                self.write_keyword("RENAME COLUMN");
10146                if *if_exists {
10147                    self.write_space();
10148                    self.write_keyword("IF EXISTS");
10149                }
10150                self.write_space();
10151                self.generate_identifier(old_name)?;
10152                self.write_space();
10153                self.write_keyword("TO");
10154                self.write_space();
10155                self.generate_identifier(new_name)?;
10156            }
10157            AlterTableAction::AlterColumn {
10158                name,
10159                action,
10160                use_modify_keyword,
10161            } => {
10162                use crate::dialects::DialectType;
10163                // MySQL uses MODIFY COLUMN for type changes (SetDataType)
10164                // but ALTER COLUMN for SET DEFAULT, DROP DEFAULT, etc.
10165                let use_modify = *use_modify_keyword
10166                    || (matches!(self.config.dialect, Some(DialectType::MySQL))
10167                        && matches!(action, AlterColumnAction::SetDataType { .. }));
10168                if use_modify {
10169                    self.write_keyword("MODIFY COLUMN");
10170                    self.write_space();
10171                    self.generate_identifier(name)?;
10172                    // For MODIFY COLUMN, output the type directly
10173                    if let AlterColumnAction::SetDataType {
10174                        data_type,
10175                        using: _,
10176                        collate,
10177                    } = action
10178                    {
10179                        self.write_space();
10180                        self.generate_data_type(data_type)?;
10181                        // Output COLLATE clause if present
10182                        if let Some(collate_name) = collate {
10183                            self.write_space();
10184                            self.write_keyword("COLLATE");
10185                            self.write_space();
10186                            // Output as single-quoted string
10187                            self.write(&format!("'{}'", collate_name));
10188                        }
10189                    } else {
10190                        self.write_space();
10191                        self.generate_alter_column_action(action)?;
10192                    }
10193                } else if matches!(self.config.dialect, Some(DialectType::Hive))
10194                    && matches!(action, AlterColumnAction::SetDataType { .. })
10195                {
10196                    // Hive uses CHANGE COLUMN col_name col_name NEW_TYPE
10197                    self.write_keyword("CHANGE COLUMN");
10198                    self.write_space();
10199                    self.generate_identifier(name)?;
10200                    self.write_space();
10201                    self.generate_identifier(name)?;
10202                    if let AlterColumnAction::SetDataType { data_type, .. } = action {
10203                        self.write_space();
10204                        self.generate_data_type(data_type)?;
10205                    }
10206                } else {
10207                    self.write_keyword("ALTER COLUMN");
10208                    self.write_space();
10209                    self.generate_identifier(name)?;
10210                    self.write_space();
10211                    self.generate_alter_column_action(action)?;
10212                }
10213            }
10214            AlterTableAction::RenameTable(new_name) => {
10215                // MySQL-like dialects (MySQL, Doris, StarRocks) use RENAME without TO
10216                let mysql_like = matches!(
10217                    self.config.dialect,
10218                    Some(DialectType::MySQL)
10219                        | Some(DialectType::Doris)
10220                        | Some(DialectType::StarRocks)
10221                        | Some(DialectType::SingleStore)
10222                );
10223                if mysql_like {
10224                    self.write_keyword("RENAME");
10225                } else {
10226                    self.write_keyword("RENAME TO");
10227                }
10228                self.write_space();
10229                // Doris, DuckDB, BigQuery, PostgreSQL strip schema/catalog from target table
10230                let rename_table_with_db = !matches!(
10231                    self.config.dialect,
10232                    Some(DialectType::Doris)
10233                        | Some(DialectType::DuckDB)
10234                        | Some(DialectType::BigQuery)
10235                        | Some(DialectType::PostgreSQL)
10236                );
10237                if !rename_table_with_db {
10238                    let mut stripped = new_name.clone();
10239                    stripped.schema = None;
10240                    stripped.catalog = None;
10241                    self.generate_table(&stripped)?;
10242                } else {
10243                    self.generate_table(new_name)?;
10244                }
10245            }
10246            AlterTableAction::AddConstraint(constraint) => {
10247                // For consecutive ADD CONSTRAINT actions (is_continuation=true), skip ADD keyword
10248                // to produce: ADD CONSTRAINT c1 ..., CONSTRAINT c2 ...
10249                if !is_continuation {
10250                    self.write_keyword("ADD");
10251                    self.write_space();
10252                }
10253                self.generate_table_constraint(constraint)?;
10254            }
10255            AlterTableAction::DropConstraint { name, if_exists } => {
10256                self.write_keyword("DROP CONSTRAINT");
10257                if *if_exists {
10258                    self.write_space();
10259                    self.write_keyword("IF EXISTS");
10260                }
10261                self.write_space();
10262                self.generate_identifier(name)?;
10263            }
10264            AlterTableAction::DropForeignKey { name } => {
10265                self.write_keyword("DROP FOREIGN KEY");
10266                self.write_space();
10267                self.generate_identifier(name)?;
10268            }
10269            AlterTableAction::DropPartition {
10270                partitions,
10271                if_exists,
10272            } => {
10273                self.write_keyword("DROP");
10274                if *if_exists {
10275                    self.write_space();
10276                    self.write_keyword("IF EXISTS");
10277                }
10278                for (i, partition) in partitions.iter().enumerate() {
10279                    if i > 0 {
10280                        self.write(",");
10281                    }
10282                    self.write_space();
10283                    self.write_keyword("PARTITION");
10284                    // Check for special ClickHouse partition formats
10285                    if partition.len() == 1 && partition[0].0.name == "__expr__" {
10286                        // ClickHouse: PARTITION <expression>
10287                        self.write_space();
10288                        self.generate_expression(&partition[0].1)?;
10289                    } else if partition.len() == 1 && partition[0].0.name == "ALL" {
10290                        // ClickHouse: PARTITION ALL
10291                        self.write_space();
10292                        self.write_keyword("ALL");
10293                    } else if partition.len() == 1 && partition[0].0.name == "ID" {
10294                        // ClickHouse: PARTITION ID 'string'
10295                        self.write_space();
10296                        self.write_keyword("ID");
10297                        self.write_space();
10298                        self.generate_expression(&partition[0].1)?;
10299                    } else {
10300                        // Standard SQL: PARTITION(key=value, ...)
10301                        self.write("(");
10302                        for (j, (key, value)) in partition.iter().enumerate() {
10303                            if j > 0 {
10304                                self.write(", ");
10305                            }
10306                            self.generate_identifier(key)?;
10307                            self.write(" = ");
10308                            self.generate_expression(value)?;
10309                        }
10310                        self.write(")");
10311                    }
10312                }
10313            }
10314            AlterTableAction::Delete { where_clause } => {
10315                self.write_keyword("DELETE");
10316                self.write_space();
10317                self.write_keyword("WHERE");
10318                self.write_space();
10319                self.generate_expression(where_clause)?;
10320            }
10321            AlterTableAction::SwapWith(target) => {
10322                self.write_keyword("SWAP WITH");
10323                self.write_space();
10324                self.generate_table(target)?;
10325            }
10326            AlterTableAction::SetProperty { properties } => {
10327                use crate::dialects::DialectType;
10328                self.write_keyword("SET");
10329                // Trino/Presto use SET PROPERTIES syntax with spaces around =
10330                let is_trino_presto = matches!(
10331                    self.config.dialect,
10332                    Some(DialectType::Trino) | Some(DialectType::Presto)
10333                );
10334                if is_trino_presto {
10335                    self.write_space();
10336                    self.write_keyword("PROPERTIES");
10337                }
10338                let eq = if is_trino_presto { " = " } else { "=" };
10339                for (i, (key, value)) in properties.iter().enumerate() {
10340                    if i > 0 {
10341                        self.write(",");
10342                    }
10343                    self.write_space();
10344                    // Handle quoted property names for Trino
10345                    if key.contains(' ') {
10346                        self.generate_string_literal(key)?;
10347                    } else {
10348                        self.write(key);
10349                    }
10350                    self.write(eq);
10351                    self.generate_expression(value)?;
10352                }
10353            }
10354            AlterTableAction::UnsetProperty { properties } => {
10355                self.write_keyword("UNSET");
10356                for (i, name) in properties.iter().enumerate() {
10357                    if i > 0 {
10358                        self.write(",");
10359                    }
10360                    self.write_space();
10361                    self.write(name);
10362                }
10363            }
10364            AlterTableAction::ClusterBy { expressions } => {
10365                self.write_keyword("CLUSTER BY");
10366                self.write(" (");
10367                for (i, expr) in expressions.iter().enumerate() {
10368                    if i > 0 {
10369                        self.write(", ");
10370                    }
10371                    self.generate_expression(expr)?;
10372                }
10373                self.write(")");
10374            }
10375            AlterTableAction::SetTag { expressions } => {
10376                self.write_keyword("SET TAG");
10377                for (i, (key, value)) in expressions.iter().enumerate() {
10378                    if i > 0 {
10379                        self.write(",");
10380                    }
10381                    self.write_space();
10382                    self.write(key);
10383                    self.write(" = ");
10384                    self.generate_expression(value)?;
10385                }
10386            }
10387            AlterTableAction::UnsetTag { names } => {
10388                self.write_keyword("UNSET TAG");
10389                for (i, name) in names.iter().enumerate() {
10390                    if i > 0 {
10391                        self.write(",");
10392                    }
10393                    self.write_space();
10394                    self.write(name);
10395                }
10396            }
10397            AlterTableAction::SetOptions { expressions } => {
10398                self.write_keyword("SET");
10399                self.write(" (");
10400                for (i, expr) in expressions.iter().enumerate() {
10401                    if i > 0 {
10402                        self.write(", ");
10403                    }
10404                    self.generate_expression(expr)?;
10405                }
10406                self.write(")");
10407            }
10408            AlterTableAction::AlterIndex { name, visible } => {
10409                self.write_keyword("ALTER INDEX");
10410                self.write_space();
10411                self.generate_identifier(name)?;
10412                self.write_space();
10413                if *visible {
10414                    self.write_keyword("VISIBLE");
10415                } else {
10416                    self.write_keyword("INVISIBLE");
10417                }
10418            }
10419            AlterTableAction::SetAttribute { attribute } => {
10420                self.write_keyword("SET");
10421                self.write_space();
10422                self.write_keyword(attribute);
10423            }
10424            AlterTableAction::SetStageFileFormat { options } => {
10425                self.write_keyword("SET");
10426                self.write_space();
10427                self.write_keyword("STAGE_FILE_FORMAT");
10428                self.write(" = (");
10429                if let Some(opts) = options {
10430                    self.generate_space_separated_properties(opts)?;
10431                }
10432                self.write(")");
10433            }
10434            AlterTableAction::SetStageCopyOptions { options } => {
10435                self.write_keyword("SET");
10436                self.write_space();
10437                self.write_keyword("STAGE_COPY_OPTIONS");
10438                self.write(" = (");
10439                if let Some(opts) = options {
10440                    self.generate_space_separated_properties(opts)?;
10441                }
10442                self.write(")");
10443            }
10444            AlterTableAction::AddColumns { columns, cascade } => {
10445                // Oracle uses ADD (...) without COLUMNS keyword
10446                // Hive/Spark uses ADD COLUMNS (...)
10447                let is_oracle = matches!(self.config.dialect, Some(DialectType::Oracle));
10448                if is_oracle {
10449                    self.write_keyword("ADD");
10450                } else {
10451                    self.write_keyword("ADD COLUMNS");
10452                }
10453                self.write(" (");
10454                for (i, col) in columns.iter().enumerate() {
10455                    if i > 0 {
10456                        self.write(", ");
10457                    }
10458                    self.generate_column_def(col)?;
10459                }
10460                self.write(")");
10461                if *cascade {
10462                    self.write_space();
10463                    self.write_keyword("CASCADE");
10464                }
10465            }
10466            AlterTableAction::ChangeColumn {
10467                old_name,
10468                new_name,
10469                data_type,
10470                comment,
10471                cascade,
10472            } => {
10473                use crate::dialects::DialectType;
10474                let is_spark = matches!(
10475                    self.config.dialect,
10476                    Some(DialectType::Spark) | Some(DialectType::Databricks)
10477                );
10478                let is_rename = old_name.name != new_name.name;
10479
10480                if is_spark {
10481                    if is_rename {
10482                        // Spark: RENAME COLUMN old TO new
10483                        self.write_keyword("RENAME COLUMN");
10484                        self.write_space();
10485                        self.generate_identifier(old_name)?;
10486                        self.write_space();
10487                        self.write_keyword("TO");
10488                        self.write_space();
10489                        self.generate_identifier(new_name)?;
10490                    } else if comment.is_some() {
10491                        // Spark: ALTER COLUMN old COMMENT 'comment'
10492                        self.write_keyword("ALTER COLUMN");
10493                        self.write_space();
10494                        self.generate_identifier(old_name)?;
10495                        self.write_space();
10496                        self.write_keyword("COMMENT");
10497                        self.write_space();
10498                        self.write("'");
10499                        self.write(comment.as_ref().unwrap());
10500                        self.write("'");
10501                    } else if data_type.is_some() {
10502                        // Spark: ALTER COLUMN old TYPE data_type
10503                        self.write_keyword("ALTER COLUMN");
10504                        self.write_space();
10505                        self.generate_identifier(old_name)?;
10506                        self.write_space();
10507                        self.write_keyword("TYPE");
10508                        self.write_space();
10509                        self.generate_data_type(data_type.as_ref().unwrap())?;
10510                    } else {
10511                        // Fallback to CHANGE COLUMN
10512                        self.write_keyword("CHANGE COLUMN");
10513                        self.write_space();
10514                        self.generate_identifier(old_name)?;
10515                        self.write_space();
10516                        self.generate_identifier(new_name)?;
10517                    }
10518                } else {
10519                    // Hive/MySQL/default: CHANGE [COLUMN] old new [type] [COMMENT '...'] [CASCADE]
10520                    if data_type.is_some() {
10521                        self.write_keyword("CHANGE COLUMN");
10522                    } else {
10523                        self.write_keyword("CHANGE");
10524                    }
10525                    self.write_space();
10526                    self.generate_identifier(old_name)?;
10527                    self.write_space();
10528                    self.generate_identifier(new_name)?;
10529                    if let Some(ref dt) = data_type {
10530                        self.write_space();
10531                        self.generate_data_type(dt)?;
10532                    }
10533                    if let Some(ref c) = comment {
10534                        self.write_space();
10535                        self.write_keyword("COMMENT");
10536                        self.write_space();
10537                        self.write("'");
10538                        self.write(c);
10539                        self.write("'");
10540                    }
10541                    if *cascade {
10542                        self.write_space();
10543                        self.write_keyword("CASCADE");
10544                    }
10545                }
10546            }
10547            AlterTableAction::AddPartition {
10548                partition,
10549                if_not_exists,
10550                location,
10551            } => {
10552                self.write_keyword("ADD");
10553                self.write_space();
10554                if *if_not_exists {
10555                    self.write_keyword("IF NOT EXISTS");
10556                    self.write_space();
10557                }
10558                self.generate_expression(partition)?;
10559                if let Some(ref loc) = location {
10560                    self.write_space();
10561                    self.write_keyword("LOCATION");
10562                    self.write_space();
10563                    self.generate_expression(loc)?;
10564                }
10565            }
10566            AlterTableAction::AlterSortKey {
10567                this,
10568                expressions,
10569                compound,
10570            } => {
10571                // Redshift: ALTER [COMPOUND] SORTKEY AUTO|NONE|(col1, col2)
10572                self.write_keyword("ALTER");
10573                if *compound {
10574                    self.write_space();
10575                    self.write_keyword("COMPOUND");
10576                }
10577                self.write_space();
10578                self.write_keyword("SORTKEY");
10579                self.write_space();
10580                if let Some(style) = this {
10581                    self.write_keyword(style);
10582                } else if !expressions.is_empty() {
10583                    self.write("(");
10584                    for (i, expr) in expressions.iter().enumerate() {
10585                        if i > 0 {
10586                            self.write(", ");
10587                        }
10588                        self.generate_expression(expr)?;
10589                    }
10590                    self.write(")");
10591                }
10592            }
10593            AlterTableAction::AlterDistStyle { style, distkey } => {
10594                // Redshift: ALTER DISTSTYLE ALL|EVEN|AUTO|KEY [DISTKEY col]
10595                self.write_keyword("ALTER");
10596                self.write_space();
10597                self.write_keyword("DISTSTYLE");
10598                self.write_space();
10599                self.write_keyword(style);
10600                if let Some(col) = distkey {
10601                    self.write_space();
10602                    self.write_keyword("DISTKEY");
10603                    self.write_space();
10604                    self.generate_identifier(col)?;
10605                }
10606            }
10607            AlterTableAction::SetTableProperties { properties } => {
10608                // Redshift: SET TABLE PROPERTIES ('a' = '5', 'b' = 'c')
10609                self.write_keyword("SET TABLE PROPERTIES");
10610                self.write(" (");
10611                for (i, (key, value)) in properties.iter().enumerate() {
10612                    if i > 0 {
10613                        self.write(", ");
10614                    }
10615                    self.generate_expression(key)?;
10616                    self.write(" = ");
10617                    self.generate_expression(value)?;
10618                }
10619                self.write(")");
10620            }
10621            AlterTableAction::SetLocation { location } => {
10622                // Redshift: SET LOCATION 's3://bucket/folder/'
10623                self.write_keyword("SET LOCATION");
10624                self.write_space();
10625                self.write("'");
10626                self.write(location);
10627                self.write("'");
10628            }
10629            AlterTableAction::SetFileFormat { format } => {
10630                // Redshift: SET FILE FORMAT AVRO
10631                self.write_keyword("SET FILE FORMAT");
10632                self.write_space();
10633                self.write_keyword(format);
10634            }
10635            AlterTableAction::ReplacePartition { partition, source } => {
10636                // ClickHouse: REPLACE PARTITION expr FROM source
10637                self.write_keyword("REPLACE PARTITION");
10638                self.write_space();
10639                self.generate_expression(partition)?;
10640                if let Some(src) = source {
10641                    self.write_space();
10642                    self.write_keyword("FROM");
10643                    self.write_space();
10644                    self.generate_expression(src)?;
10645                }
10646            }
10647            AlterTableAction::Raw { sql } => {
10648                self.write(sql);
10649            }
10650        }
10651        Ok(())
10652    }
10653
10654    fn generate_alter_column_action(&mut self, action: &AlterColumnAction) -> Result<()> {
10655        match action {
10656            AlterColumnAction::SetDataType {
10657                data_type,
10658                using,
10659                collate,
10660            } => {
10661                use crate::dialects::DialectType;
10662                // Dialect-specific type change syntax:
10663                // - TSQL/Fabric/Hive: no prefix (ALTER COLUMN col datatype)
10664                // - Redshift/Spark: TYPE (ALTER COLUMN col TYPE datatype)
10665                // - Default: SET DATA TYPE (ALTER COLUMN col SET DATA TYPE datatype)
10666                let is_no_prefix = matches!(
10667                    self.config.dialect,
10668                    Some(DialectType::TSQL) | Some(DialectType::Fabric) | Some(DialectType::Hive)
10669                );
10670                let is_type_only = matches!(
10671                    self.config.dialect,
10672                    Some(DialectType::Redshift)
10673                        | Some(DialectType::Spark)
10674                        | Some(DialectType::Databricks)
10675                );
10676                if is_type_only {
10677                    self.write_keyword("TYPE");
10678                    self.write_space();
10679                } else if !is_no_prefix {
10680                    self.write_keyword("SET DATA TYPE");
10681                    self.write_space();
10682                }
10683                self.generate_data_type(data_type)?;
10684                if let Some(ref collation) = collate {
10685                    self.write_space();
10686                    self.write_keyword("COLLATE");
10687                    self.write_space();
10688                    self.write(collation);
10689                }
10690                if let Some(ref using_expr) = using {
10691                    self.write_space();
10692                    self.write_keyword("USING");
10693                    self.write_space();
10694                    self.generate_expression(using_expr)?;
10695                }
10696            }
10697            AlterColumnAction::SetDefault(expr) => {
10698                self.write_keyword("SET DEFAULT");
10699                self.write_space();
10700                self.generate_expression(expr)?;
10701            }
10702            AlterColumnAction::DropDefault => {
10703                self.write_keyword("DROP DEFAULT");
10704            }
10705            AlterColumnAction::SetNotNull => {
10706                self.write_keyword("SET NOT NULL");
10707            }
10708            AlterColumnAction::DropNotNull => {
10709                self.write_keyword("DROP NOT NULL");
10710            }
10711            AlterColumnAction::Comment(comment) => {
10712                self.write_keyword("COMMENT");
10713                self.write_space();
10714                self.generate_string_literal(comment)?;
10715            }
10716            AlterColumnAction::SetVisible => {
10717                self.write_keyword("SET VISIBLE");
10718            }
10719            AlterColumnAction::SetInvisible => {
10720                self.write_keyword("SET INVISIBLE");
10721            }
10722        }
10723        Ok(())
10724    }
10725
10726    fn generate_create_index(&mut self, ci: &CreateIndex) -> Result<()> {
10727        self.write_keyword("CREATE");
10728
10729        if ci.unique {
10730            self.write_space();
10731            self.write_keyword("UNIQUE");
10732        }
10733
10734        // TSQL CLUSTERED/NONCLUSTERED modifier
10735        if let Some(ref clustered) = ci.clustered {
10736            self.write_space();
10737            self.write_keyword(clustered);
10738        }
10739
10740        self.write_space();
10741        self.write_keyword("INDEX");
10742
10743        // PostgreSQL CONCURRENTLY modifier
10744        if ci.concurrently {
10745            self.write_space();
10746            self.write_keyword("CONCURRENTLY");
10747        }
10748
10749        if ci.if_not_exists {
10750            self.write_space();
10751            self.write_keyword("IF NOT EXISTS");
10752        }
10753
10754        // Index name is optional in PostgreSQL when IF NOT EXISTS is specified
10755        if !ci.name.name.is_empty() {
10756            self.write_space();
10757            self.generate_identifier(&ci.name)?;
10758        }
10759        self.write_space();
10760        self.write_keyword("ON");
10761        // Hive uses ON TABLE
10762        if matches!(self.config.dialect, Some(DialectType::Hive)) {
10763            self.write_space();
10764            self.write_keyword("TABLE");
10765        }
10766        self.write_space();
10767        self.generate_table(&ci.table)?;
10768
10769        // Column list (optional for COLUMNSTORE indexes)
10770        // Standard SQL convention: ON t(a) without space before paren
10771        if !ci.columns.is_empty() || ci.using.is_some() {
10772            let space_before_paren = false;
10773
10774            if let Some(ref using) = ci.using {
10775                self.write_space();
10776                self.write_keyword("USING");
10777                self.write_space();
10778                self.write(using);
10779                if space_before_paren {
10780                    self.write(" (");
10781                } else {
10782                    self.write("(");
10783                }
10784            } else {
10785                if space_before_paren {
10786                    self.write(" (");
10787                } else {
10788                    self.write("(");
10789                }
10790            }
10791            for (i, col) in ci.columns.iter().enumerate() {
10792                if i > 0 {
10793                    self.write(", ");
10794                }
10795                self.generate_identifier(&col.column)?;
10796                if let Some(ref opclass) = col.opclass {
10797                    self.write_space();
10798                    self.write(opclass);
10799                }
10800                if col.desc {
10801                    self.write_space();
10802                    self.write_keyword("DESC");
10803                } else if col.asc {
10804                    self.write_space();
10805                    self.write_keyword("ASC");
10806                }
10807                if let Some(nulls_first) = col.nulls_first {
10808                    self.write_space();
10809                    self.write_keyword("NULLS");
10810                    self.write_space();
10811                    self.write_keyword(if nulls_first { "FIRST" } else { "LAST" });
10812                }
10813            }
10814            self.write(")");
10815        }
10816
10817        // PostgreSQL INCLUDE (col1, col2) clause
10818        if !ci.include_columns.is_empty() {
10819            self.write_space();
10820            self.write_keyword("INCLUDE");
10821            self.write(" (");
10822            for (i, col) in ci.include_columns.iter().enumerate() {
10823                if i > 0 {
10824                    self.write(", ");
10825                }
10826                self.generate_identifier(col)?;
10827            }
10828            self.write(")");
10829        }
10830
10831        // TSQL: WITH (option=value, ...) clause
10832        if !ci.with_options.is_empty() {
10833            self.write_space();
10834            self.write_keyword("WITH");
10835            self.write(" (");
10836            for (i, (key, value)) in ci.with_options.iter().enumerate() {
10837                if i > 0 {
10838                    self.write(", ");
10839                }
10840                self.write(key);
10841                self.write("=");
10842                self.write(value);
10843            }
10844            self.write(")");
10845        }
10846
10847        // PostgreSQL WHERE clause for partial indexes
10848        if let Some(ref where_clause) = ci.where_clause {
10849            self.write_space();
10850            self.write_keyword("WHERE");
10851            self.write_space();
10852            self.generate_expression(where_clause)?;
10853        }
10854
10855        // TSQL: ON filegroup or partition scheme clause
10856        if let Some(ref on_fg) = ci.on_filegroup {
10857            self.write_space();
10858            self.write_keyword("ON");
10859            self.write_space();
10860            self.write(on_fg);
10861        }
10862
10863        Ok(())
10864    }
10865
10866    fn generate_drop_index(&mut self, di: &DropIndex) -> Result<()> {
10867        self.write_keyword("DROP INDEX");
10868
10869        if di.concurrently {
10870            self.write_space();
10871            self.write_keyword("CONCURRENTLY");
10872        }
10873
10874        if di.if_exists {
10875            self.write_space();
10876            self.write_keyword("IF EXISTS");
10877        }
10878
10879        self.write_space();
10880        self.generate_identifier(&di.name)?;
10881
10882        if let Some(ref table) = di.table {
10883            self.write_space();
10884            self.write_keyword("ON");
10885            self.write_space();
10886            self.generate_table(table)?;
10887        }
10888
10889        Ok(())
10890    }
10891
10892    fn generate_create_view(&mut self, cv: &CreateView) -> Result<()> {
10893        self.write_keyword("CREATE");
10894
10895        // MySQL: ALGORITHM=...
10896        if let Some(ref algorithm) = cv.algorithm {
10897            self.write_space();
10898            self.write_keyword("ALGORITHM");
10899            self.write("=");
10900            self.write_keyword(algorithm);
10901        }
10902
10903        // MySQL: DEFINER=...
10904        if let Some(ref definer) = cv.definer {
10905            self.write_space();
10906            self.write_keyword("DEFINER");
10907            self.write("=");
10908            self.write(definer);
10909        }
10910
10911        // MySQL: SQL SECURITY DEFINER/INVOKER (before VIEW keyword, unless it appeared after view name)
10912        if cv.security_sql_style && !cv.security_after_name {
10913            if let Some(ref security) = cv.security {
10914                self.write_space();
10915                self.write_keyword("SQL SECURITY");
10916                self.write_space();
10917                match security {
10918                    FunctionSecurity::Definer => self.write_keyword("DEFINER"),
10919                    FunctionSecurity::Invoker => self.write_keyword("INVOKER"),
10920                    FunctionSecurity::None => self.write_keyword("NONE"),
10921                }
10922            }
10923        }
10924
10925        if cv.or_alter {
10926            self.write_space();
10927            self.write_keyword("OR ALTER");
10928        } else if cv.or_replace {
10929            self.write_space();
10930            self.write_keyword("OR REPLACE");
10931        }
10932
10933        if cv.temporary {
10934            self.write_space();
10935            self.write_keyword("TEMPORARY");
10936        }
10937
10938        if cv.materialized {
10939            self.write_space();
10940            self.write_keyword("MATERIALIZED");
10941        }
10942
10943        // Snowflake: SECURE VIEW
10944        if cv.secure {
10945            self.write_space();
10946            self.write_keyword("SECURE");
10947        }
10948
10949        self.write_space();
10950        self.write_keyword("VIEW");
10951
10952        if cv.if_not_exists {
10953            self.write_space();
10954            self.write_keyword("IF NOT EXISTS");
10955        }
10956
10957        self.write_space();
10958        self.generate_table(&cv.name)?;
10959
10960        // ClickHouse: ON CLUSTER clause
10961        if let Some(ref on_cluster) = cv.on_cluster {
10962            self.write_space();
10963            self.generate_on_cluster(on_cluster)?;
10964        }
10965
10966        // ClickHouse: TO destination_table
10967        if let Some(ref to_table) = cv.to_table {
10968            self.write_space();
10969            self.write_keyword("TO");
10970            self.write_space();
10971            self.generate_table(to_table)?;
10972        }
10973
10974        // For regular VIEW: columns come before COPY GRANTS
10975        // For MATERIALIZED VIEW: COPY GRANTS comes before columns
10976        if !cv.materialized {
10977            // Regular VIEW: columns first
10978            if !cv.columns.is_empty() {
10979                self.write(" (");
10980                for (i, col) in cv.columns.iter().enumerate() {
10981                    if i > 0 {
10982                        self.write(", ");
10983                    }
10984                    self.generate_identifier(&col.name)?;
10985                    // BigQuery: OPTIONS (key=value, ...) on view column
10986                    if !col.options.is_empty() {
10987                        self.write_space();
10988                        self.generate_options_clause(&col.options)?;
10989                    }
10990                    if let Some(ref comment) = col.comment {
10991                        self.write_space();
10992                        self.write_keyword("COMMENT");
10993                        self.write_space();
10994                        self.generate_string_literal(comment)?;
10995                    }
10996                }
10997                self.write(")");
10998            }
10999
11000            // Presto/Trino/StarRocks: SECURITY DEFINER/INVOKER/NONE (after columns)
11001            // Also handles SQL SECURITY after view name (security_after_name)
11002            if !cv.security_sql_style || cv.security_after_name {
11003                if let Some(ref security) = cv.security {
11004                    self.write_space();
11005                    if cv.security_sql_style {
11006                        self.write_keyword("SQL SECURITY");
11007                    } else {
11008                        self.write_keyword("SECURITY");
11009                    }
11010                    self.write_space();
11011                    match security {
11012                        FunctionSecurity::Definer => self.write_keyword("DEFINER"),
11013                        FunctionSecurity::Invoker => self.write_keyword("INVOKER"),
11014                        FunctionSecurity::None => self.write_keyword("NONE"),
11015                    }
11016                }
11017            }
11018
11019            // Snowflake: COPY GRANTS
11020            if cv.copy_grants {
11021                self.write_space();
11022                self.write_keyword("COPY GRANTS");
11023            }
11024        } else {
11025            // MATERIALIZED VIEW: COPY GRANTS first
11026            if cv.copy_grants {
11027                self.write_space();
11028                self.write_keyword("COPY GRANTS");
11029            }
11030
11031            // Doris: If we have a schema (typed columns), generate that instead
11032            if let Some(ref schema) = cv.schema {
11033                self.write(" (");
11034                for (i, expr) in schema.expressions.iter().enumerate() {
11035                    if i > 0 {
11036                        self.write(", ");
11037                    }
11038                    self.generate_expression(expr)?;
11039                }
11040                self.write(")");
11041            } else if !cv.columns.is_empty() {
11042                // Then columns (simple column names without types)
11043                self.write(" (");
11044                for (i, col) in cv.columns.iter().enumerate() {
11045                    if i > 0 {
11046                        self.write(", ");
11047                    }
11048                    self.generate_identifier(&col.name)?;
11049                    // BigQuery: OPTIONS (key=value, ...) on view column
11050                    if !col.options.is_empty() {
11051                        self.write_space();
11052                        self.generate_options_clause(&col.options)?;
11053                    }
11054                    if let Some(ref comment) = col.comment {
11055                        self.write_space();
11056                        self.write_keyword("COMMENT");
11057                        self.write_space();
11058                        self.generate_string_literal(comment)?;
11059                    }
11060                }
11061                self.write(")");
11062            }
11063
11064            // Doris: KEY (columns) for materialized views
11065            if let Some(ref unique_key) = cv.unique_key {
11066                self.write_space();
11067                self.write_keyword("KEY");
11068                self.write(" (");
11069                for (i, expr) in unique_key.expressions.iter().enumerate() {
11070                    if i > 0 {
11071                        self.write(", ");
11072                    }
11073                    self.generate_expression(expr)?;
11074                }
11075                self.write(")");
11076            }
11077        }
11078
11079        // Snowflake: COMMENT = 'text'
11080        if let Some(ref comment) = cv.comment {
11081            self.write_space();
11082            self.write_keyword("COMMENT");
11083            self.write("=");
11084            self.generate_string_literal(comment)?;
11085        }
11086
11087        // Snowflake: TAG (name='value', ...)
11088        if !cv.tags.is_empty() {
11089            self.write_space();
11090            self.write_keyword("TAG");
11091            self.write(" (");
11092            for (i, (name, value)) in cv.tags.iter().enumerate() {
11093                if i > 0 {
11094                    self.write(", ");
11095                }
11096                self.write(name);
11097                self.write("='");
11098                self.write(value);
11099                self.write("'");
11100            }
11101            self.write(")");
11102        }
11103
11104        // BigQuery: OPTIONS (key=value, ...)
11105        if !cv.options.is_empty() {
11106            self.write_space();
11107            self.generate_options_clause(&cv.options)?;
11108        }
11109
11110        // Doris: BUILD IMMEDIATE/DEFERRED for materialized views
11111        if let Some(ref build) = cv.build {
11112            self.write_space();
11113            self.write_keyword("BUILD");
11114            self.write_space();
11115            self.write_keyword(build);
11116        }
11117
11118        // Doris: REFRESH clause for materialized views
11119        if let Some(ref refresh) = cv.refresh {
11120            self.write_space();
11121            self.generate_refresh_trigger_property(refresh)?;
11122        }
11123
11124        // Redshift: AUTO REFRESH YES|NO for materialized views
11125        if let Some(auto_refresh) = cv.auto_refresh {
11126            self.write_space();
11127            self.write_keyword("AUTO REFRESH");
11128            self.write_space();
11129            if auto_refresh {
11130                self.write_keyword("YES");
11131            } else {
11132                self.write_keyword("NO");
11133            }
11134        }
11135
11136        // ClickHouse: Table properties (ENGINE, ORDER BY, SAMPLE, SETTINGS, TTL, etc.)
11137        for prop in &cv.table_properties {
11138            self.write_space();
11139            self.generate_expression(prop)?;
11140        }
11141
11142        // Only output AS clause if there's a real query (not just NULL placeholder)
11143        if !matches!(&cv.query, Expression::Null(_)) {
11144            self.write_space();
11145            self.write_keyword("AS");
11146            self.write_space();
11147
11148            // Teradata: LOCKING clause (between AS and query)
11149            if let Some(ref mode) = cv.locking_mode {
11150                self.write_keyword("LOCKING");
11151                self.write_space();
11152                self.write_keyword(mode);
11153                if let Some(ref access) = cv.locking_access {
11154                    self.write_space();
11155                    self.write_keyword("FOR");
11156                    self.write_space();
11157                    self.write_keyword(access);
11158                }
11159                self.write_space();
11160            }
11161
11162            if cv.query_parenthesized {
11163                self.write("(");
11164            }
11165            self.generate_expression(&cv.query)?;
11166            if cv.query_parenthesized {
11167                self.write(")");
11168            }
11169        }
11170
11171        // Redshift: WITH NO SCHEMA BINDING (after query)
11172        if cv.no_schema_binding {
11173            self.write_space();
11174            self.write_keyword("WITH NO SCHEMA BINDING");
11175        }
11176
11177        Ok(())
11178    }
11179
11180    fn generate_drop_view(&mut self, dv: &DropView) -> Result<()> {
11181        self.write_keyword("DROP");
11182
11183        if dv.materialized {
11184            self.write_space();
11185            self.write_keyword("MATERIALIZED");
11186        }
11187
11188        self.write_space();
11189        self.write_keyword("VIEW");
11190
11191        if dv.if_exists {
11192            self.write_space();
11193            self.write_keyword("IF EXISTS");
11194        }
11195
11196        self.write_space();
11197        self.generate_table(&dv.name)?;
11198
11199        Ok(())
11200    }
11201
11202    fn generate_truncate(&mut self, tr: &Truncate) -> Result<()> {
11203        match tr.target {
11204            TruncateTarget::Database => self.write_keyword("TRUNCATE DATABASE"),
11205            TruncateTarget::Table => self.write_keyword("TRUNCATE TABLE"),
11206        }
11207        if tr.if_exists {
11208            self.write_space();
11209            self.write_keyword("IF EXISTS");
11210        }
11211        self.write_space();
11212        self.generate_table(&tr.table)?;
11213
11214        // ClickHouse: ON CLUSTER clause
11215        if let Some(ref on_cluster) = tr.on_cluster {
11216            self.write_space();
11217            self.generate_on_cluster(on_cluster)?;
11218        }
11219
11220        // Check if first table has a * (multi-table with star)
11221        if !tr.extra_tables.is_empty() {
11222            // Check if the first entry matches the main table (star case)
11223            let skip_first = if let Some(first) = tr.extra_tables.first() {
11224                first.table.name == tr.table.name && first.star
11225            } else {
11226                false
11227            };
11228
11229            // PostgreSQL normalizes away the * suffix (it's the default behavior)
11230            let strip_star = matches!(
11231                self.config.dialect,
11232                Some(crate::dialects::DialectType::PostgreSQL)
11233                    | Some(crate::dialects::DialectType::Redshift)
11234            );
11235            if skip_first && !strip_star {
11236                self.write("*");
11237            }
11238
11239            // Generate additional tables
11240            for (i, entry) in tr.extra_tables.iter().enumerate() {
11241                if i == 0 && skip_first {
11242                    continue; // Already handled the star for first table
11243                }
11244                self.write(", ");
11245                self.generate_table(&entry.table)?;
11246                if entry.star && !strip_star {
11247                    self.write("*");
11248                }
11249            }
11250        }
11251
11252        // RESTART/CONTINUE IDENTITY
11253        if let Some(identity) = &tr.identity {
11254            self.write_space();
11255            match identity {
11256                TruncateIdentity::Restart => self.write_keyword("RESTART IDENTITY"),
11257                TruncateIdentity::Continue => self.write_keyword("CONTINUE IDENTITY"),
11258            }
11259        }
11260
11261        if tr.cascade {
11262            self.write_space();
11263            self.write_keyword("CASCADE");
11264        }
11265
11266        if tr.restrict {
11267            self.write_space();
11268            self.write_keyword("RESTRICT");
11269        }
11270
11271        // Output Hive PARTITION clause
11272        if let Some(ref partition) = tr.partition {
11273            self.write_space();
11274            self.generate_expression(partition)?;
11275        }
11276
11277        Ok(())
11278    }
11279
11280    fn generate_use(&mut self, u: &Use) -> Result<()> {
11281        // Teradata uses "DATABASE <name>" instead of "USE <name>"
11282        if matches!(self.config.dialect, Some(DialectType::Teradata)) {
11283            self.write_keyword("DATABASE");
11284            self.write_space();
11285            self.generate_identifier(&u.this)?;
11286            return Ok(());
11287        }
11288
11289        self.write_keyword("USE");
11290
11291        if let Some(kind) = &u.kind {
11292            self.write_space();
11293            match kind {
11294                UseKind::Database => self.write_keyword("DATABASE"),
11295                UseKind::Schema => self.write_keyword("SCHEMA"),
11296                UseKind::Role => self.write_keyword("ROLE"),
11297                UseKind::Warehouse => self.write_keyword("WAREHOUSE"),
11298                UseKind::Catalog => self.write_keyword("CATALOG"),
11299                UseKind::SecondaryRoles => self.write_keyword("SECONDARY ROLES"),
11300            }
11301        }
11302
11303        self.write_space();
11304        // For SECONDARY ROLES, write the value as-is (ALL, NONE, or role names)
11305        // without quoting, since these are keywords not identifiers
11306        if matches!(&u.kind, Some(UseKind::SecondaryRoles)) {
11307            self.write(&u.this.name);
11308        } else {
11309            self.generate_identifier(&u.this)?;
11310        }
11311        Ok(())
11312    }
11313
11314    fn generate_cache(&mut self, c: &Cache) -> Result<()> {
11315        self.write_keyword("CACHE");
11316        if c.lazy {
11317            self.write_space();
11318            self.write_keyword("LAZY");
11319        }
11320        self.write_space();
11321        self.write_keyword("TABLE");
11322        self.write_space();
11323        self.generate_identifier(&c.table)?;
11324
11325        // OPTIONS clause
11326        if !c.options.is_empty() {
11327            self.write_space();
11328            self.write_keyword("OPTIONS");
11329            self.write("(");
11330            for (i, (key, value)) in c.options.iter().enumerate() {
11331                if i > 0 {
11332                    self.write(", ");
11333                }
11334                self.generate_expression(key)?;
11335                self.write(" = ");
11336                self.generate_expression(value)?;
11337            }
11338            self.write(")");
11339        }
11340
11341        // AS query
11342        if let Some(query) = &c.query {
11343            self.write_space();
11344            self.write_keyword("AS");
11345            self.write_space();
11346            self.generate_expression(query)?;
11347        }
11348
11349        Ok(())
11350    }
11351
11352    fn generate_uncache(&mut self, u: &Uncache) -> Result<()> {
11353        self.write_keyword("UNCACHE TABLE");
11354        if u.if_exists {
11355            self.write_space();
11356            self.write_keyword("IF EXISTS");
11357        }
11358        self.write_space();
11359        self.generate_identifier(&u.table)?;
11360        Ok(())
11361    }
11362
11363    fn generate_load_data(&mut self, l: &LoadData) -> Result<()> {
11364        self.write_keyword("LOAD DATA");
11365        if l.local {
11366            self.write_space();
11367            self.write_keyword("LOCAL");
11368        }
11369        self.write_space();
11370        self.write_keyword("INPATH");
11371        self.write_space();
11372        self.write("'");
11373        self.write(&l.inpath);
11374        self.write("'");
11375
11376        if l.overwrite {
11377            self.write_space();
11378            self.write_keyword("OVERWRITE");
11379        }
11380
11381        self.write_space();
11382        self.write_keyword("INTO TABLE");
11383        self.write_space();
11384        self.generate_expression(&l.table)?;
11385
11386        // PARTITION clause
11387        if !l.partition.is_empty() {
11388            self.write_space();
11389            self.write_keyword("PARTITION");
11390            self.write("(");
11391            for (i, (col, val)) in l.partition.iter().enumerate() {
11392                if i > 0 {
11393                    self.write(", ");
11394                }
11395                self.generate_identifier(col)?;
11396                self.write(" = ");
11397                self.generate_expression(val)?;
11398            }
11399            self.write(")");
11400        }
11401
11402        // INPUTFORMAT clause
11403        if let Some(fmt) = &l.input_format {
11404            self.write_space();
11405            self.write_keyword("INPUTFORMAT");
11406            self.write_space();
11407            self.write("'");
11408            self.write(fmt);
11409            self.write("'");
11410        }
11411
11412        // SERDE clause
11413        if let Some(serde) = &l.serde {
11414            self.write_space();
11415            self.write_keyword("SERDE");
11416            self.write_space();
11417            self.write("'");
11418            self.write(serde);
11419            self.write("'");
11420        }
11421
11422        Ok(())
11423    }
11424
11425    fn generate_pragma(&mut self, p: &Pragma) -> Result<()> {
11426        self.write_keyword("PRAGMA");
11427        self.write_space();
11428
11429        // Schema prefix if present
11430        if let Some(schema) = &p.schema {
11431            self.generate_identifier(schema)?;
11432            self.write(".");
11433        }
11434
11435        // Pragma name
11436        self.generate_identifier(&p.name)?;
11437
11438        // Value assignment or function call
11439        if let Some(value) = &p.value {
11440            self.write(" = ");
11441            self.generate_expression(value)?;
11442        } else if !p.args.is_empty() {
11443            self.write("(");
11444            for (i, arg) in p.args.iter().enumerate() {
11445                if i > 0 {
11446                    self.write(", ");
11447                }
11448                self.generate_expression(arg)?;
11449            }
11450            self.write(")");
11451        }
11452
11453        Ok(())
11454    }
11455
11456    fn generate_grant(&mut self, g: &Grant) -> Result<()> {
11457        self.write_keyword("GRANT");
11458        self.write_space();
11459
11460        // Privileges (with optional column lists)
11461        for (i, privilege) in g.privileges.iter().enumerate() {
11462            if i > 0 {
11463                self.write(", ");
11464            }
11465            self.write_keyword(&privilege.name);
11466            // Output column list if present: SELECT(col1, col2)
11467            if !privilege.columns.is_empty() {
11468                self.write("(");
11469                for (j, col) in privilege.columns.iter().enumerate() {
11470                    if j > 0 {
11471                        self.write(", ");
11472                    }
11473                    self.write(col);
11474                }
11475                self.write(")");
11476            }
11477        }
11478
11479        self.write_space();
11480        self.write_keyword("ON");
11481        self.write_space();
11482
11483        // Object kind (TABLE, SCHEMA, etc.)
11484        if let Some(kind) = &g.kind {
11485            self.write_keyword(kind);
11486            self.write_space();
11487        }
11488
11489        // Securable - normalize function/procedure names to uppercase for PostgreSQL family
11490        {
11491            use crate::dialects::DialectType;
11492            let should_upper = matches!(
11493                self.config.dialect,
11494                Some(DialectType::PostgreSQL)
11495                    | Some(DialectType::CockroachDB)
11496                    | Some(DialectType::Materialize)
11497                    | Some(DialectType::RisingWave)
11498            ) && (g.kind.as_deref() == Some("FUNCTION")
11499                || g.kind.as_deref() == Some("PROCEDURE"));
11500            if should_upper {
11501                use crate::expressions::Identifier;
11502                let upper_id = Identifier {
11503                    name: g.securable.name.to_ascii_uppercase(),
11504                    quoted: g.securable.quoted,
11505                    ..g.securable.clone()
11506                };
11507                self.generate_identifier(&upper_id)?;
11508            } else {
11509                self.generate_identifier(&g.securable)?;
11510            }
11511        }
11512
11513        // Function parameter types (if present)
11514        if !g.function_params.is_empty() {
11515            self.write("(");
11516            for (i, param) in g.function_params.iter().enumerate() {
11517                if i > 0 {
11518                    self.write(", ");
11519                }
11520                self.write(param);
11521            }
11522            self.write(")");
11523        }
11524
11525        self.write_space();
11526        self.write_keyword("TO");
11527        self.write_space();
11528
11529        // Principals
11530        for (i, principal) in g.principals.iter().enumerate() {
11531            if i > 0 {
11532                self.write(", ");
11533            }
11534            if principal.is_role {
11535                self.write_keyword("ROLE");
11536                self.write_space();
11537            } else if principal.is_group {
11538                self.write_keyword("GROUP");
11539                self.write_space();
11540            } else if principal.is_share {
11541                self.write_keyword("SHARE");
11542                self.write_space();
11543            }
11544            self.generate_identifier(&principal.name)?;
11545        }
11546
11547        // WITH GRANT OPTION
11548        if g.grant_option {
11549            self.write_space();
11550            self.write_keyword("WITH GRANT OPTION");
11551        }
11552
11553        // TSQL: AS principal
11554        if let Some(ref principal) = g.as_principal {
11555            self.write_space();
11556            self.write_keyword("AS");
11557            self.write_space();
11558            self.generate_identifier(principal)?;
11559        }
11560
11561        Ok(())
11562    }
11563
11564    fn generate_revoke(&mut self, r: &Revoke) -> Result<()> {
11565        self.write_keyword("REVOKE");
11566        self.write_space();
11567
11568        // GRANT OPTION FOR
11569        if r.grant_option {
11570            self.write_keyword("GRANT OPTION FOR");
11571            self.write_space();
11572        }
11573
11574        // Privileges (with optional column lists)
11575        for (i, privilege) in r.privileges.iter().enumerate() {
11576            if i > 0 {
11577                self.write(", ");
11578            }
11579            self.write_keyword(&privilege.name);
11580            // Output column list if present: SELECT(col1, col2)
11581            if !privilege.columns.is_empty() {
11582                self.write("(");
11583                for (j, col) in privilege.columns.iter().enumerate() {
11584                    if j > 0 {
11585                        self.write(", ");
11586                    }
11587                    self.write(col);
11588                }
11589                self.write(")");
11590            }
11591        }
11592
11593        self.write_space();
11594        self.write_keyword("ON");
11595        self.write_space();
11596
11597        // Object kind
11598        if let Some(kind) = &r.kind {
11599            self.write_keyword(kind);
11600            self.write_space();
11601        }
11602
11603        // Securable - normalize function/procedure names to uppercase for PostgreSQL family
11604        {
11605            use crate::dialects::DialectType;
11606            let should_upper = matches!(
11607                self.config.dialect,
11608                Some(DialectType::PostgreSQL)
11609                    | Some(DialectType::CockroachDB)
11610                    | Some(DialectType::Materialize)
11611                    | Some(DialectType::RisingWave)
11612            ) && (r.kind.as_deref() == Some("FUNCTION")
11613                || r.kind.as_deref() == Some("PROCEDURE"));
11614            if should_upper {
11615                use crate::expressions::Identifier;
11616                let upper_id = Identifier {
11617                    name: r.securable.name.to_ascii_uppercase(),
11618                    quoted: r.securable.quoted,
11619                    ..r.securable.clone()
11620                };
11621                self.generate_identifier(&upper_id)?;
11622            } else {
11623                self.generate_identifier(&r.securable)?;
11624            }
11625        }
11626
11627        // Function parameter types (if present)
11628        if !r.function_params.is_empty() {
11629            self.write("(");
11630            for (i, param) in r.function_params.iter().enumerate() {
11631                if i > 0 {
11632                    self.write(", ");
11633                }
11634                self.write(param);
11635            }
11636            self.write(")");
11637        }
11638
11639        self.write_space();
11640        self.write_keyword("FROM");
11641        self.write_space();
11642
11643        // Principals
11644        for (i, principal) in r.principals.iter().enumerate() {
11645            if i > 0 {
11646                self.write(", ");
11647            }
11648            if principal.is_role {
11649                self.write_keyword("ROLE");
11650                self.write_space();
11651            } else if principal.is_group {
11652                self.write_keyword("GROUP");
11653                self.write_space();
11654            } else if principal.is_share {
11655                self.write_keyword("SHARE");
11656                self.write_space();
11657            }
11658            self.generate_identifier(&principal.name)?;
11659        }
11660
11661        // CASCADE or RESTRICT
11662        if r.cascade {
11663            self.write_space();
11664            self.write_keyword("CASCADE");
11665        } else if r.restrict {
11666            self.write_space();
11667            self.write_keyword("RESTRICT");
11668        }
11669
11670        Ok(())
11671    }
11672
11673    fn generate_comment(&mut self, c: &Comment) -> Result<()> {
11674        self.write_keyword("COMMENT");
11675
11676        // IF EXISTS
11677        if c.exists {
11678            self.write_space();
11679            self.write_keyword("IF EXISTS");
11680        }
11681
11682        self.write_space();
11683        self.write_keyword("ON");
11684
11685        // MATERIALIZED
11686        if c.materialized {
11687            self.write_space();
11688            self.write_keyword("MATERIALIZED");
11689        }
11690
11691        self.write_space();
11692        self.write_keyword(&c.kind);
11693        self.write_space();
11694
11695        // Object name
11696        self.generate_expression(&c.this)?;
11697
11698        self.write_space();
11699        self.write_keyword("IS");
11700        self.write_space();
11701
11702        // Comment expression
11703        self.generate_expression(&c.expression)?;
11704
11705        Ok(())
11706    }
11707
11708    fn generate_set_statement(&mut self, s: &SetStatement) -> Result<()> {
11709        self.write_keyword("SET");
11710
11711        for (i, item) in s.items.iter().enumerate() {
11712            if i > 0 {
11713                self.write(",");
11714            }
11715            self.write_space();
11716
11717            // Kind modifier (GLOBAL, LOCAL, SESSION, PERSIST, PERSIST_ONLY, VARIABLE)
11718            let has_variable_kind = item.kind.as_deref() == Some("VARIABLE");
11719            if let Some(ref kind) = item.kind {
11720                // For VARIABLE kind, only output the keyword for dialects that require it
11721                // (Spark, Databricks, DuckDB) - matching Python sqlglot's
11722                // SET_ASSIGNMENT_REQUIRES_VARIABLE_KEYWORD flag
11723                if has_variable_kind {
11724                    if matches!(
11725                        self.config.dialect,
11726                        Some(DialectType::Spark | DialectType::Databricks | DialectType::DuckDB)
11727                    ) {
11728                        self.write_keyword("VARIABLE");
11729                        self.write_space();
11730                    }
11731                } else {
11732                    self.write_keyword(kind);
11733                    self.write_space();
11734                }
11735            }
11736
11737            // Check for special SET forms by name
11738            let name_str = match &item.name {
11739                Expression::Identifier(id) => Some(id.name.as_str()),
11740                _ => None,
11741            };
11742
11743            let is_transaction = name_str == Some("TRANSACTION");
11744            let is_character_set = name_str == Some("CHARACTER SET");
11745            let is_names = name_str == Some("NAMES");
11746            let is_collate = name_str == Some("COLLATE");
11747            let is_value_only =
11748                matches!(&item.value, Expression::Identifier(id) if id.name.is_empty());
11749
11750            if is_transaction {
11751                // Output: SET [GLOBAL|SESSION] TRANSACTION <characteristics>
11752                self.write_keyword("TRANSACTION");
11753                if let Expression::Identifier(id) = &item.value {
11754                    if !id.name.is_empty() {
11755                        self.write_space();
11756                        self.write(&id.name);
11757                    }
11758                }
11759            } else if is_character_set {
11760                // Output: SET CHARACTER SET <charset>
11761                self.write_keyword("CHARACTER SET");
11762                self.write_space();
11763                self.generate_set_value(&item.value)?;
11764            } else if is_names {
11765                // Output: SET NAMES <charset>
11766                self.write_keyword("NAMES");
11767                self.write_space();
11768                self.generate_set_value(&item.value)?;
11769            } else if is_collate {
11770                // Output: COLLATE <collation> (part of SET NAMES ... COLLATE ...)
11771                self.write_keyword("COLLATE");
11772                self.write_space();
11773                self.generate_set_value(&item.value)?;
11774            } else if has_variable_kind {
11775                // Output: SET [VARIABLE] <name> = <value>
11776                // VARIABLE keyword already written above if dialect requires it
11777                if let Some(ns) = name_str {
11778                    self.write(ns);
11779                } else {
11780                    self.generate_expression(&item.name)?;
11781                }
11782                self.write(" = ");
11783                self.generate_set_value(&item.value)?;
11784            } else if is_value_only {
11785                // SET <name> ON/OFF without = (TSQL: SET XACT_ABORT ON)
11786                self.generate_expression(&item.name)?;
11787            } else if item.no_equals && matches!(self.config.dialect, Some(DialectType::TSQL)) {
11788                // SET key value without = (TSQL style)
11789                self.generate_expression(&item.name)?;
11790                self.write_space();
11791                self.generate_set_value(&item.value)?;
11792            } else {
11793                // Standard: variable = value
11794                // SET item names should not be quoted (they are config parameter names, not column refs)
11795                match &item.name {
11796                    Expression::Identifier(id) => {
11797                        self.write(&id.name);
11798                    }
11799                    _ => {
11800                        self.generate_expression(&item.name)?;
11801                    }
11802                }
11803                self.write(" = ");
11804                self.generate_set_value(&item.value)?;
11805            }
11806        }
11807
11808        Ok(())
11809    }
11810
11811    /// Generate a SET statement value, writing keyword values (DEFAULT, ON, OFF)
11812    /// directly to avoid reserved keyword quoting.
11813    fn generate_set_value(&mut self, value: &Expression) -> Result<()> {
11814        if let Expression::Identifier(id) = value {
11815            match id.name.as_str() {
11816                "DEFAULT" | "ON" | "OFF" => {
11817                    self.write_keyword(&id.name);
11818                    return Ok(());
11819                }
11820                _ => {}
11821            }
11822        }
11823        self.generate_expression(value)
11824    }
11825
11826    // ==================== Phase 4: Additional DDL Generation ====================
11827
11828    fn generate_alter_view(&mut self, av: &AlterView) -> Result<()> {
11829        self.write_keyword("ALTER");
11830        // MySQL modifiers before VIEW
11831        if let Some(ref algorithm) = av.algorithm {
11832            self.write_space();
11833            self.write_keyword("ALGORITHM");
11834            self.write(" = ");
11835            self.write_keyword(algorithm);
11836        }
11837        if let Some(ref definer) = av.definer {
11838            self.write_space();
11839            self.write_keyword("DEFINER");
11840            self.write(" = ");
11841            self.write(definer);
11842        }
11843        if let Some(ref sql_security) = av.sql_security {
11844            self.write_space();
11845            self.write_keyword("SQL SECURITY");
11846            self.write(" = ");
11847            self.write_keyword(sql_security);
11848        }
11849        self.write_space();
11850        self.write_keyword("VIEW");
11851        self.write_space();
11852        self.generate_table(&av.name)?;
11853
11854        // Hive: Column aliases with optional COMMENT
11855        if !av.columns.is_empty() {
11856            self.write(" (");
11857            for (i, col) in av.columns.iter().enumerate() {
11858                if i > 0 {
11859                    self.write(", ");
11860                }
11861                self.generate_identifier(&col.name)?;
11862                if let Some(ref comment) = col.comment {
11863                    self.write_space();
11864                    self.write_keyword("COMMENT");
11865                    self.write(" ");
11866                    self.generate_string_literal(comment)?;
11867                }
11868            }
11869            self.write(")");
11870        }
11871
11872        // TSQL: WITH option before actions
11873        if let Some(ref opt) = av.with_option {
11874            self.write_space();
11875            self.write_keyword("WITH");
11876            self.write_space();
11877            self.write_keyword(opt);
11878        }
11879
11880        for action in &av.actions {
11881            self.write_space();
11882            match action {
11883                AlterViewAction::Rename(new_name) => {
11884                    self.write_keyword("RENAME TO");
11885                    self.write_space();
11886                    self.generate_table(new_name)?;
11887                }
11888                AlterViewAction::OwnerTo(owner) => {
11889                    self.write_keyword("OWNER TO");
11890                    self.write_space();
11891                    self.generate_identifier(owner)?;
11892                }
11893                AlterViewAction::SetSchema(schema) => {
11894                    self.write_keyword("SET SCHEMA");
11895                    self.write_space();
11896                    self.generate_identifier(schema)?;
11897                }
11898                AlterViewAction::SetAuthorization(auth) => {
11899                    self.write_keyword("SET AUTHORIZATION");
11900                    self.write_space();
11901                    self.write(auth);
11902                }
11903                AlterViewAction::AlterColumn { name, action } => {
11904                    self.write_keyword("ALTER COLUMN");
11905                    self.write_space();
11906                    self.generate_identifier(name)?;
11907                    self.write_space();
11908                    self.generate_alter_column_action(action)?;
11909                }
11910                AlterViewAction::AsSelect(query) => {
11911                    self.write_keyword("AS");
11912                    self.write_space();
11913                    self.generate_expression(query)?;
11914                }
11915                AlterViewAction::SetTblproperties(props) => {
11916                    self.write_keyword("SET TBLPROPERTIES");
11917                    self.write(" (");
11918                    for (i, (key, value)) in props.iter().enumerate() {
11919                        if i > 0 {
11920                            self.write(", ");
11921                        }
11922                        self.generate_string_literal(key)?;
11923                        self.write("=");
11924                        self.generate_string_literal(value)?;
11925                    }
11926                    self.write(")");
11927                }
11928                AlterViewAction::UnsetTblproperties(keys) => {
11929                    self.write_keyword("UNSET TBLPROPERTIES");
11930                    self.write(" (");
11931                    for (i, key) in keys.iter().enumerate() {
11932                        if i > 0 {
11933                            self.write(", ");
11934                        }
11935                        self.generate_string_literal(key)?;
11936                    }
11937                    self.write(")");
11938                }
11939            }
11940        }
11941
11942        Ok(())
11943    }
11944
11945    fn generate_alter_index(&mut self, ai: &AlterIndex) -> Result<()> {
11946        self.write_keyword("ALTER INDEX");
11947        self.write_space();
11948        self.generate_identifier(&ai.name)?;
11949
11950        if let Some(table) = &ai.table {
11951            self.write_space();
11952            self.write_keyword("ON");
11953            self.write_space();
11954            self.generate_table(table)?;
11955        }
11956
11957        for action in &ai.actions {
11958            self.write_space();
11959            match action {
11960                AlterIndexAction::Rename(new_name) => {
11961                    self.write_keyword("RENAME TO");
11962                    self.write_space();
11963                    self.generate_identifier(new_name)?;
11964                }
11965                AlterIndexAction::SetTablespace(tablespace) => {
11966                    self.write_keyword("SET TABLESPACE");
11967                    self.write_space();
11968                    self.generate_identifier(tablespace)?;
11969                }
11970                AlterIndexAction::Visible(visible) => {
11971                    if *visible {
11972                        self.write_keyword("VISIBLE");
11973                    } else {
11974                        self.write_keyword("INVISIBLE");
11975                    }
11976                }
11977            }
11978        }
11979
11980        Ok(())
11981    }
11982
11983    fn generate_create_schema(&mut self, cs: &CreateSchema) -> Result<()> {
11984        // Output leading comments
11985        for comment in &cs.leading_comments {
11986            self.write_formatted_comment(comment);
11987            self.write_space();
11988        }
11989
11990        // Athena: CREATE SCHEMA uses Hive engine (backticks)
11991        let saved_athena_hive_context = self.athena_hive_context;
11992        if matches!(
11993            self.config.dialect,
11994            Some(crate::dialects::DialectType::Athena)
11995        ) {
11996            self.athena_hive_context = true;
11997        }
11998
11999        self.write_keyword("CREATE SCHEMA");
12000
12001        if cs.if_not_exists {
12002            self.write_space();
12003            self.write_keyword("IF NOT EXISTS");
12004        }
12005
12006        self.write_space();
12007        for (i, part) in cs.name.iter().enumerate() {
12008            if i > 0 {
12009                self.write(".");
12010            }
12011            self.generate_identifier(part)?;
12012        }
12013
12014        if let Some(ref clone_parts) = cs.clone_from {
12015            self.write_keyword(" CLONE ");
12016            for (i, part) in clone_parts.iter().enumerate() {
12017                if i > 0 {
12018                    self.write(".");
12019                }
12020                self.generate_identifier(part)?;
12021            }
12022        }
12023
12024        if let Some(ref at_clause) = cs.at_clause {
12025            self.write_space();
12026            self.generate_expression(at_clause)?;
12027        }
12028
12029        if let Some(auth) = &cs.authorization {
12030            self.write_space();
12031            self.write_keyword("AUTHORIZATION");
12032            self.write_space();
12033            self.generate_identifier(auth)?;
12034        }
12035
12036        // Generate schema properties (e.g., DEFAULT COLLATE or WITH (props))
12037        // Separate WITH properties from other properties
12038        let with_properties: Vec<_> = cs
12039            .properties
12040            .iter()
12041            .filter(|p| matches!(p, Expression::Property(_)))
12042            .collect();
12043        let other_properties: Vec<_> = cs
12044            .properties
12045            .iter()
12046            .filter(|p| !matches!(p, Expression::Property(_)))
12047            .collect();
12048
12049        // Generate WITH (props) if we have Property expressions
12050        if !with_properties.is_empty() {
12051            self.write_space();
12052            self.write_keyword("WITH");
12053            self.write(" (");
12054            for (i, prop) in with_properties.iter().enumerate() {
12055                if i > 0 {
12056                    self.write(", ");
12057                }
12058                self.generate_expression(prop)?;
12059            }
12060            self.write(")");
12061        }
12062
12063        // Generate other properties (like DEFAULT COLLATE)
12064        for prop in other_properties {
12065            self.write_space();
12066            self.generate_expression(prop)?;
12067        }
12068
12069        // Restore Athena Hive context
12070        self.athena_hive_context = saved_athena_hive_context;
12071
12072        Ok(())
12073    }
12074
12075    fn generate_drop_schema(&mut self, ds: &DropSchema) -> Result<()> {
12076        self.write_keyword("DROP SCHEMA");
12077
12078        if ds.if_exists {
12079            self.write_space();
12080            self.write_keyword("IF EXISTS");
12081        }
12082
12083        self.write_space();
12084        self.generate_identifier(&ds.name)?;
12085
12086        if ds.cascade {
12087            self.write_space();
12088            self.write_keyword("CASCADE");
12089        }
12090
12091        Ok(())
12092    }
12093
12094    fn generate_drop_namespace(&mut self, dn: &DropNamespace) -> Result<()> {
12095        self.write_keyword("DROP NAMESPACE");
12096
12097        if dn.if_exists {
12098            self.write_space();
12099            self.write_keyword("IF EXISTS");
12100        }
12101
12102        self.write_space();
12103        self.generate_identifier(&dn.name)?;
12104
12105        if dn.cascade {
12106            self.write_space();
12107            self.write_keyword("CASCADE");
12108        }
12109
12110        Ok(())
12111    }
12112
12113    fn generate_create_database(&mut self, cd: &CreateDatabase) -> Result<()> {
12114        self.write_keyword("CREATE DATABASE");
12115
12116        if cd.if_not_exists {
12117            self.write_space();
12118            self.write_keyword("IF NOT EXISTS");
12119        }
12120
12121        self.write_space();
12122        self.generate_identifier(&cd.name)?;
12123
12124        if let Some(ref clone_src) = cd.clone_from {
12125            self.write_keyword(" CLONE ");
12126            self.generate_identifier(clone_src)?;
12127        }
12128
12129        // AT/BEFORE clause for time travel (Snowflake)
12130        if let Some(ref at_clause) = cd.at_clause {
12131            self.write_space();
12132            self.generate_expression(at_clause)?;
12133        }
12134
12135        for option in &cd.options {
12136            self.write_space();
12137            match option {
12138                DatabaseOption::CharacterSet(charset) => {
12139                    self.write_keyword("CHARACTER SET");
12140                    self.write(" = ");
12141                    self.write(&format!("'{}'", charset));
12142                }
12143                DatabaseOption::Collate(collate) => {
12144                    self.write_keyword("COLLATE");
12145                    self.write(" = ");
12146                    self.write(&format!("'{}'", collate));
12147                }
12148                DatabaseOption::Owner(owner) => {
12149                    self.write_keyword("OWNER");
12150                    self.write(" = ");
12151                    self.generate_identifier(owner)?;
12152                }
12153                DatabaseOption::Template(template) => {
12154                    self.write_keyword("TEMPLATE");
12155                    self.write(" = ");
12156                    self.generate_identifier(template)?;
12157                }
12158                DatabaseOption::Encoding(encoding) => {
12159                    self.write_keyword("ENCODING");
12160                    self.write(" = ");
12161                    self.write(&format!("'{}'", encoding));
12162                }
12163                DatabaseOption::Location(location) => {
12164                    self.write_keyword("LOCATION");
12165                    self.write(" = ");
12166                    self.write(&format!("'{}'", location));
12167                }
12168            }
12169        }
12170
12171        Ok(())
12172    }
12173
12174    fn generate_drop_database(&mut self, dd: &DropDatabase) -> Result<()> {
12175        self.write_keyword("DROP DATABASE");
12176
12177        if dd.if_exists {
12178            self.write_space();
12179            self.write_keyword("IF EXISTS");
12180        }
12181
12182        self.write_space();
12183        self.generate_identifier(&dd.name)?;
12184
12185        if dd.sync {
12186            self.write_space();
12187            self.write_keyword("SYNC");
12188        }
12189
12190        Ok(())
12191    }
12192
12193    fn generate_create_function(&mut self, cf: &CreateFunction) -> Result<()> {
12194        self.write_keyword("CREATE");
12195
12196        if cf.or_alter {
12197            self.write_space();
12198            self.write_keyword("OR ALTER");
12199        } else if cf.or_replace {
12200            self.write_space();
12201            self.write_keyword("OR REPLACE");
12202        }
12203
12204        if cf.temporary {
12205            self.write_space();
12206            self.write_keyword("TEMPORARY");
12207        }
12208
12209        self.write_space();
12210        if cf.is_table_function {
12211            self.write_keyword("TABLE FUNCTION");
12212        } else {
12213            self.write_keyword("FUNCTION");
12214        }
12215
12216        if cf.if_not_exists {
12217            self.write_space();
12218            self.write_keyword("IF NOT EXISTS");
12219        }
12220
12221        self.write_space();
12222        self.generate_table(&cf.name)?;
12223        if cf.has_parens {
12224            let func_multiline = self.config.pretty
12225                && matches!(
12226                    self.config.dialect,
12227                    Some(crate::dialects::DialectType::TSQL)
12228                        | Some(crate::dialects::DialectType::Fabric)
12229                )
12230                && !cf.parameters.is_empty();
12231            if func_multiline {
12232                self.write("(\n");
12233                self.indent_level += 2;
12234                self.write_indent();
12235                self.generate_function_parameters(&cf.parameters)?;
12236                self.write("\n");
12237                self.indent_level -= 2;
12238                self.write(")");
12239            } else {
12240                self.write("(");
12241                self.generate_function_parameters(&cf.parameters)?;
12242                self.write(")");
12243            }
12244        }
12245
12246        // Output RETURNS clause (always comes first after parameters)
12247        // BigQuery and TSQL use multiline formatting for CREATE FUNCTION structure
12248        let use_multiline = self.config.pretty
12249            && matches!(
12250                self.config.dialect,
12251                Some(crate::dialects::DialectType::BigQuery)
12252                    | Some(crate::dialects::DialectType::TSQL)
12253                    | Some(crate::dialects::DialectType::Fabric)
12254            );
12255
12256        if cf.language_first {
12257            // LANGUAGE first, then SQL data access, then RETURNS
12258            if let Some(lang) = &cf.language {
12259                if use_multiline {
12260                    self.write_newline();
12261                } else {
12262                    self.write_space();
12263                }
12264                self.write_keyword("LANGUAGE");
12265                self.write_space();
12266                self.write(lang);
12267            }
12268
12269            // SQL data access comes after LANGUAGE in this case
12270            if let Some(sql_data) = &cf.sql_data_access {
12271                self.write_space();
12272                match sql_data {
12273                    SqlDataAccess::NoSql => self.write_keyword("NO SQL"),
12274                    SqlDataAccess::ContainsSql => self.write_keyword("CONTAINS SQL"),
12275                    SqlDataAccess::ReadsSqlData => self.write_keyword("READS SQL DATA"),
12276                    SqlDataAccess::ModifiesSqlData => self.write_keyword("MODIFIES SQL DATA"),
12277                }
12278            }
12279
12280            if let Some(ref rtb) = cf.returns_table_body {
12281                if use_multiline {
12282                    self.write_newline();
12283                } else {
12284                    self.write_space();
12285                }
12286                self.write_keyword("RETURNS");
12287                self.write_space();
12288                self.write(rtb);
12289            } else if let Some(return_type) = &cf.return_type {
12290                if use_multiline {
12291                    self.write_newline();
12292                } else {
12293                    self.write_space();
12294                }
12295                self.write_keyword("RETURNS");
12296                self.write_space();
12297                self.generate_data_type(return_type)?;
12298            }
12299        } else {
12300            // RETURNS first (default)
12301            // DuckDB macros: skip RETURNS output (empty marker in returns_table_body means TABLE return)
12302            let is_duckdb = matches!(
12303                self.config.dialect,
12304                Some(crate::dialects::DialectType::DuckDB)
12305            );
12306            if let Some(ref rtb) = cf.returns_table_body {
12307                if !(is_duckdb && rtb.is_empty()) {
12308                    if use_multiline {
12309                        self.write_newline();
12310                    } else {
12311                        self.write_space();
12312                    }
12313                    self.write_keyword("RETURNS");
12314                    self.write_space();
12315                    self.write(rtb);
12316                }
12317            } else if let Some(return_type) = &cf.return_type {
12318                // DuckDB: skip all RETURNS (DuckDB macros don't use RETURNS clause)
12319                if !is_duckdb {
12320                    let is_table_return = matches!(return_type, crate::expressions::DataType::Custom { ref name } if name.eq_ignore_ascii_case("TABLE"));
12321                    if use_multiline {
12322                        self.write_newline();
12323                    } else {
12324                        self.write_space();
12325                    }
12326                    self.write_keyword("RETURNS");
12327                    self.write_space();
12328                    if is_table_return {
12329                        self.write_keyword("TABLE");
12330                    } else {
12331                        self.generate_data_type(return_type)?;
12332                    }
12333                }
12334            }
12335        }
12336
12337        // If we have property_order, use it to output properties in original order
12338        if !cf.property_order.is_empty() {
12339            // For BigQuery, OPTIONS must come before AS - reorder if needed
12340            let is_bigquery = matches!(
12341                self.config.dialect,
12342                Some(crate::dialects::DialectType::BigQuery)
12343            );
12344            let property_order = if is_bigquery {
12345                // Move Options before As if both are present
12346                let mut reordered = Vec::new();
12347                let mut has_as = false;
12348                let mut has_options = false;
12349                for prop in &cf.property_order {
12350                    match prop {
12351                        FunctionPropertyKind::As => has_as = true,
12352                        FunctionPropertyKind::Options => has_options = true,
12353                        _ => {}
12354                    }
12355                }
12356                if has_as && has_options {
12357                    // Output all props except As and Options, then Options, then As
12358                    for prop in &cf.property_order {
12359                        if *prop != FunctionPropertyKind::As
12360                            && *prop != FunctionPropertyKind::Options
12361                        {
12362                            reordered.push(*prop);
12363                        }
12364                    }
12365                    reordered.push(FunctionPropertyKind::Options);
12366                    reordered.push(FunctionPropertyKind::As);
12367                    reordered
12368                } else {
12369                    cf.property_order.clone()
12370                }
12371            } else {
12372                cf.property_order.clone()
12373            };
12374
12375            for prop in &property_order {
12376                match prop {
12377                    FunctionPropertyKind::Set => {
12378                        self.generate_function_set_options(cf)?;
12379                    }
12380                    FunctionPropertyKind::As => {
12381                        self.generate_function_body(cf)?;
12382                    }
12383                    FunctionPropertyKind::Language => {
12384                        if !cf.language_first {
12385                            // Only output here if not already output above
12386                            if let Some(lang) = &cf.language {
12387                                // Only BigQuery uses multiline formatting
12388                                let use_multiline = self.config.pretty
12389                                    && matches!(
12390                                        self.config.dialect,
12391                                        Some(crate::dialects::DialectType::BigQuery)
12392                                    );
12393                                if use_multiline {
12394                                    self.write_newline();
12395                                } else {
12396                                    self.write_space();
12397                                }
12398                                self.write_keyword("LANGUAGE");
12399                                self.write_space();
12400                                self.write(lang);
12401                            }
12402                        }
12403                    }
12404                    FunctionPropertyKind::Determinism => {
12405                        self.generate_function_determinism(cf)?;
12406                    }
12407                    FunctionPropertyKind::NullInput => {
12408                        self.generate_function_null_input(cf)?;
12409                    }
12410                    FunctionPropertyKind::Security => {
12411                        self.generate_function_security(cf)?;
12412                    }
12413                    FunctionPropertyKind::SqlDataAccess => {
12414                        if !cf.language_first {
12415                            // Only output here if not already output above
12416                            self.generate_function_sql_data_access(cf)?;
12417                        }
12418                    }
12419                    FunctionPropertyKind::Options => {
12420                        if !cf.options.is_empty() {
12421                            self.write_space();
12422                            self.generate_options_clause(&cf.options)?;
12423                        }
12424                    }
12425                    FunctionPropertyKind::Environment => {
12426                        if !cf.environment.is_empty() {
12427                            self.write_space();
12428                            self.generate_environment_clause(&cf.environment)?;
12429                        }
12430                    }
12431                    FunctionPropertyKind::Handler => {
12432                        if let Some(ref h) = cf.handler {
12433                            self.write_space();
12434                            self.write_keyword("HANDLER");
12435                            self.write_space();
12436                            self.write("'");
12437                            self.write(h);
12438                            self.write("'");
12439                        }
12440                    }
12441                    FunctionPropertyKind::ParameterStyle => {
12442                        if let Some(ref ps) = cf.parameter_style {
12443                            self.write_space();
12444                            self.write_keyword("PARAMETER STYLE");
12445                            self.write_space();
12446                            self.write_keyword(ps);
12447                        }
12448                    }
12449                }
12450            }
12451
12452            // Output OPTIONS if not tracked in property_order (legacy)
12453            if !cf.options.is_empty() && !cf.property_order.contains(&FunctionPropertyKind::Options)
12454            {
12455                self.write_space();
12456                self.generate_options_clause(&cf.options)?;
12457            }
12458
12459            // Output ENVIRONMENT if not tracked in property_order (legacy)
12460            if !cf.environment.is_empty()
12461                && !cf
12462                    .property_order
12463                    .contains(&FunctionPropertyKind::Environment)
12464            {
12465                self.write_space();
12466                self.generate_environment_clause(&cf.environment)?;
12467            }
12468        } else {
12469            // Legacy behavior when property_order is empty
12470            // BigQuery: DETERMINISTIC/NOT DETERMINISTIC comes before LANGUAGE
12471            if matches!(
12472                self.config.dialect,
12473                Some(crate::dialects::DialectType::BigQuery)
12474            ) {
12475                self.generate_function_determinism(cf)?;
12476            }
12477
12478            // Only BigQuery uses multiline formatting for CREATE FUNCTION structure
12479            let use_multiline = self.config.pretty
12480                && matches!(
12481                    self.config.dialect,
12482                    Some(crate::dialects::DialectType::BigQuery)
12483                );
12484
12485            if !cf.language_first {
12486                if let Some(lang) = &cf.language {
12487                    if use_multiline {
12488                        self.write_newline();
12489                    } else {
12490                        self.write_space();
12491                    }
12492                    self.write_keyword("LANGUAGE");
12493                    self.write_space();
12494                    self.write(lang);
12495                }
12496
12497                // SQL data access characteristic comes after LANGUAGE
12498                self.generate_function_sql_data_access(cf)?;
12499            }
12500
12501            // For non-BigQuery dialects, output DETERMINISTIC/IMMUTABLE/VOLATILE here
12502            if !matches!(
12503                self.config.dialect,
12504                Some(crate::dialects::DialectType::BigQuery)
12505            ) {
12506                self.generate_function_determinism(cf)?;
12507            }
12508
12509            self.generate_function_null_input(cf)?;
12510            self.generate_function_security(cf)?;
12511            self.generate_function_set_options(cf)?;
12512
12513            // BigQuery: OPTIONS (key=value, ...) - comes before AS
12514            if !cf.options.is_empty() {
12515                self.write_space();
12516                self.generate_options_clause(&cf.options)?;
12517            }
12518
12519            // Databricks: ENVIRONMENT (dependencies = '...', ...) - comes before AS
12520            if !cf.environment.is_empty() {
12521                self.write_space();
12522                self.generate_environment_clause(&cf.environment)?;
12523            }
12524
12525            self.generate_function_body(cf)?;
12526        }
12527
12528        Ok(())
12529    }
12530
12531    /// Generate SET options for CREATE FUNCTION
12532    fn generate_function_set_options(&mut self, cf: &CreateFunction) -> Result<()> {
12533        for opt in &cf.set_options {
12534            self.write_space();
12535            self.write_keyword("SET");
12536            self.write_space();
12537            self.write(&opt.name);
12538            match &opt.value {
12539                FunctionSetValue::Value { value, use_to } => {
12540                    if *use_to {
12541                        self.write(" TO ");
12542                    } else {
12543                        self.write(" = ");
12544                    }
12545                    self.write(value);
12546                }
12547                FunctionSetValue::FromCurrent => {
12548                    self.write_space();
12549                    self.write_keyword("FROM CURRENT");
12550                }
12551            }
12552        }
12553        Ok(())
12554    }
12555
12556    /// Generate function body (AS clause)
12557    fn generate_function_body(&mut self, cf: &CreateFunction) -> Result<()> {
12558        if let Some(body) = &cf.body {
12559            // AS stays on same line as previous content (e.g., LANGUAGE js AS)
12560            self.write_space();
12561            // Only BigQuery uses multiline formatting for CREATE FUNCTION body
12562            let use_multiline = self.config.pretty
12563                && matches!(
12564                    self.config.dialect,
12565                    Some(crate::dialects::DialectType::BigQuery)
12566                );
12567            match body {
12568                FunctionBody::Block(block) => {
12569                    self.write_keyword("AS");
12570                    if matches!(
12571                        self.config.dialect,
12572                        Some(crate::dialects::DialectType::TSQL)
12573                    ) {
12574                        self.write(" BEGIN ");
12575                        self.write(block);
12576                        self.write(" END");
12577                    } else if matches!(
12578                        self.config.dialect,
12579                        Some(crate::dialects::DialectType::PostgreSQL)
12580                    ) {
12581                        self.write(" $$");
12582                        self.write(block);
12583                        self.write("$$");
12584                    } else {
12585                        // Escape content for single-quoted output
12586                        let escaped = self.escape_block_for_single_quote(block);
12587                        // In BigQuery pretty mode, body content goes on new line
12588                        if use_multiline {
12589                            self.write_newline();
12590                        } else {
12591                            self.write(" ");
12592                        }
12593                        self.write("'");
12594                        self.write(&escaped);
12595                        self.write("'");
12596                    }
12597                }
12598                FunctionBody::StringLiteral(s) => {
12599                    self.write_keyword("AS");
12600                    // In BigQuery pretty mode, body content goes on new line
12601                    if use_multiline {
12602                        self.write_newline();
12603                    } else {
12604                        self.write(" ");
12605                    }
12606                    self.write("'");
12607                    self.write(s);
12608                    self.write("'");
12609                }
12610                FunctionBody::Expression(expr) => {
12611                    self.write_keyword("AS");
12612                    self.write_space();
12613                    self.generate_expression(expr)?;
12614                }
12615                FunctionBody::External(name) => {
12616                    self.write_keyword("EXTERNAL NAME");
12617                    self.write(" '");
12618                    self.write(name);
12619                    self.write("'");
12620                }
12621                FunctionBody::Return(expr) => {
12622                    if matches!(
12623                        self.config.dialect,
12624                        Some(crate::dialects::DialectType::DuckDB)
12625                    ) {
12626                        // DuckDB macro syntax: AS [TABLE] expression (no RETURN keyword)
12627                        self.write_keyword("AS");
12628                        self.write_space();
12629                        // Check both returns_table_body marker and return_type = Custom "TABLE"
12630                        let is_table_return = cf.returns_table_body.is_some()
12631                            || matches!(&cf.return_type, Some(crate::expressions::DataType::Custom { ref name }) if name.eq_ignore_ascii_case("TABLE"));
12632                        if is_table_return {
12633                            self.write_keyword("TABLE");
12634                            self.write_space();
12635                        }
12636                        self.generate_expression(expr)?;
12637                    } else {
12638                        if self.config.create_function_return_as {
12639                            self.write_keyword("AS");
12640                            // TSQL pretty: newline between AS and RETURN
12641                            if self.config.pretty
12642                                && matches!(
12643                                    self.config.dialect,
12644                                    Some(crate::dialects::DialectType::TSQL)
12645                                        | Some(crate::dialects::DialectType::Fabric)
12646                                )
12647                            {
12648                                self.write_newline();
12649                            } else {
12650                                self.write_space();
12651                            }
12652                        }
12653                        self.write_keyword("RETURN");
12654                        self.write_space();
12655                        self.generate_expression(expr)?;
12656                    }
12657                }
12658                FunctionBody::Statements(stmts) => {
12659                    self.write_keyword("AS");
12660                    self.write(" BEGIN ");
12661                    for (i, stmt) in stmts.iter().enumerate() {
12662                        if i > 0 {
12663                            self.write(" ");
12664                        }
12665                        self.generate_expression(stmt)?;
12666                        self.write(";");
12667                    }
12668                    self.write(" END");
12669                }
12670                FunctionBody::DollarQuoted { content, tag } => {
12671                    self.write_keyword("AS");
12672                    self.write(" ");
12673                    // Dialects that support dollar-quoted strings: PostgreSQL, Databricks, Redshift, DuckDB
12674                    let supports_dollar_quoting = matches!(
12675                        self.config.dialect,
12676                        Some(crate::dialects::DialectType::PostgreSQL)
12677                            | Some(crate::dialects::DialectType::Databricks)
12678                            | Some(crate::dialects::DialectType::Redshift)
12679                            | Some(crate::dialects::DialectType::DuckDB)
12680                    );
12681                    if supports_dollar_quoting {
12682                        // Output in dollar-quoted format
12683                        self.write("$");
12684                        if let Some(t) = tag {
12685                            self.write(t);
12686                        }
12687                        self.write("$");
12688                        self.write(content);
12689                        self.write("$");
12690                        if let Some(t) = tag {
12691                            self.write(t);
12692                        }
12693                        self.write("$");
12694                    } else {
12695                        // Convert to single-quoted string for other dialects
12696                        let escaped = self.escape_block_for_single_quote(content);
12697                        self.write("'");
12698                        self.write(&escaped);
12699                        self.write("'");
12700                    }
12701                }
12702            }
12703        }
12704        Ok(())
12705    }
12706
12707    /// Generate determinism clause (IMMUTABLE/VOLATILE/DETERMINISTIC)
12708    fn generate_function_determinism(&mut self, cf: &CreateFunction) -> Result<()> {
12709        if let Some(det) = cf.deterministic {
12710            self.write_space();
12711            if matches!(
12712                self.config.dialect,
12713                Some(crate::dialects::DialectType::BigQuery)
12714            ) {
12715                // BigQuery uses DETERMINISTIC/NOT DETERMINISTIC
12716                if det {
12717                    self.write_keyword("DETERMINISTIC");
12718                } else {
12719                    self.write_keyword("NOT DETERMINISTIC");
12720                }
12721            } else {
12722                // PostgreSQL and others use IMMUTABLE/VOLATILE
12723                if det {
12724                    self.write_keyword("IMMUTABLE");
12725                } else {
12726                    self.write_keyword("VOLATILE");
12727                }
12728            }
12729        }
12730        Ok(())
12731    }
12732
12733    /// Generate null input handling clause
12734    fn generate_function_null_input(&mut self, cf: &CreateFunction) -> Result<()> {
12735        if let Some(returns_null) = cf.returns_null_on_null_input {
12736            self.write_space();
12737            if returns_null {
12738                if cf.strict {
12739                    self.write_keyword("STRICT");
12740                } else {
12741                    self.write_keyword("RETURNS NULL ON NULL INPUT");
12742                }
12743            } else {
12744                self.write_keyword("CALLED ON NULL INPUT");
12745            }
12746        }
12747        Ok(())
12748    }
12749
12750    /// Generate security clause
12751    fn generate_function_security(&mut self, cf: &CreateFunction) -> Result<()> {
12752        if let Some(security) = &cf.security {
12753            self.write_space();
12754            // MySQL uses SQL SECURITY prefix
12755            if matches!(
12756                self.config.dialect,
12757                Some(crate::dialects::DialectType::MySQL)
12758            ) {
12759                self.write_keyword("SQL SECURITY");
12760            } else {
12761                self.write_keyword("SECURITY");
12762            }
12763            self.write_space();
12764            match security {
12765                FunctionSecurity::Definer => self.write_keyword("DEFINER"),
12766                FunctionSecurity::Invoker => self.write_keyword("INVOKER"),
12767                FunctionSecurity::None => self.write_keyword("NONE"),
12768            }
12769        }
12770        Ok(())
12771    }
12772
12773    /// Generate SQL data access clause
12774    fn generate_function_sql_data_access(&mut self, cf: &CreateFunction) -> Result<()> {
12775        if let Some(sql_data) = &cf.sql_data_access {
12776            self.write_space();
12777            match sql_data {
12778                SqlDataAccess::NoSql => self.write_keyword("NO SQL"),
12779                SqlDataAccess::ContainsSql => self.write_keyword("CONTAINS SQL"),
12780                SqlDataAccess::ReadsSqlData => self.write_keyword("READS SQL DATA"),
12781                SqlDataAccess::ModifiesSqlData => self.write_keyword("MODIFIES SQL DATA"),
12782            }
12783        }
12784        Ok(())
12785    }
12786
12787    fn generate_function_parameters(&mut self, params: &[FunctionParameter]) -> Result<()> {
12788        for (i, param) in params.iter().enumerate() {
12789            if i > 0 {
12790                self.write(", ");
12791            }
12792
12793            if let Some(mode) = &param.mode {
12794                if let Some(text) = &param.mode_text {
12795                    self.write(text);
12796                } else {
12797                    match mode {
12798                        ParameterMode::In => self.write_keyword("IN"),
12799                        ParameterMode::Out => self.write_keyword("OUT"),
12800                        ParameterMode::InOut => self.write_keyword("INOUT"),
12801                        ParameterMode::Variadic => self.write_keyword("VARIADIC"),
12802                    }
12803                }
12804                self.write_space();
12805            }
12806
12807            if let Some(name) = &param.name {
12808                self.generate_identifier(name)?;
12809                // Skip space and type for empty Custom types (e.g., DuckDB macros)
12810                let skip_type =
12811                    matches!(&param.data_type, DataType::Custom { name } if name.is_empty());
12812                if !skip_type {
12813                    self.write_space();
12814                    self.generate_data_type(&param.data_type)?;
12815                }
12816            } else {
12817                self.generate_data_type(&param.data_type)?;
12818            }
12819
12820            if let Some(default) = &param.default {
12821                if self.config.parameter_default_equals {
12822                    self.write(" = ");
12823                } else {
12824                    self.write(" DEFAULT ");
12825                }
12826                self.generate_expression(default)?;
12827            }
12828        }
12829
12830        Ok(())
12831    }
12832
12833    fn generate_drop_function(&mut self, df: &DropFunction) -> Result<()> {
12834        self.write_keyword("DROP FUNCTION");
12835
12836        if df.if_exists {
12837            self.write_space();
12838            self.write_keyword("IF EXISTS");
12839        }
12840
12841        self.write_space();
12842        self.generate_table(&df.name)?;
12843
12844        if let Some(params) = &df.parameters {
12845            self.write(" (");
12846            for (i, dt) in params.iter().enumerate() {
12847                if i > 0 {
12848                    self.write(", ");
12849                }
12850                self.generate_data_type(dt)?;
12851            }
12852            self.write(")");
12853        }
12854
12855        if df.cascade {
12856            self.write_space();
12857            self.write_keyword("CASCADE");
12858        }
12859
12860        Ok(())
12861    }
12862
12863    fn generate_create_procedure(&mut self, cp: &CreateProcedure) -> Result<()> {
12864        self.write_keyword("CREATE");
12865
12866        if cp.or_alter {
12867            self.write_space();
12868            self.write_keyword("OR ALTER");
12869        } else if cp.or_replace {
12870            self.write_space();
12871            self.write_keyword("OR REPLACE");
12872        }
12873
12874        self.write_space();
12875        if cp.use_proc_keyword {
12876            self.write_keyword("PROC");
12877        } else {
12878            self.write_keyword("PROCEDURE");
12879        }
12880
12881        if cp.if_not_exists {
12882            self.write_space();
12883            self.write_keyword("IF NOT EXISTS");
12884        }
12885
12886        self.write_space();
12887        self.generate_table(&cp.name)?;
12888        if cp.has_parens {
12889            self.write("(");
12890            self.generate_function_parameters(&cp.parameters)?;
12891            self.write(")");
12892        } else if !cp.parameters.is_empty() {
12893            // TSQL: unparenthesized parameters
12894            self.write_space();
12895            self.generate_function_parameters(&cp.parameters)?;
12896        }
12897
12898        // RETURNS clause (Snowflake)
12899        if let Some(return_type) = &cp.return_type {
12900            self.write_space();
12901            self.write_keyword("RETURNS");
12902            self.write_space();
12903            self.generate_data_type(return_type)?;
12904        }
12905
12906        // EXECUTE AS clause (Snowflake)
12907        if let Some(execute_as) = &cp.execute_as {
12908            self.write_space();
12909            self.write_keyword("EXECUTE AS");
12910            self.write_space();
12911            self.write_keyword(execute_as);
12912        }
12913
12914        if let Some(lang) = &cp.language {
12915            self.write_space();
12916            self.write_keyword("LANGUAGE");
12917            self.write_space();
12918            self.write(lang);
12919        }
12920
12921        if let Some(security) = &cp.security {
12922            self.write_space();
12923            self.write_keyword("SECURITY");
12924            self.write_space();
12925            match security {
12926                FunctionSecurity::Definer => self.write_keyword("DEFINER"),
12927                FunctionSecurity::Invoker => self.write_keyword("INVOKER"),
12928                FunctionSecurity::None => self.write_keyword("NONE"),
12929            }
12930        }
12931
12932        // TSQL WITH options (ENCRYPTION, RECOMPILE, etc.)
12933        if !cp.with_options.is_empty() {
12934            self.write_space();
12935            self.write_keyword("WITH");
12936            self.write_space();
12937            for (i, opt) in cp.with_options.iter().enumerate() {
12938                if i > 0 {
12939                    self.write(", ");
12940                }
12941                self.write(opt);
12942            }
12943        }
12944
12945        if let Some(body) = &cp.body {
12946            self.write_space();
12947            match body {
12948                FunctionBody::Block(block) => {
12949                    self.write_keyword("AS");
12950                    if matches!(
12951                        self.config.dialect,
12952                        Some(crate::dialects::DialectType::TSQL)
12953                    ) {
12954                        self.write(" BEGIN ");
12955                        self.write(block);
12956                        self.write(" END");
12957                    } else if matches!(
12958                        self.config.dialect,
12959                        Some(crate::dialects::DialectType::PostgreSQL)
12960                    ) {
12961                        self.write(" $$");
12962                        self.write(block);
12963                        self.write("$$");
12964                    } else {
12965                        // Escape content for single-quoted output
12966                        let escaped = self.escape_block_for_single_quote(block);
12967                        self.write(" '");
12968                        self.write(&escaped);
12969                        self.write("'");
12970                    }
12971                }
12972                FunctionBody::StringLiteral(s) => {
12973                    self.write_keyword("AS");
12974                    self.write(" '");
12975                    self.write(s);
12976                    self.write("'");
12977                }
12978                FunctionBody::Expression(expr) => {
12979                    self.write_keyword("AS");
12980                    self.write_space();
12981                    self.generate_expression(expr)?;
12982                }
12983                FunctionBody::External(name) => {
12984                    self.write_keyword("EXTERNAL NAME");
12985                    self.write(" '");
12986                    self.write(name);
12987                    self.write("'");
12988                }
12989                FunctionBody::Return(expr) => {
12990                    self.write_keyword("RETURN");
12991                    self.write_space();
12992                    self.generate_expression(expr)?;
12993                }
12994                FunctionBody::Statements(stmts) => {
12995                    self.write_keyword("AS");
12996                    self.write(" BEGIN ");
12997                    for (i, stmt) in stmts.iter().enumerate() {
12998                        if i > 0 {
12999                            self.write(" ");
13000                        }
13001                        self.generate_expression(stmt)?;
13002                        self.write(";");
13003                    }
13004                    self.write(" END");
13005                }
13006                FunctionBody::DollarQuoted { content, tag } => {
13007                    self.write_keyword("AS");
13008                    self.write(" ");
13009                    // Dialects that support dollar-quoted strings: PostgreSQL, Databricks, Redshift, DuckDB
13010                    let supports_dollar_quoting = matches!(
13011                        self.config.dialect,
13012                        Some(crate::dialects::DialectType::PostgreSQL)
13013                            | Some(crate::dialects::DialectType::Databricks)
13014                            | Some(crate::dialects::DialectType::Redshift)
13015                            | Some(crate::dialects::DialectType::DuckDB)
13016                    );
13017                    if supports_dollar_quoting {
13018                        // Output in dollar-quoted format
13019                        self.write("$");
13020                        if let Some(t) = tag {
13021                            self.write(t);
13022                        }
13023                        self.write("$");
13024                        self.write(content);
13025                        self.write("$");
13026                        if let Some(t) = tag {
13027                            self.write(t);
13028                        }
13029                        self.write("$");
13030                    } else {
13031                        // Convert to single-quoted string for other dialects
13032                        let escaped = self.escape_block_for_single_quote(content);
13033                        self.write("'");
13034                        self.write(&escaped);
13035                        self.write("'");
13036                    }
13037                }
13038            }
13039        }
13040
13041        Ok(())
13042    }
13043
13044    fn generate_drop_procedure(&mut self, dp: &DropProcedure) -> Result<()> {
13045        self.write_keyword("DROP PROCEDURE");
13046
13047        if dp.if_exists {
13048            self.write_space();
13049            self.write_keyword("IF EXISTS");
13050        }
13051
13052        self.write_space();
13053        self.generate_table(&dp.name)?;
13054
13055        if let Some(params) = &dp.parameters {
13056            self.write(" (");
13057            for (i, dt) in params.iter().enumerate() {
13058                if i > 0 {
13059                    self.write(", ");
13060                }
13061                self.generate_data_type(dt)?;
13062            }
13063            self.write(")");
13064        }
13065
13066        if dp.cascade {
13067            self.write_space();
13068            self.write_keyword("CASCADE");
13069        }
13070
13071        Ok(())
13072    }
13073
13074    fn generate_create_sequence(&mut self, cs: &CreateSequence) -> Result<()> {
13075        self.write_keyword("CREATE");
13076
13077        if cs.or_replace {
13078            self.write_space();
13079            self.write_keyword("OR REPLACE");
13080        }
13081
13082        if cs.temporary {
13083            self.write_space();
13084            self.write_keyword("TEMPORARY");
13085        }
13086
13087        self.write_space();
13088        self.write_keyword("SEQUENCE");
13089
13090        if cs.if_not_exists {
13091            self.write_space();
13092            self.write_keyword("IF NOT EXISTS");
13093        }
13094
13095        self.write_space();
13096        self.generate_table(&cs.name)?;
13097
13098        // Output AS <type> if present
13099        if let Some(as_type) = &cs.as_type {
13100            self.write_space();
13101            self.write_keyword("AS");
13102            self.write_space();
13103            self.generate_data_type(as_type)?;
13104        }
13105
13106        // Output COMMENT first (Snowflake convention: COMMENT comes before other properties)
13107        if let Some(comment) = &cs.comment {
13108            self.write_space();
13109            self.write_keyword("COMMENT");
13110            self.write("=");
13111            self.generate_string_literal(comment)?;
13112        }
13113
13114        // If property_order is available, use it to preserve original order
13115        if !cs.property_order.is_empty() {
13116            for prop in &cs.property_order {
13117                match prop {
13118                    SeqPropKind::Start => {
13119                        if let Some(start) = cs.start {
13120                            self.write_space();
13121                            self.write_keyword("START WITH");
13122                            self.write(&format!(" {}", start));
13123                        }
13124                    }
13125                    SeqPropKind::Increment => {
13126                        if let Some(inc) = cs.increment {
13127                            self.write_space();
13128                            self.write_keyword("INCREMENT BY");
13129                            self.write(&format!(" {}", inc));
13130                        }
13131                    }
13132                    SeqPropKind::Minvalue => {
13133                        if let Some(min) = &cs.minvalue {
13134                            self.write_space();
13135                            match min {
13136                                SequenceBound::Value(v) => {
13137                                    self.write_keyword("MINVALUE");
13138                                    self.write(&format!(" {}", v));
13139                                }
13140                                SequenceBound::None => {
13141                                    self.write_keyword("NO MINVALUE");
13142                                }
13143                            }
13144                        }
13145                    }
13146                    SeqPropKind::Maxvalue => {
13147                        if let Some(max) = &cs.maxvalue {
13148                            self.write_space();
13149                            match max {
13150                                SequenceBound::Value(v) => {
13151                                    self.write_keyword("MAXVALUE");
13152                                    self.write(&format!(" {}", v));
13153                                }
13154                                SequenceBound::None => {
13155                                    self.write_keyword("NO MAXVALUE");
13156                                }
13157                            }
13158                        }
13159                    }
13160                    SeqPropKind::Cache => {
13161                        if let Some(cache) = cs.cache {
13162                            self.write_space();
13163                            self.write_keyword("CACHE");
13164                            self.write(&format!(" {}", cache));
13165                        }
13166                    }
13167                    SeqPropKind::NoCache => {
13168                        self.write_space();
13169                        self.write_keyword("NO CACHE");
13170                    }
13171                    SeqPropKind::NoCacheWord => {
13172                        self.write_space();
13173                        self.write_keyword("NOCACHE");
13174                    }
13175                    SeqPropKind::Cycle => {
13176                        self.write_space();
13177                        self.write_keyword("CYCLE");
13178                    }
13179                    SeqPropKind::NoCycle => {
13180                        self.write_space();
13181                        self.write_keyword("NO CYCLE");
13182                    }
13183                    SeqPropKind::NoCycleWord => {
13184                        self.write_space();
13185                        self.write_keyword("NOCYCLE");
13186                    }
13187                    SeqPropKind::OwnedBy => {
13188                        // Skip OWNED BY NONE (it's a no-op)
13189                        if !cs.owned_by_none {
13190                            if let Some(owned) = &cs.owned_by {
13191                                self.write_space();
13192                                self.write_keyword("OWNED BY");
13193                                self.write_space();
13194                                self.generate_table(owned)?;
13195                            }
13196                        }
13197                    }
13198                    SeqPropKind::Order => {
13199                        self.write_space();
13200                        self.write_keyword("ORDER");
13201                    }
13202                    SeqPropKind::NoOrder => {
13203                        self.write_space();
13204                        self.write_keyword("NOORDER");
13205                    }
13206                    SeqPropKind::Comment => {
13207                        // COMMENT is output above, before property_order iteration
13208                    }
13209                    SeqPropKind::Sharing => {
13210                        if let Some(val) = &cs.sharing {
13211                            self.write_space();
13212                            self.write(&format!("SHARING={}", val));
13213                        }
13214                    }
13215                    SeqPropKind::Keep => {
13216                        self.write_space();
13217                        self.write_keyword("KEEP");
13218                    }
13219                    SeqPropKind::NoKeep => {
13220                        self.write_space();
13221                        self.write_keyword("NOKEEP");
13222                    }
13223                    SeqPropKind::Scale => {
13224                        self.write_space();
13225                        self.write_keyword("SCALE");
13226                        if let Some(modifier) = &cs.scale_modifier {
13227                            if !modifier.is_empty() {
13228                                self.write_space();
13229                                self.write_keyword(modifier);
13230                            }
13231                        }
13232                    }
13233                    SeqPropKind::NoScale => {
13234                        self.write_space();
13235                        self.write_keyword("NOSCALE");
13236                    }
13237                    SeqPropKind::Shard => {
13238                        self.write_space();
13239                        self.write_keyword("SHARD");
13240                        if let Some(modifier) = &cs.shard_modifier {
13241                            if !modifier.is_empty() {
13242                                self.write_space();
13243                                self.write_keyword(modifier);
13244                            }
13245                        }
13246                    }
13247                    SeqPropKind::NoShard => {
13248                        self.write_space();
13249                        self.write_keyword("NOSHARD");
13250                    }
13251                    SeqPropKind::Session => {
13252                        self.write_space();
13253                        self.write_keyword("SESSION");
13254                    }
13255                    SeqPropKind::Global => {
13256                        self.write_space();
13257                        self.write_keyword("GLOBAL");
13258                    }
13259                    SeqPropKind::NoMinvalueWord => {
13260                        self.write_space();
13261                        self.write_keyword("NOMINVALUE");
13262                    }
13263                    SeqPropKind::NoMaxvalueWord => {
13264                        self.write_space();
13265                        self.write_keyword("NOMAXVALUE");
13266                    }
13267                }
13268            }
13269        } else {
13270            // Fallback: default order for backwards compatibility
13271            if let Some(inc) = cs.increment {
13272                self.write_space();
13273                self.write_keyword("INCREMENT BY");
13274                self.write(&format!(" {}", inc));
13275            }
13276
13277            if let Some(min) = &cs.minvalue {
13278                self.write_space();
13279                match min {
13280                    SequenceBound::Value(v) => {
13281                        self.write_keyword("MINVALUE");
13282                        self.write(&format!(" {}", v));
13283                    }
13284                    SequenceBound::None => {
13285                        self.write_keyword("NO MINVALUE");
13286                    }
13287                }
13288            }
13289
13290            if let Some(max) = &cs.maxvalue {
13291                self.write_space();
13292                match max {
13293                    SequenceBound::Value(v) => {
13294                        self.write_keyword("MAXVALUE");
13295                        self.write(&format!(" {}", v));
13296                    }
13297                    SequenceBound::None => {
13298                        self.write_keyword("NO MAXVALUE");
13299                    }
13300                }
13301            }
13302
13303            if let Some(start) = cs.start {
13304                self.write_space();
13305                self.write_keyword("START WITH");
13306                self.write(&format!(" {}", start));
13307            }
13308
13309            if let Some(cache) = cs.cache {
13310                self.write_space();
13311                self.write_keyword("CACHE");
13312                self.write(&format!(" {}", cache));
13313            }
13314
13315            if cs.cycle {
13316                self.write_space();
13317                self.write_keyword("CYCLE");
13318            }
13319
13320            if let Some(owned) = &cs.owned_by {
13321                self.write_space();
13322                self.write_keyword("OWNED BY");
13323                self.write_space();
13324                self.generate_table(owned)?;
13325            }
13326        }
13327
13328        Ok(())
13329    }
13330
13331    fn generate_drop_sequence(&mut self, ds: &DropSequence) -> Result<()> {
13332        self.write_keyword("DROP SEQUENCE");
13333
13334        if ds.if_exists {
13335            self.write_space();
13336            self.write_keyword("IF EXISTS");
13337        }
13338
13339        self.write_space();
13340        self.generate_table(&ds.name)?;
13341
13342        if ds.cascade {
13343            self.write_space();
13344            self.write_keyword("CASCADE");
13345        }
13346
13347        Ok(())
13348    }
13349
13350    fn generate_alter_sequence(&mut self, als: &AlterSequence) -> Result<()> {
13351        self.write_keyword("ALTER SEQUENCE");
13352
13353        if als.if_exists {
13354            self.write_space();
13355            self.write_keyword("IF EXISTS");
13356        }
13357
13358        self.write_space();
13359        self.generate_table(&als.name)?;
13360
13361        if let Some(inc) = als.increment {
13362            self.write_space();
13363            self.write_keyword("INCREMENT BY");
13364            self.write(&format!(" {}", inc));
13365        }
13366
13367        if let Some(min) = &als.minvalue {
13368            self.write_space();
13369            match min {
13370                SequenceBound::Value(v) => {
13371                    self.write_keyword("MINVALUE");
13372                    self.write(&format!(" {}", v));
13373                }
13374                SequenceBound::None => {
13375                    self.write_keyword("NO MINVALUE");
13376                }
13377            }
13378        }
13379
13380        if let Some(max) = &als.maxvalue {
13381            self.write_space();
13382            match max {
13383                SequenceBound::Value(v) => {
13384                    self.write_keyword("MAXVALUE");
13385                    self.write(&format!(" {}", v));
13386                }
13387                SequenceBound::None => {
13388                    self.write_keyword("NO MAXVALUE");
13389                }
13390            }
13391        }
13392
13393        if let Some(start) = als.start {
13394            self.write_space();
13395            self.write_keyword("START WITH");
13396            self.write(&format!(" {}", start));
13397        }
13398
13399        if let Some(restart) = &als.restart {
13400            self.write_space();
13401            self.write_keyword("RESTART");
13402            if let Some(val) = restart {
13403                self.write_keyword(" WITH");
13404                self.write(&format!(" {}", val));
13405            }
13406        }
13407
13408        if let Some(cache) = als.cache {
13409            self.write_space();
13410            self.write_keyword("CACHE");
13411            self.write(&format!(" {}", cache));
13412        }
13413
13414        if let Some(cycle) = als.cycle {
13415            self.write_space();
13416            if cycle {
13417                self.write_keyword("CYCLE");
13418            } else {
13419                self.write_keyword("NO CYCLE");
13420            }
13421        }
13422
13423        if let Some(owned) = &als.owned_by {
13424            self.write_space();
13425            self.write_keyword("OWNED BY");
13426            self.write_space();
13427            if let Some(table) = owned {
13428                self.generate_table(table)?;
13429            } else {
13430                self.write_keyword("NONE");
13431            }
13432        }
13433
13434        Ok(())
13435    }
13436
13437    fn generate_create_trigger(&mut self, ct: &CreateTrigger) -> Result<()> {
13438        self.write_keyword("CREATE");
13439
13440        if ct.or_alter {
13441            self.write_space();
13442            self.write_keyword("OR ALTER");
13443        } else if ct.or_replace {
13444            self.write_space();
13445            self.write_keyword("OR REPLACE");
13446        }
13447
13448        if ct.constraint {
13449            self.write_space();
13450            self.write_keyword("CONSTRAINT");
13451        }
13452
13453        self.write_space();
13454        self.write_keyword("TRIGGER");
13455        self.write_space();
13456        self.generate_identifier(&ct.name)?;
13457
13458        self.write_space();
13459        match ct.timing {
13460            TriggerTiming::Before => self.write_keyword("BEFORE"),
13461            TriggerTiming::After => self.write_keyword("AFTER"),
13462            TriggerTiming::InsteadOf => self.write_keyword("INSTEAD OF"),
13463        }
13464
13465        // Events
13466        for (i, event) in ct.events.iter().enumerate() {
13467            if i > 0 {
13468                self.write_keyword(" OR");
13469            }
13470            self.write_space();
13471            match event {
13472                TriggerEvent::Insert => self.write_keyword("INSERT"),
13473                TriggerEvent::Update(cols) => {
13474                    self.write_keyword("UPDATE");
13475                    if let Some(cols) = cols {
13476                        self.write_space();
13477                        self.write_keyword("OF");
13478                        for (j, col) in cols.iter().enumerate() {
13479                            if j > 0 {
13480                                self.write(",");
13481                            }
13482                            self.write_space();
13483                            self.generate_identifier(col)?;
13484                        }
13485                    }
13486                }
13487                TriggerEvent::Delete => self.write_keyword("DELETE"),
13488                TriggerEvent::Truncate => self.write_keyword("TRUNCATE"),
13489            }
13490        }
13491
13492        self.write_space();
13493        self.write_keyword("ON");
13494        self.write_space();
13495        self.generate_table(&ct.table)?;
13496
13497        // Referencing clause
13498        if let Some(ref_clause) = &ct.referencing {
13499            self.write_space();
13500            self.write_keyword("REFERENCING");
13501            if let Some(old_table) = &ref_clause.old_table {
13502                self.write_space();
13503                self.write_keyword("OLD TABLE AS");
13504                self.write_space();
13505                self.generate_identifier(old_table)?;
13506            }
13507            if let Some(new_table) = &ref_clause.new_table {
13508                self.write_space();
13509                self.write_keyword("NEW TABLE AS");
13510                self.write_space();
13511                self.generate_identifier(new_table)?;
13512            }
13513            if let Some(old_row) = &ref_clause.old_row {
13514                self.write_space();
13515                self.write_keyword("OLD ROW AS");
13516                self.write_space();
13517                self.generate_identifier(old_row)?;
13518            }
13519            if let Some(new_row) = &ref_clause.new_row {
13520                self.write_space();
13521                self.write_keyword("NEW ROW AS");
13522                self.write_space();
13523                self.generate_identifier(new_row)?;
13524            }
13525        }
13526
13527        // Deferrable options for constraint triggers (must come before FOR EACH)
13528        if let Some(deferrable) = ct.deferrable {
13529            self.write_space();
13530            if deferrable {
13531                self.write_keyword("DEFERRABLE");
13532            } else {
13533                self.write_keyword("NOT DEFERRABLE");
13534            }
13535        }
13536
13537        if let Some(initially) = ct.initially_deferred {
13538            self.write_space();
13539            self.write_keyword("INITIALLY");
13540            self.write_space();
13541            if initially {
13542                self.write_keyword("DEFERRED");
13543            } else {
13544                self.write_keyword("IMMEDIATE");
13545            }
13546        }
13547
13548        if let Some(for_each) = ct.for_each {
13549            self.write_space();
13550            self.write_keyword("FOR EACH");
13551            self.write_space();
13552            match for_each {
13553                TriggerForEach::Row => self.write_keyword("ROW"),
13554                TriggerForEach::Statement => self.write_keyword("STATEMENT"),
13555            }
13556        }
13557
13558        // When clause
13559        if let Some(when) = &ct.when {
13560            self.write_space();
13561            self.write_keyword("WHEN");
13562            if ct.when_paren {
13563                self.write(" (");
13564                self.generate_expression(when)?;
13565                self.write(")");
13566            } else {
13567                self.write_space();
13568                self.generate_expression(when)?;
13569            }
13570        }
13571
13572        // Body
13573        self.write_space();
13574        match &ct.body {
13575            TriggerBody::Execute { function, args } => {
13576                self.write_keyword("EXECUTE FUNCTION");
13577                self.write_space();
13578                self.generate_table(function)?;
13579                self.write("(");
13580                for (i, arg) in args.iter().enumerate() {
13581                    if i > 0 {
13582                        self.write(", ");
13583                    }
13584                    self.generate_expression(arg)?;
13585                }
13586                self.write(")");
13587            }
13588            TriggerBody::Block(block) => {
13589                self.write_keyword("BEGIN");
13590                self.write_space();
13591                self.write(block);
13592                self.write_space();
13593                self.write_keyword("END");
13594            }
13595        }
13596
13597        Ok(())
13598    }
13599
13600    fn generate_drop_trigger(&mut self, dt: &DropTrigger) -> Result<()> {
13601        self.write_keyword("DROP TRIGGER");
13602
13603        if dt.if_exists {
13604            self.write_space();
13605            self.write_keyword("IF EXISTS");
13606        }
13607
13608        self.write_space();
13609        self.generate_identifier(&dt.name)?;
13610
13611        if let Some(table) = &dt.table {
13612            self.write_space();
13613            self.write_keyword("ON");
13614            self.write_space();
13615            self.generate_table(table)?;
13616        }
13617
13618        if dt.cascade {
13619            self.write_space();
13620            self.write_keyword("CASCADE");
13621        }
13622
13623        Ok(())
13624    }
13625
13626    fn generate_create_type(&mut self, ct: &CreateType) -> Result<()> {
13627        self.write_keyword("CREATE TYPE");
13628
13629        if ct.if_not_exists {
13630            self.write_space();
13631            self.write_keyword("IF NOT EXISTS");
13632        }
13633
13634        self.write_space();
13635        self.generate_table(&ct.name)?;
13636
13637        self.write_space();
13638        self.write_keyword("AS");
13639        self.write_space();
13640
13641        match &ct.definition {
13642            TypeDefinition::Enum(values) => {
13643                self.write_keyword("ENUM");
13644                self.write(" (");
13645                for (i, val) in values.iter().enumerate() {
13646                    if i > 0 {
13647                        self.write(", ");
13648                    }
13649                    self.write(&format!("'{}'", val));
13650                }
13651                self.write(")");
13652            }
13653            TypeDefinition::Composite(attrs) => {
13654                self.write("(");
13655                for (i, attr) in attrs.iter().enumerate() {
13656                    if i > 0 {
13657                        self.write(", ");
13658                    }
13659                    self.generate_identifier(&attr.name)?;
13660                    self.write_space();
13661                    self.generate_data_type(&attr.data_type)?;
13662                    if let Some(collate) = &attr.collate {
13663                        self.write_space();
13664                        self.write_keyword("COLLATE");
13665                        self.write_space();
13666                        self.generate_identifier(collate)?;
13667                    }
13668                }
13669                self.write(")");
13670            }
13671            TypeDefinition::Range {
13672                subtype,
13673                subtype_diff,
13674                canonical,
13675            } => {
13676                self.write_keyword("RANGE");
13677                self.write(" (");
13678                self.write_keyword("SUBTYPE");
13679                self.write(" = ");
13680                self.generate_data_type(subtype)?;
13681                if let Some(diff) = subtype_diff {
13682                    self.write(", ");
13683                    self.write_keyword("SUBTYPE_DIFF");
13684                    self.write(" = ");
13685                    self.write(diff);
13686                }
13687                if let Some(canon) = canonical {
13688                    self.write(", ");
13689                    self.write_keyword("CANONICAL");
13690                    self.write(" = ");
13691                    self.write(canon);
13692                }
13693                self.write(")");
13694            }
13695            TypeDefinition::Base {
13696                input,
13697                output,
13698                internallength,
13699            } => {
13700                self.write("(");
13701                self.write_keyword("INPUT");
13702                self.write(" = ");
13703                self.write(input);
13704                self.write(", ");
13705                self.write_keyword("OUTPUT");
13706                self.write(" = ");
13707                self.write(output);
13708                if let Some(len) = internallength {
13709                    self.write(", ");
13710                    self.write_keyword("INTERNALLENGTH");
13711                    self.write(" = ");
13712                    self.write(&len.to_string());
13713                }
13714                self.write(")");
13715            }
13716            TypeDefinition::Domain {
13717                base_type,
13718                default,
13719                constraints,
13720            } => {
13721                self.generate_data_type(base_type)?;
13722                if let Some(def) = default {
13723                    self.write_space();
13724                    self.write_keyword("DEFAULT");
13725                    self.write_space();
13726                    self.generate_expression(def)?;
13727                }
13728                for constr in constraints {
13729                    self.write_space();
13730                    if let Some(name) = &constr.name {
13731                        self.write_keyword("CONSTRAINT");
13732                        self.write_space();
13733                        self.generate_identifier(name)?;
13734                        self.write_space();
13735                    }
13736                    self.write_keyword("CHECK");
13737                    self.write(" (");
13738                    self.generate_expression(&constr.check)?;
13739                    self.write(")");
13740                }
13741            }
13742        }
13743
13744        Ok(())
13745    }
13746
13747    fn generate_create_task(&mut self, task: &crate::expressions::CreateTask) -> Result<()> {
13748        self.write_keyword("CREATE");
13749        if task.or_replace {
13750            self.write_space();
13751            self.write_keyword("OR REPLACE");
13752        }
13753        self.write_space();
13754        self.write_keyword("TASK");
13755        if task.if_not_exists {
13756            self.write_space();
13757            self.write_keyword("IF NOT EXISTS");
13758        }
13759        self.write_space();
13760        self.write(&task.name);
13761        if !task.properties.is_empty() {
13762            // Properties already include leading whitespace from tokens_to_sql
13763            if !task.properties.starts_with('\n') && !task.properties.starts_with(' ') {
13764                self.write_space();
13765            }
13766            self.write(&task.properties);
13767        }
13768        self.write_space();
13769        self.write_keyword("AS");
13770        self.write_space();
13771        self.generate_expression(&task.body)?;
13772        Ok(())
13773    }
13774
13775    fn generate_drop_type(&mut self, dt: &DropType) -> Result<()> {
13776        self.write_keyword("DROP TYPE");
13777
13778        if dt.if_exists {
13779            self.write_space();
13780            self.write_keyword("IF EXISTS");
13781        }
13782
13783        self.write_space();
13784        self.generate_table(&dt.name)?;
13785
13786        if dt.cascade {
13787            self.write_space();
13788            self.write_keyword("CASCADE");
13789        }
13790
13791        Ok(())
13792    }
13793
13794    fn generate_describe(&mut self, d: &Describe) -> Result<()> {
13795        // Athena: DESCRIBE uses Hive engine (backticks)
13796        let saved_athena_hive_context = self.athena_hive_context;
13797        if matches!(
13798            self.config.dialect,
13799            Some(crate::dialects::DialectType::Athena)
13800        ) {
13801            self.athena_hive_context = true;
13802        }
13803
13804        // Output leading comments before DESCRIBE
13805        for comment in &d.leading_comments {
13806            self.write_formatted_comment(comment);
13807            self.write(" ");
13808        }
13809
13810        self.write_keyword("DESCRIBE");
13811
13812        if d.extended {
13813            self.write_space();
13814            self.write_keyword("EXTENDED");
13815        } else if d.formatted {
13816            self.write_space();
13817            self.write_keyword("FORMATTED");
13818        }
13819
13820        // Output style like ANALYZE, HISTORY
13821        if let Some(ref style) = d.style {
13822            self.write_space();
13823            self.write_keyword(style);
13824        }
13825
13826        // Handle object kind (TABLE, VIEW) based on dialect
13827        let should_output_kind = match self.config.dialect {
13828            // Spark doesn't use TABLE/VIEW after DESCRIBE
13829            Some(DialectType::Spark) | Some(DialectType::Databricks) | Some(DialectType::Hive) => {
13830                false
13831            }
13832            // Snowflake always includes TABLE
13833            Some(DialectType::Snowflake) => true,
13834            _ => d.kind.is_some(),
13835        };
13836        if should_output_kind {
13837            if let Some(ref kind) = d.kind {
13838                self.write_space();
13839                self.write_keyword(kind);
13840            } else if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
13841                self.write_space();
13842                self.write_keyword("TABLE");
13843            }
13844        }
13845
13846        self.write_space();
13847        self.generate_expression(&d.target)?;
13848
13849        // Output PARTITION clause if present (the Partition expression outputs its own PARTITION keyword)
13850        if let Some(ref partition) = d.partition {
13851            self.write_space();
13852            self.generate_expression(partition)?;
13853        }
13854
13855        // Databricks: AS JSON
13856        if d.as_json {
13857            self.write_space();
13858            self.write_keyword("AS JSON");
13859        }
13860
13861        // Output properties like type=stage
13862        for (name, value) in &d.properties {
13863            self.write_space();
13864            self.write(name);
13865            self.write("=");
13866            self.write(value);
13867        }
13868
13869        // Restore Athena Hive context
13870        self.athena_hive_context = saved_athena_hive_context;
13871
13872        Ok(())
13873    }
13874
13875    /// Generate SHOW statement (Snowflake, MySQL, etc.)
13876    /// SHOW [TERSE] <object_type> [HISTORY] [LIKE pattern] [IN <scope>] [STARTS WITH pattern] [LIMIT n] [FROM object]
13877    fn generate_show(&mut self, s: &Show) -> Result<()> {
13878        self.write_keyword("SHOW");
13879        self.write_space();
13880
13881        // TERSE keyword - but not for PRIMARY KEYS, UNIQUE KEYS, IMPORTED KEYS
13882        // where TERSE is syntactically valid but has no effect on output
13883        let show_terse = s.terse
13884            && !matches!(
13885                s.this.as_str(),
13886                "PRIMARY KEYS" | "UNIQUE KEYS" | "IMPORTED KEYS"
13887            );
13888        if show_terse {
13889            self.write_keyword("TERSE");
13890            self.write_space();
13891        }
13892
13893        // Object type (USERS, TABLES, DATABASES, etc.)
13894        self.write_keyword(&s.this);
13895
13896        // Target identifier (MySQL: engine name in SHOW ENGINE, preserved case)
13897        if let Some(ref target_expr) = s.target {
13898            self.write_space();
13899            self.generate_expression(target_expr)?;
13900        }
13901
13902        // HISTORY keyword
13903        if s.history {
13904            self.write_space();
13905            self.write_keyword("HISTORY");
13906        }
13907
13908        // FOR target (MySQL: SHOW GRANTS FOR foo, SHOW PROFILE ... FOR QUERY 5)
13909        if let Some(ref for_target) = s.for_target {
13910            self.write_space();
13911            self.write_keyword("FOR");
13912            self.write_space();
13913            self.generate_expression(for_target)?;
13914        }
13915
13916        // Determine ordering based on dialect:
13917        // - Snowflake: LIKE, IN, STARTS WITH, LIMIT, FROM
13918        // - MySQL: IN, FROM, LIKE (when FROM is present)
13919        use crate::dialects::DialectType;
13920        let is_snowflake = matches!(self.config.dialect, Some(DialectType::Snowflake));
13921
13922        if !is_snowflake && s.from.is_some() {
13923            // MySQL ordering: IN, FROM, LIKE
13924
13925            // IN scope_kind [scope]
13926            if let Some(ref scope_kind) = s.scope_kind {
13927                self.write_space();
13928                self.write_keyword("IN");
13929                self.write_space();
13930                self.write_keyword(scope_kind);
13931                if let Some(ref scope) = s.scope {
13932                    self.write_space();
13933                    self.generate_expression(scope)?;
13934                }
13935            } else if let Some(ref scope) = s.scope {
13936                self.write_space();
13937                self.write_keyword("IN");
13938                self.write_space();
13939                self.generate_expression(scope)?;
13940            }
13941
13942            // FROM clause
13943            if let Some(ref from) = s.from {
13944                self.write_space();
13945                self.write_keyword("FROM");
13946                self.write_space();
13947                self.generate_expression(from)?;
13948            }
13949
13950            // Second FROM clause (db name)
13951            if let Some(ref db) = s.db {
13952                self.write_space();
13953                self.write_keyword("FROM");
13954                self.write_space();
13955                self.generate_expression(db)?;
13956            }
13957
13958            // LIKE pattern
13959            if let Some(ref like) = s.like {
13960                self.write_space();
13961                self.write_keyword("LIKE");
13962                self.write_space();
13963                self.generate_expression(like)?;
13964            }
13965        } else {
13966            // Snowflake ordering: LIKE, IN, STARTS WITH, LIMIT, FROM
13967
13968            // LIKE pattern
13969            if let Some(ref like) = s.like {
13970                self.write_space();
13971                self.write_keyword("LIKE");
13972                self.write_space();
13973                self.generate_expression(like)?;
13974            }
13975
13976            // IN scope_kind [scope]
13977            if let Some(ref scope_kind) = s.scope_kind {
13978                self.write_space();
13979                self.write_keyword("IN");
13980                self.write_space();
13981                self.write_keyword(scope_kind);
13982                if let Some(ref scope) = s.scope {
13983                    self.write_space();
13984                    self.generate_expression(scope)?;
13985                }
13986            } else if let Some(ref scope) = s.scope {
13987                self.write_space();
13988                self.write_keyword("IN");
13989                self.write_space();
13990                self.generate_expression(scope)?;
13991            }
13992        }
13993
13994        // STARTS WITH pattern
13995        if let Some(ref starts_with) = s.starts_with {
13996            self.write_space();
13997            self.write_keyword("STARTS WITH");
13998            self.write_space();
13999            self.generate_expression(starts_with)?;
14000        }
14001
14002        // LIMIT clause
14003        if let Some(ref limit) = s.limit {
14004            self.write_space();
14005            self.generate_limit(limit)?;
14006        }
14007
14008        // FROM clause (for Snowflake, FROM comes after STARTS WITH and LIMIT)
14009        if is_snowflake {
14010            if let Some(ref from) = s.from {
14011                self.write_space();
14012                self.write_keyword("FROM");
14013                self.write_space();
14014                self.generate_expression(from)?;
14015            }
14016        }
14017
14018        // WHERE clause (MySQL: SHOW STATUS WHERE condition)
14019        if let Some(ref where_clause) = s.where_clause {
14020            self.write_space();
14021            self.write_keyword("WHERE");
14022            self.write_space();
14023            self.generate_expression(where_clause)?;
14024        }
14025
14026        // MUTEX/STATUS suffix (MySQL: SHOW ENGINE foo STATUS/MUTEX)
14027        if let Some(is_mutex) = s.mutex {
14028            self.write_space();
14029            if is_mutex {
14030                self.write_keyword("MUTEX");
14031            } else {
14032                self.write_keyword("STATUS");
14033            }
14034        }
14035
14036        // WITH PRIVILEGES clause (Snowflake: SHOW ... WITH PRIVILEGES USAGE, MODIFY)
14037        if !s.privileges.is_empty() {
14038            self.write_space();
14039            self.write_keyword("WITH PRIVILEGES");
14040            self.write_space();
14041            for (i, priv_name) in s.privileges.iter().enumerate() {
14042                if i > 0 {
14043                    self.write(", ");
14044                }
14045                self.write_keyword(priv_name);
14046            }
14047        }
14048
14049        Ok(())
14050    }
14051
14052    // ==================== End DDL Generation ====================
14053
14054    fn generate_literal(&mut self, lit: &Literal) -> Result<()> {
14055        use crate::dialects::DialectType;
14056        match lit {
14057            Literal::String(s) => {
14058                self.generate_string_literal(s)?;
14059            }
14060            Literal::Number(n) => {
14061                if matches!(self.config.dialect, Some(DialectType::MySQL))
14062                    && n.len() > 2
14063                    && (n.starts_with("0x") || n.starts_with("0X"))
14064                    && !n[2..].chars().all(|c| c.is_ascii_hexdigit())
14065                {
14066                    return self.generate_identifier(&Identifier {
14067                        name: n.clone(),
14068                        quoted: true,
14069                        trailing_comments: Vec::new(),
14070                        span: None,
14071                    });
14072                }
14073                // Strip underscore digit separators (e.g., 1_000_000 -> 1000000)
14074                // for dialects that don't support them (MySQL interprets as identifier).
14075                // ClickHouse, DuckDB, PostgreSQL, and Hive/Spark/Databricks support them.
14076                let n = if n.contains('_')
14077                    && !matches!(
14078                        self.config.dialect,
14079                        Some(DialectType::ClickHouse)
14080                            | Some(DialectType::DuckDB)
14081                            | Some(DialectType::PostgreSQL)
14082                            | Some(DialectType::Hive)
14083                            | Some(DialectType::Spark)
14084                            | Some(DialectType::Databricks)
14085                    ) {
14086                    std::borrow::Cow::Owned(n.replace('_', ""))
14087                } else {
14088                    std::borrow::Cow::Borrowed(n.as_str())
14089                };
14090                // Normalize numbers starting with decimal point to have leading zero
14091                // e.g., .25 -> 0.25 (matches sqlglot behavior)
14092                if n.starts_with('.') {
14093                    self.write("0");
14094                    self.write(&n);
14095                } else if n.starts_with("-.") {
14096                    // Handle negative numbers like -.25 -> -0.25
14097                    self.write("-0");
14098                    self.write(&n[1..]);
14099                } else {
14100                    self.write(&n);
14101                }
14102            }
14103            Literal::HexString(h) => {
14104                // Most dialects use lowercase x'...' for hex literals; Spark/Databricks/Teradata use uppercase X'...'
14105                match self.config.dialect {
14106                    Some(DialectType::Spark)
14107                    | Some(DialectType::Databricks)
14108                    | Some(DialectType::Teradata) => self.write("X'"),
14109                    _ => self.write("x'"),
14110                }
14111                self.write(h);
14112                self.write("'");
14113            }
14114            Literal::HexNumber(h) => {
14115                // Hex number (0xA) - integer in hex notation (from BigQuery)
14116                // For BigQuery, TSQL, Fabric output as 0xHEX (native hex notation)
14117                // For other dialects, convert to decimal integer
14118                match self.config.dialect {
14119                    Some(DialectType::BigQuery)
14120                    | Some(DialectType::TSQL)
14121                    | Some(DialectType::Fabric) => {
14122                        self.write("0x");
14123                        self.write(h);
14124                    }
14125                    _ => {
14126                        // Convert hex to decimal
14127                        if let Ok(val) = u64::from_str_radix(h, 16) {
14128                            self.write(&val.to_string());
14129                        } else {
14130                            // Fallback: keep as 0x notation
14131                            self.write("0x");
14132                            self.write(h);
14133                        }
14134                    }
14135                }
14136            }
14137            Literal::BitString(b) => {
14138                // Bit string B'0101...'
14139                self.write("B'");
14140                self.write(b);
14141                self.write("'");
14142            }
14143            Literal::ByteString(b) => {
14144                // Byte string b'...' (BigQuery style)
14145                self.write("b'");
14146                // Escape special characters for output
14147                self.write_escaped_byte_string(b);
14148                self.write("'");
14149            }
14150            Literal::NationalString(s) => {
14151                // N'string' is supported by TSQL, Oracle, MySQL, and generic SQL
14152                // Other dialects strip the N prefix and output as regular string
14153                let keep_n_prefix = matches!(
14154                    self.config.dialect,
14155                    Some(DialectType::TSQL)
14156                        | Some(DialectType::Oracle)
14157                        | Some(DialectType::MySQL)
14158                        | None
14159                );
14160                if keep_n_prefix {
14161                    self.write("N'");
14162                } else {
14163                    self.write("'");
14164                }
14165                self.write(s);
14166                self.write("'");
14167            }
14168            Literal::Date(d) => {
14169                self.generate_date_literal(d)?;
14170            }
14171            Literal::Time(t) => {
14172                self.generate_time_literal(t)?;
14173            }
14174            Literal::Timestamp(ts) => {
14175                self.generate_timestamp_literal(ts)?;
14176            }
14177            Literal::Datetime(dt) => {
14178                self.generate_datetime_literal(dt)?;
14179            }
14180            Literal::TripleQuotedString(s, _quote_char) => {
14181                // For BigQuery and other dialects that don't support triple-quote, normalize to regular strings
14182                if matches!(
14183                    self.config.dialect,
14184                    Some(crate::dialects::DialectType::BigQuery)
14185                        | Some(crate::dialects::DialectType::DuckDB)
14186                        | Some(crate::dialects::DialectType::Snowflake)
14187                        | Some(crate::dialects::DialectType::Spark)
14188                        | Some(crate::dialects::DialectType::Hive)
14189                        | Some(crate::dialects::DialectType::Presto)
14190                        | Some(crate::dialects::DialectType::Trino)
14191                        | Some(crate::dialects::DialectType::PostgreSQL)
14192                        | Some(crate::dialects::DialectType::MySQL)
14193                        | Some(crate::dialects::DialectType::Redshift)
14194                        | Some(crate::dialects::DialectType::TSQL)
14195                        | Some(crate::dialects::DialectType::Oracle)
14196                        | Some(crate::dialects::DialectType::ClickHouse)
14197                        | Some(crate::dialects::DialectType::Databricks)
14198                        | Some(crate::dialects::DialectType::SQLite)
14199                ) {
14200                    self.generate_string_literal(s)?;
14201                } else {
14202                    // Preserve triple-quoted string syntax for generic/unknown dialects
14203                    let quotes = format!("{0}{0}{0}", _quote_char);
14204                    self.write(&quotes);
14205                    self.write(s);
14206                    self.write(&quotes);
14207                }
14208            }
14209            Literal::EscapeString(s) => {
14210                // PostgreSQL escape string: e'...' or E'...'
14211                // Token text format is "e:content" or "E:content"
14212                // Normalize escape sequences: \' -> '' (standard SQL doubled quote)
14213                use crate::dialects::DialectType;
14214                let content = if let Some(c) = s.strip_prefix("e:") {
14215                    c
14216                } else if let Some(c) = s.strip_prefix("E:") {
14217                    c
14218                } else {
14219                    s.as_str()
14220                };
14221
14222                // MySQL: output the content without quotes or prefix
14223                if matches!(
14224                    self.config.dialect,
14225                    Some(DialectType::MySQL) | Some(DialectType::TiDB)
14226                ) {
14227                    self.write(content);
14228                } else {
14229                    // Some dialects use lowercase e' prefix
14230                    let prefix = if matches!(
14231                        self.config.dialect,
14232                        Some(DialectType::SingleStore)
14233                            | Some(DialectType::DuckDB)
14234                            | Some(DialectType::PostgreSQL)
14235                            | Some(DialectType::CockroachDB)
14236                            | Some(DialectType::Materialize)
14237                            | Some(DialectType::RisingWave)
14238                    ) {
14239                        "e'"
14240                    } else {
14241                        "E'"
14242                    };
14243
14244                    // Normalize \' to '' for output
14245                    let normalized = content.replace("\\'", "''");
14246                    self.write(prefix);
14247                    self.write(&normalized);
14248                    self.write("'");
14249                }
14250            }
14251            Literal::DollarString(s) => {
14252                // Convert dollar-quoted strings to single-quoted strings
14253                // (like Python sqlglot's rawstring_sql)
14254                use crate::dialects::DialectType;
14255                // Extract content from tag\x00content format
14256                let (_tag, content) = crate::tokens::parse_dollar_string_token(s);
14257                // Step 1: Escape backslashes if the dialect uses backslash as a string escape
14258                let escape_backslash = matches!(self.config.dialect, Some(DialectType::Snowflake));
14259                // Step 2: Determine quote escaping style
14260                // Snowflake: ' -> \' (backslash escape)
14261                // PostgreSQL, DuckDB, others: ' -> '' (doubled quote)
14262                let use_backslash_quote =
14263                    matches!(self.config.dialect, Some(DialectType::Snowflake));
14264
14265                let mut escaped = String::with_capacity(content.len() + 4);
14266                for ch in content.chars() {
14267                    if escape_backslash && ch == '\\' {
14268                        // Escape backslash first (before quote escaping)
14269                        escaped.push('\\');
14270                        escaped.push('\\');
14271                    } else if ch == '\'' {
14272                        if use_backslash_quote {
14273                            escaped.push('\\');
14274                            escaped.push('\'');
14275                        } else {
14276                            escaped.push('\'');
14277                            escaped.push('\'');
14278                        }
14279                    } else {
14280                        escaped.push(ch);
14281                    }
14282                }
14283                self.write("'");
14284                self.write(&escaped);
14285                self.write("'");
14286            }
14287            Literal::RawString(s) => {
14288                // Raw strings (r"..." or r'...') contain literal backslashes.
14289                // When converting to a regular string, this follows Python sqlglot's rawstring_sql:
14290                // 1. If \\ is in STRING_ESCAPES, double all backslashes
14291                // 2. Apply ESCAPED_SEQUENCES for special chars (but NOT for backslash itself)
14292                // 3. Escape quotes using STRING_ESCAPES[0] + quote_char
14293                use crate::dialects::DialectType;
14294
14295                // Dialects where \\ is in STRING_ESCAPES (backslashes need doubling)
14296                let escape_backslash = matches!(
14297                    self.config.dialect,
14298                    Some(DialectType::BigQuery)
14299                        | Some(DialectType::MySQL)
14300                        | Some(DialectType::SingleStore)
14301                        | Some(DialectType::TiDB)
14302                        | Some(DialectType::Hive)
14303                        | Some(DialectType::Spark)
14304                        | Some(DialectType::Databricks)
14305                        | Some(DialectType::Drill)
14306                        | Some(DialectType::Snowflake)
14307                        | Some(DialectType::Redshift)
14308                        | Some(DialectType::ClickHouse)
14309                );
14310
14311                // Dialects where backslash is the PRIMARY string escape (STRING_ESCAPES[0] = "\\")
14312                // These escape quotes as \' instead of ''
14313                let backslash_escapes_quote = matches!(
14314                    self.config.dialect,
14315                    Some(DialectType::BigQuery)
14316                        | Some(DialectType::Hive)
14317                        | Some(DialectType::Spark)
14318                        | Some(DialectType::Databricks)
14319                        | Some(DialectType::Drill)
14320                        | Some(DialectType::Snowflake)
14321                        | Some(DialectType::Redshift)
14322                );
14323
14324                // Whether this dialect supports escaped sequences (ESCAPED_SEQUENCES mapping)
14325                // This is True when \\ is in STRING_ESCAPES (same as escape_backslash)
14326                let supports_escape_sequences = escape_backslash;
14327
14328                let mut escaped = String::with_capacity(s.len() + 4);
14329                for ch in s.chars() {
14330                    if escape_backslash && ch == '\\' {
14331                        // Double the backslash for the target dialect
14332                        escaped.push('\\');
14333                        escaped.push('\\');
14334                    } else if ch == '\'' {
14335                        if backslash_escapes_quote {
14336                            // Use backslash to escape the quote: \'
14337                            escaped.push('\\');
14338                            escaped.push('\'');
14339                        } else {
14340                            // Use SQL standard quote doubling: ''
14341                            escaped.push('\'');
14342                            escaped.push('\'');
14343                        }
14344                    } else if supports_escape_sequences {
14345                        // Apply ESCAPED_SEQUENCES mapping for special chars
14346                        // (escape_backslash=False in rawstring_sql, so \\ is NOT escaped here)
14347                        match ch {
14348                            '\n' => {
14349                                escaped.push('\\');
14350                                escaped.push('n');
14351                            }
14352                            '\r' => {
14353                                escaped.push('\\');
14354                                escaped.push('r');
14355                            }
14356                            '\t' => {
14357                                escaped.push('\\');
14358                                escaped.push('t');
14359                            }
14360                            '\x07' => {
14361                                escaped.push('\\');
14362                                escaped.push('a');
14363                            }
14364                            '\x08' => {
14365                                escaped.push('\\');
14366                                escaped.push('b');
14367                            }
14368                            '\x0C' => {
14369                                escaped.push('\\');
14370                                escaped.push('f');
14371                            }
14372                            '\x0B' => {
14373                                escaped.push('\\');
14374                                escaped.push('v');
14375                            }
14376                            _ => escaped.push(ch),
14377                        }
14378                    } else {
14379                        escaped.push(ch);
14380                    }
14381                }
14382                self.write("'");
14383                self.write(&escaped);
14384                self.write("'");
14385            }
14386        }
14387        Ok(())
14388    }
14389
14390    /// Generate a DATE literal with dialect-specific formatting
14391    fn generate_date_literal(&mut self, d: &str) -> Result<()> {
14392        use crate::dialects::DialectType;
14393
14394        match self.config.dialect {
14395            // SQL Server uses CONVERT or CAST
14396            Some(DialectType::TSQL) => {
14397                self.write("CAST('");
14398                self.write(d);
14399                self.write("' AS DATE)");
14400            }
14401            // BigQuery uses CAST syntax for type literals
14402            // DATE 'value' -> CAST('value' AS DATE)
14403            Some(DialectType::BigQuery) => {
14404                self.write("CAST('");
14405                self.write(d);
14406                self.write("' AS DATE)");
14407            }
14408            // Exasol uses CAST syntax for DATE literals
14409            // DATE 'value' -> CAST('value' AS DATE)
14410            Some(DialectType::Exasol) => {
14411                self.write("CAST('");
14412                self.write(d);
14413                self.write("' AS DATE)");
14414            }
14415            // Snowflake uses CAST syntax for DATE literals
14416            // DATE 'value' -> CAST('value' AS DATE)
14417            Some(DialectType::Snowflake) => {
14418                self.write("CAST('");
14419                self.write(d);
14420                self.write("' AS DATE)");
14421            }
14422            // PostgreSQL, MySQL, Redshift: DATE 'value' -> CAST('value' AS DATE)
14423            Some(DialectType::PostgreSQL)
14424            | Some(DialectType::MySQL)
14425            | Some(DialectType::SingleStore)
14426            | Some(DialectType::TiDB)
14427            | Some(DialectType::Redshift) => {
14428                self.write("CAST('");
14429                self.write(d);
14430                self.write("' AS DATE)");
14431            }
14432            // DuckDB, Presto, Trino, Spark: DATE 'value' -> CAST('value' AS DATE)
14433            Some(DialectType::DuckDB)
14434            | Some(DialectType::Presto)
14435            | Some(DialectType::Trino)
14436            | Some(DialectType::Athena)
14437            | Some(DialectType::Spark)
14438            | Some(DialectType::Databricks)
14439            | Some(DialectType::Hive) => {
14440                self.write("CAST('");
14441                self.write(d);
14442                self.write("' AS DATE)");
14443            }
14444            // Oracle: DATE 'value' -> TO_DATE('value', 'YYYY-MM-DD')
14445            Some(DialectType::Oracle) => {
14446                self.write("TO_DATE('");
14447                self.write(d);
14448                self.write("', 'YYYY-MM-DD')");
14449            }
14450            // Standard SQL: DATE '...'
14451            _ => {
14452                self.write_keyword("DATE");
14453                self.write(" '");
14454                self.write(d);
14455                self.write("'");
14456            }
14457        }
14458        Ok(())
14459    }
14460
14461    /// Generate a TIME literal with dialect-specific formatting
14462    fn generate_time_literal(&mut self, t: &str) -> Result<()> {
14463        use crate::dialects::DialectType;
14464
14465        match self.config.dialect {
14466            // SQL Server uses CONVERT or CAST
14467            Some(DialectType::TSQL) => {
14468                self.write("CAST('");
14469                self.write(t);
14470                self.write("' AS TIME)");
14471            }
14472            // Standard SQL: TIME '...'
14473            _ => {
14474                self.write_keyword("TIME");
14475                self.write(" '");
14476                self.write(t);
14477                self.write("'");
14478            }
14479        }
14480        Ok(())
14481    }
14482
14483    /// Generate a date expression for Dremio, converting DATE literals to CAST
14484    fn generate_dremio_date_expression(&mut self, expr: &Expression) -> Result<()> {
14485        use crate::expressions::Literal;
14486
14487        match expr {
14488            Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Date(_)) => {
14489                let Literal::Date(d) = lit.as_ref() else {
14490                    unreachable!()
14491                };
14492                // DATE 'value' -> CAST('value' AS DATE)
14493                self.write("CAST('");
14494                self.write(d);
14495                self.write("' AS DATE)");
14496            }
14497            _ => {
14498                // For all other expressions, generate normally
14499                self.generate_expression(expr)?;
14500            }
14501        }
14502        Ok(())
14503    }
14504
14505    /// Generate a TIMESTAMP literal with dialect-specific formatting
14506    fn generate_timestamp_literal(&mut self, ts: &str) -> Result<()> {
14507        use crate::dialects::DialectType;
14508
14509        match self.config.dialect {
14510            // SQL Server uses CONVERT or CAST
14511            Some(DialectType::TSQL) => {
14512                self.write("CAST('");
14513                self.write(ts);
14514                self.write("' AS DATETIME2)");
14515            }
14516            // BigQuery uses CAST syntax for type literals
14517            // TIMESTAMP 'value' -> CAST('value' AS TIMESTAMP)
14518            Some(DialectType::BigQuery) => {
14519                self.write("CAST('");
14520                self.write(ts);
14521                self.write("' AS TIMESTAMP)");
14522            }
14523            // Snowflake uses CAST syntax for TIMESTAMP literals
14524            // TIMESTAMP 'value' -> CAST('value' AS TIMESTAMP)
14525            Some(DialectType::Snowflake) => {
14526                self.write("CAST('");
14527                self.write(ts);
14528                self.write("' AS TIMESTAMP)");
14529            }
14530            // Dremio uses CAST syntax for TIMESTAMP literals
14531            // TIMESTAMP 'value' -> CAST('value' AS TIMESTAMP)
14532            Some(DialectType::Dremio) => {
14533                self.write("CAST('");
14534                self.write(ts);
14535                self.write("' AS TIMESTAMP)");
14536            }
14537            // Exasol uses CAST syntax for TIMESTAMP literals
14538            // TIMESTAMP 'value' -> CAST('value' AS TIMESTAMP)
14539            Some(DialectType::Exasol) => {
14540                self.write("CAST('");
14541                self.write(ts);
14542                self.write("' AS TIMESTAMP)");
14543            }
14544            // Oracle prefers TO_TIMESTAMP function call
14545            // TIMESTAMP 'value' -> TO_TIMESTAMP('value', 'YYYY-MM-DD HH24:MI:SS.FF6')
14546            Some(DialectType::Oracle) => {
14547                self.write("TO_TIMESTAMP('");
14548                self.write(ts);
14549                self.write("', 'YYYY-MM-DD HH24:MI:SS.FF6')");
14550            }
14551            // Presto/Trino: always use CAST for TIMESTAMP literals
14552            Some(DialectType::Presto) | Some(DialectType::Trino) => {
14553                if Self::timestamp_has_timezone(ts) {
14554                    self.write("CAST('");
14555                    self.write(ts);
14556                    self.write("' AS TIMESTAMP WITH TIME ZONE)");
14557                } else {
14558                    self.write("CAST('");
14559                    self.write(ts);
14560                    self.write("' AS TIMESTAMP)");
14561                }
14562            }
14563            // ClickHouse: CAST('...' AS Nullable(DateTime))
14564            Some(DialectType::ClickHouse) => {
14565                self.write("CAST('");
14566                self.write(ts);
14567                self.write("' AS Nullable(DateTime))");
14568            }
14569            // Spark: CAST('...' AS TIMESTAMP)
14570            Some(DialectType::Spark) => {
14571                self.write("CAST('");
14572                self.write(ts);
14573                self.write("' AS TIMESTAMP)");
14574            }
14575            // Redshift: CAST('...' AS TIMESTAMP) for regular timestamps,
14576            // but TIMESTAMP '...' for special values like 'epoch'
14577            Some(DialectType::Redshift) => {
14578                if ts == "epoch" {
14579                    self.write_keyword("TIMESTAMP");
14580                    self.write(" '");
14581                    self.write(ts);
14582                    self.write("'");
14583                } else {
14584                    self.write("CAST('");
14585                    self.write(ts);
14586                    self.write("' AS TIMESTAMP)");
14587                }
14588            }
14589            // PostgreSQL, Hive, DuckDB, etc.: CAST('...' AS TIMESTAMP)
14590            Some(DialectType::PostgreSQL)
14591            | Some(DialectType::Hive)
14592            | Some(DialectType::SQLite)
14593            | Some(DialectType::DuckDB)
14594            | Some(DialectType::Athena)
14595            | Some(DialectType::Drill)
14596            | Some(DialectType::Teradata) => {
14597                self.write("CAST('");
14598                self.write(ts);
14599                self.write("' AS TIMESTAMP)");
14600            }
14601            // MySQL/StarRocks: CAST('...' AS DATETIME)
14602            Some(DialectType::MySQL) | Some(DialectType::StarRocks) | Some(DialectType::Doris) => {
14603                self.write("CAST('");
14604                self.write(ts);
14605                self.write("' AS DATETIME)");
14606            }
14607            // Databricks: CAST('...' AS TIMESTAMP_NTZ)
14608            Some(DialectType::Databricks) => {
14609                self.write("CAST('");
14610                self.write(ts);
14611                self.write("' AS TIMESTAMP_NTZ)");
14612            }
14613            // Standard SQL: TIMESTAMP '...'
14614            _ => {
14615                self.write_keyword("TIMESTAMP");
14616                self.write(" '");
14617                self.write(ts);
14618                self.write("'");
14619            }
14620        }
14621        Ok(())
14622    }
14623
14624    /// Check if a timestamp string contains a timezone identifier
14625    /// This detects IANA timezone names like Europe/Prague, America/New_York, etc.
14626    fn timestamp_has_timezone(ts: &str) -> bool {
14627        // Check for common IANA timezone patterns: Continent/City format
14628        // Examples: Europe/Prague, America/New_York, Asia/Tokyo, etc.
14629        // Also handles: UTC, GMT, Etc/GMT+0, etc.
14630        let ts_lower = ts.to_ascii_lowercase();
14631
14632        // Check for Continent/City pattern (most common)
14633        let continent_prefixes = [
14634            "africa/",
14635            "america/",
14636            "antarctica/",
14637            "arctic/",
14638            "asia/",
14639            "atlantic/",
14640            "australia/",
14641            "europe/",
14642            "indian/",
14643            "pacific/",
14644            "etc/",
14645            "brazil/",
14646            "canada/",
14647            "chile/",
14648            "mexico/",
14649            "us/",
14650        ];
14651
14652        for prefix in &continent_prefixes {
14653            if ts_lower.contains(prefix) {
14654                return true;
14655            }
14656        }
14657
14658        // Check for standalone timezone abbreviations at the end
14659        // These typically appear after the time portion
14660        let tz_abbrevs = [
14661            " utc", " gmt", " cet", " cest", " eet", " eest", " wet", " west", " est", " edt",
14662            " cst", " cdt", " mst", " mdt", " pst", " pdt", " ist", " bst", " jst", " kst", " hkt",
14663            " sgt", " aest", " aedt", " acst", " acdt", " awst",
14664        ];
14665
14666        for abbrev in &tz_abbrevs {
14667            if ts_lower.ends_with(abbrev) {
14668                return true;
14669            }
14670        }
14671
14672        // Check for numeric timezone offsets: +N, -N, +NN:NN, -NN:NN
14673        // Examples: "2012-10-31 01:00 -2", "2012-10-31 01:00 +02:00"
14674        // Look for pattern: space followed by + or - and digits (optionally with :)
14675        let trimmed = ts.trim();
14676        if let Some(last_space) = trimmed.rfind(' ') {
14677            let suffix = &trimmed[last_space + 1..];
14678            if (suffix.starts_with('+') || suffix.starts_with('-')) && suffix.len() > 1 {
14679                // Check if rest is numeric (possibly with : for hh:mm format)
14680                let rest = &suffix[1..];
14681                if rest.chars().all(|c| c.is_ascii_digit() || c == ':') {
14682                    return true;
14683                }
14684            }
14685        }
14686
14687        false
14688    }
14689
14690    /// Generate a DATETIME literal with dialect-specific formatting
14691    fn generate_datetime_literal(&mut self, dt: &str) -> Result<()> {
14692        use crate::dialects::DialectType;
14693
14694        match self.config.dialect {
14695            // BigQuery uses CAST syntax for type literals
14696            // DATETIME 'value' -> CAST('value' AS DATETIME)
14697            Some(DialectType::BigQuery) => {
14698                self.write("CAST('");
14699                self.write(dt);
14700                self.write("' AS DATETIME)");
14701            }
14702            // DuckDB: DATETIME -> CAST('value' AS TIMESTAMP)
14703            Some(DialectType::DuckDB) => {
14704                self.write("CAST('");
14705                self.write(dt);
14706                self.write("' AS TIMESTAMP)");
14707            }
14708            // DATETIME is primarily a BigQuery type
14709            // Output as DATETIME '...' for dialects that support it
14710            _ => {
14711                self.write_keyword("DATETIME");
14712                self.write(" '");
14713                self.write(dt);
14714                self.write("'");
14715            }
14716        }
14717        Ok(())
14718    }
14719
14720    /// Generate a string literal with dialect-specific escaping
14721    fn generate_string_literal(&mut self, s: &str) -> Result<()> {
14722        use crate::dialects::DialectType;
14723
14724        match self.config.dialect {
14725            // MySQL/Hive: Uses SQL standard quote escaping ('') for quotes,
14726            // and backslash escaping for special characters like newlines
14727            // Hive STRING_ESCAPES = ["\\"] - uses backslash escapes
14728            Some(DialectType::Hive) | Some(DialectType::Spark) | Some(DialectType::Databricks) => {
14729                // Hive/Spark use backslash escaping for quotes (\') and special chars
14730                self.write("'");
14731                for c in s.chars() {
14732                    match c {
14733                        '\'' => self.write("\\'"),
14734                        '\\' => self.write("\\\\"),
14735                        '\n' => self.write("\\n"),
14736                        '\r' => self.write("\\r"),
14737                        '\t' => self.write("\\t"),
14738                        '\0' => self.write("\\0"),
14739                        _ => self.output.push(c),
14740                    }
14741                }
14742                self.write("'");
14743            }
14744            Some(DialectType::Drill) => {
14745                // Drill uses SQL-standard quote doubling ('') for quotes,
14746                // but backslash escaping for special characters
14747                self.write("'");
14748                for c in s.chars() {
14749                    match c {
14750                        '\'' => self.write("''"),
14751                        '\\' => self.write("\\\\"),
14752                        '\n' => self.write("\\n"),
14753                        '\r' => self.write("\\r"),
14754                        '\t' => self.write("\\t"),
14755                        '\0' => self.write("\\0"),
14756                        _ => self.output.push(c),
14757                    }
14758                }
14759                self.write("'");
14760            }
14761            Some(DialectType::MySQL) | Some(DialectType::SingleStore) | Some(DialectType::TiDB) => {
14762                self.write("'");
14763                for c in s.chars() {
14764                    match c {
14765                        // MySQL uses SQL standard quote doubling
14766                        '\'' => self.write("''"),
14767                        '\\' => self.write("\\\\"),
14768                        '\n' => self.write("\\n"),
14769                        '\r' => self.write("\\r"),
14770                        '\t' => self.write("\\t"),
14771                        // sqlglot writes a literal NUL for this case
14772                        '\0' => self.output.push('\0'),
14773                        _ => self.output.push(c),
14774                    }
14775                }
14776                self.write("'");
14777            }
14778            // BigQuery: Uses backslash escaping
14779            Some(DialectType::BigQuery) => {
14780                self.write("'");
14781                for c in s.chars() {
14782                    match c {
14783                        '\'' => self.write("\\'"),
14784                        '\\' => self.write("\\\\"),
14785                        '\n' => self.write("\\n"),
14786                        '\r' => self.write("\\r"),
14787                        '\t' => self.write("\\t"),
14788                        '\0' => self.write("\\0"),
14789                        '\x07' => self.write("\\a"),
14790                        '\x08' => self.write("\\b"),
14791                        '\x0C' => self.write("\\f"),
14792                        '\x0B' => self.write("\\v"),
14793                        _ => self.output.push(c),
14794                    }
14795                }
14796                self.write("'");
14797            }
14798            // Athena: Uses different escaping for DDL (Hive) vs DML (Trino)
14799            // In Hive context (DDL): backslash escaping for single quotes (\') and backslashes (\\)
14800            // In Trino context (DML): SQL-standard escaping ('') and literal backslashes
14801            Some(DialectType::Athena) => {
14802                if self.athena_hive_context {
14803                    // Hive-style: backslash escaping
14804                    self.write("'");
14805                    for c in s.chars() {
14806                        match c {
14807                            '\'' => self.write("\\'"),
14808                            '\\' => self.write("\\\\"),
14809                            '\n' => self.write("\\n"),
14810                            '\r' => self.write("\\r"),
14811                            '\t' => self.write("\\t"),
14812                            '\0' => self.write("\\0"),
14813                            _ => self.output.push(c),
14814                        }
14815                    }
14816                    self.write("'");
14817                } else {
14818                    // Trino-style: SQL-standard escaping, preserve backslashes
14819                    self.write("'");
14820                    for c in s.chars() {
14821                        match c {
14822                            '\'' => self.write("''"),
14823                            // Preserve backslashes literally (no re-escaping)
14824                            _ => self.output.push(c),
14825                        }
14826                    }
14827                    self.write("'");
14828                }
14829            }
14830            // Snowflake: Uses backslash escaping (STRING_ESCAPES = ["\\", "'"])
14831            // The tokenizer preserves backslash escape sequences literally (e.g., input '\\'
14832            // becomes string value '\\'), so we should NOT re-escape backslashes.
14833            // We only need to escape single quotes.
14834            Some(DialectType::Snowflake) => {
14835                self.write("'");
14836                for c in s.chars() {
14837                    match c {
14838                        '\'' => self.write("\\'"),
14839                        // Backslashes are already escaped in the tokenized string, don't re-escape
14840                        // Only escape special characters that might not have been escaped
14841                        '\n' => self.write("\\n"),
14842                        '\r' => self.write("\\r"),
14843                        '\t' => self.write("\\t"),
14844                        _ => self.output.push(c),
14845                    }
14846                }
14847                self.write("'");
14848            }
14849            // PostgreSQL: Output special characters as literal chars in strings (no E-string prefix)
14850            Some(DialectType::PostgreSQL) => {
14851                self.write("'");
14852                for c in s.chars() {
14853                    match c {
14854                        '\'' => self.write("''"),
14855                        _ => self.output.push(c),
14856                    }
14857                }
14858                self.write("'");
14859            }
14860            // Redshift: Uses backslash escaping for single quotes
14861            Some(DialectType::Redshift) => {
14862                self.write("'");
14863                for c in s.chars() {
14864                    match c {
14865                        '\'' => self.write("\\'"),
14866                        _ => self.output.push(c),
14867                    }
14868                }
14869                self.write("'");
14870            }
14871            // Oracle: Uses standard double single-quote escaping
14872            Some(DialectType::Oracle) => {
14873                self.write("'");
14874                for ch in s.chars() {
14875                    if ch == '\'' {
14876                        self.output.push_str("''");
14877                    } else {
14878                        self.output.push(ch);
14879                    }
14880                }
14881                self.write("'");
14882            }
14883            // ClickHouse: Uses SQL-standard quote doubling ('') for quotes,
14884            // backslash escaping for backslashes and special characters
14885            Some(DialectType::ClickHouse) => {
14886                self.write("'");
14887                for c in s.chars() {
14888                    match c {
14889                        '\'' => self.write("''"),
14890                        '\\' => self.write("\\\\"),
14891                        '\n' => self.write("\\n"),
14892                        '\r' => self.write("\\r"),
14893                        '\t' => self.write("\\t"),
14894                        '\0' => self.write("\\0"),
14895                        '\x07' => self.write("\\a"),
14896                        '\x08' => self.write("\\b"),
14897                        '\x0C' => self.write("\\f"),
14898                        '\x0B' => self.write("\\v"),
14899                        // Non-printable characters: emit as \xNN hex escapes
14900                        c if c.is_control() || (c as u32) < 0x20 => {
14901                            let byte = c as u32;
14902                            if byte < 256 {
14903                                self.write(&format!("\\x{:02X}", byte));
14904                            } else {
14905                                self.output.push(c);
14906                            }
14907                        }
14908                        _ => self.output.push(c),
14909                    }
14910                }
14911                self.write("'");
14912            }
14913            // Default: SQL standard double single quotes (works for most dialects)
14914            // PostgreSQL, Snowflake, DuckDB, TSQL, etc.
14915            _ => {
14916                self.write("'");
14917                for ch in s.chars() {
14918                    if ch == '\'' {
14919                        self.output.push_str("''");
14920                    } else {
14921                        self.output.push(ch);
14922                    }
14923                }
14924                self.write("'");
14925            }
14926        }
14927        Ok(())
14928    }
14929
14930    /// Write a byte string with proper escaping for BigQuery-style byte literals
14931    /// Escapes characters as \xNN hex escapes where needed
14932    fn write_escaped_byte_string(&mut self, s: &str) {
14933        for c in s.chars() {
14934            match c {
14935                // Escape single quotes
14936                '\'' => self.write("\\'"),
14937                // Escape backslashes
14938                '\\' => self.write("\\\\"),
14939                // Keep all printable characters (including non-ASCII) as-is
14940                _ if !c.is_control() => self.output.push(c),
14941                // Escape control characters as hex
14942                _ => {
14943                    let byte = c as u32;
14944                    if byte < 256 {
14945                        self.write(&format!("\\x{:02x}", byte));
14946                    } else {
14947                        // For unicode characters, write each UTF-8 byte
14948                        for b in c.to_string().as_bytes() {
14949                            self.write(&format!("\\x{:02x}", b));
14950                        }
14951                    }
14952                }
14953            }
14954        }
14955    }
14956
14957    fn generate_boolean(&mut self, b: &BooleanLiteral) -> Result<()> {
14958        use crate::dialects::DialectType;
14959
14960        // Different dialects have different boolean literal formats
14961        match self.config.dialect {
14962            // SQL Server typically uses 1/0 for boolean literals in many contexts
14963            // However, TRUE/FALSE also works in modern versions
14964            Some(DialectType::TSQL) => {
14965                self.write(if b.value { "1" } else { "0" });
14966            }
14967            // Oracle traditionally uses 1/0 (no native boolean until recent versions)
14968            Some(DialectType::Oracle) => {
14969                self.write(if b.value { "1" } else { "0" });
14970            }
14971            // MySQL accepts TRUE/FALSE as aliases for 1/0
14972            Some(DialectType::MySQL) => {
14973                self.write_keyword(if b.value { "TRUE" } else { "FALSE" });
14974            }
14975            // Most other dialects support TRUE/FALSE
14976            _ => {
14977                self.write_keyword(if b.value { "TRUE" } else { "FALSE" });
14978            }
14979        }
14980        Ok(())
14981    }
14982
14983    /// Generate an identifier that's used as an alias name
14984    /// This quotes reserved keywords in addition to already-quoted identifiers
14985    fn generate_alias_identifier(&mut self, id: &Identifier) -> Result<()> {
14986        let name = &id.name;
14987        let quote_style = &self.config.identifier_quote_style;
14988
14989        // For aliases, quote if:
14990        // 1. The identifier was explicitly quoted in the source
14991        // 2. The identifier is a reserved keyword for the current dialect
14992        let needs_quoting = id.quoted || self.is_reserved_keyword(name);
14993
14994        // Normalize identifier if configured
14995        let output_name = if self.config.normalize_identifiers && !id.quoted {
14996            name.to_ascii_lowercase()
14997        } else {
14998            name.to_string()
14999        };
15000
15001        if needs_quoting {
15002            // Escape any quote characters within the identifier
15003            let escaped_name = if quote_style.start == quote_style.end {
15004                output_name.replace(
15005                    quote_style.end,
15006                    &format!("{}{}", quote_style.end, quote_style.end),
15007                )
15008            } else {
15009                output_name.replace(
15010                    quote_style.end,
15011                    &format!("{}{}", quote_style.end, quote_style.end),
15012                )
15013            };
15014            self.write(&format!(
15015                "{}{}{}",
15016                quote_style.start, escaped_name, quote_style.end
15017            ));
15018        } else {
15019            self.write(&output_name);
15020        }
15021
15022        // Output trailing comments
15023        for comment in &id.trailing_comments {
15024            self.write(" ");
15025            self.write_formatted_comment(comment);
15026        }
15027        Ok(())
15028    }
15029
15030    fn generate_identifier(&mut self, id: &Identifier) -> Result<()> {
15031        use crate::dialects::DialectType;
15032
15033        let name = &id.name;
15034
15035        // For Athena, use backticks in Hive context, double quotes in Trino context
15036        let quote_style = if matches!(self.config.dialect, Some(DialectType::Athena))
15037            && self.athena_hive_context
15038        {
15039            &IdentifierQuoteStyle::BACKTICK
15040        } else {
15041            &self.config.identifier_quote_style
15042        };
15043
15044        // Quote if:
15045        // 1. The identifier was explicitly quoted in the source
15046        // 2. The identifier is a reserved keyword for the current dialect
15047        // 3. The config says to always quote identifiers (e.g., Athena/Presto)
15048        // This matches Python sqlglot's identifier_sql behavior
15049        // Also quote identifiers starting with digits if the target dialect doesn't support them
15050        let starts_with_digit = name.chars().next().map_or(false, |c| c.is_ascii_digit());
15051        let needs_digit_quoting = starts_with_digit
15052            && !self.config.identifiers_can_start_with_digit
15053            && self.config.dialect.is_some();
15054        let mysql_invalid_hex_identifier = matches!(self.config.dialect, Some(DialectType::MySQL))
15055            && name.len() > 2
15056            && (name.starts_with("0x") || name.starts_with("0X"))
15057            && !name[2..].chars().all(|c| c.is_ascii_hexdigit());
15058        let needs_quoting = id.quoted
15059            || self.is_reserved_keyword(name)
15060            || self.config.always_quote_identifiers
15061            || needs_digit_quoting
15062            || mysql_invalid_hex_identifier;
15063
15064        // Check for MySQL index column prefix length: name(16) or name(16) ASC/DESC
15065        // When quoted, we need to output `name`(16) not `name(16)`
15066        let (base_name, suffix) = if needs_quoting {
15067            // Try to extract prefix length from identifier: name(number) or name(number) ASC/DESC
15068            if let Some(paren_pos) = name.find('(') {
15069                let base = &name[..paren_pos];
15070                let rest = &name[paren_pos..];
15071                // Verify it looks like (digits) or (digits) ASC/DESC
15072                if rest.starts_with('(')
15073                    && (rest.ends_with(')') || rest.ends_with(") ASC") || rest.ends_with(") DESC"))
15074                {
15075                    // Check if content between parens is all digits
15076                    let close_paren = rest.find(')').unwrap_or(rest.len());
15077                    let inside = &rest[1..close_paren];
15078                    if inside.chars().all(|c| c.is_ascii_digit()) {
15079                        (base.to_string(), rest.to_string())
15080                    } else {
15081                        (name.to_string(), String::new())
15082                    }
15083                } else {
15084                    (name.to_string(), String::new())
15085                }
15086            } else if name.ends_with(" ASC") {
15087                let base = &name[..name.len() - 4];
15088                (base.to_string(), " ASC".to_string())
15089            } else if name.ends_with(" DESC") {
15090                let base = &name[..name.len() - 5];
15091                (base.to_string(), " DESC".to_string())
15092            } else {
15093                (name.to_string(), String::new())
15094            }
15095        } else {
15096            (name.to_string(), String::new())
15097        };
15098
15099        // Normalize identifier if configured, with special handling for Exasol
15100        // Exasol uses UPPERCASE normalization strategy, so reserved keywords that need quoting
15101        // should be uppercased when not already quoted (to match Python sqlglot behavior)
15102        let output_name = if self.config.normalize_identifiers && !id.quoted {
15103            base_name.to_ascii_lowercase()
15104        } else if matches!(self.config.dialect, Some(DialectType::Exasol))
15105            && !id.quoted
15106            && self.is_reserved_keyword(name)
15107        {
15108            // Exasol: uppercase reserved keywords when quoting them
15109            // This matches Python sqlglot's behavior with NORMALIZATION_STRATEGY = UPPERCASE
15110            base_name.to_ascii_uppercase()
15111        } else {
15112            base_name
15113        };
15114
15115        if needs_quoting {
15116            // Escape any quote characters within the identifier
15117            let escaped_name = if quote_style.start == quote_style.end {
15118                // Same start/end char (e.g., " or `) - double the quote char
15119                output_name.replace(
15120                    quote_style.end,
15121                    &format!("{}{}", quote_style.end, quote_style.end),
15122                )
15123            } else {
15124                // Different start/end (e.g., [ and ]) - escape only the end char
15125                output_name.replace(
15126                    quote_style.end,
15127                    &format!("{}{}", quote_style.end, quote_style.end),
15128                )
15129            };
15130            self.write(&format!(
15131                "{}{}{}{}",
15132                quote_style.start, escaped_name, quote_style.end, suffix
15133            ));
15134        } else {
15135            self.write(&output_name);
15136        }
15137
15138        // Output trailing comments
15139        for comment in &id.trailing_comments {
15140            self.write(" ");
15141            self.write_formatted_comment(comment);
15142        }
15143        Ok(())
15144    }
15145
15146    fn generate_column(&mut self, col: &Column) -> Result<()> {
15147        use crate::dialects::DialectType;
15148
15149        if let Some(table) = &col.table {
15150            // Exasol special case: LOCAL as column table prefix should NOT be quoted
15151            // LOCAL is a special keyword in Exasol for referencing aliases from the current scope
15152            // Only applies when: dialect is Exasol, name is "LOCAL" (case-insensitive), and not already quoted
15153            let is_exasol_local_prefix = matches!(self.config.dialect, Some(DialectType::Exasol))
15154                && !table.quoted
15155                && table.name.eq_ignore_ascii_case("LOCAL");
15156
15157            if is_exasol_local_prefix {
15158                // Write LOCAL unquoted (this is special Exasol syntax, not a table reference)
15159                self.write("LOCAL");
15160            } else {
15161                self.generate_identifier(table)?;
15162            }
15163            self.write(".");
15164        }
15165        self.generate_identifier(&col.name)?;
15166        // Oracle-style join marker (+)
15167        // Only output if dialect supports it (Oracle, Exasol)
15168        if col.join_mark && self.config.supports_column_join_marks {
15169            self.write(" (+)");
15170        }
15171        // Output trailing comments
15172        for comment in &col.trailing_comments {
15173            self.write_space();
15174            self.write_formatted_comment(comment);
15175        }
15176        Ok(())
15177    }
15178
15179    /// Generate a pseudocolumn (Oracle ROWNUM, ROWID, LEVEL, etc.)
15180    /// Pseudocolumns should NEVER be quoted, as quoting breaks them in Oracle
15181    fn generate_pseudocolumn(&mut self, pc: &Pseudocolumn) -> Result<()> {
15182        use crate::dialects::DialectType;
15183        use crate::expressions::PseudocolumnType;
15184
15185        // SYSDATE -> CURRENT_TIMESTAMP for non-Oracle/Redshift dialects
15186        if pc.kind == PseudocolumnType::Sysdate
15187            && !matches!(
15188                self.config.dialect,
15189                Some(DialectType::Oracle) | Some(DialectType::Redshift) | None
15190            )
15191        {
15192            self.write_keyword("CURRENT_TIMESTAMP");
15193            // Add () for dialects that expect it
15194            if matches!(
15195                self.config.dialect,
15196                Some(DialectType::MySQL)
15197                    | Some(DialectType::ClickHouse)
15198                    | Some(DialectType::Spark)
15199                    | Some(DialectType::Databricks)
15200                    | Some(DialectType::Hive)
15201            ) {
15202                self.write("()");
15203            }
15204        } else {
15205            self.write(pc.kind.as_str());
15206        }
15207        Ok(())
15208    }
15209
15210    /// Generate CONNECT BY clause (Oracle hierarchical queries)
15211    fn generate_connect(&mut self, connect: &Connect) -> Result<()> {
15212        use crate::dialects::DialectType;
15213
15214        // Generate native CONNECT BY for Oracle and Snowflake
15215        // For other dialects, add a comment noting manual conversion needed
15216        let supports_connect_by = matches!(
15217            self.config.dialect,
15218            Some(DialectType::Oracle) | Some(DialectType::Snowflake)
15219        );
15220
15221        if !supports_connect_by && self.config.dialect.is_some() {
15222            // Add comment for unsupported dialects
15223            if self.config.pretty {
15224                self.write_newline();
15225            } else {
15226                self.write_space();
15227            }
15228            self.write_unsupported_comment(
15229                "CONNECT BY requires manual conversion to recursive CTE",
15230            )?;
15231        }
15232
15233        // Generate START WITH if present (before CONNECT BY)
15234        if let Some(start) = &connect.start {
15235            if self.config.pretty {
15236                self.write_newline();
15237            } else {
15238                self.write_space();
15239            }
15240            self.write_keyword("START WITH");
15241            self.write_space();
15242            self.generate_expression(start)?;
15243        }
15244
15245        // Generate CONNECT BY
15246        if self.config.pretty {
15247            self.write_newline();
15248        } else {
15249            self.write_space();
15250        }
15251        self.write_keyword("CONNECT BY");
15252        if connect.nocycle {
15253            self.write_space();
15254            self.write_keyword("NOCYCLE");
15255        }
15256        self.write_space();
15257        self.generate_expression(&connect.connect)?;
15258
15259        Ok(())
15260    }
15261
15262    /// Generate Connect expression (for Expression::Connect variant)
15263    fn generate_connect_expr(&mut self, connect: &Connect) -> Result<()> {
15264        self.generate_connect(connect)
15265    }
15266
15267    /// Generate PRIOR expression
15268    fn generate_prior(&mut self, prior: &Prior) -> Result<()> {
15269        self.write_keyword("PRIOR");
15270        self.write_space();
15271        self.generate_expression(&prior.this)?;
15272        Ok(())
15273    }
15274
15275    /// Generate CONNECT_BY_ROOT function
15276    /// Syntax: CONNECT_BY_ROOT column (no parentheses)
15277    fn generate_connect_by_root(&mut self, cbr: &ConnectByRoot) -> Result<()> {
15278        self.write_keyword("CONNECT_BY_ROOT");
15279        self.write_space();
15280        self.generate_expression(&cbr.this)?;
15281        Ok(())
15282    }
15283
15284    /// Generate MATCH_RECOGNIZE clause
15285    fn generate_match_recognize(&mut self, mr: &MatchRecognize) -> Result<()> {
15286        use crate::dialects::DialectType;
15287
15288        // MATCH_RECOGNIZE is supported in Oracle, Snowflake, Presto, and Trino
15289        let supports_match_recognize = matches!(
15290            self.config.dialect,
15291            Some(DialectType::Oracle)
15292                | Some(DialectType::Snowflake)
15293                | Some(DialectType::Presto)
15294                | Some(DialectType::Trino)
15295        );
15296
15297        // Generate the source table first
15298        if let Some(source) = &mr.this {
15299            self.generate_expression(source)?;
15300        }
15301
15302        if !supports_match_recognize {
15303            self.write_unsupported_comment("MATCH_RECOGNIZE not supported in this dialect")?;
15304            return Ok(());
15305        }
15306
15307        // In pretty mode, MATCH_RECOGNIZE should be on a new line
15308        if self.config.pretty {
15309            self.write_newline();
15310        } else {
15311            self.write_space();
15312        }
15313
15314        self.write_keyword("MATCH_RECOGNIZE");
15315        self.write(" (");
15316
15317        if self.config.pretty {
15318            self.indent_level += 1;
15319        }
15320
15321        let mut needs_separator = false;
15322
15323        // PARTITION BY
15324        if let Some(partition_by) = &mr.partition_by {
15325            if !partition_by.is_empty() {
15326                if self.config.pretty {
15327                    self.write_newline();
15328                    self.write_indent();
15329                }
15330                self.write_keyword("PARTITION BY");
15331                self.write_space();
15332                for (i, expr) in partition_by.iter().enumerate() {
15333                    if i > 0 {
15334                        self.write(", ");
15335                    }
15336                    self.generate_expression(expr)?;
15337                }
15338                needs_separator = true;
15339            }
15340        }
15341
15342        // ORDER BY
15343        if let Some(order_by) = &mr.order_by {
15344            if !order_by.is_empty() {
15345                if needs_separator {
15346                    if self.config.pretty {
15347                        self.write_newline();
15348                        self.write_indent();
15349                    } else {
15350                        self.write_space();
15351                    }
15352                } else if self.config.pretty {
15353                    self.write_newline();
15354                    self.write_indent();
15355                }
15356                self.write_keyword("ORDER BY");
15357                // In pretty mode, put each ORDER BY column on a new indented line
15358                if self.config.pretty {
15359                    self.indent_level += 1;
15360                    for (i, ordered) in order_by.iter().enumerate() {
15361                        if i > 0 {
15362                            self.write(",");
15363                        }
15364                        self.write_newline();
15365                        self.write_indent();
15366                        self.generate_ordered(ordered)?;
15367                    }
15368                    self.indent_level -= 1;
15369                } else {
15370                    self.write_space();
15371                    for (i, ordered) in order_by.iter().enumerate() {
15372                        if i > 0 {
15373                            self.write(", ");
15374                        }
15375                        self.generate_ordered(ordered)?;
15376                    }
15377                }
15378                needs_separator = true;
15379            }
15380        }
15381
15382        // MEASURES
15383        if let Some(measures) = &mr.measures {
15384            if !measures.is_empty() {
15385                if needs_separator {
15386                    if self.config.pretty {
15387                        self.write_newline();
15388                        self.write_indent();
15389                    } else {
15390                        self.write_space();
15391                    }
15392                } else if self.config.pretty {
15393                    self.write_newline();
15394                    self.write_indent();
15395                }
15396                self.write_keyword("MEASURES");
15397                // In pretty mode, put each MEASURE on a new indented line
15398                if self.config.pretty {
15399                    self.indent_level += 1;
15400                    for (i, measure) in measures.iter().enumerate() {
15401                        if i > 0 {
15402                            self.write(",");
15403                        }
15404                        self.write_newline();
15405                        self.write_indent();
15406                        // Handle RUNNING/FINAL prefix
15407                        if let Some(semantics) = &measure.window_frame {
15408                            match semantics {
15409                                MatchRecognizeSemantics::Running => {
15410                                    self.write_keyword("RUNNING");
15411                                    self.write_space();
15412                                }
15413                                MatchRecognizeSemantics::Final => {
15414                                    self.write_keyword("FINAL");
15415                                    self.write_space();
15416                                }
15417                            }
15418                        }
15419                        self.generate_expression(&measure.this)?;
15420                    }
15421                    self.indent_level -= 1;
15422                } else {
15423                    self.write_space();
15424                    for (i, measure) in measures.iter().enumerate() {
15425                        if i > 0 {
15426                            self.write(", ");
15427                        }
15428                        // Handle RUNNING/FINAL prefix
15429                        if let Some(semantics) = &measure.window_frame {
15430                            match semantics {
15431                                MatchRecognizeSemantics::Running => {
15432                                    self.write_keyword("RUNNING");
15433                                    self.write_space();
15434                                }
15435                                MatchRecognizeSemantics::Final => {
15436                                    self.write_keyword("FINAL");
15437                                    self.write_space();
15438                                }
15439                            }
15440                        }
15441                        self.generate_expression(&measure.this)?;
15442                    }
15443                }
15444                needs_separator = true;
15445            }
15446        }
15447
15448        // Row semantics (ONE ROW PER MATCH, ALL ROWS PER MATCH, etc.)
15449        if let Some(rows) = &mr.rows {
15450            if needs_separator {
15451                if self.config.pretty {
15452                    self.write_newline();
15453                    self.write_indent();
15454                } else {
15455                    self.write_space();
15456                }
15457            } else if self.config.pretty {
15458                self.write_newline();
15459                self.write_indent();
15460            }
15461            match rows {
15462                MatchRecognizeRows::OneRowPerMatch => {
15463                    self.write_keyword("ONE ROW PER MATCH");
15464                }
15465                MatchRecognizeRows::AllRowsPerMatch => {
15466                    self.write_keyword("ALL ROWS PER MATCH");
15467                }
15468                MatchRecognizeRows::AllRowsPerMatchShowEmptyMatches => {
15469                    self.write_keyword("ALL ROWS PER MATCH SHOW EMPTY MATCHES");
15470                }
15471                MatchRecognizeRows::AllRowsPerMatchOmitEmptyMatches => {
15472                    self.write_keyword("ALL ROWS PER MATCH OMIT EMPTY MATCHES");
15473                }
15474                MatchRecognizeRows::AllRowsPerMatchWithUnmatchedRows => {
15475                    self.write_keyword("ALL ROWS PER MATCH WITH UNMATCHED ROWS");
15476                }
15477            }
15478            needs_separator = true;
15479        }
15480
15481        // AFTER MATCH SKIP
15482        if let Some(after) = &mr.after {
15483            if needs_separator {
15484                if self.config.pretty {
15485                    self.write_newline();
15486                    self.write_indent();
15487                } else {
15488                    self.write_space();
15489                }
15490            } else if self.config.pretty {
15491                self.write_newline();
15492                self.write_indent();
15493            }
15494            match after {
15495                MatchRecognizeAfter::PastLastRow => {
15496                    self.write_keyword("AFTER MATCH SKIP PAST LAST ROW");
15497                }
15498                MatchRecognizeAfter::ToNextRow => {
15499                    self.write_keyword("AFTER MATCH SKIP TO NEXT ROW");
15500                }
15501                MatchRecognizeAfter::ToFirst(ident) => {
15502                    self.write_keyword("AFTER MATCH SKIP TO FIRST");
15503                    self.write_space();
15504                    self.generate_identifier(ident)?;
15505                }
15506                MatchRecognizeAfter::ToLast(ident) => {
15507                    self.write_keyword("AFTER MATCH SKIP TO LAST");
15508                    self.write_space();
15509                    self.generate_identifier(ident)?;
15510                }
15511            }
15512            needs_separator = true;
15513        }
15514
15515        // PATTERN
15516        if let Some(pattern) = &mr.pattern {
15517            if needs_separator {
15518                if self.config.pretty {
15519                    self.write_newline();
15520                    self.write_indent();
15521                } else {
15522                    self.write_space();
15523                }
15524            } else if self.config.pretty {
15525                self.write_newline();
15526                self.write_indent();
15527            }
15528            self.write_keyword("PATTERN");
15529            self.write_space();
15530            self.write("(");
15531            self.write(pattern);
15532            self.write(")");
15533            needs_separator = true;
15534        }
15535
15536        // DEFINE
15537        if let Some(define) = &mr.define {
15538            if !define.is_empty() {
15539                if needs_separator {
15540                    if self.config.pretty {
15541                        self.write_newline();
15542                        self.write_indent();
15543                    } else {
15544                        self.write_space();
15545                    }
15546                } else if self.config.pretty {
15547                    self.write_newline();
15548                    self.write_indent();
15549                }
15550                self.write_keyword("DEFINE");
15551                // In pretty mode, put each DEFINE on a new indented line
15552                if self.config.pretty {
15553                    self.indent_level += 1;
15554                    for (i, (name, expr)) in define.iter().enumerate() {
15555                        if i > 0 {
15556                            self.write(",");
15557                        }
15558                        self.write_newline();
15559                        self.write_indent();
15560                        self.generate_identifier(name)?;
15561                        self.write(" AS ");
15562                        self.generate_expression(expr)?;
15563                    }
15564                    self.indent_level -= 1;
15565                } else {
15566                    self.write_space();
15567                    for (i, (name, expr)) in define.iter().enumerate() {
15568                        if i > 0 {
15569                            self.write(", ");
15570                        }
15571                        self.generate_identifier(name)?;
15572                        self.write(" AS ");
15573                        self.generate_expression(expr)?;
15574                    }
15575                }
15576            }
15577        }
15578
15579        if self.config.pretty {
15580            self.indent_level -= 1;
15581            self.write_newline();
15582        }
15583        self.write(")");
15584
15585        // Alias - only include AS if it was explicitly present in the input
15586        if let Some(alias) = &mr.alias {
15587            self.write(" ");
15588            if mr.alias_explicit_as {
15589                self.write_keyword("AS");
15590                self.write(" ");
15591            }
15592            self.generate_identifier(alias)?;
15593        }
15594
15595        Ok(())
15596    }
15597
15598    /// Generate a query hint /*+ ... */
15599    fn generate_hint(&mut self, hint: &Hint) -> Result<()> {
15600        use crate::dialects::DialectType;
15601
15602        // Output hints for dialects that support them, or when no dialect is specified (identity tests)
15603        let supports_hints = matches!(
15604            self.config.dialect,
15605            None |  // No dialect = preserve everything
15606            Some(DialectType::Oracle) | Some(DialectType::MySQL) |
15607            Some(DialectType::Spark) | Some(DialectType::Hive) |
15608            Some(DialectType::Databricks) | Some(DialectType::PostgreSQL)
15609        );
15610
15611        if !supports_hints || hint.expressions.is_empty() {
15612            return Ok(());
15613        }
15614
15615        // First, expand raw hint text into individual hint strings
15616        // This handles the case where the parser stored multiple hints as a single raw string
15617        let mut hint_strings: Vec<String> = Vec::new();
15618        for expr in &hint.expressions {
15619            match expr {
15620                HintExpression::Raw(text) => {
15621                    // Parse raw hint text into individual hint function calls
15622                    let parsed = self.parse_raw_hint_text(text);
15623                    hint_strings.extend(parsed);
15624                }
15625                _ => {
15626                    hint_strings.push(self.hint_expression_to_string(expr)?);
15627                }
15628            }
15629        }
15630
15631        // In pretty mode with multiple hints, always use multiline format
15632        // This matches Python sqlglot's behavior where expressions() with default dynamic=False
15633        // always joins with newlines in pretty mode
15634        let use_multiline = self.config.pretty && hint_strings.len() > 1;
15635
15636        if use_multiline {
15637            // Pretty print with each hint on its own line
15638            self.write(" /*+ ");
15639            for (i, hint_str) in hint_strings.iter().enumerate() {
15640                if i > 0 {
15641                    self.write_newline();
15642                    self.write("  "); // 2-space indent within hint block
15643                }
15644                self.write(hint_str);
15645            }
15646            self.write(" */");
15647        } else {
15648            // Single line format
15649            self.write(" /*+ ");
15650            let sep = match self.config.dialect {
15651                Some(DialectType::Spark) | Some(DialectType::Databricks) => ", ",
15652                _ => " ",
15653            };
15654            for (i, hint_str) in hint_strings.iter().enumerate() {
15655                if i > 0 {
15656                    self.write(sep);
15657                }
15658                self.write(hint_str);
15659            }
15660            self.write(" */");
15661        }
15662
15663        Ok(())
15664    }
15665
15666    /// Parse raw hint text into individual hint function calls
15667    /// e.g., "LEADING(a b) USE_NL(c)" -> ["LEADING(a b)", "USE_NL(c)"]
15668    /// If the hint contains unparseable content (like SQL keywords), return as single raw string
15669    fn parse_raw_hint_text(&self, text: &str) -> Vec<String> {
15670        let mut results = Vec::new();
15671        let mut chars = text.chars().peekable();
15672        let mut current = String::new();
15673        let mut paren_depth = 0;
15674        let mut has_unparseable_content = false;
15675        let mut position_after_last_function = 0;
15676        let mut char_position = 0;
15677
15678        while let Some(c) = chars.next() {
15679            char_position += c.len_utf8();
15680            match c {
15681                '(' => {
15682                    paren_depth += 1;
15683                    current.push(c);
15684                }
15685                ')' => {
15686                    paren_depth -= 1;
15687                    current.push(c);
15688                    // When we close the outer parenthesis, we've completed a hint function
15689                    if paren_depth == 0 {
15690                        let trimmed = current.trim().to_string();
15691                        if !trimmed.is_empty() {
15692                            // Format this hint for pretty printing if needed
15693                            let formatted = self.format_hint_function(&trimmed);
15694                            results.push(formatted);
15695                        }
15696                        current.clear();
15697                        position_after_last_function = char_position;
15698                    }
15699                }
15700                ' ' | '\t' | '\n' | ',' if paren_depth == 0 => {
15701                    // Space/comma/whitespace outside parentheses - skip
15702                }
15703                _ if paren_depth == 0 => {
15704                    // Character outside parentheses - accumulate for potential hint name
15705                    current.push(c);
15706                }
15707                _ => {
15708                    current.push(c);
15709                }
15710            }
15711        }
15712
15713        // Check if there's remaining text after the last function call
15714        let remaining_text = text[position_after_last_function..].trim();
15715        if !remaining_text.is_empty() {
15716            // Check if it looks like valid hint function names
15717            // Valid hint identifiers typically are uppercase alphanumeric with underscores
15718            // If we see multiple words without parens, it's likely unparseable
15719            let words: Vec<&str> = remaining_text.split_whitespace().collect();
15720            let looks_like_hint_functions = words.iter().all(|word| {
15721                // A valid hint name followed by opening paren, or a standalone uppercase identifier
15722                word.contains('(') || (word.chars().all(|c| c.is_ascii_uppercase() || c == '_'))
15723            });
15724
15725            if !looks_like_hint_functions && words.len() > 1 {
15726                has_unparseable_content = true;
15727            }
15728        }
15729
15730        // If we detected unparseable content (like SQL keywords), return the whole hint as-is
15731        if has_unparseable_content {
15732            return vec![text.trim().to_string()];
15733        }
15734
15735        // If we couldn't parse anything, return the original text as a single hint
15736        if results.is_empty() {
15737            results.push(text.trim().to_string());
15738        }
15739
15740        results
15741    }
15742
15743    /// Format a hint function for pretty printing
15744    /// e.g., "LEADING(aaa bbb ccc ddd)" -> multiline if args are too wide
15745    fn format_hint_function(&self, hint: &str) -> String {
15746        if !self.config.pretty {
15747            return hint.to_string();
15748        }
15749
15750        // Try to parse NAME(args) pattern
15751        if let Some(paren_pos) = hint.find('(') {
15752            if hint.ends_with(')') {
15753                let name = &hint[..paren_pos];
15754                let args_str = &hint[paren_pos + 1..hint.len() - 1];
15755
15756                // Parse arguments (space-separated for Oracle hints)
15757                let args: Vec<&str> = args_str.split_whitespace().collect();
15758
15759                // Calculate total width of arguments
15760                let total_args_width: usize =
15761                    args.iter().map(|s| s.len()).sum::<usize>() + args.len().saturating_sub(1); // spaces between args
15762
15763                // If too wide, format on multiple lines
15764                if total_args_width > self.config.max_text_width && !args.is_empty() {
15765                    let mut result = format!("{}(\n", name);
15766                    for arg in &args {
15767                        result.push_str("    "); // 4-space indent for args
15768                        result.push_str(arg);
15769                        result.push('\n');
15770                    }
15771                    result.push_str("  )"); // 2-space indent for closing paren
15772                    return result;
15773                }
15774            }
15775        }
15776
15777        hint.to_string()
15778    }
15779
15780    /// Convert a hint expression to a string, handling multiline formatting for long arguments
15781    fn hint_expression_to_string(&mut self, expr: &HintExpression) -> Result<String> {
15782        match expr {
15783            HintExpression::Function { name, args } => {
15784                // Generate each argument to a string
15785                let arg_strings: Vec<String> = args
15786                    .iter()
15787                    .map(|arg| {
15788                        let mut gen = Generator::with_arc_config(self.config.clone());
15789                        gen.generate_expression(arg)?;
15790                        Ok(gen.output)
15791                    })
15792                    .collect::<Result<Vec<_>>>()?;
15793
15794                // Oracle hints use space-separated arguments, not comma-separated
15795                let total_args_width: usize = arg_strings.iter().map(|s| s.len()).sum::<usize>()
15796                    + arg_strings.len().saturating_sub(1); // spaces between args
15797
15798                // Check if function args need multiline formatting
15799                // Use too_wide check for argument formatting
15800                let args_multiline =
15801                    self.config.pretty && total_args_width > self.config.max_text_width;
15802
15803                if args_multiline && !arg_strings.is_empty() {
15804                    // Multiline format for long argument lists
15805                    let mut result = format!("{}(\n", name);
15806                    for arg_str in &arg_strings {
15807                        result.push_str("    "); // 4-space indent for args
15808                        result.push_str(arg_str);
15809                        result.push('\n');
15810                    }
15811                    result.push_str("  )"); // 2-space indent for closing paren
15812                    Ok(result)
15813                } else {
15814                    // Single line format with space-separated args (Oracle style)
15815                    let args_str = arg_strings.join(" ");
15816                    Ok(format!("{}({})", name, args_str))
15817                }
15818            }
15819            HintExpression::Identifier(name) => Ok(name.clone()),
15820            HintExpression::Raw(text) => {
15821                // For pretty printing, try to format the raw text
15822                if self.config.pretty {
15823                    Ok(self.format_hint_function(text))
15824                } else {
15825                    Ok(text.clone())
15826                }
15827            }
15828        }
15829    }
15830
15831    fn generate_table(&mut self, table: &TableRef) -> Result<()> {
15832        // PostgreSQL ONLY modifier: prevents scanning child tables
15833        if table.only {
15834            self.write_keyword("ONLY");
15835            self.write_space();
15836        }
15837
15838        // Check for IDENTIFIER() (Snowflake) or OPENDATASOURCE(...).db.schema.table (TSQL)
15839        if let Some(ref identifier_func) = table.identifier_func {
15840            self.generate_expression(identifier_func)?;
15841            // If table name parts are present, emit .catalog.schema.name after the function
15842            if !table.name.name.is_empty() {
15843                if let Some(catalog) = &table.catalog {
15844                    self.write(".");
15845                    self.generate_identifier(catalog)?;
15846                }
15847                if let Some(schema) = &table.schema {
15848                    self.write(".");
15849                    self.generate_identifier(schema)?;
15850                }
15851                self.write(".");
15852                self.generate_identifier(&table.name)?;
15853            }
15854        } else {
15855            if let Some(catalog) = &table.catalog {
15856                self.generate_identifier(catalog)?;
15857                self.write(".");
15858            }
15859            if let Some(schema) = &table.schema {
15860                self.generate_identifier(schema)?;
15861                self.write(".");
15862            }
15863            self.generate_identifier(&table.name)?;
15864        }
15865
15866        // Output Snowflake CHANGES clause (before partition, includes its own AT/BEFORE/END)
15867        if let Some(changes) = &table.changes {
15868            self.write(" ");
15869            self.generate_changes(changes)?;
15870        }
15871
15872        // Output MySQL PARTITION clause: t1 PARTITION(p0, p1)
15873        if !table.partitions.is_empty() {
15874            self.write_space();
15875            self.write_keyword("PARTITION");
15876            self.write("(");
15877            for (i, partition) in table.partitions.iter().enumerate() {
15878                if i > 0 {
15879                    self.write(", ");
15880                }
15881                self.generate_identifier(partition)?;
15882            }
15883            self.write(")");
15884        }
15885
15886        // Output time travel clause: BEFORE (STATEMENT => ...) or AT (TIMESTAMP => ...)
15887        // Skip if CHANGES clause is present (CHANGES includes its own time travel)
15888        if table.changes.is_none() {
15889            if let Some(when) = &table.when {
15890                self.write_space();
15891                self.generate_historical_data(when)?;
15892            }
15893        }
15894
15895        // Output TSQL FOR SYSTEM_TIME temporal clause (before alias, except BigQuery)
15896        let system_time_post_alias = matches!(self.config.dialect, Some(DialectType::BigQuery));
15897        if !system_time_post_alias {
15898            if let Some(ref system_time) = table.system_time {
15899                self.write_space();
15900                self.write(system_time);
15901            }
15902        }
15903
15904        // Output Presto/Trino time travel: FOR VERSION AS OF / FOR TIMESTAMP AS OF
15905        if let Some(ref version) = table.version {
15906            self.write_space();
15907            self.generate_version(version)?;
15908        }
15909
15910        // When alias_post_tablesample is true, the order is: table TABLESAMPLE (...) alias
15911        // When alias_post_tablesample is false (default), the order is: table alias TABLESAMPLE (...)
15912        // Oracle, Hive, Spark use ALIAS_POST_TABLESAMPLE = true (alias comes after sample)
15913        let alias_post_tablesample = self.config.alias_post_tablesample;
15914
15915        if alias_post_tablesample {
15916            // TABLESAMPLE before alias (Oracle, Hive, Spark)
15917            self.generate_table_sample_clause(table)?;
15918        }
15919
15920        // Output table hints (TSQL: WITH (TABLOCK, INDEX(myindex), ...))
15921        // For SQLite, INDEXED BY hints come after the alias, so skip here
15922        let is_sqlite_hint = matches!(self.config.dialect, Some(DialectType::SQLite))
15923            && table.hints.iter().any(|h| {
15924                if let Expression::Identifier(id) = h {
15925                    id.name.starts_with("INDEXED BY") || id.name == "NOT INDEXED"
15926                } else {
15927                    false
15928                }
15929            });
15930        if !table.hints.is_empty() && !is_sqlite_hint {
15931            for hint in &table.hints {
15932                self.write_space();
15933                self.generate_expression(hint)?;
15934            }
15935        }
15936
15937        if let Some(alias) = &table.alias {
15938            self.write_space();
15939            // Output AS if it was explicitly present in the input, OR for certain dialects/cases
15940            // Generic mode and most dialects always use AS for table aliases
15941            let always_use_as = self.config.dialect.is_none()
15942                || matches!(
15943                    self.config.dialect,
15944                    Some(DialectType::Generic)
15945                        | Some(DialectType::PostgreSQL)
15946                        | Some(DialectType::Redshift)
15947                        | Some(DialectType::Snowflake)
15948                        | Some(DialectType::BigQuery)
15949                        | Some(DialectType::DuckDB)
15950                        | Some(DialectType::Presto)
15951                        | Some(DialectType::Trino)
15952                        | Some(DialectType::TSQL)
15953                        | Some(DialectType::Fabric)
15954                        | Some(DialectType::MySQL)
15955                        | Some(DialectType::Spark)
15956                        | Some(DialectType::Hive)
15957                        | Some(DialectType::SQLite)
15958                        | Some(DialectType::Drill)
15959                );
15960            let is_stage_ref = table.name.name.starts_with('@');
15961            // Oracle never uses AS for table aliases
15962            let suppress_as = matches!(self.config.dialect, Some(DialectType::Oracle));
15963            if !suppress_as && (table.alias_explicit_as || always_use_as || is_stage_ref) {
15964                self.write_keyword("AS");
15965                self.write_space();
15966            }
15967            self.generate_identifier(alias)?;
15968
15969            // Output column aliases if present: AS t(c1, c2)
15970            // Skip for dialects that don't support table alias columns (BigQuery, SQLite)
15971            if !table.column_aliases.is_empty() && self.config.supports_table_alias_columns {
15972                self.write("(");
15973                for (i, col_alias) in table.column_aliases.iter().enumerate() {
15974                    if i > 0 {
15975                        self.write(", ");
15976                    }
15977                    self.generate_identifier(col_alias)?;
15978                }
15979                self.write(")");
15980            }
15981        }
15982
15983        // BigQuery: FOR SYSTEM_TIME AS OF after alias
15984        if system_time_post_alias {
15985            if let Some(ref system_time) = table.system_time {
15986                self.write_space();
15987                self.write(system_time);
15988            }
15989        }
15990
15991        // For default behavior (alias_post_tablesample = false), output TABLESAMPLE after alias
15992        if !alias_post_tablesample {
15993            self.generate_table_sample_clause(table)?;
15994        }
15995
15996        // Output SQLite INDEXED BY / NOT INDEXED hints after alias
15997        if is_sqlite_hint {
15998            for hint in &table.hints {
15999                self.write_space();
16000                self.generate_expression(hint)?;
16001            }
16002        }
16003
16004        // ClickHouse FINAL modifier
16005        if table.final_ && matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
16006            self.write_space();
16007            self.write_keyword("FINAL");
16008        }
16009
16010        // Output trailing comments
16011        for comment in &table.trailing_comments {
16012            self.write_space();
16013            self.write_formatted_comment(comment);
16014        }
16015        // Note: leading_comments (from before table in FROM clause) are intentionally NOT
16016        // output here - they are output by the FROM/PIVOT generator after the full expression
16017
16018        Ok(())
16019    }
16020
16021    /// Helper to output TABLESAMPLE clause for a table reference
16022    fn generate_table_sample_clause(&mut self, table: &TableRef) -> Result<()> {
16023        if let Some(ref ts) = table.table_sample {
16024            self.write_space();
16025            if ts.is_using_sample {
16026                self.write_keyword("USING SAMPLE");
16027            } else {
16028                // Use the configured tablesample keyword (e.g., "TABLESAMPLE" or "SAMPLE")
16029                self.write_keyword(self.config.tablesample_keywords);
16030            }
16031            self.generate_sample_body(ts)?;
16032            // Seed for table-level sample - use dialect's configured keyword
16033            if let Some(ref seed) = ts.seed {
16034                self.write_space();
16035                self.write_keyword(self.config.tablesample_seed_keyword);
16036                self.write(" (");
16037                self.generate_expression(seed)?;
16038                self.write(")");
16039            }
16040        }
16041        Ok(())
16042    }
16043
16044    fn generate_stage_reference(&mut self, sr: &StageReference) -> Result<()> {
16045        // Output: '@stage_name/path' if quoted, or @stage_name/path otherwise
16046        // Optionally followed by (FILE_FORMAT => 'fmt', PATTERN => '*.csv')
16047
16048        if sr.quoted {
16049            self.write("'");
16050        }
16051
16052        self.write(&sr.name);
16053        if let Some(path) = &sr.path {
16054            self.write(path);
16055        }
16056
16057        if sr.quoted {
16058            self.write("'");
16059        }
16060
16061        // Output FILE_FORMAT and PATTERN if present
16062        let has_options = sr.file_format.is_some() || sr.pattern.is_some();
16063        if has_options {
16064            self.write(" (");
16065            let mut first = true;
16066
16067            if let Some(file_format) = &sr.file_format {
16068                if !first {
16069                    self.write(", ");
16070                }
16071                self.write_keyword("FILE_FORMAT");
16072                self.write(" => ");
16073                self.generate_expression(file_format)?;
16074                first = false;
16075            }
16076
16077            if let Some(pattern) = &sr.pattern {
16078                if !first {
16079                    self.write(", ");
16080                }
16081                self.write_keyword("PATTERN");
16082                self.write(" => '");
16083                self.write(pattern);
16084                self.write("'");
16085            }
16086
16087            self.write(")");
16088        }
16089        Ok(())
16090    }
16091
16092    fn generate_star(&mut self, star: &Star) -> Result<()> {
16093        use crate::dialects::DialectType;
16094
16095        if let Some(table) = &star.table {
16096            self.generate_identifier(table)?;
16097            self.write(".");
16098        }
16099        self.write("*");
16100
16101        // Generate EXCLUDE/EXCEPT clause based on dialect
16102        if let Some(except) = &star.except {
16103            if !except.is_empty() {
16104                self.write_space();
16105                // Use dialect-appropriate keyword
16106                match self.config.dialect {
16107                    Some(DialectType::BigQuery) => self.write_keyword("EXCEPT"),
16108                    Some(DialectType::DuckDB) | Some(DialectType::Snowflake) => {
16109                        self.write_keyword("EXCLUDE")
16110                    }
16111                    _ => self.write_keyword("EXCEPT"), // Default to EXCEPT
16112                }
16113                self.write(" (");
16114                for (i, col) in except.iter().enumerate() {
16115                    if i > 0 {
16116                        self.write(", ");
16117                    }
16118                    self.generate_identifier(col)?;
16119                }
16120                self.write(")");
16121            }
16122        }
16123
16124        // Generate REPLACE clause
16125        if let Some(replace) = &star.replace {
16126            if !replace.is_empty() {
16127                self.write_space();
16128                self.write_keyword("REPLACE");
16129                self.write(" (");
16130                for (i, alias) in replace.iter().enumerate() {
16131                    if i > 0 {
16132                        self.write(", ");
16133                    }
16134                    self.generate_expression(&alias.this)?;
16135                    self.write_space();
16136                    self.write_keyword("AS");
16137                    self.write_space();
16138                    self.generate_identifier(&alias.alias)?;
16139                }
16140                self.write(")");
16141            }
16142        }
16143
16144        // Generate RENAME clause (Snowflake specific)
16145        if let Some(rename) = &star.rename {
16146            if !rename.is_empty() {
16147                self.write_space();
16148                self.write_keyword("RENAME");
16149                self.write(" (");
16150                for (i, (old_name, new_name)) in rename.iter().enumerate() {
16151                    if i > 0 {
16152                        self.write(", ");
16153                    }
16154                    self.generate_identifier(old_name)?;
16155                    self.write_space();
16156                    self.write_keyword("AS");
16157                    self.write_space();
16158                    self.generate_identifier(new_name)?;
16159                }
16160                self.write(")");
16161            }
16162        }
16163
16164        // Output trailing comments
16165        for comment in &star.trailing_comments {
16166            self.write_space();
16167            self.write_formatted_comment(comment);
16168        }
16169
16170        Ok(())
16171    }
16172
16173    /// Generate Snowflake braced wildcard syntax: {*}, {tbl.*}, {* EXCLUDE (...)}, {* ILIKE '...'}
16174    fn generate_braced_wildcard(&mut self, expr: &Expression) -> Result<()> {
16175        self.write("{");
16176        match expr {
16177            Expression::Star(star) => {
16178                // Generate the star (table.* or just * with optional EXCLUDE)
16179                self.generate_star(star)?;
16180            }
16181            Expression::ILike(ilike) => {
16182                // {* ILIKE 'pattern'} syntax
16183                self.generate_expression(&ilike.left)?;
16184                self.write_space();
16185                self.write_keyword("ILIKE");
16186                self.write_space();
16187                self.generate_expression(&ilike.right)?;
16188            }
16189            _ => {
16190                self.generate_expression(expr)?;
16191            }
16192        }
16193        self.write("}");
16194        Ok(())
16195    }
16196
16197    fn generate_alias(&mut self, alias: &Alias) -> Result<()> {
16198        // Generate inner expression, but skip trailing comments if they're in pre_alias_comments
16199        // to avoid duplication (comments are captured as both Column.trailing_comments
16200        // and Alias.pre_alias_comments during parsing)
16201        match &alias.this {
16202            Expression::Column(col) => {
16203                // Generate column without trailing comments - they're in pre_alias_comments
16204                if let Some(table) = &col.table {
16205                    self.generate_identifier(table)?;
16206                    self.write(".");
16207                }
16208                self.generate_identifier(&col.name)?;
16209            }
16210            _ => {
16211                self.generate_expression(&alias.this)?;
16212            }
16213        }
16214
16215        // Handle pre-alias comments: when there are no trailing_comments, sqlglot
16216        // moves pre-alias comments to after the alias. When there are also trailing_comments,
16217        // keep pre-alias comments in their original position (between expression and AS).
16218        if !alias.pre_alias_comments.is_empty() && !alias.trailing_comments.is_empty() {
16219            for comment in &alias.pre_alias_comments {
16220                self.write_space();
16221                self.write_formatted_comment(comment);
16222            }
16223        }
16224
16225        use crate::dialects::DialectType;
16226
16227        // Determine if we should skip AS keyword for table-valued function aliases
16228        // Oracle and some other dialects don't use AS for table aliases
16229        // Note: We specifically use TableFromRows here, NOT Function, because Function
16230        // matches regular functions like MATCH_NUMBER() which should include the AS keyword.
16231        // TableFromRows represents TABLE(expr) constructs which are actual table-valued functions.
16232        let is_table_source = matches!(
16233            &alias.this,
16234            Expression::JSONTable(_)
16235                | Expression::XMLTable(_)
16236                | Expression::TableFromRows(_)
16237                | Expression::Unnest(_)
16238                | Expression::MatchRecognize(_)
16239                | Expression::Select(_)
16240                | Expression::Subquery(_)
16241                | Expression::Paren(_)
16242        );
16243        let dialect_skips_table_alias_as = matches!(self.config.dialect, Some(DialectType::Oracle));
16244        let skip_as = is_table_source && dialect_skips_table_alias_as;
16245
16246        self.write_space();
16247        if !skip_as {
16248            self.write_keyword("AS");
16249            self.write_space();
16250        }
16251
16252        // BigQuery doesn't support column aliases in table aliases: AS t(c1, c2)
16253        let skip_column_aliases = matches!(self.config.dialect, Some(DialectType::BigQuery));
16254
16255        // Check if we have column aliases only (no table alias name)
16256        if alias.alias.is_empty() && !alias.column_aliases.is_empty() && !skip_column_aliases {
16257            // Generate AS (col1, col2, ...)
16258            self.write("(");
16259            for (i, col_alias) in alias.column_aliases.iter().enumerate() {
16260                if i > 0 {
16261                    self.write(", ");
16262                }
16263                self.generate_alias_identifier(col_alias)?;
16264            }
16265            self.write(")");
16266        } else if !alias.column_aliases.is_empty() && !skip_column_aliases {
16267            // Generate AS alias(col1, col2, ...)
16268            self.generate_alias_identifier(&alias.alias)?;
16269            self.write("(");
16270            for (i, col_alias) in alias.column_aliases.iter().enumerate() {
16271                if i > 0 {
16272                    self.write(", ");
16273                }
16274                self.generate_alias_identifier(col_alias)?;
16275            }
16276            self.write(")");
16277        } else {
16278            // Simple alias (or BigQuery without column aliases)
16279            self.generate_alias_identifier(&alias.alias)?;
16280        }
16281
16282        // Output trailing comments (comments after the alias)
16283        for comment in &alias.trailing_comments {
16284            self.write_space();
16285            self.write_formatted_comment(comment);
16286        }
16287
16288        // Output pre-alias comments: when there are no trailing_comments, sqlglot
16289        // moves pre-alias comments to after the alias. When there are trailing_comments,
16290        // the pre-alias comments were already lost (consumed as column trailing comments
16291        // that were then used as pre_alias_comments). We always emit them after alias.
16292        if alias.trailing_comments.is_empty() {
16293            for comment in &alias.pre_alias_comments {
16294                self.write_space();
16295                self.write_formatted_comment(comment);
16296            }
16297        }
16298
16299        Ok(())
16300    }
16301
16302    fn generate_cast(&mut self, cast: &Cast) -> Result<()> {
16303        use crate::dialects::DialectType;
16304
16305        // SingleStore uses :> syntax
16306        if matches!(self.config.dialect, Some(DialectType::SingleStore)) {
16307            self.generate_expression(&cast.this)?;
16308            self.write(" :> ");
16309            self.generate_data_type(&cast.to)?;
16310            return Ok(());
16311        }
16312
16313        // Teradata: CAST(x AS FORMAT 'fmt') (no data type)
16314        if matches!(self.config.dialect, Some(DialectType::Teradata)) {
16315            let is_unknown_type = matches!(cast.to, DataType::Unknown)
16316                || matches!(cast.to, DataType::Custom { ref name } if name.is_empty());
16317            if is_unknown_type {
16318                if let Some(format) = &cast.format {
16319                    self.write_keyword("CAST");
16320                    self.write("(");
16321                    self.generate_expression(&cast.this)?;
16322                    self.write_space();
16323                    self.write_keyword("AS");
16324                    self.write_space();
16325                    self.write_keyword("FORMAT");
16326                    self.write_space();
16327                    self.generate_expression(format)?;
16328                    self.write(")");
16329                    return Ok(());
16330                }
16331            }
16332        }
16333
16334        // Oracle: CAST(x AS DATE/TIMESTAMP ..., 'format') -> TO_DATE/TO_TIMESTAMP(x, 'format')
16335        // This follows Python sqlglot's behavior of transforming CAST with format to native functions
16336        if matches!(self.config.dialect, Some(DialectType::Oracle)) {
16337            if let Some(format) = &cast.format {
16338                // Check if target type is DATE or TIMESTAMP
16339                let is_date = matches!(cast.to, DataType::Date);
16340                let is_timestamp = matches!(cast.to, DataType::Timestamp { .. });
16341
16342                if is_date || is_timestamp {
16343                    let func_name = if is_date { "TO_DATE" } else { "TO_TIMESTAMP" };
16344                    self.write_keyword(func_name);
16345                    self.write("(");
16346                    self.generate_expression(&cast.this)?;
16347                    self.write(", ");
16348
16349                    // Normalize format string for Oracle (HH -> HH12)
16350                    // Oracle HH is 12-hour format, same as HH12. For clarity, Python sqlglot uses HH12.
16351                    if let Expression::Literal(lit) = format.as_ref() {
16352                        if let Literal::String(fmt_str) = lit.as_ref() {
16353                            let normalized = self.normalize_oracle_format(fmt_str);
16354                            self.write("'");
16355                            self.write(&normalized);
16356                            self.write("'");
16357                        }
16358                    } else {
16359                        self.generate_expression(format)?;
16360                    }
16361
16362                    self.write(")");
16363                    return Ok(());
16364                }
16365            }
16366        }
16367
16368        // BigQuery: CAST(ARRAY[...] AS ARRAY<T>) -> ARRAY<T>[...]
16369        // This preserves sqlglot's typed inline array literal output.
16370        if matches!(self.config.dialect, Some(DialectType::BigQuery)) {
16371            if let Expression::Array(arr) = &cast.this {
16372                self.generate_data_type(&cast.to)?;
16373                // Output just the bracket content [values] without the ARRAY prefix
16374                self.write("[");
16375                for (i, expr) in arr.expressions.iter().enumerate() {
16376                    if i > 0 {
16377                        self.write(", ");
16378                    }
16379                    self.generate_expression(expr)?;
16380                }
16381                self.write("]");
16382                return Ok(());
16383            }
16384            if matches!(&cast.this, Expression::ArrayFunc(_)) {
16385                self.generate_data_type(&cast.to)?;
16386                self.generate_expression(&cast.this)?;
16387                return Ok(());
16388            }
16389        }
16390
16391        // DuckDB/Presto/Trino: When CAST(Struct([unnamed]) AS STRUCT(...)),
16392        // convert the inner Struct to ROW(values...) format
16393        if matches!(
16394            self.config.dialect,
16395            Some(DialectType::DuckDB) | Some(DialectType::Presto) | Some(DialectType::Trino)
16396        ) {
16397            if let Expression::Struct(ref s) = cast.this {
16398                let all_unnamed = s.fields.iter().all(|(name, _)| name.is_none());
16399                if all_unnamed && matches!(cast.to, DataType::Struct { .. }) {
16400                    self.write_keyword("CAST");
16401                    self.write("(");
16402                    self.generate_struct_as_row(s)?;
16403                    self.write_space();
16404                    self.write_keyword("AS");
16405                    self.write_space();
16406                    self.generate_data_type(&cast.to)?;
16407                    self.write(")");
16408                    return Ok(());
16409                }
16410            }
16411        }
16412
16413        // Determine if we should use :: syntax based on dialect
16414        // PostgreSQL prefers :: for identity, most others prefer CAST()
16415        let use_double_colon = cast.double_colon_syntax && self.dialect_prefers_double_colon();
16416
16417        if use_double_colon {
16418            // PostgreSQL :: syntax: expr::type
16419            self.generate_expression(&cast.this)?;
16420            self.write("::");
16421            self.generate_data_type(&cast.to)?;
16422        } else {
16423            // Standard CAST() syntax
16424            self.write_keyword("CAST");
16425            self.write("(");
16426            self.generate_expression(&cast.this)?;
16427            self.write_space();
16428            self.write_keyword("AS");
16429            self.write_space();
16430            // For MySQL/SingleStore/TiDB, map text/blob variant types to CHAR in CAST
16431            // This matches Python sqlglot's CAST_MAPPING behavior
16432            if matches!(
16433                self.config.dialect,
16434                Some(DialectType::MySQL) | Some(DialectType::SingleStore) | Some(DialectType::TiDB)
16435            ) {
16436                match &cast.to {
16437                    DataType::Custom { ref name } => {
16438                        if name.eq_ignore_ascii_case("LONGTEXT")
16439                            || name.eq_ignore_ascii_case("MEDIUMTEXT")
16440                            || name.eq_ignore_ascii_case("TINYTEXT")
16441                            || name.eq_ignore_ascii_case("LONGBLOB")
16442                            || name.eq_ignore_ascii_case("MEDIUMBLOB")
16443                            || name.eq_ignore_ascii_case("TINYBLOB")
16444                        {
16445                            self.write_keyword("CHAR");
16446                        } else {
16447                            self.generate_data_type(&cast.to)?;
16448                        }
16449                    }
16450                    DataType::VarChar { length, .. } => {
16451                        // MySQL CAST: VARCHAR -> CHAR
16452                        self.write_keyword("CHAR");
16453                        if let Some(n) = length {
16454                            self.write(&format!("({})", n));
16455                        }
16456                    }
16457                    DataType::Text => {
16458                        // MySQL CAST: TEXT -> CHAR
16459                        self.write_keyword("CHAR");
16460                    }
16461                    DataType::Timestamp {
16462                        precision,
16463                        timezone: false,
16464                    } => {
16465                        // MySQL CAST: TIMESTAMP -> DATETIME
16466                        self.write_keyword("DATETIME");
16467                        if let Some(p) = precision {
16468                            self.write(&format!("({})", p));
16469                        }
16470                    }
16471                    _ => {
16472                        self.generate_data_type(&cast.to)?;
16473                    }
16474                }
16475            } else if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
16476                // Snowflake CAST: STRING -> VARCHAR
16477                match &cast.to {
16478                    DataType::String { length } => {
16479                        self.write_keyword("VARCHAR");
16480                        if let Some(n) = length {
16481                            self.write(&format!("({})", n));
16482                        }
16483                    }
16484                    _ => {
16485                        self.generate_data_type(&cast.to)?;
16486                    }
16487                }
16488            } else {
16489                self.generate_data_type(&cast.to)?;
16490            }
16491
16492            // Output DEFAULT ... ON CONVERSION ERROR clause if present (Oracle)
16493            if let Some(default) = &cast.default {
16494                self.write_space();
16495                self.write_keyword("DEFAULT");
16496                self.write_space();
16497                self.generate_expression(default)?;
16498                self.write_space();
16499                self.write_keyword("ON");
16500                self.write_space();
16501                self.write_keyword("CONVERSION");
16502                self.write_space();
16503                self.write_keyword("ERROR");
16504            }
16505
16506            // Output FORMAT clause if present (BigQuery: CAST(x AS STRING FORMAT 'format'))
16507            // For Oracle with comma-separated format: CAST(x AS DATE DEFAULT NULL ON CONVERSION ERROR, 'format')
16508            if let Some(format) = &cast.format {
16509                // Check if Oracle dialect - use comma syntax
16510                if matches!(
16511                    self.config.dialect,
16512                    Some(crate::dialects::DialectType::Oracle)
16513                ) {
16514                    self.write(", ");
16515                } else {
16516                    self.write_space();
16517                    self.write_keyword("FORMAT");
16518                    self.write_space();
16519                }
16520                self.generate_expression(format)?;
16521            }
16522
16523            self.write(")");
16524            // Output trailing comments
16525            for comment in &cast.trailing_comments {
16526                self.write_space();
16527                self.write_formatted_comment(comment);
16528            }
16529        }
16530        Ok(())
16531    }
16532
16533    /// Generate a Struct as ROW(values...) format, recursively converting inner Struct to ROW too.
16534    /// Used for DuckDB/Presto/Trino CAST(Struct AS STRUCT(...)) context.
16535    fn generate_struct_as_row(&mut self, s: &crate::expressions::Struct) -> Result<()> {
16536        self.write_keyword("ROW");
16537        self.write("(");
16538        for (i, (_, expr)) in s.fields.iter().enumerate() {
16539            if i > 0 {
16540                self.write(", ");
16541            }
16542            // Recursively convert inner Struct to ROW format
16543            if let Expression::Struct(ref inner_s) = expr {
16544                self.generate_struct_as_row(inner_s)?;
16545            } else {
16546                self.generate_expression(expr)?;
16547            }
16548        }
16549        self.write(")");
16550        Ok(())
16551    }
16552
16553    /// Normalize Oracle date/time format strings
16554    /// HH -> HH12 (both are 12-hour format, but Python sqlglot prefers explicit HH12)
16555    fn normalize_oracle_format(&self, format: &str) -> String {
16556        // Replace standalone HH with HH12 (but not HH12 or HH24)
16557        // We need to be careful not to replace HH12 -> HH1212 or HH24 -> HH1224
16558        let mut result = String::new();
16559        let chars: Vec<char> = format.chars().collect();
16560        let mut i = 0;
16561
16562        while i < chars.len() {
16563            if i + 1 < chars.len() && chars[i] == 'H' && chars[i + 1] == 'H' {
16564                // Check what follows HH
16565                if i + 2 < chars.len() {
16566                    let next = chars[i + 2];
16567                    if next == '1' || next == '2' {
16568                        // This is HH12 or HH24, keep as is
16569                        result.push('H');
16570                        result.push('H');
16571                        i += 2;
16572                        continue;
16573                    }
16574                }
16575                // Standalone HH -> HH12
16576                result.push_str("HH12");
16577                i += 2;
16578            } else {
16579                result.push(chars[i]);
16580                i += 1;
16581            }
16582        }
16583
16584        result
16585    }
16586
16587    /// Check if the current dialect prefers :: cast syntax
16588    /// Note: Python sqlglot normalizes all :: to CAST() for output, even for PostgreSQL
16589    /// So we return false for all dialects to match Python sqlglot's behavior
16590    fn dialect_prefers_double_colon(&self) -> bool {
16591        // Python sqlglot normalizes :: syntax to CAST() for all dialects
16592        // Even PostgreSQL outputs CAST() not ::
16593        false
16594    }
16595
16596    /// Generate MOD function - uses % operator for Snowflake/MySQL/Presto/Trino, MOD() for others
16597    fn generate_mod_func(&mut self, f: &crate::expressions::BinaryFunc) -> Result<()> {
16598        use crate::dialects::DialectType;
16599
16600        // Snowflake, MySQL, Presto, Trino, PostgreSQL, and DuckDB prefer x % y instead of MOD(x, y)
16601        let use_percent_operator = matches!(
16602            self.config.dialect,
16603            Some(DialectType::Snowflake)
16604                | Some(DialectType::MySQL)
16605                | Some(DialectType::Presto)
16606                | Some(DialectType::Trino)
16607                | Some(DialectType::PostgreSQL)
16608                | Some(DialectType::DuckDB)
16609                | Some(DialectType::Hive)
16610                | Some(DialectType::Spark)
16611                | Some(DialectType::Databricks)
16612                | Some(DialectType::Athena)
16613        );
16614
16615        if use_percent_operator {
16616            // Wrap complex expressions in parens to preserve precedence
16617            // Since % has higher precedence than +/-, we need parens for Add/Sub on either side
16618            let needs_paren = |e: &Expression| matches!(e, Expression::Add(_) | Expression::Sub(_));
16619            if needs_paren(&f.this) {
16620                self.write("(");
16621                self.generate_expression(&f.this)?;
16622                self.write(")");
16623            } else {
16624                self.generate_expression(&f.this)?;
16625            }
16626            self.write(" % ");
16627            if needs_paren(&f.expression) {
16628                self.write("(");
16629                self.generate_expression(&f.expression)?;
16630                self.write(")");
16631            } else {
16632                self.generate_expression(&f.expression)?;
16633            }
16634            Ok(())
16635        } else {
16636            self.generate_binary_func("MOD", &f.this, &f.expression)
16637        }
16638    }
16639
16640    /// Generate IFNULL - uses COALESCE for Snowflake, IFNULL for others
16641    fn generate_ifnull(&mut self, f: &crate::expressions::BinaryFunc) -> Result<()> {
16642        use crate::dialects::DialectType;
16643
16644        // Snowflake normalizes IFNULL to COALESCE
16645        let func_name = match self.config.dialect {
16646            Some(DialectType::Snowflake) => "COALESCE",
16647            _ => "IFNULL",
16648        };
16649
16650        self.generate_binary_func(func_name, &f.this, &f.expression)
16651    }
16652
16653    /// Generate NVL - preserves original name if available, otherwise uses dialect-specific output
16654    fn generate_nvl(&mut self, f: &crate::expressions::BinaryFunc) -> Result<()> {
16655        // Use original function name if preserved (for identity tests)
16656        if let Some(ref original_name) = f.original_name {
16657            return self.generate_binary_func(original_name, &f.this, &f.expression);
16658        }
16659
16660        // Otherwise, use dialect-specific function names
16661        use crate::dialects::DialectType;
16662        let func_name = match self.config.dialect {
16663            Some(DialectType::Snowflake)
16664            | Some(DialectType::ClickHouse)
16665            | Some(DialectType::PostgreSQL)
16666            | Some(DialectType::Presto)
16667            | Some(DialectType::Trino)
16668            | Some(DialectType::Athena)
16669            | Some(DialectType::DuckDB)
16670            | Some(DialectType::BigQuery)
16671            | Some(DialectType::Spark)
16672            | Some(DialectType::Databricks)
16673            | Some(DialectType::Hive) => "COALESCE",
16674            Some(DialectType::MySQL)
16675            | Some(DialectType::Doris)
16676            | Some(DialectType::StarRocks)
16677            | Some(DialectType::SingleStore)
16678            | Some(DialectType::TiDB) => "IFNULL",
16679            _ => "NVL",
16680        };
16681
16682        self.generate_binary_func(func_name, &f.this, &f.expression)
16683    }
16684
16685    /// Generate STDDEV_SAMP - uses STDDEV for Snowflake, STDDEV_SAMP for others
16686    fn generate_stddev_samp(&mut self, f: &crate::expressions::AggFunc) -> Result<()> {
16687        use crate::dialects::DialectType;
16688
16689        // Snowflake normalizes STDDEV_SAMP to STDDEV
16690        let func_name = match self.config.dialect {
16691            Some(DialectType::Snowflake) => "STDDEV",
16692            _ => "STDDEV_SAMP",
16693        };
16694
16695        self.generate_agg_func(func_name, f)
16696    }
16697
16698    fn generate_collation(&mut self, coll: &CollationExpr) -> Result<()> {
16699        self.generate_expression(&coll.this)?;
16700        self.write_space();
16701        self.write_keyword("COLLATE");
16702        self.write_space();
16703        if coll.quoted {
16704            // Single-quoted string: COLLATE 'de_DE'
16705            self.write("'");
16706            self.write(&coll.collation);
16707            self.write("'");
16708        } else if coll.double_quoted {
16709            // Double-quoted identifier: COLLATE "de_DE"
16710            self.write("\"");
16711            self.write(&coll.collation);
16712            self.write("\"");
16713        } else {
16714            // Unquoted identifier: COLLATE de_DE
16715            self.write(&coll.collation);
16716        }
16717        Ok(())
16718    }
16719
16720    fn generate_case(&mut self, case: &Case) -> Result<()> {
16721        // In pretty mode, decide whether to expand based on total text width
16722        let multiline_case = if self.config.pretty {
16723            // Build the flat representation to check width
16724            let mut statements: Vec<String> = Vec::new();
16725            let operand_str = if let Some(operand) = &case.operand {
16726                let s = self.generate_to_string(operand)?;
16727                statements.push(format!("CASE {}", s));
16728                s
16729            } else {
16730                statements.push("CASE".to_string());
16731                String::new()
16732            };
16733            let _ = operand_str;
16734            for (condition, result) in &case.whens {
16735                statements.push(format!("WHEN {}", self.generate_to_string(condition)?));
16736                statements.push(format!("THEN {}", self.generate_to_string(result)?));
16737            }
16738            if let Some(else_) = &case.else_ {
16739                statements.push(format!("ELSE {}", self.generate_to_string(else_)?));
16740            }
16741            statements.push("END".to_string());
16742            self.too_wide(&statements)
16743        } else {
16744            false
16745        };
16746
16747        self.write_keyword("CASE");
16748        if let Some(operand) = &case.operand {
16749            self.write_space();
16750            self.generate_expression(operand)?;
16751        }
16752        if multiline_case {
16753            self.indent_level += 1;
16754        }
16755        for (condition, result) in &case.whens {
16756            if multiline_case {
16757                self.write_newline();
16758                self.write_indent();
16759            } else {
16760                self.write_space();
16761            }
16762            self.write_keyword("WHEN");
16763            self.write_space();
16764            self.generate_expression(condition)?;
16765            if multiline_case {
16766                self.write_newline();
16767                self.write_indent();
16768            } else {
16769                self.write_space();
16770            }
16771            self.write_keyword("THEN");
16772            self.write_space();
16773            self.generate_expression(result)?;
16774        }
16775        if let Some(else_) = &case.else_ {
16776            if multiline_case {
16777                self.write_newline();
16778                self.write_indent();
16779            } else {
16780                self.write_space();
16781            }
16782            self.write_keyword("ELSE");
16783            self.write_space();
16784            self.generate_expression(else_)?;
16785        }
16786        if multiline_case {
16787            self.indent_level -= 1;
16788            self.write_newline();
16789            self.write_indent();
16790        } else {
16791            self.write_space();
16792        }
16793        self.write_keyword("END");
16794        // Emit any comments that were attached to the CASE keyword
16795        for comment in &case.comments {
16796            self.write(" ");
16797            self.write_formatted_comment(comment);
16798        }
16799        Ok(())
16800    }
16801
16802    fn generate_function(&mut self, func: &Function) -> Result<()> {
16803        // Normalize function name based on dialect settings
16804        let normalized_name = self.normalize_func_name(&func.name);
16805
16806        // DuckDB: ARRAY_CONSTRUCT_COMPACT(a, b, c) -> LIST_FILTER([a, b, c], _u -> NOT _u IS NULL)
16807        if matches!(self.config.dialect, Some(DialectType::DuckDB))
16808            && func.name.eq_ignore_ascii_case("ARRAY_CONSTRUCT_COMPACT")
16809        {
16810            self.write("LIST_FILTER(");
16811            self.write("[");
16812            for (i, arg) in func.args.iter().enumerate() {
16813                if i > 0 {
16814                    self.write(", ");
16815                }
16816                self.generate_expression(arg)?;
16817            }
16818            self.write("], _u -> NOT _u IS NULL)");
16819            return Ok(());
16820        }
16821
16822        // STRUCT function: BigQuery STRUCT('Alice' AS name, 85 AS score) -> dialect-specific
16823        if func.name.eq_ignore_ascii_case("STRUCT")
16824            && !matches!(
16825                self.config.dialect,
16826                Some(DialectType::BigQuery)
16827                    | Some(DialectType::Spark)
16828                    | Some(DialectType::Databricks)
16829                    | Some(DialectType::Hive)
16830                    | None
16831            )
16832        {
16833            return self.generate_struct_function_cross_dialect(func);
16834        }
16835
16836        // SingleStore: __SS_JSON_PATH_QMARK__(expr, key) -> expr::?key
16837        // This is an internal marker function for ::? JSON path syntax
16838        if func.name.eq_ignore_ascii_case("__SS_JSON_PATH_QMARK__") && func.args.len() == 2 {
16839            self.generate_expression(&func.args[0])?;
16840            self.write("::?");
16841            // Extract the key from the string literal
16842            if let Expression::Literal(lit) = &func.args[1] {
16843                if let crate::expressions::Literal::String(key) = lit.as_ref() {
16844                    self.write(key);
16845                }
16846            } else {
16847                self.generate_expression(&func.args[1])?;
16848            }
16849            return Ok(());
16850        }
16851
16852        // PostgreSQL: __PG_BITWISE_XOR__(a, b) -> a # b
16853        if func.name.eq_ignore_ascii_case("__PG_BITWISE_XOR__") && func.args.len() == 2 {
16854            self.generate_expression(&func.args[0])?;
16855            self.write(" # ");
16856            self.generate_expression(&func.args[1])?;
16857            return Ok(());
16858        }
16859
16860        // Spark/Hive family: unwrap TRY(expr) since these dialects don't emit TRY as a scalar wrapper.
16861        if matches!(
16862            self.config.dialect,
16863            Some(DialectType::Spark | DialectType::Databricks | DialectType::Hive)
16864        ) && func.name.eq_ignore_ascii_case("TRY")
16865            && func.args.len() == 1
16866        {
16867            self.generate_expression(&func.args[0])?;
16868            return Ok(());
16869        }
16870
16871        // ClickHouse normalization: toStartOfDay(x) -> dateTrunc('DAY', x)
16872        if self.config.dialect == Some(DialectType::ClickHouse)
16873            && func.name.eq_ignore_ascii_case("TOSTARTOFDAY")
16874            && func.args.len() == 1
16875        {
16876            self.write("dateTrunc('DAY', ");
16877            self.generate_expression(&func.args[0])?;
16878            self.write(")");
16879            return Ok(());
16880        }
16881
16882        // Redshift: CONCAT(a, b, ...) -> a || b || ...
16883        if self.config.dialect == Some(DialectType::Redshift)
16884            && func.name.eq_ignore_ascii_case("CONCAT")
16885            && func.args.len() >= 2
16886        {
16887            for (i, arg) in func.args.iter().enumerate() {
16888                if i > 0 {
16889                    self.write(" || ");
16890                }
16891                self.generate_expression(arg)?;
16892            }
16893            return Ok(());
16894        }
16895
16896        // Redshift: CONCAT_WS(delim, a, b, c) -> a || delim || b || delim || c
16897        if self.config.dialect == Some(DialectType::Redshift)
16898            && func.name.eq_ignore_ascii_case("CONCAT_WS")
16899            && func.args.len() >= 2
16900        {
16901            let sep = &func.args[0];
16902            for (i, arg) in func.args.iter().skip(1).enumerate() {
16903                if i > 0 {
16904                    self.write(" || ");
16905                    self.generate_expression(sep)?;
16906                    self.write(" || ");
16907                }
16908                self.generate_expression(arg)?;
16909            }
16910            return Ok(());
16911        }
16912
16913        // Redshift: DATEDIFF/DATE_DIFF(unit, start, end) -> DATEDIFF(UNIT, start, end)
16914        // Unit should be unquoted uppercase identifier
16915        if self.config.dialect == Some(DialectType::Redshift)
16916            && (func.name.eq_ignore_ascii_case("DATEDIFF")
16917                || func.name.eq_ignore_ascii_case("DATE_DIFF"))
16918            && func.args.len() == 3
16919        {
16920            self.write_keyword("DATEDIFF");
16921            self.write("(");
16922            // First arg is unit - normalize to unquoted uppercase
16923            self.write_redshift_date_part(&func.args[0]);
16924            self.write(", ");
16925            self.generate_expression(&func.args[1])?;
16926            self.write(", ");
16927            self.generate_expression(&func.args[2])?;
16928            self.write(")");
16929            return Ok(());
16930        }
16931
16932        // Redshift: DATEADD/DATE_ADD(unit, interval, date) -> DATEADD(UNIT, interval, date)
16933        // Unit should be unquoted uppercase identifier
16934        if self.config.dialect == Some(DialectType::Redshift)
16935            && (func.name.eq_ignore_ascii_case("DATEADD")
16936                || func.name.eq_ignore_ascii_case("DATE_ADD"))
16937            && func.args.len() == 3
16938        {
16939            self.write_keyword("DATEADD");
16940            self.write("(");
16941            // First arg is unit - normalize to unquoted uppercase
16942            self.write_redshift_date_part(&func.args[0]);
16943            self.write(", ");
16944            self.generate_expression(&func.args[1])?;
16945            self.write(", ");
16946            self.generate_expression(&func.args[2])?;
16947            self.write(")");
16948            return Ok(());
16949        }
16950
16951        // UUID_STRING(args) from Snowflake -> dialect-specific UUID function (dropping args)
16952        if func.name.eq_ignore_ascii_case("UUID_STRING")
16953            && !matches!(self.config.dialect, Some(DialectType::Snowflake) | None)
16954        {
16955            let func_name = match self.config.dialect {
16956                Some(DialectType::PostgreSQL) | Some(DialectType::Redshift) => "GEN_RANDOM_UUID",
16957                Some(DialectType::BigQuery) => "GENERATE_UUID",
16958                _ => "UUID",
16959            };
16960            self.write_keyword(func_name);
16961            self.write("()");
16962            return Ok(());
16963        }
16964
16965        // Snowflake: GENERATOR(val) -> GENERATOR(ROWCOUNT => val)
16966        // GENERATOR(val1, val2) -> GENERATOR(ROWCOUNT => val1, TIMELIMIT => val2)
16967        // Positional args are mapped to named parameters.
16968        if matches!(self.config.dialect, Some(DialectType::Snowflake))
16969            && func.name.eq_ignore_ascii_case("GENERATOR")
16970        {
16971            let has_positional_args =
16972                !func.args.is_empty() && !matches!(&func.args[0], Expression::NamedArgument(_));
16973            if has_positional_args {
16974                let param_names = ["ROWCOUNT", "TIMELIMIT"];
16975                self.write_keyword("GENERATOR");
16976                self.write("(");
16977                for (i, arg) in func.args.iter().enumerate() {
16978                    if i > 0 {
16979                        self.write(", ");
16980                    }
16981                    if i < param_names.len() {
16982                        self.write_keyword(param_names[i]);
16983                        self.write(" => ");
16984                        self.generate_expression(arg)?;
16985                    } else {
16986                        self.generate_expression(arg)?;
16987                    }
16988                }
16989                self.write(")");
16990                return Ok(());
16991            }
16992        }
16993
16994        // Redshift: DATE_TRUNC('unit', date) -> DATE_TRUNC('UNIT', date)
16995        // Unit should be quoted uppercase string
16996        if self.config.dialect == Some(DialectType::Redshift)
16997            && func.name.eq_ignore_ascii_case("DATE_TRUNC")
16998            && func.args.len() == 2
16999        {
17000            self.write_keyword("DATE_TRUNC");
17001            self.write("(");
17002            // First arg is unit - normalize to quoted uppercase
17003            self.write_redshift_date_part_quoted(&func.args[0]);
17004            self.write(", ");
17005            self.generate_expression(&func.args[1])?;
17006            self.write(")");
17007            return Ok(());
17008        }
17009
17010        // TSQL/Fabric: DATE_PART -> DATEPART (no underscore)
17011        if matches!(
17012            self.config.dialect,
17013            Some(DialectType::TSQL) | Some(DialectType::Fabric)
17014        ) && (func.name.eq_ignore_ascii_case("DATE_PART")
17015            || func.name.eq_ignore_ascii_case("DATEPART"))
17016            && func.args.len() == 2
17017        {
17018            self.write_keyword("DATEPART");
17019            self.write("(");
17020            self.generate_expression(&func.args[0])?;
17021            self.write(", ");
17022            self.generate_expression(&func.args[1])?;
17023            self.write(")");
17024            return Ok(());
17025        }
17026
17027        // PostgreSQL/Redshift: DATE_PART(part, value) -> EXTRACT(part FROM value)
17028        if matches!(
17029            self.config.dialect,
17030            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
17031        ) && (func.name.eq_ignore_ascii_case("DATE_PART")
17032            || func.name.eq_ignore_ascii_case("DATEPART"))
17033            && func.args.len() == 2
17034        {
17035            self.write_keyword("EXTRACT");
17036            self.write("(");
17037            // Extract the datetime field - if it's a string literal, strip quotes to make it a keyword
17038            match &func.args[0] {
17039                Expression::Literal(lit)
17040                    if matches!(lit.as_ref(), crate::expressions::Literal::String(_)) =>
17041                {
17042                    let crate::expressions::Literal::String(s) = lit.as_ref() else {
17043                        unreachable!()
17044                    };
17045                    self.write(&s.to_ascii_lowercase());
17046                }
17047                _ => self.generate_expression(&func.args[0])?,
17048            }
17049            self.write_space();
17050            self.write_keyword("FROM");
17051            self.write_space();
17052            self.generate_expression(&func.args[1])?;
17053            self.write(")");
17054            return Ok(());
17055        }
17056
17057        // Dremio: DATE_PART(part, value) -> EXTRACT(part FROM value)
17058        // Also DATE literals in Dremio should be CAST(...AS DATE)
17059        if self.config.dialect == Some(DialectType::Dremio)
17060            && (func.name.eq_ignore_ascii_case("DATE_PART")
17061                || func.name.eq_ignore_ascii_case("DATEPART"))
17062            && func.args.len() == 2
17063        {
17064            self.write_keyword("EXTRACT");
17065            self.write("(");
17066            self.generate_expression(&func.args[0])?;
17067            self.write_space();
17068            self.write_keyword("FROM");
17069            self.write_space();
17070            // For Dremio, DATE literals should become CAST('value' AS DATE)
17071            self.generate_dremio_date_expression(&func.args[1])?;
17072            self.write(")");
17073            return Ok(());
17074        }
17075
17076        // Dremio: CURRENT_DATE_UTC() -> CURRENT_DATE_UTC (no parentheses)
17077        if self.config.dialect == Some(DialectType::Dremio)
17078            && func.name.eq_ignore_ascii_case("CURRENT_DATE_UTC")
17079            && func.args.is_empty()
17080        {
17081            self.write_keyword("CURRENT_DATE_UTC");
17082            return Ok(());
17083        }
17084
17085        // Dremio: DATETYPE(year, month, day) transformation
17086        // - If all args are integer literals: DATE('YYYY-MM-DD')
17087        // - If args are expressions: CAST(CONCAT(x, '-', y, '-', z) AS DATE)
17088        if self.config.dialect == Some(DialectType::Dremio)
17089            && func.name.eq_ignore_ascii_case("DATETYPE")
17090            && func.args.len() == 3
17091        {
17092            // Helper function to extract integer from number literal
17093            fn get_int_literal(expr: &Expression) -> Option<i64> {
17094                if let Expression::Literal(lit) = expr {
17095                    if let crate::expressions::Literal::Number(s) = lit.as_ref() {
17096                        s.parse::<i64>().ok()
17097                    } else {
17098                        None
17099                    }
17100                } else {
17101                    None
17102                }
17103            }
17104
17105            // Check if all arguments are integer literals
17106            if let (Some(year), Some(month), Some(day)) = (
17107                get_int_literal(&func.args[0]),
17108                get_int_literal(&func.args[1]),
17109                get_int_literal(&func.args[2]),
17110            ) {
17111                // All are integer literals: DATE('YYYY-MM-DD')
17112                self.write_keyword("DATE");
17113                self.write(&format!("('{:04}-{:02}-{:02}')", year, month, day));
17114                return Ok(());
17115            }
17116
17117            // For expressions: CAST(CONCAT(x, '-', y, '-', z) AS DATE)
17118            self.write_keyword("CAST");
17119            self.write("(");
17120            self.write_keyword("CONCAT");
17121            self.write("(");
17122            self.generate_expression(&func.args[0])?;
17123            self.write(", '-', ");
17124            self.generate_expression(&func.args[1])?;
17125            self.write(", '-', ");
17126            self.generate_expression(&func.args[2])?;
17127            self.write(")");
17128            self.write_space();
17129            self.write_keyword("AS");
17130            self.write_space();
17131            self.write_keyword("DATE");
17132            self.write(")");
17133            return Ok(());
17134        }
17135
17136        // Presto/Trino: DATE_ADD('unit', interval, date) - wrap interval in CAST(...AS BIGINT)
17137        // when it's not an integer literal
17138        let is_presto_like = matches!(
17139            self.config.dialect,
17140            Some(DialectType::Presto) | Some(DialectType::Trino)
17141        );
17142        if is_presto_like && func.name.eq_ignore_ascii_case("DATE_ADD") && func.args.len() == 3 {
17143            self.write_keyword("DATE_ADD");
17144            self.write("(");
17145            // First arg: unit (pass through as-is, e.g., 'DAY')
17146            self.generate_expression(&func.args[0])?;
17147            self.write(", ");
17148            // Second arg: interval - wrap in CAST(...AS BIGINT) if it doesn't return integer type
17149            let interval = &func.args[1];
17150            let needs_cast = !self.returns_integer_type(interval);
17151            if needs_cast {
17152                self.write_keyword("CAST");
17153                self.write("(");
17154            }
17155            self.generate_expression(interval)?;
17156            if needs_cast {
17157                self.write_space();
17158                self.write_keyword("AS");
17159                self.write_space();
17160                self.write_keyword("BIGINT");
17161                self.write(")");
17162            }
17163            self.write(", ");
17164            // Third arg: date
17165            self.generate_expression(&func.args[2])?;
17166            self.write(")");
17167            return Ok(());
17168        }
17169
17170        // Use bracket syntax if the function was parsed with brackets (e.g., MAP[keys, values])
17171        let use_brackets = func.use_bracket_syntax;
17172
17173        // Special case: functions WITH ORDINALITY need special output order
17174        // Input: FUNC(args) WITH ORDINALITY
17175        // Stored as: name="FUNC WITH ORDINALITY", args=[...]
17176        // Output must be: FUNC(args) WITH ORDINALITY
17177        let has_ordinality = func.name.len() >= 16
17178            && func.name[func.name.len() - 16..].eq_ignore_ascii_case(" WITH ORDINALITY");
17179        let output_name = if has_ordinality {
17180            let base_name = &func.name[..func.name.len() - " WITH ORDINALITY".len()];
17181            self.normalize_func_name(base_name)
17182        } else {
17183            normalized_name.clone()
17184        };
17185
17186        // For qualified names (schema.function or object.method), preserve original case
17187        // because they can be case-sensitive (e.g., TSQL XML methods like .nodes(), .value())
17188        if func.name.contains('.') && !has_ordinality {
17189            // Don't normalize qualified functions - preserve original case
17190            // If the function was quoted (e.g., BigQuery `p.d.UdF`), wrap it in backticks
17191            if func.quoted {
17192                self.write("`");
17193                self.write(&func.name);
17194                self.write("`");
17195            } else {
17196                self.write(&func.name);
17197            }
17198        } else {
17199            self.write(&output_name);
17200        }
17201
17202        // If no_parens is true and there are no args, output just the function name
17203        // Unless the target dialect requires parens for this function
17204        let force_parens = func.no_parens && func.args.is_empty() && !func.distinct && {
17205            let needs_parens = if func.name.eq_ignore_ascii_case("CURRENT_USER")
17206                || func.name.eq_ignore_ascii_case("SESSION_USER")
17207                || func.name.eq_ignore_ascii_case("SYSTEM_USER")
17208            {
17209                matches!(
17210                    self.config.dialect,
17211                    Some(DialectType::Snowflake)
17212                        | Some(DialectType::Spark)
17213                        | Some(DialectType::Databricks)
17214                        | Some(DialectType::Hive)
17215                )
17216            } else {
17217                false
17218            };
17219            !needs_parens
17220        };
17221        if force_parens {
17222            // Output trailing comments
17223            for comment in &func.trailing_comments {
17224                self.write_space();
17225                self.write_formatted_comment(comment);
17226            }
17227            return Ok(());
17228        }
17229
17230        // CUBE, ROLLUP, GROUPING SETS need a space before the parenthesis
17231        if func.name.eq_ignore_ascii_case("CUBE")
17232            || func.name.eq_ignore_ascii_case("ROLLUP")
17233            || func.name.eq_ignore_ascii_case("GROUPING SETS")
17234        {
17235            self.write(" (");
17236        } else if use_brackets {
17237            self.write("[");
17238        } else {
17239            self.write("(");
17240        }
17241        if func.distinct {
17242            self.write_keyword("DISTINCT");
17243            self.write_space();
17244        }
17245
17246        // Check if arguments should be split onto multiple lines (pretty + too wide)
17247        let compact_pretty_func = matches!(self.config.dialect, Some(DialectType::Snowflake))
17248            && (func.name.eq_ignore_ascii_case("TABLE")
17249                || func.name.eq_ignore_ascii_case("FLATTEN"));
17250        // GROUPING SETS, CUBE, ROLLUP always expand in pretty mode
17251        let is_grouping_func = func.name.eq_ignore_ascii_case("GROUPING SETS")
17252            || func.name.eq_ignore_ascii_case("CUBE")
17253            || func.name.eq_ignore_ascii_case("ROLLUP");
17254        let should_split = if self.config.pretty && !func.args.is_empty() && !compact_pretty_func {
17255            if is_grouping_func {
17256                true
17257            } else {
17258                // Pre-render arguments to check total width
17259                let mut expr_strings: Vec<String> = Vec::with_capacity(func.args.len());
17260                for arg in &func.args {
17261                    let mut temp_gen = Generator::with_arc_config(self.config.clone());
17262                    Arc::make_mut(&mut temp_gen.config).pretty = false; // Don't recurse into pretty
17263                    temp_gen.generate_expression(arg)?;
17264                    expr_strings.push(temp_gen.output);
17265                }
17266                self.too_wide(&expr_strings)
17267            }
17268        } else {
17269            false
17270        };
17271
17272        if should_split {
17273            // Split onto multiple lines
17274            self.write_newline();
17275            self.indent_level += 1;
17276            for (i, arg) in func.args.iter().enumerate() {
17277                self.write_indent();
17278                self.generate_expression(arg)?;
17279                if i + 1 < func.args.len() {
17280                    self.write(",");
17281                }
17282                self.write_newline();
17283            }
17284            self.indent_level -= 1;
17285            self.write_indent();
17286        } else {
17287            // All on one line
17288            for (i, arg) in func.args.iter().enumerate() {
17289                if i > 0 {
17290                    self.write(", ");
17291                }
17292                self.generate_expression(arg)?;
17293            }
17294        }
17295
17296        if use_brackets {
17297            self.write("]");
17298        } else {
17299            self.write(")");
17300        }
17301        // Append WITH ORDINALITY after closing paren for table-valued functions
17302        if has_ordinality {
17303            self.write_space();
17304            self.write_keyword("WITH ORDINALITY");
17305        }
17306        // Output trailing comments
17307        for comment in &func.trailing_comments {
17308            self.write_space();
17309            self.write_formatted_comment(comment);
17310        }
17311        Ok(())
17312    }
17313
17314    fn generate_function_emits(&mut self, fe: &FunctionEmits) -> Result<()> {
17315        self.generate_expression(&fe.this)?;
17316        self.write_keyword(" EMITS ");
17317        self.generate_expression(&fe.emits)?;
17318        Ok(())
17319    }
17320
17321    fn generate_aggregate_function(&mut self, func: &AggregateFunction) -> Result<()> {
17322        // Normalize function name based on dialect settings
17323        let mut normalized_name = self.normalize_func_name(&func.name);
17324
17325        // Dialect-specific name mappings for aggregate functions
17326        if func.name.eq_ignore_ascii_case("MAX_BY") || func.name.eq_ignore_ascii_case("MIN_BY") {
17327            let is_max = func.name.eq_ignore_ascii_case("MAX_BY");
17328            match self.config.dialect {
17329                Some(DialectType::ClickHouse) => {
17330                    normalized_name = if is_max {
17331                        Cow::Borrowed("argMax")
17332                    } else {
17333                        Cow::Borrowed("argMin")
17334                    };
17335                }
17336                Some(DialectType::DuckDB) => {
17337                    normalized_name = if is_max {
17338                        Cow::Borrowed("ARG_MAX")
17339                    } else {
17340                        Cow::Borrowed("ARG_MIN")
17341                    };
17342                }
17343                _ => {}
17344            }
17345        }
17346        self.write(normalized_name.as_ref());
17347        self.write("(");
17348        if func.distinct {
17349            self.write_keyword("DISTINCT");
17350            self.write_space();
17351        }
17352
17353        // Check if we need to transform multi-arg COUNT DISTINCT
17354        // When dialect doesn't support multi_arg_distinct, transform:
17355        // COUNT(DISTINCT a, b) -> COUNT(DISTINCT CASE WHEN a IS NULL THEN NULL WHEN b IS NULL THEN NULL ELSE (a, b) END)
17356        let is_count = normalized_name.eq_ignore_ascii_case("COUNT");
17357        let needs_multi_arg_transform =
17358            func.distinct && is_count && func.args.len() > 1 && !self.config.multi_arg_distinct;
17359
17360        if needs_multi_arg_transform {
17361            // Generate: CASE WHEN a IS NULL THEN NULL WHEN b IS NULL THEN NULL ELSE (a, b) END
17362            self.write_keyword("CASE");
17363            for arg in &func.args {
17364                self.write_space();
17365                self.write_keyword("WHEN");
17366                self.write_space();
17367                self.generate_expression(arg)?;
17368                self.write_space();
17369                self.write_keyword("IS NULL THEN NULL");
17370            }
17371            self.write_space();
17372            self.write_keyword("ELSE");
17373            self.write(" (");
17374            for (i, arg) in func.args.iter().enumerate() {
17375                if i > 0 {
17376                    self.write(", ");
17377                }
17378                self.generate_expression(arg)?;
17379            }
17380            self.write(")");
17381            self.write_space();
17382            self.write_keyword("END");
17383        } else {
17384            for (i, arg) in func.args.iter().enumerate() {
17385                if i > 0 {
17386                    self.write(", ");
17387                }
17388                self.generate_expression(arg)?;
17389            }
17390        }
17391
17392        // IGNORE NULLS / RESPECT NULLS inside parens (for BigQuery style or when config says in_func)
17393        if self.config.ignore_nulls_in_func
17394            && !matches!(self.config.dialect, Some(DialectType::DuckDB))
17395        {
17396            if let Some(ignore) = func.ignore_nulls {
17397                self.write_space();
17398                if ignore {
17399                    self.write_keyword("IGNORE NULLS");
17400                } else {
17401                    self.write_keyword("RESPECT NULLS");
17402                }
17403            }
17404        }
17405
17406        // ORDER BY inside aggregate
17407        if !func.order_by.is_empty() {
17408            self.write_space();
17409            self.write_keyword("ORDER BY");
17410            self.write_space();
17411            for (i, ord) in func.order_by.iter().enumerate() {
17412                if i > 0 {
17413                    self.write(", ");
17414                }
17415                self.generate_ordered(ord)?;
17416            }
17417        }
17418
17419        // LIMIT inside aggregate
17420        if let Some(limit) = &func.limit {
17421            self.write_space();
17422            self.write_keyword("LIMIT");
17423            self.write_space();
17424            // Check if this is a Tuple representing LIMIT offset, count
17425            if let Expression::Tuple(t) = limit.as_ref() {
17426                if t.expressions.len() == 2 {
17427                    self.generate_expression(&t.expressions[0])?;
17428                    self.write(", ");
17429                    self.generate_expression(&t.expressions[1])?;
17430                } else {
17431                    self.generate_expression(limit)?;
17432                }
17433            } else {
17434                self.generate_expression(limit)?;
17435            }
17436        }
17437
17438        self.write(")");
17439
17440        // IGNORE NULLS / RESPECT NULLS outside parens (standard style)
17441        if !self.config.ignore_nulls_in_func
17442            && !matches!(self.config.dialect, Some(DialectType::DuckDB))
17443        {
17444            if let Some(ignore) = func.ignore_nulls {
17445                self.write_space();
17446                if ignore {
17447                    self.write_keyword("IGNORE NULLS");
17448                } else {
17449                    self.write_keyword("RESPECT NULLS");
17450                }
17451            }
17452        }
17453
17454        if let Some(filter) = &func.filter {
17455            self.write_space();
17456            self.write_keyword("FILTER");
17457            self.write("(");
17458            self.write_keyword("WHERE");
17459            self.write_space();
17460            self.generate_expression(filter)?;
17461            self.write(")");
17462        }
17463
17464        Ok(())
17465    }
17466
17467    fn generate_window_function(&mut self, wf: &WindowFunction) -> Result<()> {
17468        self.generate_expression(&wf.this)?;
17469
17470        // Generate KEEP clause if present (Oracle KEEP (DENSE_RANK FIRST|LAST ORDER BY ...))
17471        if let Some(keep) = &wf.keep {
17472            self.write_space();
17473            self.write_keyword("KEEP");
17474            self.write(" (");
17475            self.write_keyword("DENSE_RANK");
17476            self.write_space();
17477            if keep.first {
17478                self.write_keyword("FIRST");
17479            } else {
17480                self.write_keyword("LAST");
17481            }
17482            self.write_space();
17483            self.write_keyword("ORDER BY");
17484            self.write_space();
17485            for (i, ord) in keep.order_by.iter().enumerate() {
17486                if i > 0 {
17487                    self.write(", ");
17488                }
17489                self.generate_ordered(ord)?;
17490            }
17491            self.write(")");
17492        }
17493
17494        // Check if there's any OVER clause content
17495        let has_over = !wf.over.partition_by.is_empty()
17496            || !wf.over.order_by.is_empty()
17497            || wf.over.frame.is_some()
17498            || wf.over.window_name.is_some();
17499
17500        // Only output OVER if there's actual window specification (not just KEEP alone)
17501        if has_over {
17502            self.write_space();
17503            self.write_keyword("OVER");
17504
17505            // Check if this is just a bare named window reference (no parens needed)
17506            let has_specs = !wf.over.partition_by.is_empty()
17507                || !wf.over.order_by.is_empty()
17508                || wf.over.frame.is_some();
17509
17510            if wf.over.window_name.is_some() && !has_specs {
17511                // OVER window_name (without parentheses)
17512                self.write_space();
17513                self.write(&wf.over.window_name.as_ref().unwrap().name);
17514            } else {
17515                // OVER (...) or OVER (window_name ...)
17516                self.write(" (");
17517                self.generate_over(&wf.over)?;
17518                self.write(")");
17519            }
17520        } else if wf.keep.is_none() {
17521            // No KEEP and no OVER content, but still a WindowFunction - output empty OVER ()
17522            self.write_space();
17523            self.write_keyword("OVER");
17524            self.write(" ()");
17525        }
17526
17527        Ok(())
17528    }
17529
17530    /// Generate WITHIN GROUP clause (for ordered-set aggregate functions)
17531    fn generate_within_group(&mut self, wg: &WithinGroup) -> Result<()> {
17532        self.generate_expression(&wg.this)?;
17533        self.write_space();
17534        self.write_keyword("WITHIN GROUP");
17535        self.write(" (");
17536        self.write_keyword("ORDER BY");
17537        self.write_space();
17538        for (i, ord) in wg.order_by.iter().enumerate() {
17539            if i > 0 {
17540                self.write(", ");
17541            }
17542            self.generate_ordered(ord)?;
17543        }
17544        self.write(")");
17545        Ok(())
17546    }
17547
17548    /// Generate the contents of an OVER clause (without parentheses)
17549    fn generate_over(&mut self, over: &Over) -> Result<()> {
17550        let mut has_content = false;
17551
17552        // Named window reference
17553        if let Some(name) = &over.window_name {
17554            self.write(&name.name);
17555            has_content = true;
17556        }
17557
17558        // PARTITION BY
17559        if !over.partition_by.is_empty() {
17560            if has_content {
17561                self.write_space();
17562            }
17563            self.write_keyword("PARTITION BY");
17564            self.write_space();
17565            for (i, expr) in over.partition_by.iter().enumerate() {
17566                if i > 0 {
17567                    self.write(", ");
17568                }
17569                self.generate_expression(expr)?;
17570            }
17571            has_content = true;
17572        }
17573
17574        // ORDER BY
17575        if !over.order_by.is_empty() {
17576            if has_content {
17577                self.write_space();
17578            }
17579            self.write_keyword("ORDER BY");
17580            self.write_space();
17581            for (i, ordered) in over.order_by.iter().enumerate() {
17582                if i > 0 {
17583                    self.write(", ");
17584                }
17585                self.generate_ordered(ordered)?;
17586            }
17587            has_content = true;
17588        }
17589
17590        // Window frame
17591        if let Some(frame) = &over.frame {
17592            if has_content {
17593                self.write_space();
17594            }
17595            self.generate_window_frame(frame)?;
17596        }
17597
17598        Ok(())
17599    }
17600
17601    fn generate_window_frame(&mut self, frame: &WindowFrame) -> Result<()> {
17602        // Exasol uses lowercase for frame kind (rows/range/groups)
17603        let lowercase_frame = self.config.lowercase_window_frame_keywords;
17604
17605        // Use preserved kind_text if available (for case preservation), unless lowercase override is active
17606        if !lowercase_frame {
17607            if let Some(kind_text) = &frame.kind_text {
17608                self.write(kind_text);
17609            } else {
17610                match frame.kind {
17611                    WindowFrameKind::Rows => self.write_keyword("ROWS"),
17612                    WindowFrameKind::Range => self.write_keyword("RANGE"),
17613                    WindowFrameKind::Groups => self.write_keyword("GROUPS"),
17614                }
17615            }
17616        } else {
17617            match frame.kind {
17618                WindowFrameKind::Rows => self.write("rows"),
17619                WindowFrameKind::Range => self.write("range"),
17620                WindowFrameKind::Groups => self.write("groups"),
17621            }
17622        }
17623
17624        // Use BETWEEN format only when there's an explicit end bound,
17625        // or when normalize_window_frame_between is enabled and the start is a directional bound
17626        self.write_space();
17627        let should_normalize = self.config.normalize_window_frame_between
17628            && frame.end.is_none()
17629            && matches!(
17630                frame.start,
17631                WindowFrameBound::Preceding(_)
17632                    | WindowFrameBound::Following(_)
17633                    | WindowFrameBound::UnboundedPreceding
17634                    | WindowFrameBound::UnboundedFollowing
17635            );
17636
17637        if let Some(end) = &frame.end {
17638            // BETWEEN format: RANGE BETWEEN start AND end
17639            self.write_keyword("BETWEEN");
17640            self.write_space();
17641            self.generate_window_frame_bound(&frame.start, frame.start_side_text.as_deref())?;
17642            self.write_space();
17643            self.write_keyword("AND");
17644            self.write_space();
17645            self.generate_window_frame_bound(end, frame.end_side_text.as_deref())?;
17646        } else if should_normalize {
17647            // Normalize single-bound to BETWEEN form: ROWS 1 PRECEDING → ROWS BETWEEN 1 PRECEDING AND CURRENT ROW
17648            self.write_keyword("BETWEEN");
17649            self.write_space();
17650            self.generate_window_frame_bound(&frame.start, frame.start_side_text.as_deref())?;
17651            self.write_space();
17652            self.write_keyword("AND");
17653            self.write_space();
17654            self.write_keyword("CURRENT ROW");
17655        } else {
17656            // Single bound format: RANGE CURRENT ROW
17657            self.generate_window_frame_bound(&frame.start, frame.start_side_text.as_deref())?;
17658        }
17659
17660        // EXCLUDE clause
17661        if let Some(exclude) = &frame.exclude {
17662            self.write_space();
17663            self.write_keyword("EXCLUDE");
17664            self.write_space();
17665            match exclude {
17666                WindowFrameExclude::CurrentRow => self.write_keyword("CURRENT ROW"),
17667                WindowFrameExclude::Group => self.write_keyword("GROUP"),
17668                WindowFrameExclude::Ties => self.write_keyword("TIES"),
17669                WindowFrameExclude::NoOthers => self.write_keyword("NO OTHERS"),
17670            }
17671        }
17672
17673        Ok(())
17674    }
17675
17676    fn generate_window_frame_bound(
17677        &mut self,
17678        bound: &WindowFrameBound,
17679        side_text: Option<&str>,
17680    ) -> Result<()> {
17681        // Exasol uses lowercase for preceding/following
17682        let lowercase_frame = self.config.lowercase_window_frame_keywords;
17683
17684        match bound {
17685            WindowFrameBound::CurrentRow => {
17686                self.write_keyword("CURRENT ROW");
17687            }
17688            WindowFrameBound::UnboundedPreceding => {
17689                self.write_keyword("UNBOUNDED");
17690                self.write_space();
17691                if lowercase_frame {
17692                    self.write("preceding");
17693                } else if let Some(text) = side_text {
17694                    self.write(text);
17695                } else {
17696                    self.write_keyword("PRECEDING");
17697                }
17698            }
17699            WindowFrameBound::UnboundedFollowing => {
17700                self.write_keyword("UNBOUNDED");
17701                self.write_space();
17702                if lowercase_frame {
17703                    self.write("following");
17704                } else if let Some(text) = side_text {
17705                    self.write(text);
17706                } else {
17707                    self.write_keyword("FOLLOWING");
17708                }
17709            }
17710            WindowFrameBound::Preceding(expr) => {
17711                self.generate_expression(expr)?;
17712                self.write_space();
17713                if lowercase_frame {
17714                    self.write("preceding");
17715                } else if let Some(text) = side_text {
17716                    self.write(text);
17717                } else {
17718                    self.write_keyword("PRECEDING");
17719                }
17720            }
17721            WindowFrameBound::Following(expr) => {
17722                self.generate_expression(expr)?;
17723                self.write_space();
17724                if lowercase_frame {
17725                    self.write("following");
17726                } else if let Some(text) = side_text {
17727                    self.write(text);
17728                } else {
17729                    self.write_keyword("FOLLOWING");
17730                }
17731            }
17732            WindowFrameBound::BarePreceding => {
17733                if lowercase_frame {
17734                    self.write("preceding");
17735                } else if let Some(text) = side_text {
17736                    self.write(text);
17737                } else {
17738                    self.write_keyword("PRECEDING");
17739                }
17740            }
17741            WindowFrameBound::BareFollowing => {
17742                if lowercase_frame {
17743                    self.write("following");
17744                } else if let Some(text) = side_text {
17745                    self.write(text);
17746                } else {
17747                    self.write_keyword("FOLLOWING");
17748                }
17749            }
17750            WindowFrameBound::Value(expr) => {
17751                // Bare numeric bound without PRECEDING/FOLLOWING
17752                self.generate_expression(expr)?;
17753            }
17754        }
17755        Ok(())
17756    }
17757
17758    fn generate_interval(&mut self, interval: &Interval) -> Result<()> {
17759        // For Oracle with ExprSpan: only output INTERVAL if `this` is a literal
17760        // (e.g., `(expr) DAY(9) TO SECOND(3)` should NOT have INTERVAL prefix)
17761        let skip_interval_keyword = matches!(self.config.dialect, Some(DialectType::Oracle))
17762            && matches!(&interval.unit, Some(IntervalUnitSpec::ExprSpan(_)))
17763            && !matches!(&interval.this, Some(Expression::Literal(_)));
17764
17765        // SINGLE_STRING_INTERVAL: combine value and unit into a single quoted string
17766        // e.g., INTERVAL '1' DAY -> INTERVAL '1 DAY'
17767        if self.config.single_string_interval {
17768            if let (
17769                Some(Expression::Literal(lit)),
17770                Some(IntervalUnitSpec::Simple {
17771                    ref unit,
17772                    ref use_plural,
17773                }),
17774            ) = (&interval.this, &interval.unit)
17775            {
17776                if let Literal::String(ref val) = lit.as_ref() {
17777                    self.write_keyword("INTERVAL");
17778                    self.write_space();
17779                    let effective_plural = *use_plural && self.config.interval_allows_plural_form;
17780                    let unit_str = self.interval_unit_str(unit, effective_plural);
17781                    self.write("'");
17782                    self.write(val);
17783                    self.write(" ");
17784                    self.write(&unit_str);
17785                    self.write("'");
17786                    return Ok(());
17787                }
17788            }
17789        }
17790
17791        if !skip_interval_keyword {
17792            self.write_keyword("INTERVAL");
17793        }
17794
17795        // Generate value if present
17796        if let Some(ref value) = interval.this {
17797            if !skip_interval_keyword {
17798                self.write_space();
17799            }
17800            // If the value is a complex expression (not a literal/column/function call)
17801            // and there's a unit, wrap it in parentheses
17802            // e.g., INTERVAL (2 * 2) MONTH, INTERVAL (DAYOFMONTH(dt) - 1) DAY
17803            let needs_parens = interval.unit.is_some()
17804                && matches!(
17805                    value,
17806                    Expression::Add(_)
17807                        | Expression::Sub(_)
17808                        | Expression::Mul(_)
17809                        | Expression::Div(_)
17810                        | Expression::Mod(_)
17811                        | Expression::BitwiseAnd(_)
17812                        | Expression::BitwiseOr(_)
17813                        | Expression::BitwiseXor(_)
17814                );
17815            if needs_parens {
17816                self.write("(");
17817            }
17818            self.generate_expression(value)?;
17819            if needs_parens {
17820                self.write(")");
17821            }
17822        }
17823
17824        // Generate unit if present
17825        if let Some(ref unit_spec) = interval.unit {
17826            self.write_space();
17827            self.write_interval_unit_spec(unit_spec)?;
17828        }
17829
17830        Ok(())
17831    }
17832
17833    /// Return the string representation of an interval unit
17834    fn interval_unit_str(&self, unit: &IntervalUnit, use_plural: bool) -> &'static str {
17835        match (unit, use_plural) {
17836            (IntervalUnit::Year, false) => "YEAR",
17837            (IntervalUnit::Year, true) => "YEARS",
17838            (IntervalUnit::Quarter, false) => "QUARTER",
17839            (IntervalUnit::Quarter, true) => "QUARTERS",
17840            (IntervalUnit::Month, false) => "MONTH",
17841            (IntervalUnit::Month, true) => "MONTHS",
17842            (IntervalUnit::Week, false) => "WEEK",
17843            (IntervalUnit::Week, true) => "WEEKS",
17844            (IntervalUnit::Day, false) => "DAY",
17845            (IntervalUnit::Day, true) => "DAYS",
17846            (IntervalUnit::Hour, false) => "HOUR",
17847            (IntervalUnit::Hour, true) => "HOURS",
17848            (IntervalUnit::Minute, false) => "MINUTE",
17849            (IntervalUnit::Minute, true) => "MINUTES",
17850            (IntervalUnit::Second, false) => "SECOND",
17851            (IntervalUnit::Second, true) => "SECONDS",
17852            (IntervalUnit::Millisecond, false) => "MILLISECOND",
17853            (IntervalUnit::Millisecond, true) => "MILLISECONDS",
17854            (IntervalUnit::Microsecond, false) => "MICROSECOND",
17855            (IntervalUnit::Microsecond, true) => "MICROSECONDS",
17856            (IntervalUnit::Nanosecond, false) => "NANOSECOND",
17857            (IntervalUnit::Nanosecond, true) => "NANOSECONDS",
17858        }
17859    }
17860
17861    fn write_interval_unit_spec(&mut self, unit_spec: &IntervalUnitSpec) -> Result<()> {
17862        match unit_spec {
17863            IntervalUnitSpec::Simple { unit, use_plural } => {
17864                // If dialect doesn't allow plural forms, force singular
17865                let effective_plural = *use_plural && self.config.interval_allows_plural_form;
17866                self.write_simple_interval_unit(unit, effective_plural);
17867            }
17868            IntervalUnitSpec::Span(span) => {
17869                self.write_simple_interval_unit(&span.this, false);
17870                self.write_space();
17871                self.write_keyword("TO");
17872                self.write_space();
17873                self.write_simple_interval_unit(&span.expression, false);
17874            }
17875            IntervalUnitSpec::ExprSpan(span) => {
17876                // Expression-based interval span (e.g., DAY(9) TO SECOND(3))
17877                self.generate_expression(&span.this)?;
17878                self.write_space();
17879                self.write_keyword("TO");
17880                self.write_space();
17881                self.generate_expression(&span.expression)?;
17882            }
17883            IntervalUnitSpec::Expr(expr) => {
17884                self.generate_expression(expr)?;
17885            }
17886        }
17887        Ok(())
17888    }
17889
17890    fn write_simple_interval_unit(&mut self, unit: &IntervalUnit, use_plural: bool) {
17891        // Output interval unit, respecting plural preference
17892        match (unit, use_plural) {
17893            (IntervalUnit::Year, false) => self.write_keyword("YEAR"),
17894            (IntervalUnit::Year, true) => self.write_keyword("YEARS"),
17895            (IntervalUnit::Quarter, false) => self.write_keyword("QUARTER"),
17896            (IntervalUnit::Quarter, true) => self.write_keyword("QUARTERS"),
17897            (IntervalUnit::Month, false) => self.write_keyword("MONTH"),
17898            (IntervalUnit::Month, true) => self.write_keyword("MONTHS"),
17899            (IntervalUnit::Week, false) => self.write_keyword("WEEK"),
17900            (IntervalUnit::Week, true) => self.write_keyword("WEEKS"),
17901            (IntervalUnit::Day, false) => self.write_keyword("DAY"),
17902            (IntervalUnit::Day, true) => self.write_keyword("DAYS"),
17903            (IntervalUnit::Hour, false) => self.write_keyword("HOUR"),
17904            (IntervalUnit::Hour, true) => self.write_keyword("HOURS"),
17905            (IntervalUnit::Minute, false) => self.write_keyword("MINUTE"),
17906            (IntervalUnit::Minute, true) => self.write_keyword("MINUTES"),
17907            (IntervalUnit::Second, false) => self.write_keyword("SECOND"),
17908            (IntervalUnit::Second, true) => self.write_keyword("SECONDS"),
17909            (IntervalUnit::Millisecond, false) => self.write_keyword("MILLISECOND"),
17910            (IntervalUnit::Millisecond, true) => self.write_keyword("MILLISECONDS"),
17911            (IntervalUnit::Microsecond, false) => self.write_keyword("MICROSECOND"),
17912            (IntervalUnit::Microsecond, true) => self.write_keyword("MICROSECONDS"),
17913            (IntervalUnit::Nanosecond, false) => self.write_keyword("NANOSECOND"),
17914            (IntervalUnit::Nanosecond, true) => self.write_keyword("NANOSECONDS"),
17915        }
17916    }
17917
17918    /// Normalize a date part expression to unquoted uppercase for Redshift DATEDIFF/DATEADD
17919    /// Converts: 'day', 'days', day, days, DAY -> DAY (unquoted)
17920    fn write_redshift_date_part(&mut self, expr: &Expression) {
17921        let part_str = self.extract_date_part_string(expr);
17922        if let Some(part) = part_str {
17923            let normalized = self.normalize_date_part(&part);
17924            self.write_keyword(&normalized);
17925        } else {
17926            // If we can't extract a date part string, fall back to generating the expression
17927            let _ = self.generate_expression(expr);
17928        }
17929    }
17930
17931    /// Normalize a date part expression to quoted uppercase for Redshift DATE_TRUNC
17932    /// Converts: 'day', day, DAY -> 'DAY' (quoted)
17933    fn write_redshift_date_part_quoted(&mut self, expr: &Expression) {
17934        let part_str = self.extract_date_part_string(expr);
17935        if let Some(part) = part_str {
17936            let normalized = self.normalize_date_part(&part);
17937            self.write("'");
17938            self.write(&normalized);
17939            self.write("'");
17940        } else {
17941            // If we can't extract a date part string, fall back to generating the expression
17942            let _ = self.generate_expression(expr);
17943        }
17944    }
17945
17946    /// Extract date part string from expression (handles string literals and identifiers)
17947    fn extract_date_part_string(&self, expr: &Expression) -> Option<String> {
17948        match expr {
17949            Expression::Literal(lit)
17950                if matches!(lit.as_ref(), crate::expressions::Literal::String(_)) =>
17951            {
17952                let crate::expressions::Literal::String(s) = lit.as_ref() else {
17953                    unreachable!()
17954                };
17955                Some(s.clone())
17956            }
17957            Expression::Identifier(id) => Some(id.name.clone()),
17958            Expression::Column(col) if col.table.is_none() => {
17959                // Simple column reference without table prefix, treat as identifier
17960                Some(col.name.name.clone())
17961            }
17962            _ => None,
17963        }
17964    }
17965
17966    /// Normalize date part to uppercase singular form
17967    /// days -> DAY, months -> MONTH, etc.
17968    fn normalize_date_part(&self, part: &str) -> String {
17969        let mut buf = [0u8; 64];
17970        let lower: &str = if part.len() <= 64 {
17971            for (i, b) in part.bytes().enumerate() {
17972                buf[i] = b.to_ascii_lowercase();
17973            }
17974            std::str::from_utf8(&buf[..part.len()]).unwrap_or(part)
17975        } else {
17976            return part.to_ascii_uppercase();
17977        };
17978        match lower {
17979            "day" | "days" | "d" => "DAY".to_string(),
17980            "month" | "months" | "mon" | "mm" => "MONTH".to_string(),
17981            "year" | "years" | "y" | "yy" | "yyyy" => "YEAR".to_string(),
17982            "week" | "weeks" | "w" | "wk" => "WEEK".to_string(),
17983            "hour" | "hours" | "h" | "hh" => "HOUR".to_string(),
17984            "minute" | "minutes" | "m" | "mi" | "n" => "MINUTE".to_string(),
17985            "second" | "seconds" | "s" | "ss" => "SECOND".to_string(),
17986            "millisecond" | "milliseconds" | "ms" => "MILLISECOND".to_string(),
17987            "microsecond" | "microseconds" | "us" => "MICROSECOND".to_string(),
17988            "quarter" | "quarters" | "q" | "qq" => "QUARTER".to_string(),
17989            _ => part.to_ascii_uppercase(),
17990        }
17991    }
17992
17993    fn write_datetime_field(&mut self, field: &DateTimeField) {
17994        match field {
17995            DateTimeField::Year => self.write_keyword("YEAR"),
17996            DateTimeField::Month => self.write_keyword("MONTH"),
17997            DateTimeField::Day => self.write_keyword("DAY"),
17998            DateTimeField::Hour => self.write_keyword("HOUR"),
17999            DateTimeField::Minute => self.write_keyword("MINUTE"),
18000            DateTimeField::Second => self.write_keyword("SECOND"),
18001            DateTimeField::Millisecond => self.write_keyword("MILLISECOND"),
18002            DateTimeField::Microsecond => self.write_keyword("MICROSECOND"),
18003            DateTimeField::DayOfWeek => {
18004                let name = match self.config.dialect {
18005                    Some(DialectType::DuckDB) | Some(DialectType::Snowflake) => "DAYOFWEEK",
18006                    Some(DialectType::TSQL) | Some(DialectType::Fabric) => "WEEKDAY",
18007                    _ => "DOW",
18008                };
18009                self.write_keyword(name);
18010            }
18011            DateTimeField::DayOfYear => {
18012                let name = match self.config.dialect {
18013                    Some(DialectType::DuckDB) | Some(DialectType::Snowflake) => "DAYOFYEAR",
18014                    _ => "DOY",
18015                };
18016                self.write_keyword(name);
18017            }
18018            DateTimeField::Week => self.write_keyword("WEEK"),
18019            DateTimeField::WeekWithModifier(modifier) => {
18020                self.write_keyword("WEEK");
18021                self.write("(");
18022                self.write(modifier);
18023                self.write(")");
18024            }
18025            DateTimeField::Quarter => self.write_keyword("QUARTER"),
18026            DateTimeField::Epoch => self.write_keyword("EPOCH"),
18027            DateTimeField::Timezone => self.write_keyword("TIMEZONE"),
18028            DateTimeField::TimezoneHour => self.write_keyword("TIMEZONE_HOUR"),
18029            DateTimeField::TimezoneMinute => self.write_keyword("TIMEZONE_MINUTE"),
18030            DateTimeField::Date => self.write_keyword("DATE"),
18031            DateTimeField::Time => self.write_keyword("TIME"),
18032            DateTimeField::Custom(name) => self.write(name),
18033        }
18034    }
18035
18036    /// Write datetime field in lowercase (for Spark/Hive/Databricks)
18037    fn write_datetime_field_lower(&mut self, field: &DateTimeField) {
18038        match field {
18039            DateTimeField::Year => self.write("year"),
18040            DateTimeField::Month => self.write("month"),
18041            DateTimeField::Day => self.write("day"),
18042            DateTimeField::Hour => self.write("hour"),
18043            DateTimeField::Minute => self.write("minute"),
18044            DateTimeField::Second => self.write("second"),
18045            DateTimeField::Millisecond => self.write("millisecond"),
18046            DateTimeField::Microsecond => self.write("microsecond"),
18047            DateTimeField::DayOfWeek => self.write("dow"),
18048            DateTimeField::DayOfYear => self.write("doy"),
18049            DateTimeField::Week => self.write("week"),
18050            DateTimeField::WeekWithModifier(modifier) => {
18051                self.write("week(");
18052                self.write(modifier);
18053                self.write(")");
18054            }
18055            DateTimeField::Quarter => self.write("quarter"),
18056            DateTimeField::Epoch => self.write("epoch"),
18057            DateTimeField::Timezone => self.write("timezone"),
18058            DateTimeField::TimezoneHour => self.write("timezone_hour"),
18059            DateTimeField::TimezoneMinute => self.write("timezone_minute"),
18060            DateTimeField::Date => self.write("date"),
18061            DateTimeField::Time => self.write("time"),
18062            DateTimeField::Custom(name) => self.write(name),
18063        }
18064    }
18065
18066    // Helper function generators
18067
18068    fn generate_simple_func(&mut self, name: &str, arg: &Expression) -> Result<()> {
18069        self.write_keyword(name);
18070        self.write("(");
18071        self.generate_expression(arg)?;
18072        self.write(")");
18073        Ok(())
18074    }
18075
18076    /// Generate a unary function, using the original name if available for round-trip preservation
18077    fn generate_unary_func(
18078        &mut self,
18079        default_name: &str,
18080        f: &crate::expressions::UnaryFunc,
18081    ) -> Result<()> {
18082        let name = f.original_name.as_deref().unwrap_or(default_name);
18083        self.write_keyword(name);
18084        self.write("(");
18085        self.generate_expression(&f.this)?;
18086        self.write(")");
18087        Ok(())
18088    }
18089
18090    /// Generate SQRT/CBRT - always use function form (matches Python SQLGlot normalization)
18091    fn generate_sqrt_cbrt(
18092        &mut self,
18093        f: &crate::expressions::UnaryFunc,
18094        func_name: &str,
18095        _op: &str,
18096    ) -> Result<()> {
18097        // Python SQLGlot normalizes |/ and ||/ to SQRT() and CBRT()
18098        // Always use function syntax for consistency
18099        self.write_keyword(func_name);
18100        self.write("(");
18101        self.generate_expression(&f.this)?;
18102        self.write(")");
18103        Ok(())
18104    }
18105
18106    fn generate_binary_func(
18107        &mut self,
18108        name: &str,
18109        arg1: &Expression,
18110        arg2: &Expression,
18111    ) -> Result<()> {
18112        self.write_keyword(name);
18113        self.write("(");
18114        self.generate_expression(arg1)?;
18115        self.write(", ");
18116        self.generate_expression(arg2)?;
18117        self.write(")");
18118        Ok(())
18119    }
18120
18121    /// Generate CHAR/CHR function with optional USING charset
18122    /// e.g., CHAR(77, 77.3, '77.3' USING utf8mb4)
18123    /// e.g., CHR(187 USING NCHAR_CS) -- Oracle
18124    fn generate_char_func(&mut self, f: &crate::expressions::CharFunc) -> Result<()> {
18125        // Use stored name if available, otherwise default to CHAR
18126        let func_name = f.name.as_deref().unwrap_or("CHAR");
18127        self.write_keyword(func_name);
18128        self.write("(");
18129        for (i, arg) in f.args.iter().enumerate() {
18130            if i > 0 {
18131                self.write(", ");
18132            }
18133            self.generate_expression(arg)?;
18134        }
18135        if let Some(ref charset) = f.charset {
18136            self.write(" ");
18137            self.write_keyword("USING");
18138            self.write(" ");
18139            self.write(charset);
18140        }
18141        self.write(")");
18142        Ok(())
18143    }
18144
18145    fn generate_power(&mut self, f: &BinaryFunc) -> Result<()> {
18146        use crate::dialects::DialectType;
18147
18148        match self.config.dialect {
18149            Some(DialectType::Teradata) => {
18150                // Teradata uses ** operator for exponentiation
18151                self.generate_expression(&f.this)?;
18152                self.write(" ** ");
18153                self.generate_expression(&f.expression)?;
18154                Ok(())
18155            }
18156            _ => {
18157                // Other dialects use POWER function
18158                self.generate_binary_func("POWER", &f.this, &f.expression)
18159            }
18160        }
18161    }
18162
18163    fn generate_vararg_func(&mut self, name: &str, args: &[Expression]) -> Result<()> {
18164        self.write_func_name(name);
18165        self.write("(");
18166        for (i, arg) in args.iter().enumerate() {
18167            if i > 0 {
18168                self.write(", ");
18169            }
18170            self.generate_expression(arg)?;
18171        }
18172        self.write(")");
18173        Ok(())
18174    }
18175
18176    // String function generators
18177
18178    fn generate_concat_ws(&mut self, f: &ConcatWs) -> Result<()> {
18179        self.write_keyword("CONCAT_WS");
18180        self.write("(");
18181        self.generate_expression(&f.separator)?;
18182        for expr in &f.expressions {
18183            self.write(", ");
18184            self.generate_expression(expr)?;
18185        }
18186        self.write(")");
18187        Ok(())
18188    }
18189
18190    fn collect_concat_operands<'a>(expr: &'a Expression, out: &mut Vec<&'a Expression>) {
18191        if let Expression::Concat(op) = expr {
18192            Self::collect_concat_operands(&op.left, out);
18193            Self::collect_concat_operands(&op.right, out);
18194        } else {
18195            out.push(expr);
18196        }
18197    }
18198
18199    fn generate_mysql_concat_from_concat(&mut self, op: &BinaryOp) -> Result<()> {
18200        let mut operands = Vec::new();
18201        Self::collect_concat_operands(&op.left, &mut operands);
18202        Self::collect_concat_operands(&op.right, &mut operands);
18203
18204        self.write_keyword("CONCAT");
18205        self.write("(");
18206        for (i, operand) in operands.iter().enumerate() {
18207            if i > 0 {
18208                self.write(", ");
18209            }
18210            self.generate_expression(operand)?;
18211        }
18212        self.write(")");
18213        Ok(())
18214    }
18215
18216    fn collect_dpipe_operands<'a>(expr: &'a Expression, out: &mut Vec<&'a Expression>) {
18217        if let Expression::DPipe(dpipe) = expr {
18218            Self::collect_dpipe_operands(&dpipe.this, out);
18219            Self::collect_dpipe_operands(&dpipe.expression, out);
18220        } else {
18221            out.push(expr);
18222        }
18223    }
18224
18225    fn generate_mysql_concat_from_dpipe(&mut self, e: &DPipe) -> Result<()> {
18226        let mut operands = Vec::new();
18227        Self::collect_dpipe_operands(&e.this, &mut operands);
18228        Self::collect_dpipe_operands(&e.expression, &mut operands);
18229
18230        self.write_keyword("CONCAT");
18231        self.write("(");
18232        for (i, operand) in operands.iter().enumerate() {
18233            if i > 0 {
18234                self.write(", ");
18235            }
18236            self.generate_expression(operand)?;
18237        }
18238        self.write(")");
18239        Ok(())
18240    }
18241
18242    fn generate_substring(&mut self, f: &SubstringFunc) -> Result<()> {
18243        // Oracle uses SUBSTR; most others use SUBSTRING
18244        let is_oracle = matches!(self.config.dialect, Some(DialectType::Oracle));
18245        if is_oracle {
18246            self.write_keyword("SUBSTR");
18247        } else {
18248            self.write_keyword("SUBSTRING");
18249        }
18250        self.write("(");
18251        self.generate_expression(&f.this)?;
18252        // PostgreSQL always uses FROM/FOR syntax
18253        let force_from_for = matches!(self.config.dialect, Some(DialectType::PostgreSQL));
18254        // Spark/Hive use comma syntax, not FROM/FOR syntax
18255        let use_comma_syntax = matches!(
18256            self.config.dialect,
18257            Some(DialectType::Spark) | Some(DialectType::Hive) | Some(DialectType::Databricks)
18258        );
18259        if (f.from_for_syntax || force_from_for) && !use_comma_syntax {
18260            // SQL standard syntax: SUBSTRING(str FROM pos FOR len)
18261            self.write_space();
18262            self.write_keyword("FROM");
18263            self.write_space();
18264            self.generate_expression(&f.start)?;
18265            if let Some(length) = &f.length {
18266                self.write_space();
18267                self.write_keyword("FOR");
18268                self.write_space();
18269                self.generate_expression(length)?;
18270            }
18271        } else {
18272            // Comma-separated syntax: SUBSTRING(str, pos, len) or SUBSTR(str, pos, len)
18273            self.write(", ");
18274            self.generate_expression(&f.start)?;
18275            if let Some(length) = &f.length {
18276                self.write(", ");
18277                self.generate_expression(length)?;
18278            }
18279        }
18280        self.write(")");
18281        Ok(())
18282    }
18283
18284    fn generate_overlay(&mut self, f: &OverlayFunc) -> Result<()> {
18285        self.write_keyword("OVERLAY");
18286        self.write("(");
18287        self.generate_expression(&f.this)?;
18288        self.write_space();
18289        self.write_keyword("PLACING");
18290        self.write_space();
18291        self.generate_expression(&f.replacement)?;
18292        self.write_space();
18293        self.write_keyword("FROM");
18294        self.write_space();
18295        self.generate_expression(&f.from)?;
18296        if let Some(length) = &f.length {
18297            self.write_space();
18298            self.write_keyword("FOR");
18299            self.write_space();
18300            self.generate_expression(length)?;
18301        }
18302        self.write(")");
18303        Ok(())
18304    }
18305
18306    fn generate_trim(&mut self, f: &TrimFunc) -> Result<()> {
18307        // Special case: TRIM(LEADING str) -> LTRIM(str), TRIM(TRAILING str) -> RTRIM(str)
18308        // when no characters are specified (PostgreSQL style)
18309        if f.position_explicit && f.characters.is_none() {
18310            match f.position {
18311                TrimPosition::Leading => {
18312                    self.write_keyword("LTRIM");
18313                    self.write("(");
18314                    self.generate_expression(&f.this)?;
18315                    self.write(")");
18316                    return Ok(());
18317                }
18318                TrimPosition::Trailing => {
18319                    self.write_keyword("RTRIM");
18320                    self.write("(");
18321                    self.generate_expression(&f.this)?;
18322                    self.write(")");
18323                    return Ok(());
18324                }
18325                TrimPosition::Both => {
18326                    // TRIM(BOTH str) -> BTRIM(str) in PostgreSQL, but TRIM(str) is more standard
18327                    // Fall through to standard TRIM handling
18328                }
18329            }
18330        }
18331
18332        self.write_keyword("TRIM");
18333        self.write("(");
18334        // When BOTH is specified without trim characters, simplify to just TRIM(str)
18335        // Force standard syntax for dialects that require it (Hive, Spark, Databricks, ClickHouse)
18336        let force_standard = f.characters.is_some()
18337            && !f.sql_standard_syntax
18338            && matches!(
18339                self.config.dialect,
18340                Some(DialectType::Hive)
18341                    | Some(DialectType::Spark)
18342                    | Some(DialectType::Databricks)
18343                    | Some(DialectType::ClickHouse)
18344            );
18345        let use_standard = (f.sql_standard_syntax || force_standard)
18346            && !(f.position_explicit
18347                && f.characters.is_none()
18348                && matches!(f.position, TrimPosition::Both));
18349        if use_standard {
18350            // SQL standard syntax: TRIM(BOTH chars FROM str)
18351            // Only output position if it was explicitly specified
18352            if f.position_explicit {
18353                match f.position {
18354                    TrimPosition::Both => self.write_keyword("BOTH"),
18355                    TrimPosition::Leading => self.write_keyword("LEADING"),
18356                    TrimPosition::Trailing => self.write_keyword("TRAILING"),
18357                }
18358                self.write_space();
18359            }
18360            if let Some(chars) = &f.characters {
18361                self.generate_expression(chars)?;
18362                self.write_space();
18363            }
18364            self.write_keyword("FROM");
18365            self.write_space();
18366            self.generate_expression(&f.this)?;
18367        } else {
18368            // Simple function syntax: TRIM(str) or TRIM(str, chars)
18369            self.generate_expression(&f.this)?;
18370            if let Some(chars) = &f.characters {
18371                self.write(", ");
18372                self.generate_expression(chars)?;
18373            }
18374        }
18375        self.write(")");
18376        Ok(())
18377    }
18378
18379    fn generate_replace(&mut self, f: &ReplaceFunc) -> Result<()> {
18380        self.write_keyword("REPLACE");
18381        self.write("(");
18382        self.generate_expression(&f.this)?;
18383        self.write(", ");
18384        self.generate_expression(&f.old)?;
18385        self.write(", ");
18386        self.generate_expression(&f.new)?;
18387        self.write(")");
18388        Ok(())
18389    }
18390
18391    fn generate_left_right(&mut self, name: &str, f: &LeftRightFunc) -> Result<()> {
18392        self.write_keyword(name);
18393        self.write("(");
18394        self.generate_expression(&f.this)?;
18395        self.write(", ");
18396        self.generate_expression(&f.length)?;
18397        self.write(")");
18398        Ok(())
18399    }
18400
18401    fn generate_repeat(&mut self, f: &RepeatFunc) -> Result<()> {
18402        self.write_keyword("REPEAT");
18403        self.write("(");
18404        self.generate_expression(&f.this)?;
18405        self.write(", ");
18406        self.generate_expression(&f.times)?;
18407        self.write(")");
18408        Ok(())
18409    }
18410
18411    fn generate_pad(&mut self, name: &str, f: &PadFunc) -> Result<()> {
18412        self.write_keyword(name);
18413        self.write("(");
18414        self.generate_expression(&f.this)?;
18415        self.write(", ");
18416        self.generate_expression(&f.length)?;
18417        if let Some(fill) = &f.fill {
18418            self.write(", ");
18419            self.generate_expression(fill)?;
18420        }
18421        self.write(")");
18422        Ok(())
18423    }
18424
18425    fn generate_split(&mut self, f: &SplitFunc) -> Result<()> {
18426        self.write_keyword("SPLIT");
18427        self.write("(");
18428        self.generate_expression(&f.this)?;
18429        self.write(", ");
18430        self.generate_expression(&f.delimiter)?;
18431        self.write(")");
18432        Ok(())
18433    }
18434
18435    fn generate_regexp_like(&mut self, f: &RegexpFunc) -> Result<()> {
18436        use crate::dialects::DialectType;
18437        // PostgreSQL uses ~ operator for regex matching
18438        if matches!(self.config.dialect, Some(DialectType::PostgreSQL)) && f.flags.is_none() {
18439            self.generate_expression(&f.this)?;
18440            self.write(" ~ ");
18441            self.generate_expression(&f.pattern)?;
18442        } else if matches!(self.config.dialect, Some(DialectType::Exasol)) && f.flags.is_none() {
18443            // Exasol uses REGEXP_LIKE as infix binary operator
18444            self.generate_expression(&f.this)?;
18445            self.write_keyword(" REGEXP_LIKE ");
18446            self.generate_expression(&f.pattern)?;
18447        } else if matches!(
18448            self.config.dialect,
18449            Some(DialectType::SingleStore)
18450                | Some(DialectType::Spark)
18451                | Some(DialectType::Hive)
18452                | Some(DialectType::Databricks)
18453        ) && f.flags.is_none()
18454        {
18455            // SingleStore/Spark/Hive/Databricks use RLIKE infix operator
18456            self.generate_expression(&f.this)?;
18457            self.write_keyword(" RLIKE ");
18458            self.generate_expression(&f.pattern)?;
18459        } else if matches!(self.config.dialect, Some(DialectType::StarRocks)) {
18460            // StarRocks uses REGEXP function syntax
18461            self.write_keyword("REGEXP");
18462            self.write("(");
18463            self.generate_expression(&f.this)?;
18464            self.write(", ");
18465            self.generate_expression(&f.pattern)?;
18466            if let Some(flags) = &f.flags {
18467                self.write(", ");
18468                self.generate_expression(flags)?;
18469            }
18470            self.write(")");
18471        } else {
18472            self.write_keyword("REGEXP_LIKE");
18473            self.write("(");
18474            self.generate_expression(&f.this)?;
18475            self.write(", ");
18476            self.generate_expression(&f.pattern)?;
18477            if let Some(flags) = &f.flags {
18478                self.write(", ");
18479                self.generate_expression(flags)?;
18480            }
18481            self.write(")");
18482        }
18483        Ok(())
18484    }
18485
18486    fn generate_regexp_replace(&mut self, f: &RegexpReplaceFunc) -> Result<()> {
18487        self.write_keyword("REGEXP_REPLACE");
18488        self.write("(");
18489        self.generate_expression(&f.this)?;
18490        self.write(", ");
18491        self.generate_expression(&f.pattern)?;
18492        self.write(", ");
18493        self.generate_expression(&f.replacement)?;
18494        if let Some(flags) = &f.flags {
18495            self.write(", ");
18496            self.generate_expression(flags)?;
18497        }
18498        self.write(")");
18499        Ok(())
18500    }
18501
18502    fn generate_regexp_extract(&mut self, f: &RegexpExtractFunc) -> Result<()> {
18503        self.write_keyword("REGEXP_EXTRACT");
18504        self.write("(");
18505        self.generate_expression(&f.this)?;
18506        self.write(", ");
18507        self.generate_expression(&f.pattern)?;
18508        if let Some(group) = &f.group {
18509            self.write(", ");
18510            self.generate_expression(group)?;
18511        }
18512        self.write(")");
18513        Ok(())
18514    }
18515
18516    // Math function generators
18517
18518    fn generate_round(&mut self, f: &RoundFunc) -> Result<()> {
18519        self.write_keyword("ROUND");
18520        self.write("(");
18521        self.generate_expression(&f.this)?;
18522        if let Some(decimals) = &f.decimals {
18523            self.write(", ");
18524            self.generate_expression(decimals)?;
18525        }
18526        self.write(")");
18527        Ok(())
18528    }
18529
18530    fn generate_floor(&mut self, f: &FloorFunc) -> Result<()> {
18531        self.write_keyword("FLOOR");
18532        self.write("(");
18533        self.generate_expression(&f.this)?;
18534        // Handle Druid-style FLOOR(time TO unit) syntax
18535        if let Some(to) = &f.to {
18536            self.write(" ");
18537            self.write_keyword("TO");
18538            self.write(" ");
18539            self.generate_expression(to)?;
18540        } else if let Some(scale) = &f.scale {
18541            self.write(", ");
18542            self.generate_expression(scale)?;
18543        }
18544        self.write(")");
18545        Ok(())
18546    }
18547
18548    fn generate_ceil(&mut self, f: &CeilFunc) -> Result<()> {
18549        self.write_keyword("CEIL");
18550        self.write("(");
18551        self.generate_expression(&f.this)?;
18552        // Handle Druid-style CEIL(time TO unit) syntax
18553        if let Some(to) = &f.to {
18554            self.write(" ");
18555            self.write_keyword("TO");
18556            self.write(" ");
18557            self.generate_expression(to)?;
18558        } else if let Some(decimals) = &f.decimals {
18559            self.write(", ");
18560            self.generate_expression(decimals)?;
18561        }
18562        self.write(")");
18563        Ok(())
18564    }
18565
18566    fn generate_log(&mut self, f: &LogFunc) -> Result<()> {
18567        use crate::expressions::Literal;
18568
18569        if let Some(base) = &f.base {
18570            // Check for LOG_BASE_FIRST = None dialects (Presto, Trino, ClickHouse, Athena)
18571            // These dialects use LOG2()/LOG10() instead of LOG(base, value)
18572            if self.is_log_base_none() {
18573                if matches!(base, Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(s) if s == "2"))
18574                {
18575                    self.write_func_name("LOG2");
18576                    self.write("(");
18577                    self.generate_expression(&f.this)?;
18578                    self.write(")");
18579                    return Ok(());
18580                } else if matches!(base, Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(s) if s == "10"))
18581                {
18582                    self.write_func_name("LOG10");
18583                    self.write("(");
18584                    self.generate_expression(&f.this)?;
18585                    self.write(")");
18586                    return Ok(());
18587                }
18588                // Other bases: fall through to LOG(base, value) — best effort
18589            }
18590
18591            self.write_func_name("LOG");
18592            self.write("(");
18593            if self.is_log_value_first() {
18594                // BigQuery, TSQL, Tableau, Fabric: LOG(value, base)
18595                self.generate_expression(&f.this)?;
18596                self.write(", ");
18597                self.generate_expression(base)?;
18598            } else {
18599                // Default (PostgreSQL, etc.): LOG(base, value)
18600                self.generate_expression(base)?;
18601                self.write(", ");
18602                self.generate_expression(&f.this)?;
18603            }
18604            self.write(")");
18605        } else {
18606            // Single arg: LOG(x) — unspecified base (log base 10 in default dialect)
18607            self.write_func_name("LOG");
18608            self.write("(");
18609            self.generate_expression(&f.this)?;
18610            self.write(")");
18611        }
18612        Ok(())
18613    }
18614
18615    /// Whether the target dialect uses LOG(value, base) order (value first).
18616    /// BigQuery, TSQL, Tableau, Fabric use LOG(value, base).
18617    fn is_log_value_first(&self) -> bool {
18618        use crate::dialects::DialectType;
18619        matches!(
18620            self.config.dialect,
18621            Some(DialectType::BigQuery)
18622                | Some(DialectType::TSQL)
18623                | Some(DialectType::Tableau)
18624                | Some(DialectType::Fabric)
18625        )
18626    }
18627
18628    /// Whether the target dialect has LOG_BASE_FIRST = None (uses LOG2/LOG10 instead).
18629    /// Presto, Trino, ClickHouse, Athena.
18630    fn is_log_base_none(&self) -> bool {
18631        use crate::dialects::DialectType;
18632        matches!(
18633            self.config.dialect,
18634            Some(DialectType::Presto)
18635                | Some(DialectType::Trino)
18636                | Some(DialectType::ClickHouse)
18637                | Some(DialectType::Athena)
18638        )
18639    }
18640
18641    // Date/time function generators
18642
18643    fn generate_current_time(&mut self, f: &CurrentTime) -> Result<()> {
18644        self.write_keyword("CURRENT_TIME");
18645        if let Some(precision) = f.precision {
18646            self.write(&format!("({})", precision));
18647        } else if matches!(
18648            self.config.dialect,
18649            Some(crate::dialects::DialectType::MySQL)
18650                | Some(crate::dialects::DialectType::SingleStore)
18651                | Some(crate::dialects::DialectType::TiDB)
18652        ) {
18653            self.write("()");
18654        }
18655        Ok(())
18656    }
18657
18658    fn generate_current_timestamp(&mut self, f: &CurrentTimestamp) -> Result<()> {
18659        use crate::dialects::DialectType;
18660
18661        // Oracle/Redshift SYSDATE handling
18662        if f.sysdate {
18663            match self.config.dialect {
18664                Some(DialectType::Oracle) | Some(DialectType::Redshift) => {
18665                    self.write_keyword("SYSDATE");
18666                    return Ok(());
18667                }
18668                Some(DialectType::Snowflake) => {
18669                    // Snowflake uses SYSDATE() function
18670                    self.write_keyword("SYSDATE");
18671                    self.write("()");
18672                    return Ok(());
18673                }
18674                _ => {
18675                    // Other dialects use CURRENT_TIMESTAMP for SYSDATE
18676                }
18677            }
18678        }
18679
18680        self.write_keyword("CURRENT_TIMESTAMP");
18681        // MySQL, Spark, Hive always use CURRENT_TIMESTAMP() with parentheses
18682        if let Some(precision) = f.precision {
18683            self.write(&format!("({})", precision));
18684        } else if matches!(
18685            self.config.dialect,
18686            Some(crate::dialects::DialectType::MySQL)
18687                | Some(crate::dialects::DialectType::SingleStore)
18688                | Some(crate::dialects::DialectType::TiDB)
18689                | Some(crate::dialects::DialectType::Spark)
18690                | Some(crate::dialects::DialectType::Hive)
18691                | Some(crate::dialects::DialectType::Databricks)
18692                | Some(crate::dialects::DialectType::ClickHouse)
18693                | Some(crate::dialects::DialectType::BigQuery)
18694                | Some(crate::dialects::DialectType::Snowflake)
18695                | Some(crate::dialects::DialectType::Exasol)
18696        ) {
18697            self.write("()");
18698        }
18699        Ok(())
18700    }
18701
18702    fn generate_at_time_zone(&mut self, f: &AtTimeZone) -> Result<()> {
18703        // Exasol uses CONVERT_TZ(timestamp, 'UTC', zone) instead of AT TIME ZONE
18704        if self.config.dialect == Some(DialectType::Exasol) {
18705            self.write_keyword("CONVERT_TZ");
18706            self.write("(");
18707            self.generate_expression(&f.this)?;
18708            self.write(", 'UTC', ");
18709            self.generate_expression(&f.zone)?;
18710            self.write(")");
18711            return Ok(());
18712        }
18713
18714        self.generate_expression(&f.this)?;
18715        self.write_space();
18716        self.write_keyword("AT TIME ZONE");
18717        self.write_space();
18718        self.generate_expression(&f.zone)?;
18719        Ok(())
18720    }
18721
18722    fn generate_date_add(&mut self, f: &DateAddFunc, name: &str) -> Result<()> {
18723        use crate::dialects::DialectType;
18724
18725        // Presto/Trino use DATE_ADD('unit', interval, date) format
18726        // with the interval cast to BIGINT when needed
18727        let is_presto_like = matches!(
18728            self.config.dialect,
18729            Some(DialectType::Presto) | Some(DialectType::Trino)
18730        );
18731
18732        if is_presto_like {
18733            self.write_keyword(name);
18734            self.write("(");
18735            // Unit as string literal
18736            self.write("'");
18737            self.write_simple_interval_unit(&f.unit, false);
18738            self.write("'");
18739            self.write(", ");
18740            // Interval - wrap in CAST(...AS BIGINT) if it doesn't return integer type
18741            let needs_cast = !self.returns_integer_type(&f.interval);
18742            if needs_cast {
18743                self.write_keyword("CAST");
18744                self.write("(");
18745            }
18746            self.generate_expression(&f.interval)?;
18747            if needs_cast {
18748                self.write_space();
18749                self.write_keyword("AS");
18750                self.write_space();
18751                self.write_keyword("BIGINT");
18752                self.write(")");
18753            }
18754            self.write(", ");
18755            self.generate_expression(&f.this)?;
18756            self.write(")");
18757        } else {
18758            self.write_keyword(name);
18759            self.write("(");
18760            self.generate_expression(&f.this)?;
18761            self.write(", ");
18762            self.write_keyword("INTERVAL");
18763            self.write_space();
18764            self.generate_expression(&f.interval)?;
18765            self.write_space();
18766            self.write_simple_interval_unit(&f.unit, false); // Use singular form for DATEADD
18767            self.write(")");
18768        }
18769        Ok(())
18770    }
18771
18772    /// Check if an expression returns an integer type (doesn't need cast to BIGINT in Presto DATE_ADD)
18773    /// This is a heuristic to avoid full type inference
18774    fn returns_integer_type(&self, expr: &Expression) -> bool {
18775        use crate::expressions::{DataType, Literal};
18776        match expr {
18777            // Integer literals (no decimal point)
18778            Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(_)) => {
18779                let Literal::Number(n) = lit.as_ref() else {
18780                    unreachable!()
18781                };
18782                !n.contains('.')
18783            }
18784
18785            // FLOOR(x) returns integer if x is integer
18786            Expression::Floor(f) => self.returns_integer_type(&f.this),
18787
18788            // ROUND(x) returns integer if x is integer
18789            Expression::Round(f) => {
18790                // Only if no decimals arg or it's returning an integer
18791                f.decimals.is_none() && self.returns_integer_type(&f.this)
18792            }
18793
18794            // SIGN returns integer if input is integer
18795            Expression::Sign(f) => self.returns_integer_type(&f.this),
18796
18797            // ABS returns the same type as input
18798            Expression::Abs(f) => self.returns_integer_type(&f.this),
18799
18800            // Arithmetic operations on integers return integers
18801            Expression::Mul(op) => {
18802                self.returns_integer_type(&op.left) && self.returns_integer_type(&op.right)
18803            }
18804            Expression::Add(op) => {
18805                self.returns_integer_type(&op.left) && self.returns_integer_type(&op.right)
18806            }
18807            Expression::Sub(op) => {
18808                self.returns_integer_type(&op.left) && self.returns_integer_type(&op.right)
18809            }
18810            Expression::Mod(op) => self.returns_integer_type(&op.left),
18811
18812            // CAST(x AS BIGINT/INT/INTEGER/SMALLINT/TINYINT) returns integer
18813            Expression::Cast(c) => matches!(
18814                &c.to,
18815                DataType::BigInt { .. }
18816                    | DataType::Int { .. }
18817                    | DataType::SmallInt { .. }
18818                    | DataType::TinyInt { .. }
18819            ),
18820
18821            // Negation: -x returns integer if x is integer
18822            Expression::Neg(op) => self.returns_integer_type(&op.this),
18823
18824            // Parenthesized expression
18825            Expression::Paren(p) => self.returns_integer_type(&p.this),
18826
18827            // Column references and most expressions are assumed to need casting
18828            // since we don't have full type information
18829            _ => false,
18830        }
18831    }
18832
18833    fn generate_datediff(&mut self, f: &DateDiffFunc) -> Result<()> {
18834        self.write_keyword("DATEDIFF");
18835        self.write("(");
18836        if let Some(unit) = &f.unit {
18837            self.write_simple_interval_unit(unit, false); // Use singular form for DATEDIFF
18838            self.write(", ");
18839        }
18840        self.generate_expression(&f.this)?;
18841        self.write(", ");
18842        self.generate_expression(&f.expression)?;
18843        self.write(")");
18844        Ok(())
18845    }
18846
18847    fn generate_date_trunc(&mut self, f: &DateTruncFunc) -> Result<()> {
18848        self.write_keyword("DATE_TRUNC");
18849        self.write("('");
18850        self.write_datetime_field(&f.unit);
18851        self.write("', ");
18852        self.generate_expression(&f.this)?;
18853        self.write(")");
18854        Ok(())
18855    }
18856
18857    fn generate_last_day(&mut self, f: &LastDayFunc) -> Result<()> {
18858        use crate::dialects::DialectType;
18859        use crate::expressions::DateTimeField;
18860
18861        self.write_keyword("LAST_DAY");
18862        self.write("(");
18863        self.generate_expression(&f.this)?;
18864        if let Some(unit) = &f.unit {
18865            self.write(", ");
18866            // BigQuery: strip week-start modifier from WEEK(SUNDAY), WEEK(MONDAY), etc.
18867            // WEEK(SUNDAY) -> WEEK
18868            if matches!(self.config.dialect, Some(DialectType::BigQuery)) {
18869                if let DateTimeField::WeekWithModifier(_) = unit {
18870                    self.write_keyword("WEEK");
18871                } else {
18872                    self.write_datetime_field(unit);
18873                }
18874            } else {
18875                self.write_datetime_field(unit);
18876            }
18877        }
18878        self.write(")");
18879        Ok(())
18880    }
18881
18882    fn generate_extract(&mut self, f: &ExtractFunc) -> Result<()> {
18883        // TSQL/Fabric use DATEPART(part, expr) instead of EXTRACT(part FROM expr)
18884        if matches!(
18885            self.config.dialect,
18886            Some(DialectType::TSQL) | Some(DialectType::Fabric)
18887        ) {
18888            self.write_keyword("DATEPART");
18889            self.write("(");
18890            self.write_datetime_field(&f.field);
18891            self.write(", ");
18892            self.generate_expression(&f.this)?;
18893            self.write(")");
18894            return Ok(());
18895        }
18896        self.write_keyword("EXTRACT");
18897        self.write("(");
18898        // Hive/Spark use lowercase datetime fields in EXTRACT
18899        if matches!(
18900            self.config.dialect,
18901            Some(DialectType::Hive) | Some(DialectType::Spark) | Some(DialectType::Databricks)
18902        ) {
18903            self.write_datetime_field_lower(&f.field);
18904        } else {
18905            self.write_datetime_field(&f.field);
18906        }
18907        self.write_space();
18908        self.write_keyword("FROM");
18909        self.write_space();
18910        self.generate_expression(&f.this)?;
18911        self.write(")");
18912        Ok(())
18913    }
18914
18915    fn generate_to_date(&mut self, f: &ToDateFunc) -> Result<()> {
18916        self.write_keyword("TO_DATE");
18917        self.write("(");
18918        self.generate_expression(&f.this)?;
18919        if let Some(format) = &f.format {
18920            self.write(", ");
18921            self.generate_expression(format)?;
18922        }
18923        self.write(")");
18924        Ok(())
18925    }
18926
18927    fn generate_to_timestamp(&mut self, f: &ToTimestampFunc) -> Result<()> {
18928        self.write_keyword("TO_TIMESTAMP");
18929        self.write("(");
18930        self.generate_expression(&f.this)?;
18931        if let Some(format) = &f.format {
18932            self.write(", ");
18933            self.generate_expression(format)?;
18934        }
18935        self.write(")");
18936        Ok(())
18937    }
18938
18939    // Control flow function generators
18940
18941    fn generate_if_func(&mut self, f: &IfFunc) -> Result<()> {
18942        use crate::dialects::DialectType;
18943
18944        // Generic mode: normalize IF to CASE WHEN
18945        if self.config.dialect.is_none() || self.config.dialect == Some(DialectType::Generic) {
18946            self.write_keyword("CASE WHEN");
18947            self.write_space();
18948            self.generate_expression(&f.condition)?;
18949            self.write_space();
18950            self.write_keyword("THEN");
18951            self.write_space();
18952            self.generate_expression(&f.true_value)?;
18953            if let Some(false_val) = &f.false_value {
18954                self.write_space();
18955                self.write_keyword("ELSE");
18956                self.write_space();
18957                self.generate_expression(false_val)?;
18958            }
18959            self.write_space();
18960            self.write_keyword("END");
18961            return Ok(());
18962        }
18963
18964        // Exasol uses IF condition THEN true_value ELSE false_value ENDIF syntax
18965        if self.config.dialect == Some(DialectType::Exasol) {
18966            self.write_keyword("IF");
18967            self.write_space();
18968            self.generate_expression(&f.condition)?;
18969            self.write_space();
18970            self.write_keyword("THEN");
18971            self.write_space();
18972            self.generate_expression(&f.true_value)?;
18973            if let Some(false_val) = &f.false_value {
18974                self.write_space();
18975                self.write_keyword("ELSE");
18976                self.write_space();
18977                self.generate_expression(false_val)?;
18978            }
18979            self.write_space();
18980            self.write_keyword("ENDIF");
18981            return Ok(());
18982        }
18983
18984        // Choose function name based on target dialect
18985        let func_name = match self.config.dialect {
18986            Some(DialectType::Snowflake) => "IFF",
18987            Some(DialectType::SQLite) | Some(DialectType::TSQL) => "IIF",
18988            Some(DialectType::Drill) => "`IF`",
18989            _ => "IF",
18990        };
18991        self.write(func_name);
18992        self.write("(");
18993        self.generate_expression(&f.condition)?;
18994        self.write(", ");
18995        self.generate_expression(&f.true_value)?;
18996        if let Some(false_val) = &f.false_value {
18997            self.write(", ");
18998            self.generate_expression(false_val)?;
18999        }
19000        self.write(")");
19001        Ok(())
19002    }
19003
19004    fn generate_nvl2(&mut self, f: &Nvl2Func) -> Result<()> {
19005        self.write_keyword("NVL2");
19006        self.write("(");
19007        self.generate_expression(&f.this)?;
19008        self.write(", ");
19009        self.generate_expression(&f.true_value)?;
19010        self.write(", ");
19011        self.generate_expression(&f.false_value)?;
19012        self.write(")");
19013        Ok(())
19014    }
19015
19016    // Typed aggregate function generators
19017
19018    fn generate_count(&mut self, f: &CountFunc) -> Result<()> {
19019        // Use normalize_functions for COUNT to respect ClickHouse case preservation
19020        let count_name = match self.config.normalize_functions {
19021            NormalizeFunctions::Upper => "COUNT".to_string(),
19022            NormalizeFunctions::Lower => "count".to_string(),
19023            NormalizeFunctions::None => f
19024                .original_name
19025                .clone()
19026                .unwrap_or_else(|| "COUNT".to_string()),
19027        };
19028        self.write(&count_name);
19029        self.write("(");
19030        if f.distinct {
19031            self.write_keyword("DISTINCT");
19032            self.write_space();
19033        }
19034        if f.star {
19035            self.write("*");
19036        } else if let Some(ref expr) = f.this {
19037            // For COUNT(DISTINCT a, b), unwrap the Tuple to avoid extra parentheses
19038            if let Expression::Tuple(tuple) = expr {
19039                // Check if we need to transform multi-arg COUNT DISTINCT
19040                // When dialect doesn't support multi_arg_distinct, transform:
19041                // COUNT(DISTINCT a, b) -> COUNT(DISTINCT CASE WHEN a IS NULL THEN NULL WHEN b IS NULL THEN NULL ELSE (a, b) END)
19042                let needs_transform =
19043                    f.distinct && tuple.expressions.len() > 1 && !self.config.multi_arg_distinct;
19044
19045                if needs_transform {
19046                    // Generate: CASE WHEN a IS NULL THEN NULL WHEN b IS NULL THEN NULL ELSE (a, b) END
19047                    self.write_keyword("CASE");
19048                    for e in &tuple.expressions {
19049                        self.write_space();
19050                        self.write_keyword("WHEN");
19051                        self.write_space();
19052                        self.generate_expression(e)?;
19053                        self.write_space();
19054                        self.write_keyword("IS NULL THEN NULL");
19055                    }
19056                    self.write_space();
19057                    self.write_keyword("ELSE");
19058                    self.write(" (");
19059                    for (i, e) in tuple.expressions.iter().enumerate() {
19060                        if i > 0 {
19061                            self.write(", ");
19062                        }
19063                        self.generate_expression(e)?;
19064                    }
19065                    self.write(")");
19066                    self.write_space();
19067                    self.write_keyword("END");
19068                } else {
19069                    for (i, e) in tuple.expressions.iter().enumerate() {
19070                        if i > 0 {
19071                            self.write(", ");
19072                        }
19073                        self.generate_expression(e)?;
19074                    }
19075                }
19076            } else {
19077                self.generate_expression(expr)?;
19078            }
19079        }
19080        // RESPECT NULLS / IGNORE NULLS
19081        if let Some(ignore) = f.ignore_nulls {
19082            self.write_space();
19083            if ignore {
19084                self.write_keyword("IGNORE NULLS");
19085            } else {
19086                self.write_keyword("RESPECT NULLS");
19087            }
19088        }
19089        self.write(")");
19090        if let Some(ref filter) = f.filter {
19091            self.write_space();
19092            self.write_keyword("FILTER");
19093            self.write("(");
19094            self.write_keyword("WHERE");
19095            self.write_space();
19096            self.generate_expression(filter)?;
19097            self.write(")");
19098        }
19099        Ok(())
19100    }
19101
19102    fn generate_agg_func(&mut self, name: &str, f: &AggFunc) -> Result<()> {
19103        // Apply function name normalization based on config
19104        let func_name: Cow<'_, str> = match self.config.normalize_functions {
19105            NormalizeFunctions::Upper => Cow::Owned(name.to_ascii_uppercase()),
19106            NormalizeFunctions::Lower => Cow::Owned(name.to_ascii_lowercase()),
19107            NormalizeFunctions::None => {
19108                // Use the original function name from parsing if available,
19109                // otherwise fall back to lowercase of the hardcoded constant
19110                if let Some(ref original) = f.name {
19111                    Cow::Owned(original.clone())
19112                } else {
19113                    Cow::Owned(name.to_ascii_lowercase())
19114                }
19115            }
19116        };
19117        self.write(func_name.as_ref());
19118        self.write("(");
19119        if f.distinct {
19120            self.write_keyword("DISTINCT");
19121            self.write_space();
19122        }
19123        // Skip generating the expression if it's a NULL placeholder for zero-arg aggregates like MODE()
19124        if !matches!(f.this, Expression::Null(_)) {
19125            self.generate_expression(&f.this)?;
19126        }
19127        // Generate IGNORE NULLS / RESPECT NULLS inside parens if config says so (BigQuery style)
19128        // DuckDB doesn't support IGNORE NULLS / RESPECT NULLS in aggregate functions - skip it
19129        if self.config.ignore_nulls_in_func
19130            && !matches!(self.config.dialect, Some(DialectType::DuckDB))
19131        {
19132            match f.ignore_nulls {
19133                Some(true) => {
19134                    self.write_space();
19135                    self.write_keyword("IGNORE NULLS");
19136                }
19137                Some(false) => {
19138                    self.write_space();
19139                    self.write_keyword("RESPECT NULLS");
19140                }
19141                None => {}
19142            }
19143        }
19144        // Generate HAVING MAX/MIN if present (BigQuery syntax)
19145        // e.g., ANY_VALUE(fruit HAVING MAX sold)
19146        if let Some((ref expr, is_max)) = f.having_max {
19147            self.write_space();
19148            self.write_keyword("HAVING");
19149            self.write_space();
19150            if is_max {
19151                self.write_keyword("MAX");
19152            } else {
19153                self.write_keyword("MIN");
19154            }
19155            self.write_space();
19156            self.generate_expression(expr)?;
19157        }
19158        // Generate ORDER BY if present (for aggregates like ARRAY_AGG(x ORDER BY y))
19159        if !f.order_by.is_empty() {
19160            self.write_space();
19161            self.write_keyword("ORDER BY");
19162            self.write_space();
19163            for (i, ord) in f.order_by.iter().enumerate() {
19164                if i > 0 {
19165                    self.write(", ");
19166                }
19167                self.generate_ordered(ord)?;
19168            }
19169        }
19170        // Generate LIMIT if present (for aggregates like ARRAY_AGG(x ORDER BY y LIMIT 2))
19171        if let Some(ref limit) = f.limit {
19172            self.write_space();
19173            self.write_keyword("LIMIT");
19174            self.write_space();
19175            // Check if this is a Tuple representing LIMIT offset, count
19176            if let Expression::Tuple(t) = limit.as_ref() {
19177                if t.expressions.len() == 2 {
19178                    self.generate_expression(&t.expressions[0])?;
19179                    self.write(", ");
19180                    self.generate_expression(&t.expressions[1])?;
19181                } else {
19182                    self.generate_expression(limit)?;
19183                }
19184            } else {
19185                self.generate_expression(limit)?;
19186            }
19187        }
19188        self.write(")");
19189        // Generate IGNORE NULLS / RESPECT NULLS outside parens if config says so (standard style)
19190        // DuckDB doesn't support IGNORE NULLS / RESPECT NULLS in aggregate functions - skip it
19191        if !self.config.ignore_nulls_in_func
19192            && !matches!(self.config.dialect, Some(DialectType::DuckDB))
19193        {
19194            match f.ignore_nulls {
19195                Some(true) => {
19196                    self.write_space();
19197                    self.write_keyword("IGNORE NULLS");
19198                }
19199                Some(false) => {
19200                    self.write_space();
19201                    self.write_keyword("RESPECT NULLS");
19202                }
19203                None => {}
19204            }
19205        }
19206        if let Some(ref filter) = f.filter {
19207            self.write_space();
19208            self.write_keyword("FILTER");
19209            self.write("(");
19210            self.write_keyword("WHERE");
19211            self.write_space();
19212            self.generate_expression(filter)?;
19213            self.write(")");
19214        }
19215        Ok(())
19216    }
19217
19218    /// Generate FIRST/LAST aggregate functions with Hive/Spark2-style boolean argument
19219    /// for IGNORE NULLS. In Hive/Spark2, `FIRST(col) IGNORE NULLS` is written as `FIRST(col, TRUE)`.
19220    fn generate_agg_func_with_ignore_nulls_bool(&mut self, name: &str, f: &AggFunc) -> Result<()> {
19221        // For Hive/Spark2 dialects, convert IGNORE NULLS to boolean TRUE argument
19222        if matches!(self.config.dialect, Some(DialectType::Hive)) && f.ignore_nulls == Some(true) {
19223            // Create a modified copy without ignore_nulls, add TRUE as part of the output
19224            let func_name: Cow<'_, str> = match self.config.normalize_functions {
19225                NormalizeFunctions::Upper => Cow::Owned(name.to_ascii_uppercase()),
19226                NormalizeFunctions::Lower => Cow::Owned(name.to_ascii_lowercase()),
19227                NormalizeFunctions::None => {
19228                    if let Some(ref original) = f.name {
19229                        Cow::Owned(original.clone())
19230                    } else {
19231                        Cow::Owned(name.to_ascii_lowercase())
19232                    }
19233                }
19234            };
19235            self.write(func_name.as_ref());
19236            self.write("(");
19237            if f.distinct {
19238                self.write_keyword("DISTINCT");
19239                self.write_space();
19240            }
19241            if !matches!(f.this, Expression::Null(_)) {
19242                self.generate_expression(&f.this)?;
19243            }
19244            self.write(", ");
19245            self.write_keyword("TRUE");
19246            self.write(")");
19247            return Ok(());
19248        }
19249        self.generate_agg_func(name, f)
19250    }
19251
19252    fn generate_group_concat(&mut self, f: &GroupConcatFunc) -> Result<()> {
19253        self.write_keyword("GROUP_CONCAT");
19254        self.write("(");
19255        if f.distinct {
19256            self.write_keyword("DISTINCT");
19257            self.write_space();
19258        }
19259        self.generate_expression(&f.this)?;
19260        if let Some(ref order_by) = f.order_by {
19261            self.write_space();
19262            self.write_keyword("ORDER BY");
19263            self.write_space();
19264            for (i, ord) in order_by.iter().enumerate() {
19265                if i > 0 {
19266                    self.write(", ");
19267                }
19268                self.generate_ordered(ord)?;
19269            }
19270        }
19271        if let Some(ref sep) = f.separator {
19272            // SQLite uses GROUP_CONCAT(x, sep) syntax (comma-separated)
19273            // MySQL and others use GROUP_CONCAT(x SEPARATOR sep) syntax
19274            if matches!(
19275                self.config.dialect,
19276                Some(crate::dialects::DialectType::SQLite)
19277            ) {
19278                self.write(", ");
19279                self.generate_expression(sep)?;
19280            } else {
19281                self.write_space();
19282                self.write_keyword("SEPARATOR");
19283                self.write_space();
19284                self.generate_expression(sep)?;
19285            }
19286        }
19287        self.write(")");
19288        if let Some(ref filter) = f.filter {
19289            self.write_space();
19290            self.write_keyword("FILTER");
19291            self.write("(");
19292            self.write_keyword("WHERE");
19293            self.write_space();
19294            self.generate_expression(filter)?;
19295            self.write(")");
19296        }
19297        Ok(())
19298    }
19299
19300    fn generate_string_agg(&mut self, f: &StringAggFunc) -> Result<()> {
19301        let is_tsql = matches!(
19302            self.config.dialect,
19303            Some(crate::dialects::DialectType::TSQL)
19304        );
19305        self.write_keyword("STRING_AGG");
19306        self.write("(");
19307        if f.distinct {
19308            self.write_keyword("DISTINCT");
19309            self.write_space();
19310        }
19311        self.generate_expression(&f.this)?;
19312        if let Some(ref separator) = f.separator {
19313            self.write(", ");
19314            self.generate_expression(separator)?;
19315        }
19316        // For TSQL, ORDER BY goes in WITHIN GROUP clause after the closing paren
19317        if !is_tsql {
19318            if let Some(ref order_by) = f.order_by {
19319                self.write_space();
19320                self.write_keyword("ORDER BY");
19321                self.write_space();
19322                for (i, ord) in order_by.iter().enumerate() {
19323                    if i > 0 {
19324                        self.write(", ");
19325                    }
19326                    self.generate_ordered(ord)?;
19327                }
19328            }
19329        }
19330        if let Some(ref limit) = f.limit {
19331            self.write_space();
19332            self.write_keyword("LIMIT");
19333            self.write_space();
19334            self.generate_expression(limit)?;
19335        }
19336        self.write(")");
19337        // TSQL uses WITHIN GROUP (ORDER BY ...) after the function call
19338        if is_tsql {
19339            if let Some(ref order_by) = f.order_by {
19340                self.write_space();
19341                self.write_keyword("WITHIN GROUP");
19342                self.write(" (");
19343                self.write_keyword("ORDER BY");
19344                self.write_space();
19345                for (i, ord) in order_by.iter().enumerate() {
19346                    if i > 0 {
19347                        self.write(", ");
19348                    }
19349                    self.generate_ordered(ord)?;
19350                }
19351                self.write(")");
19352            }
19353        }
19354        if let Some(ref filter) = f.filter {
19355            self.write_space();
19356            self.write_keyword("FILTER");
19357            self.write("(");
19358            self.write_keyword("WHERE");
19359            self.write_space();
19360            self.generate_expression(filter)?;
19361            self.write(")");
19362        }
19363        Ok(())
19364    }
19365
19366    fn generate_listagg(&mut self, f: &ListAggFunc) -> Result<()> {
19367        use crate::dialects::DialectType;
19368        self.write_keyword("LISTAGG");
19369        self.write("(");
19370        if f.distinct {
19371            self.write_keyword("DISTINCT");
19372            self.write_space();
19373        }
19374        self.generate_expression(&f.this)?;
19375        if let Some(ref sep) = f.separator {
19376            self.write(", ");
19377            self.generate_expression(sep)?;
19378        } else if matches!(
19379            self.config.dialect,
19380            Some(DialectType::Trino) | Some(DialectType::Presto)
19381        ) {
19382            // Trino/Presto require explicit separator; default to ','
19383            self.write(", ','");
19384        }
19385        if let Some(ref overflow) = f.on_overflow {
19386            self.write_space();
19387            self.write_keyword("ON OVERFLOW");
19388            self.write_space();
19389            match overflow {
19390                ListAggOverflow::Error => self.write_keyword("ERROR"),
19391                ListAggOverflow::Truncate { filler, with_count } => {
19392                    self.write_keyword("TRUNCATE");
19393                    if let Some(ref fill) = filler {
19394                        self.write_space();
19395                        self.generate_expression(fill)?;
19396                    }
19397                    if *with_count {
19398                        self.write_space();
19399                        self.write_keyword("WITH COUNT");
19400                    } else {
19401                        self.write_space();
19402                        self.write_keyword("WITHOUT COUNT");
19403                    }
19404                }
19405            }
19406        }
19407        self.write(")");
19408        if let Some(ref order_by) = f.order_by {
19409            self.write_space();
19410            self.write_keyword("WITHIN GROUP");
19411            self.write(" (");
19412            self.write_keyword("ORDER BY");
19413            self.write_space();
19414            for (i, ord) in order_by.iter().enumerate() {
19415                if i > 0 {
19416                    self.write(", ");
19417                }
19418                self.generate_ordered(ord)?;
19419            }
19420            self.write(")");
19421        }
19422        if let Some(ref filter) = f.filter {
19423            self.write_space();
19424            self.write_keyword("FILTER");
19425            self.write("(");
19426            self.write_keyword("WHERE");
19427            self.write_space();
19428            self.generate_expression(filter)?;
19429            self.write(")");
19430        }
19431        Ok(())
19432    }
19433
19434    fn generate_sum_if(&mut self, f: &SumIfFunc) -> Result<()> {
19435        self.write_keyword("SUM_IF");
19436        self.write("(");
19437        self.generate_expression(&f.this)?;
19438        self.write(", ");
19439        self.generate_expression(&f.condition)?;
19440        self.write(")");
19441        if let Some(ref filter) = f.filter {
19442            self.write_space();
19443            self.write_keyword("FILTER");
19444            self.write("(");
19445            self.write_keyword("WHERE");
19446            self.write_space();
19447            self.generate_expression(filter)?;
19448            self.write(")");
19449        }
19450        Ok(())
19451    }
19452
19453    fn generate_approx_percentile(&mut self, f: &ApproxPercentileFunc) -> Result<()> {
19454        self.write_keyword("APPROX_PERCENTILE");
19455        self.write("(");
19456        self.generate_expression(&f.this)?;
19457        self.write(", ");
19458        self.generate_expression(&f.percentile)?;
19459        if let Some(ref acc) = f.accuracy {
19460            self.write(", ");
19461            self.generate_expression(acc)?;
19462        }
19463        self.write(")");
19464        if let Some(ref filter) = f.filter {
19465            self.write_space();
19466            self.write_keyword("FILTER");
19467            self.write("(");
19468            self.write_keyword("WHERE");
19469            self.write_space();
19470            self.generate_expression(filter)?;
19471            self.write(")");
19472        }
19473        Ok(())
19474    }
19475
19476    fn generate_percentile(&mut self, name: &str, f: &PercentileFunc) -> Result<()> {
19477        self.write_keyword(name);
19478        self.write("(");
19479        self.generate_expression(&f.percentile)?;
19480        self.write(")");
19481        if let Some(ref order_by) = f.order_by {
19482            self.write_space();
19483            self.write_keyword("WITHIN GROUP");
19484            self.write(" (");
19485            self.write_keyword("ORDER BY");
19486            self.write_space();
19487            self.generate_expression(&f.this)?;
19488            for ord in order_by.iter() {
19489                if ord.desc {
19490                    self.write_space();
19491                    self.write_keyword("DESC");
19492                }
19493            }
19494            self.write(")");
19495        }
19496        if let Some(ref filter) = f.filter {
19497            self.write_space();
19498            self.write_keyword("FILTER");
19499            self.write("(");
19500            self.write_keyword("WHERE");
19501            self.write_space();
19502            self.generate_expression(filter)?;
19503            self.write(")");
19504        }
19505        Ok(())
19506    }
19507
19508    // Window function generators
19509
19510    fn generate_ntile(&mut self, f: &NTileFunc) -> Result<()> {
19511        self.write_keyword("NTILE");
19512        self.write("(");
19513        if let Some(num_buckets) = &f.num_buckets {
19514            self.generate_expression(num_buckets)?;
19515        }
19516        if let Some(order_by) = &f.order_by {
19517            self.write_keyword(" ORDER BY ");
19518            for (i, ob) in order_by.iter().enumerate() {
19519                if i > 0 {
19520                    self.write(", ");
19521                }
19522                self.generate_ordered(ob)?;
19523            }
19524        }
19525        self.write(")");
19526        Ok(())
19527    }
19528
19529    fn generate_lead_lag(&mut self, name: &str, f: &LeadLagFunc) -> Result<()> {
19530        self.write_keyword(name);
19531        self.write("(");
19532        self.generate_expression(&f.this)?;
19533        if let Some(ref offset) = f.offset {
19534            self.write(", ");
19535            self.generate_expression(offset)?;
19536            if let Some(ref default) = f.default {
19537                self.write(", ");
19538                self.generate_expression(default)?;
19539            }
19540        }
19541        // IGNORE NULLS / RESPECT NULLS inside parens for dialects like BigQuery
19542        if self.config.ignore_nulls_in_func {
19543            match f.ignore_nulls {
19544                Some(true) => {
19545                    self.write_space();
19546                    self.write_keyword("IGNORE NULLS");
19547                }
19548                Some(false) => {
19549                    self.write_space();
19550                    self.write_keyword("RESPECT NULLS");
19551                }
19552                None => {}
19553            }
19554        }
19555        self.write(")");
19556        // IGNORE NULLS / RESPECT NULLS outside parens for other dialects
19557        if !self.config.ignore_nulls_in_func {
19558            match f.ignore_nulls {
19559                Some(true) => {
19560                    self.write_space();
19561                    self.write_keyword("IGNORE NULLS");
19562                }
19563                Some(false) => {
19564                    self.write_space();
19565                    self.write_keyword("RESPECT NULLS");
19566                }
19567                None => {}
19568            }
19569        }
19570        Ok(())
19571    }
19572
19573    fn generate_value_func(&mut self, name: &str, f: &ValueFunc) -> Result<()> {
19574        self.write_keyword(name);
19575        self.write("(");
19576        self.generate_expression(&f.this)?;
19577        // ORDER BY inside parens (e.g., DuckDB: LAST_VALUE(x ORDER BY x))
19578        if !f.order_by.is_empty() {
19579            self.write_space();
19580            self.write_keyword("ORDER BY");
19581            self.write_space();
19582            for (i, ordered) in f.order_by.iter().enumerate() {
19583                if i > 0 {
19584                    self.write(", ");
19585                }
19586                self.generate_ordered(ordered)?;
19587            }
19588        }
19589        // IGNORE NULLS / RESPECT NULLS inside parens for dialects like BigQuery, DuckDB
19590        if self.config.ignore_nulls_in_func {
19591            match f.ignore_nulls {
19592                Some(true) => {
19593                    self.write_space();
19594                    self.write_keyword("IGNORE NULLS");
19595                }
19596                Some(false) => {
19597                    self.write_space();
19598                    self.write_keyword("RESPECT NULLS");
19599                }
19600                None => {}
19601            }
19602        }
19603        self.write(")");
19604        // IGNORE NULLS / RESPECT NULLS outside parens for other dialects
19605        if !self.config.ignore_nulls_in_func {
19606            match f.ignore_nulls {
19607                Some(true) => {
19608                    self.write_space();
19609                    self.write_keyword("IGNORE NULLS");
19610                }
19611                Some(false) => {
19612                    self.write_space();
19613                    self.write_keyword("RESPECT NULLS");
19614                }
19615                None => {}
19616            }
19617        }
19618        Ok(())
19619    }
19620
19621    /// Generate FIRST_VALUE/LAST_VALUE with Hive/Spark2-style boolean argument for IGNORE NULLS.
19622    /// In Hive/Spark2, `FIRST_VALUE(col) IGNORE NULLS` is written as `FIRST_VALUE(col, TRUE)`.
19623    fn generate_value_func_with_ignore_nulls_bool(
19624        &mut self,
19625        name: &str,
19626        f: &ValueFunc,
19627    ) -> Result<()> {
19628        if matches!(self.config.dialect, Some(DialectType::Hive)) && f.ignore_nulls == Some(true) {
19629            self.write_keyword(name);
19630            self.write("(");
19631            self.generate_expression(&f.this)?;
19632            self.write(", ");
19633            self.write_keyword("TRUE");
19634            self.write(")");
19635            return Ok(());
19636        }
19637        self.generate_value_func(name, f)
19638    }
19639
19640    fn generate_nth_value(&mut self, f: &NthValueFunc) -> Result<()> {
19641        self.write_keyword("NTH_VALUE");
19642        self.write("(");
19643        self.generate_expression(&f.this)?;
19644        self.write(", ");
19645        self.generate_expression(&f.offset)?;
19646        // IGNORE NULLS / RESPECT NULLS inside parens for dialects like BigQuery, DuckDB
19647        if self.config.ignore_nulls_in_func {
19648            match f.ignore_nulls {
19649                Some(true) => {
19650                    self.write_space();
19651                    self.write_keyword("IGNORE NULLS");
19652                }
19653                Some(false) => {
19654                    self.write_space();
19655                    self.write_keyword("RESPECT NULLS");
19656                }
19657                None => {}
19658            }
19659        }
19660        self.write(")");
19661        // FROM FIRST / FROM LAST (Snowflake-specific, before IGNORE/RESPECT NULLS)
19662        if matches!(
19663            self.config.dialect,
19664            Some(crate::dialects::DialectType::Snowflake)
19665        ) {
19666            match f.from_first {
19667                Some(true) => {
19668                    self.write_space();
19669                    self.write_keyword("FROM FIRST");
19670                }
19671                Some(false) => {
19672                    self.write_space();
19673                    self.write_keyword("FROM LAST");
19674                }
19675                None => {}
19676            }
19677        }
19678        // IGNORE NULLS / RESPECT NULLS outside parens for other dialects
19679        if !self.config.ignore_nulls_in_func {
19680            match f.ignore_nulls {
19681                Some(true) => {
19682                    self.write_space();
19683                    self.write_keyword("IGNORE NULLS");
19684                }
19685                Some(false) => {
19686                    self.write_space();
19687                    self.write_keyword("RESPECT NULLS");
19688                }
19689                None => {}
19690            }
19691        }
19692        Ok(())
19693    }
19694
19695    // Additional string function generators
19696
19697    fn generate_position(&mut self, f: &PositionFunc) -> Result<()> {
19698        // Standard syntax: POSITION(substr IN str)
19699        // ClickHouse prefers comma syntax with reversed arg order: POSITION(str, substr[, start])
19700        if matches!(
19701            self.config.dialect,
19702            Some(crate::dialects::DialectType::ClickHouse)
19703        ) {
19704            self.write_keyword("POSITION");
19705            self.write("(");
19706            self.generate_expression(&f.string)?;
19707            self.write(", ");
19708            self.generate_expression(&f.substring)?;
19709            if let Some(ref start) = f.start {
19710                self.write(", ");
19711                self.generate_expression(start)?;
19712            }
19713            self.write(")");
19714            return Ok(());
19715        }
19716
19717        self.write_keyword("POSITION");
19718        self.write("(");
19719        self.generate_expression(&f.substring)?;
19720        self.write_space();
19721        self.write_keyword("IN");
19722        self.write_space();
19723        self.generate_expression(&f.string)?;
19724        if let Some(ref start) = f.start {
19725            self.write(", ");
19726            self.generate_expression(start)?;
19727        }
19728        self.write(")");
19729        Ok(())
19730    }
19731
19732    // Additional math function generators
19733
19734    fn generate_rand(&mut self, f: &Rand) -> Result<()> {
19735        // Teradata RANDOM(lower, upper)
19736        if f.lower.is_some() || f.upper.is_some() {
19737            self.write_keyword("RANDOM");
19738            self.write("(");
19739            if let Some(ref lower) = f.lower {
19740                self.generate_expression(lower)?;
19741            }
19742            if let Some(ref upper) = f.upper {
19743                self.write(", ");
19744                self.generate_expression(upper)?;
19745            }
19746            self.write(")");
19747            return Ok(());
19748        }
19749        // Snowflake uses RANDOM instead of RAND, DuckDB uses RANDOM without seed
19750        let func_name = match self.config.dialect {
19751            Some(crate::dialects::DialectType::Snowflake)
19752            | Some(crate::dialects::DialectType::DuckDB) => "RANDOM",
19753            _ => "RAND",
19754        };
19755        self.write_keyword(func_name);
19756        self.write("(");
19757        // DuckDB doesn't support seeded RANDOM, so skip the seed
19758        if !matches!(
19759            self.config.dialect,
19760            Some(crate::dialects::DialectType::DuckDB)
19761        ) {
19762            if let Some(ref seed) = f.seed {
19763                self.generate_expression(seed)?;
19764            }
19765        }
19766        self.write(")");
19767        Ok(())
19768    }
19769
19770    fn generate_truncate_func(&mut self, f: &TruncateFunc) -> Result<()> {
19771        self.write_keyword("TRUNCATE");
19772        self.write("(");
19773        self.generate_expression(&f.this)?;
19774        if let Some(ref decimals) = f.decimals {
19775            self.write(", ");
19776            self.generate_expression(decimals)?;
19777        }
19778        self.write(")");
19779        Ok(())
19780    }
19781
19782    // Control flow generators
19783
19784    fn generate_decode(&mut self, f: &DecodeFunc) -> Result<()> {
19785        self.write_keyword("DECODE");
19786        self.write("(");
19787        self.generate_expression(&f.this)?;
19788        for (search, result) in &f.search_results {
19789            self.write(", ");
19790            self.generate_expression(search)?;
19791            self.write(", ");
19792            self.generate_expression(result)?;
19793        }
19794        if let Some(ref default) = f.default {
19795            self.write(", ");
19796            self.generate_expression(default)?;
19797        }
19798        self.write(")");
19799        Ok(())
19800    }
19801
19802    // Date/time function generators
19803
19804    fn generate_date_format(&mut self, name: &str, f: &DateFormatFunc) -> Result<()> {
19805        self.write_keyword(name);
19806        self.write("(");
19807        self.generate_expression(&f.this)?;
19808        self.write(", ");
19809        self.generate_expression(&f.format)?;
19810        self.write(")");
19811        Ok(())
19812    }
19813
19814    fn generate_from_unixtime(&mut self, f: &FromUnixtimeFunc) -> Result<()> {
19815        self.write_keyword("FROM_UNIXTIME");
19816        self.write("(");
19817        self.generate_expression(&f.this)?;
19818        if let Some(ref format) = f.format {
19819            self.write(", ");
19820            self.generate_expression(format)?;
19821        }
19822        self.write(")");
19823        Ok(())
19824    }
19825
19826    fn generate_unix_timestamp(&mut self, f: &UnixTimestampFunc) -> Result<()> {
19827        self.write_keyword("UNIX_TIMESTAMP");
19828        self.write("(");
19829        if let Some(ref expr) = f.this {
19830            self.generate_expression(expr)?;
19831            if let Some(ref format) = f.format {
19832                self.write(", ");
19833                self.generate_expression(format)?;
19834            }
19835        } else if matches!(
19836            self.config.dialect,
19837            Some(DialectType::Spark) | Some(DialectType::Hive) | Some(DialectType::Databricks)
19838        ) {
19839            // Spark/Hive: UNIX_TIMESTAMP() -> UNIX_TIMESTAMP(CURRENT_TIMESTAMP())
19840            self.write_keyword("CURRENT_TIMESTAMP");
19841            self.write("()");
19842        }
19843        self.write(")");
19844        Ok(())
19845    }
19846
19847    fn generate_make_date(&mut self, f: &MakeDateFunc) -> Result<()> {
19848        self.write_keyword("MAKE_DATE");
19849        self.write("(");
19850        self.generate_expression(&f.year)?;
19851        self.write(", ");
19852        self.generate_expression(&f.month)?;
19853        self.write(", ");
19854        self.generate_expression(&f.day)?;
19855        self.write(")");
19856        Ok(())
19857    }
19858
19859    fn generate_make_timestamp(&mut self, f: &MakeTimestampFunc) -> Result<()> {
19860        self.write_keyword("MAKE_TIMESTAMP");
19861        self.write("(");
19862        self.generate_expression(&f.year)?;
19863        self.write(", ");
19864        self.generate_expression(&f.month)?;
19865        self.write(", ");
19866        self.generate_expression(&f.day)?;
19867        self.write(", ");
19868        self.generate_expression(&f.hour)?;
19869        self.write(", ");
19870        self.generate_expression(&f.minute)?;
19871        self.write(", ");
19872        self.generate_expression(&f.second)?;
19873        if let Some(ref tz) = f.timezone {
19874            self.write(", ");
19875            self.generate_expression(tz)?;
19876        }
19877        self.write(")");
19878        Ok(())
19879    }
19880
19881    /// Extract field names from a struct expression (either Struct or Function named STRUCT with Alias args)
19882    fn extract_struct_field_names(expr: &Expression) -> Option<Vec<String>> {
19883        match expr {
19884            Expression::Struct(s) => {
19885                if s.fields.iter().all(|(name, _)| name.is_some()) {
19886                    Some(
19887                        s.fields
19888                            .iter()
19889                            .map(|(name, _)| name.as_deref().unwrap_or("").to_string())
19890                            .collect(),
19891                    )
19892                } else {
19893                    None
19894                }
19895            }
19896            Expression::Function(f) if f.name.eq_ignore_ascii_case("STRUCT") => {
19897                // Check if all args are Alias (named fields)
19898                if f.args.iter().all(|a| matches!(a, Expression::Alias(_))) {
19899                    Some(
19900                        f.args
19901                            .iter()
19902                            .filter_map(|a| {
19903                                if let Expression::Alias(alias) = a {
19904                                    Some(alias.alias.name.clone())
19905                                } else {
19906                                    None
19907                                }
19908                            })
19909                            .collect(),
19910                    )
19911                } else {
19912                    None
19913                }
19914            }
19915            _ => None,
19916        }
19917    }
19918
19919    /// Check if a struct expression has any unnamed fields
19920    fn struct_has_unnamed_fields(expr: &Expression) -> bool {
19921        match expr {
19922            Expression::Struct(s) => s.fields.iter().any(|(name, _)| name.is_none()),
19923            Expression::Function(f) if f.name.eq_ignore_ascii_case("STRUCT") => {
19924                f.args.iter().any(|a| !matches!(a, Expression::Alias(_)))
19925            }
19926            _ => false,
19927        }
19928    }
19929
19930    /// Get the field count of a struct expression
19931    fn struct_field_count(expr: &Expression) -> usize {
19932        match expr {
19933            Expression::Struct(s) => s.fields.len(),
19934            Expression::Function(f) if f.name.eq_ignore_ascii_case("STRUCT") => f.args.len(),
19935            _ => 0,
19936        }
19937    }
19938
19939    /// Apply field names to an unnamed struct expression, producing a new expression with names
19940    fn apply_struct_field_names(expr: &Expression, field_names: &[String]) -> Expression {
19941        match expr {
19942            Expression::Struct(s) => {
19943                let mut new_fields = Vec::with_capacity(s.fields.len());
19944                for (i, (name, value)) in s.fields.iter().enumerate() {
19945                    if name.is_none() && i < field_names.len() {
19946                        new_fields.push((Some(field_names[i].clone()), value.clone()));
19947                    } else {
19948                        new_fields.push((name.clone(), value.clone()));
19949                    }
19950                }
19951                Expression::Struct(Box::new(crate::expressions::Struct { fields: new_fields }))
19952            }
19953            Expression::Function(f) if f.name.eq_ignore_ascii_case("STRUCT") => {
19954                let mut new_args = Vec::with_capacity(f.args.len());
19955                for (i, arg) in f.args.iter().enumerate() {
19956                    if !matches!(arg, Expression::Alias(_)) && i < field_names.len() {
19957                        // Wrap the value in an Alias with the inherited name
19958                        new_args.push(Expression::Alias(Box::new(crate::expressions::Alias {
19959                            this: arg.clone(),
19960                            alias: crate::expressions::Identifier::new(field_names[i].clone()),
19961                            column_aliases: Vec::new(),
19962                            pre_alias_comments: Vec::new(),
19963                            trailing_comments: Vec::new(),
19964                            inferred_type: None,
19965                        })));
19966                    } else {
19967                        new_args.push(arg.clone());
19968                    }
19969                }
19970                Expression::Function(Box::new(crate::expressions::Function {
19971                    name: f.name.clone(),
19972                    args: new_args,
19973                    distinct: f.distinct,
19974                    trailing_comments: f.trailing_comments.clone(),
19975                    use_bracket_syntax: f.use_bracket_syntax,
19976                    no_parens: f.no_parens,
19977                    quoted: f.quoted,
19978                    span: None,
19979                    inferred_type: None,
19980                }))
19981            }
19982            _ => expr.clone(),
19983        }
19984    }
19985
19986    /// Propagate struct field names from the first struct in an array to subsequent unnamed structs.
19987    /// This implements BigQuery's implicit field name inheritance for struct arrays.
19988    /// Handles both Expression::Struct and Expression::Function named "STRUCT".
19989    fn inherit_struct_field_names(expressions: &[Expression]) -> Vec<Expression> {
19990        let first = match expressions.first() {
19991            Some(e) => e,
19992            None => return expressions.to_vec(),
19993        };
19994
19995        let field_names = match Self::extract_struct_field_names(first) {
19996            Some(names) if !names.is_empty() => names,
19997            _ => return expressions.to_vec(),
19998        };
19999
20000        let mut result = Vec::with_capacity(expressions.len());
20001        for (idx, expr) in expressions.iter().enumerate() {
20002            if idx == 0 {
20003                result.push(expr.clone());
20004                continue;
20005            }
20006            // Check if this is a struct with unnamed fields that needs name propagation
20007            if Self::struct_field_count(expr) == field_names.len()
20008                && Self::struct_has_unnamed_fields(expr)
20009            {
20010                result.push(Self::apply_struct_field_names(expr, &field_names));
20011            } else {
20012                result.push(expr.clone());
20013            }
20014        }
20015        result
20016    }
20017
20018    // Array function generators
20019
20020    fn generate_array_constructor(&mut self, f: &ArrayConstructor) -> Result<()> {
20021        // Apply struct name inheritance for target dialects that need it
20022        // (DuckDB, Spark, Databricks, Hive, Snowflake, Presto, Trino)
20023        let needs_inheritance = matches!(
20024            self.config.dialect,
20025            Some(DialectType::DuckDB)
20026                | Some(DialectType::Spark)
20027                | Some(DialectType::Databricks)
20028                | Some(DialectType::Hive)
20029                | Some(DialectType::Snowflake)
20030                | Some(DialectType::Presto)
20031                | Some(DialectType::Trino)
20032        );
20033        let propagated: Vec<Expression>;
20034        let expressions = if needs_inheritance && f.expressions.len() > 1 {
20035            propagated = Self::inherit_struct_field_names(&f.expressions);
20036            &propagated
20037        } else {
20038            &f.expressions
20039        };
20040
20041        // Check if elements should be split onto multiple lines (pretty + too wide)
20042        let should_split = if self.config.pretty && !expressions.is_empty() {
20043            let mut expr_strings: Vec<String> = Vec::with_capacity(expressions.len());
20044            for expr in expressions {
20045                let mut temp_gen = Generator::with_arc_config(self.config.clone());
20046                Arc::make_mut(&mut temp_gen.config).pretty = false;
20047                temp_gen.generate_expression(expr)?;
20048                expr_strings.push(temp_gen.output);
20049            }
20050            self.too_wide(&expr_strings)
20051        } else {
20052            false
20053        };
20054
20055        if f.bracket_notation {
20056            // For Spark/Databricks, use ARRAY(...) with parens
20057            // For Presto/Trino/PostgreSQL, use ARRAY[...] with keyword prefix
20058            // For others (DuckDB, Snowflake), use bare [...]
20059            let (open, close) = match self.config.dialect {
20060                None
20061                | Some(DialectType::Generic)
20062                | Some(DialectType::Spark)
20063                | Some(DialectType::Databricks)
20064                | Some(DialectType::Hive) => {
20065                    self.write_keyword("ARRAY");
20066                    ("(", ")")
20067                }
20068                Some(DialectType::Presto)
20069                | Some(DialectType::Trino)
20070                | Some(DialectType::PostgreSQL)
20071                | Some(DialectType::Redshift)
20072                | Some(DialectType::Materialize)
20073                | Some(DialectType::RisingWave)
20074                | Some(DialectType::CockroachDB) => {
20075                    self.write_keyword("ARRAY");
20076                    ("[", "]")
20077                }
20078                _ => ("[", "]"),
20079            };
20080            self.write(open);
20081            if should_split {
20082                self.write_newline();
20083                self.indent_level += 1;
20084                for (i, expr) in expressions.iter().enumerate() {
20085                    self.write_indent();
20086                    self.generate_expression(expr)?;
20087                    if i + 1 < expressions.len() {
20088                        self.write(",");
20089                    }
20090                    self.write_newline();
20091                }
20092                self.indent_level -= 1;
20093                self.write_indent();
20094            } else {
20095                for (i, expr) in expressions.iter().enumerate() {
20096                    if i > 0 {
20097                        self.write(", ");
20098                    }
20099                    self.generate_expression(expr)?;
20100                }
20101            }
20102            self.write(close);
20103        } else {
20104            // Use LIST keyword if that was the original syntax (DuckDB)
20105            if f.use_list_keyword {
20106                self.write_keyword("LIST");
20107            } else {
20108                self.write_keyword("ARRAY");
20109            }
20110            // For Spark/Hive, always use ARRAY(...) with parens
20111            // Also use parens for BigQuery when the array contains a subquery (ARRAY(SELECT ...))
20112            let has_subquery = expressions
20113                .iter()
20114                .any(|e| matches!(e, Expression::Select(_)));
20115            let (open, close) = if matches!(
20116                self.config.dialect,
20117                Some(DialectType::Spark) | Some(DialectType::Databricks) | Some(DialectType::Hive)
20118            ) || (matches!(self.config.dialect, Some(DialectType::BigQuery))
20119                && has_subquery)
20120            {
20121                ("(", ")")
20122            } else {
20123                ("[", "]")
20124            };
20125            self.write(open);
20126            if should_split {
20127                self.write_newline();
20128                self.indent_level += 1;
20129                for (i, expr) in expressions.iter().enumerate() {
20130                    self.write_indent();
20131                    self.generate_expression(expr)?;
20132                    if i + 1 < expressions.len() {
20133                        self.write(",");
20134                    }
20135                    self.write_newline();
20136                }
20137                self.indent_level -= 1;
20138                self.write_indent();
20139            } else {
20140                for (i, expr) in expressions.iter().enumerate() {
20141                    if i > 0 {
20142                        self.write(", ");
20143                    }
20144                    self.generate_expression(expr)?;
20145                }
20146            }
20147            self.write(close);
20148        }
20149        Ok(())
20150    }
20151
20152    fn generate_array_sort(&mut self, f: &ArraySortFunc) -> Result<()> {
20153        self.write_keyword("ARRAY_SORT");
20154        self.write("(");
20155        self.generate_expression(&f.this)?;
20156        if let Some(ref comp) = f.comparator {
20157            self.write(", ");
20158            self.generate_expression(comp)?;
20159        }
20160        self.write(")");
20161        Ok(())
20162    }
20163
20164    fn generate_array_join(&mut self, name: &str, f: &ArrayJoinFunc) -> Result<()> {
20165        self.write_keyword(name);
20166        self.write("(");
20167        self.generate_expression(&f.this)?;
20168        self.write(", ");
20169        self.generate_expression(&f.separator)?;
20170        if let Some(ref null_rep) = f.null_replacement {
20171            self.write(", ");
20172            self.generate_expression(null_rep)?;
20173        }
20174        self.write(")");
20175        Ok(())
20176    }
20177
20178    fn generate_unnest(&mut self, f: &UnnestFunc) -> Result<()> {
20179        self.write_keyword("UNNEST");
20180        self.write("(");
20181        self.generate_expression(&f.this)?;
20182        for extra in &f.expressions {
20183            self.write(", ");
20184            self.generate_expression(extra)?;
20185        }
20186        self.write(")");
20187        if f.with_ordinality {
20188            self.write_space();
20189            if self.config.unnest_with_ordinality {
20190                // Presto/Trino: UNNEST(arr) WITH ORDINALITY [AS alias]
20191                self.write_keyword("WITH ORDINALITY");
20192            } else if f.offset_alias.is_some() {
20193                // BigQuery: UNNEST(arr) [AS col] WITH OFFSET AS pos
20194                // Alias (if any) comes BEFORE WITH OFFSET
20195                if let Some(ref alias) = f.alias {
20196                    self.write_keyword("AS");
20197                    self.write_space();
20198                    self.generate_identifier(alias)?;
20199                    self.write_space();
20200                }
20201                self.write_keyword("WITH OFFSET");
20202                if let Some(ref offset_alias) = f.offset_alias {
20203                    self.write_space();
20204                    self.write_keyword("AS");
20205                    self.write_space();
20206                    self.generate_identifier(offset_alias)?;
20207                }
20208            } else {
20209                // WITH OFFSET (BigQuery identity) - add default "AS offset" if no explicit alias
20210                self.write_keyword("WITH OFFSET");
20211                if f.alias.is_none() {
20212                    self.write(" AS offset");
20213                }
20214            }
20215        }
20216        if let Some(ref alias) = f.alias {
20217            // Add alias for: non-WITH-OFFSET cases, Presto/Trino WITH ORDINALITY, or BigQuery WITH OFFSET + alias (no offset_alias)
20218            let should_add_alias = if !f.with_ordinality {
20219                true
20220            } else if self.config.unnest_with_ordinality {
20221                // Presto/Trino: alias comes after WITH ORDINALITY
20222                true
20223            } else if f.offset_alias.is_some() {
20224                // BigQuery expansion: alias already handled above
20225                false
20226            } else {
20227                // BigQuery WITH OFFSET + alias but no offset_alias: alias comes after
20228                true
20229            };
20230            if should_add_alias {
20231                self.write_space();
20232                self.write_keyword("AS");
20233                self.write_space();
20234                self.generate_identifier(alias)?;
20235            }
20236        }
20237        Ok(())
20238    }
20239
20240    fn generate_array_filter(&mut self, f: &ArrayFilterFunc) -> Result<()> {
20241        self.write_keyword("FILTER");
20242        self.write("(");
20243        self.generate_expression(&f.this)?;
20244        self.write(", ");
20245        self.generate_expression(&f.filter)?;
20246        self.write(")");
20247        Ok(())
20248    }
20249
20250    fn generate_array_transform(&mut self, f: &ArrayTransformFunc) -> Result<()> {
20251        self.write_keyword("TRANSFORM");
20252        self.write("(");
20253        self.generate_expression(&f.this)?;
20254        self.write(", ");
20255        self.generate_expression(&f.transform)?;
20256        self.write(")");
20257        Ok(())
20258    }
20259
20260    fn generate_sequence(&mut self, name: &str, f: &SequenceFunc) -> Result<()> {
20261        self.write_keyword(name);
20262        self.write("(");
20263        self.generate_expression(&f.start)?;
20264        self.write(", ");
20265        self.generate_expression(&f.stop)?;
20266        if let Some(ref step) = f.step {
20267            self.write(", ");
20268            self.generate_expression(step)?;
20269        }
20270        self.write(")");
20271        Ok(())
20272    }
20273
20274    // Struct function generators
20275
20276    fn generate_struct_constructor(&mut self, f: &StructConstructor) -> Result<()> {
20277        self.write_keyword("STRUCT");
20278        self.write("(");
20279        for (i, (name, expr)) in f.fields.iter().enumerate() {
20280            if i > 0 {
20281                self.write(", ");
20282            }
20283            if let Some(ref id) = name {
20284                self.generate_identifier(id)?;
20285                self.write(" ");
20286                self.write_keyword("AS");
20287                self.write(" ");
20288            }
20289            self.generate_expression(expr)?;
20290        }
20291        self.write(")");
20292        Ok(())
20293    }
20294
20295    /// Convert BigQuery STRUCT function (parsed as Function with Alias args) to target dialect
20296    fn generate_struct_function_cross_dialect(&mut self, func: &Function) -> Result<()> {
20297        // Extract named/unnamed fields from function args
20298        // Args are either Alias(this=value, alias=name) for named or plain expressions for unnamed
20299        let mut names: Vec<Option<String>> = Vec::new();
20300        let mut values: Vec<&Expression> = Vec::new();
20301        let mut all_named = true;
20302
20303        for arg in &func.args {
20304            match arg {
20305                Expression::Alias(a) => {
20306                    names.push(Some(a.alias.name.clone()));
20307                    values.push(&a.this);
20308                }
20309                _ => {
20310                    names.push(None);
20311                    values.push(arg);
20312                    all_named = false;
20313                }
20314            }
20315        }
20316
20317        if matches!(self.config.dialect, Some(DialectType::DuckDB)) {
20318            // DuckDB: {'name': value, ...} for named, {'_0': value, ...} for unnamed
20319            self.write("{");
20320            for (i, (name, value)) in names.iter().zip(values.iter()).enumerate() {
20321                if i > 0 {
20322                    self.write(", ");
20323                }
20324                if let Some(n) = name {
20325                    self.write("'");
20326                    self.write(n);
20327                    self.write("'");
20328                } else {
20329                    self.write("'_");
20330                    self.write(&i.to_string());
20331                    self.write("'");
20332                }
20333                self.write(": ");
20334                self.generate_expression(value)?;
20335            }
20336            self.write("}");
20337            return Ok(());
20338        }
20339
20340        if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
20341            // Snowflake: OBJECT_CONSTRUCT('name', value, ...)
20342            self.write_keyword("OBJECT_CONSTRUCT");
20343            self.write("(");
20344            for (i, (name, value)) in names.iter().zip(values.iter()).enumerate() {
20345                if i > 0 {
20346                    self.write(", ");
20347                }
20348                if let Some(n) = name {
20349                    self.write("'");
20350                    self.write(n);
20351                    self.write("'");
20352                } else {
20353                    self.write("'_");
20354                    self.write(&i.to_string());
20355                    self.write("'");
20356                }
20357                self.write(", ");
20358                self.generate_expression(value)?;
20359            }
20360            self.write(")");
20361            return Ok(());
20362        }
20363
20364        if matches!(
20365            self.config.dialect,
20366            Some(DialectType::Presto) | Some(DialectType::Trino)
20367        ) {
20368            if all_named && !names.is_empty() {
20369                // Presto/Trino: CAST(ROW(values...) AS ROW(name TYPE, ...))
20370                // Need to infer types from values
20371                self.write_keyword("CAST");
20372                self.write("(");
20373                self.write_keyword("ROW");
20374                self.write("(");
20375                for (i, value) in values.iter().enumerate() {
20376                    if i > 0 {
20377                        self.write(", ");
20378                    }
20379                    self.generate_expression(value)?;
20380                }
20381                self.write(")");
20382                self.write(" ");
20383                self.write_keyword("AS");
20384                self.write(" ");
20385                self.write_keyword("ROW");
20386                self.write("(");
20387                for (i, (name, value)) in names.iter().zip(values.iter()).enumerate() {
20388                    if i > 0 {
20389                        self.write(", ");
20390                    }
20391                    if let Some(n) = name {
20392                        self.write(n);
20393                    }
20394                    self.write(" ");
20395                    let type_str = Self::infer_sql_type_for_presto(value);
20396                    self.write_keyword(&type_str);
20397                }
20398                self.write(")");
20399                self.write(")");
20400            } else {
20401                // Unnamed: ROW(values...)
20402                self.write_keyword("ROW");
20403                self.write("(");
20404                for (i, value) in values.iter().enumerate() {
20405                    if i > 0 {
20406                        self.write(", ");
20407                    }
20408                    self.generate_expression(value)?;
20409                }
20410                self.write(")");
20411            }
20412            return Ok(());
20413        }
20414
20415        // Default: ROW(values...) for other dialects
20416        self.write_keyword("ROW");
20417        self.write("(");
20418        for (i, value) in values.iter().enumerate() {
20419            if i > 0 {
20420                self.write(", ");
20421            }
20422            self.generate_expression(value)?;
20423        }
20424        self.write(")");
20425        Ok(())
20426    }
20427
20428    /// Infer SQL type name for a Presto/Trino ROW CAST from a literal expression
20429    fn infer_sql_type_for_presto(expr: &Expression) -> String {
20430        match expr {
20431            Expression::Literal(lit)
20432                if matches!(lit.as_ref(), crate::expressions::Literal::String(_)) =>
20433            {
20434                "VARCHAR".to_string()
20435            }
20436            Expression::Literal(lit)
20437                if matches!(lit.as_ref(), crate::expressions::Literal::Number(_)) =>
20438            {
20439                let crate::expressions::Literal::Number(n) = lit.as_ref() else {
20440                    unreachable!()
20441                };
20442                if n.contains('.') {
20443                    "DOUBLE".to_string()
20444                } else {
20445                    "INTEGER".to_string()
20446                }
20447            }
20448            Expression::Boolean(_) => "BOOLEAN".to_string(),
20449            Expression::Literal(lit)
20450                if matches!(lit.as_ref(), crate::expressions::Literal::Date(_)) =>
20451            {
20452                "DATE".to_string()
20453            }
20454            Expression::Literal(lit)
20455                if matches!(lit.as_ref(), crate::expressions::Literal::Timestamp(_)) =>
20456            {
20457                "TIMESTAMP".to_string()
20458            }
20459            Expression::Literal(lit)
20460                if matches!(lit.as_ref(), crate::expressions::Literal::Datetime(_)) =>
20461            {
20462                "TIMESTAMP".to_string()
20463            }
20464            Expression::Array(_) | Expression::ArrayFunc(_) => {
20465                // Try to infer element type from first element
20466                "ARRAY(VARCHAR)".to_string()
20467            }
20468            // For nested structs - generate a nested ROW type by inspecting fields
20469            Expression::Struct(_) | Expression::StructFunc(_) => "ROW".to_string(),
20470            Expression::Function(f) => {
20471                if f.name.eq_ignore_ascii_case("STRUCT") {
20472                    "ROW".to_string()
20473                } else if f.name.eq_ignore_ascii_case("CURRENT_DATE") {
20474                    "DATE".to_string()
20475                } else if f.name.eq_ignore_ascii_case("CURRENT_TIMESTAMP")
20476                    || f.name.eq_ignore_ascii_case("NOW")
20477                {
20478                    "TIMESTAMP".to_string()
20479                } else {
20480                    "VARCHAR".to_string()
20481                }
20482            }
20483            _ => "VARCHAR".to_string(),
20484        }
20485    }
20486
20487    fn generate_struct_extract(&mut self, f: &StructExtractFunc) -> Result<()> {
20488        // DuckDB uses STRUCT_EXTRACT function syntax
20489        if matches!(self.config.dialect, Some(DialectType::DuckDB)) {
20490            self.write_keyword("STRUCT_EXTRACT");
20491            self.write("(");
20492            self.generate_expression(&f.this)?;
20493            self.write(", ");
20494            // Output field name as string literal
20495            self.write("'");
20496            self.write(&f.field.name);
20497            self.write("'");
20498            self.write(")");
20499            return Ok(());
20500        }
20501        self.generate_expression(&f.this)?;
20502        self.write(".");
20503        self.generate_identifier(&f.field)
20504    }
20505
20506    fn generate_named_struct(&mut self, f: &NamedStructFunc) -> Result<()> {
20507        self.write_keyword("NAMED_STRUCT");
20508        self.write("(");
20509        for (i, (name, value)) in f.pairs.iter().enumerate() {
20510            if i > 0 {
20511                self.write(", ");
20512            }
20513            self.generate_expression(name)?;
20514            self.write(", ");
20515            self.generate_expression(value)?;
20516        }
20517        self.write(")");
20518        Ok(())
20519    }
20520
20521    // Map function generators
20522
20523    fn generate_map_constructor(&mut self, f: &MapConstructor) -> Result<()> {
20524        if f.curly_brace_syntax {
20525            // Curly brace syntax: MAP {'a': 1, 'b': 2} or just {'a': 1, 'b': 2}
20526            if f.with_map_keyword {
20527                self.write_keyword("MAP");
20528                self.write(" ");
20529            }
20530            self.write("{");
20531            for (i, (key, val)) in f.keys.iter().zip(f.values.iter()).enumerate() {
20532                if i > 0 {
20533                    self.write(", ");
20534                }
20535                self.generate_expression(key)?;
20536                self.write(": ");
20537                self.generate_expression(val)?;
20538            }
20539            self.write("}");
20540        } else {
20541            // MAP function syntax: MAP(ARRAY[keys], ARRAY[values])
20542            self.write_keyword("MAP");
20543            self.write("(");
20544            self.write_keyword("ARRAY");
20545            self.write("[");
20546            for (i, key) in f.keys.iter().enumerate() {
20547                if i > 0 {
20548                    self.write(", ");
20549                }
20550                self.generate_expression(key)?;
20551            }
20552            self.write("], ");
20553            self.write_keyword("ARRAY");
20554            self.write("[");
20555            for (i, val) in f.values.iter().enumerate() {
20556                if i > 0 {
20557                    self.write(", ");
20558                }
20559                self.generate_expression(val)?;
20560            }
20561            self.write("])");
20562        }
20563        Ok(())
20564    }
20565
20566    fn generate_transform_func(&mut self, name: &str, f: &TransformFunc) -> Result<()> {
20567        self.write_keyword(name);
20568        self.write("(");
20569        self.generate_expression(&f.this)?;
20570        self.write(", ");
20571        self.generate_expression(&f.transform)?;
20572        self.write(")");
20573        Ok(())
20574    }
20575
20576    // JSON function generators
20577
20578    fn generate_json_extract(&mut self, name: &str, f: &JsonExtractFunc) -> Result<()> {
20579        use crate::dialects::DialectType;
20580
20581        // Check if we should use arrow syntax (-> or ->>)
20582        let use_arrow = f.arrow_syntax && self.dialect_supports_json_arrow();
20583
20584        if use_arrow {
20585            // Output arrow syntax: expr -> path or expr ->> path
20586            self.generate_expression(&f.this)?;
20587            if name == "JSON_EXTRACT_SCALAR" || name == "JSON_EXTRACT_PATH_TEXT" {
20588                self.write(" ->> ");
20589            } else {
20590                self.write(" -> ");
20591            }
20592            self.generate_expression(&f.path)?;
20593            return Ok(());
20594        }
20595
20596        // PostgreSQL uses #>> operator for JSONB path text extraction (only when hash_arrow_syntax is true)
20597        if f.hash_arrow_syntax
20598            && matches!(
20599                self.config.dialect,
20600                Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
20601            )
20602        {
20603            self.generate_expression(&f.this)?;
20604            self.write(" #>> ");
20605            self.generate_expression(&f.path)?;
20606            return Ok(());
20607        }
20608
20609        // For PostgreSQL/Redshift, use JSON_EXTRACT_PATH / JSON_EXTRACT_PATH_TEXT for extraction without arrow syntax
20610        // Redshift maps everything to JSON_EXTRACT_PATH_TEXT since it doesn't have JSON_EXTRACT_PATH
20611        let func_name = if matches!(self.config.dialect, Some(DialectType::Redshift)) {
20612            match name {
20613                "JSON_EXTRACT_SCALAR"
20614                | "JSON_EXTRACT_PATH_TEXT"
20615                | "JSON_EXTRACT"
20616                | "JSON_EXTRACT_PATH" => "JSON_EXTRACT_PATH_TEXT",
20617                _ => name,
20618            }
20619        } else if matches!(self.config.dialect, Some(DialectType::PostgreSQL)) {
20620            match name {
20621                "JSON_EXTRACT_SCALAR" | "JSON_EXTRACT_PATH_TEXT" => "JSON_EXTRACT_PATH_TEXT",
20622                "JSON_EXTRACT" | "JSON_EXTRACT_PATH" => "JSON_EXTRACT_PATH",
20623                _ => name,
20624            }
20625        } else {
20626            name
20627        };
20628
20629        self.write_keyword(func_name);
20630        self.write("(");
20631        // For Redshift, strip CAST(... AS JSON) wrapper from the expression
20632        if matches!(self.config.dialect, Some(DialectType::Redshift)) {
20633            if let Expression::Cast(ref cast) = f.this {
20634                if matches!(cast.to, crate::expressions::DataType::Json) {
20635                    self.generate_expression(&cast.this)?;
20636                } else {
20637                    self.generate_expression(&f.this)?;
20638                }
20639            } else {
20640                self.generate_expression(&f.this)?;
20641            }
20642        } else {
20643            self.generate_expression(&f.this)?;
20644        }
20645        // For PostgreSQL/Redshift JSON_EXTRACT_PATH/JSON_EXTRACT_PATH_TEXT,
20646        // decompose JSON path into separate string arguments
20647        if matches!(
20648            self.config.dialect,
20649            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
20650        ) && (func_name == "JSON_EXTRACT_PATH" || func_name == "JSON_EXTRACT_PATH_TEXT")
20651        {
20652            if let Expression::Literal(ref lit) = f.path {
20653                if let Literal::String(ref s) = lit.as_ref() {
20654                    let parts = Self::decompose_json_path(s);
20655                    for part in &parts {
20656                        self.write(", '");
20657                        self.write(part);
20658                        self.write("'");
20659                    }
20660                }
20661            } else {
20662                self.write(", ");
20663                self.generate_expression(&f.path)?;
20664            }
20665        } else {
20666            self.write(", ");
20667            self.generate_expression(&f.path)?;
20668        }
20669
20670        // Output JSON_QUERY/JSON_VALUE options (Trino/Presto style)
20671        // These go BEFORE the closing parenthesis
20672        if let Some(ref wrapper) = f.wrapper_option {
20673            self.write_space();
20674            self.write_keyword(wrapper);
20675        }
20676        if let Some(ref quotes) = f.quotes_option {
20677            self.write_space();
20678            self.write_keyword(quotes);
20679            if f.on_scalar_string {
20680                self.write_space();
20681                self.write_keyword("ON SCALAR STRING");
20682            }
20683        }
20684        if let Some(ref on_err) = f.on_error {
20685            self.write_space();
20686            self.write_keyword(on_err);
20687        }
20688        if let Some(ref ret_type) = f.returning {
20689            self.write_space();
20690            self.write_keyword("RETURNING");
20691            self.write_space();
20692            self.generate_data_type(ret_type)?;
20693        }
20694
20695        self.write(")");
20696        Ok(())
20697    }
20698
20699    /// Check if the current dialect supports JSON arrow operators (-> and ->>)
20700    fn dialect_supports_json_arrow(&self) -> bool {
20701        use crate::dialects::DialectType;
20702        match self.config.dialect {
20703            // PostgreSQL, MySQL, DuckDB support -> and ->> operators
20704            Some(DialectType::PostgreSQL) => true,
20705            Some(DialectType::MySQL) => true,
20706            Some(DialectType::DuckDB) => true,
20707            Some(DialectType::CockroachDB) => true,
20708            Some(DialectType::StarRocks) => true,
20709            Some(DialectType::SQLite) => true,
20710            // Other dialects use function syntax
20711            _ => false,
20712        }
20713    }
20714
20715    fn generate_json_path(&mut self, name: &str, f: &JsonPathFunc) -> Result<()> {
20716        use crate::dialects::DialectType;
20717
20718        // PostgreSQL uses #> operator for JSONB path extraction
20719        if matches!(
20720            self.config.dialect,
20721            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
20722        ) && name == "JSON_EXTRACT_PATH"
20723        {
20724            self.generate_expression(&f.this)?;
20725            self.write(" #> ");
20726            if f.paths.len() == 1 {
20727                self.generate_expression(&f.paths[0])?;
20728            } else {
20729                // Multiple paths: ARRAY[path1, path2, ...]
20730                self.write_keyword("ARRAY");
20731                self.write("[");
20732                for (i, path) in f.paths.iter().enumerate() {
20733                    if i > 0 {
20734                        self.write(", ");
20735                    }
20736                    self.generate_expression(path)?;
20737                }
20738                self.write("]");
20739            }
20740            return Ok(());
20741        }
20742
20743        self.write_keyword(name);
20744        self.write("(");
20745        self.generate_expression(&f.this)?;
20746        for path in &f.paths {
20747            self.write(", ");
20748            self.generate_expression(path)?;
20749        }
20750        self.write(")");
20751        Ok(())
20752    }
20753
20754    fn generate_json_object(&mut self, f: &JsonObjectFunc) -> Result<()> {
20755        use crate::dialects::DialectType;
20756
20757        self.write_keyword("JSON_OBJECT");
20758        self.write("(");
20759        if f.star {
20760            self.write("*");
20761        } else {
20762            // BigQuery, MySQL, and SQLite use comma syntax: JSON_OBJECT('key', value)
20763            // Standard SQL uses colon syntax: JSON_OBJECT('key': value)
20764            // Also respect the json_key_value_pair_sep config
20765            let use_comma_syntax = self.config.json_key_value_pair_sep == ","
20766                || matches!(
20767                    self.config.dialect,
20768                    Some(DialectType::BigQuery)
20769                        | Some(DialectType::MySQL)
20770                        | Some(DialectType::SQLite)
20771                );
20772
20773            for (i, (key, value)) in f.pairs.iter().enumerate() {
20774                if i > 0 {
20775                    self.write(", ");
20776                }
20777                self.generate_expression(key)?;
20778                if use_comma_syntax {
20779                    self.write(", ");
20780                } else {
20781                    self.write(": ");
20782                }
20783                self.generate_expression(value)?;
20784            }
20785        }
20786        if let Some(null_handling) = f.null_handling {
20787            self.write_space();
20788            match null_handling {
20789                JsonNullHandling::NullOnNull => self.write_keyword("NULL ON NULL"),
20790                JsonNullHandling::AbsentOnNull => self.write_keyword("ABSENT ON NULL"),
20791            }
20792        }
20793        if f.with_unique_keys {
20794            self.write_space();
20795            self.write_keyword("WITH UNIQUE KEYS");
20796        }
20797        if let Some(ref ret_type) = f.returning_type {
20798            self.write_space();
20799            self.write_keyword("RETURNING");
20800            self.write_space();
20801            self.generate_data_type(ret_type)?;
20802            if f.format_json {
20803                self.write_space();
20804                self.write_keyword("FORMAT JSON");
20805            }
20806            if let Some(ref enc) = f.encoding {
20807                self.write_space();
20808                self.write_keyword("ENCODING");
20809                self.write_space();
20810                self.write(enc);
20811            }
20812        }
20813        self.write(")");
20814        Ok(())
20815    }
20816
20817    fn generate_json_modify(&mut self, name: &str, f: &JsonModifyFunc) -> Result<()> {
20818        self.write_keyword(name);
20819        self.write("(");
20820        self.generate_expression(&f.this)?;
20821        for (path, value) in &f.path_values {
20822            self.write(", ");
20823            self.generate_expression(path)?;
20824            self.write(", ");
20825            self.generate_expression(value)?;
20826        }
20827        self.write(")");
20828        Ok(())
20829    }
20830
20831    fn generate_json_array_agg(&mut self, f: &JsonArrayAggFunc) -> Result<()> {
20832        self.write_keyword("JSON_ARRAYAGG");
20833        self.write("(");
20834        self.generate_expression(&f.this)?;
20835        if let Some(ref order_by) = f.order_by {
20836            self.write_space();
20837            self.write_keyword("ORDER BY");
20838            self.write_space();
20839            for (i, ord) in order_by.iter().enumerate() {
20840                if i > 0 {
20841                    self.write(", ");
20842                }
20843                self.generate_ordered(ord)?;
20844            }
20845        }
20846        if let Some(null_handling) = f.null_handling {
20847            self.write_space();
20848            match null_handling {
20849                JsonNullHandling::NullOnNull => self.write_keyword("NULL ON NULL"),
20850                JsonNullHandling::AbsentOnNull => self.write_keyword("ABSENT ON NULL"),
20851            }
20852        }
20853        self.write(")");
20854        if let Some(ref filter) = f.filter {
20855            self.write_space();
20856            self.write_keyword("FILTER");
20857            self.write("(");
20858            self.write_keyword("WHERE");
20859            self.write_space();
20860            self.generate_expression(filter)?;
20861            self.write(")");
20862        }
20863        Ok(())
20864    }
20865
20866    fn generate_json_object_agg(&mut self, f: &JsonObjectAggFunc) -> Result<()> {
20867        self.write_keyword("JSON_OBJECTAGG");
20868        self.write("(");
20869        self.generate_expression(&f.key)?;
20870        self.write(": ");
20871        self.generate_expression(&f.value)?;
20872        if let Some(null_handling) = f.null_handling {
20873            self.write_space();
20874            match null_handling {
20875                JsonNullHandling::NullOnNull => self.write_keyword("NULL ON NULL"),
20876                JsonNullHandling::AbsentOnNull => self.write_keyword("ABSENT ON NULL"),
20877            }
20878        }
20879        self.write(")");
20880        if let Some(ref filter) = f.filter {
20881            self.write_space();
20882            self.write_keyword("FILTER");
20883            self.write("(");
20884            self.write_keyword("WHERE");
20885            self.write_space();
20886            self.generate_expression(filter)?;
20887            self.write(")");
20888        }
20889        Ok(())
20890    }
20891
20892    // Type casting/conversion generators
20893
20894    fn generate_convert(&mut self, f: &ConvertFunc) -> Result<()> {
20895        use crate::dialects::DialectType;
20896
20897        // Redshift: CONVERT(type, expr) -> CAST(expr AS type)
20898        if self.config.dialect == Some(DialectType::Redshift) {
20899            self.write_keyword("CAST");
20900            self.write("(");
20901            self.generate_expression(&f.this)?;
20902            self.write_space();
20903            self.write_keyword("AS");
20904            self.write_space();
20905            self.generate_data_type(&f.to)?;
20906            self.write(")");
20907            return Ok(());
20908        }
20909
20910        self.write_keyword("CONVERT");
20911        self.write("(");
20912        self.generate_data_type(&f.to)?;
20913        self.write(", ");
20914        self.generate_expression(&f.this)?;
20915        if let Some(ref style) = f.style {
20916            self.write(", ");
20917            self.generate_expression(style)?;
20918        }
20919        self.write(")");
20920        Ok(())
20921    }
20922
20923    // Additional expression generators
20924
20925    fn generate_lambda(&mut self, f: &LambdaExpr) -> Result<()> {
20926        if f.colon {
20927            // DuckDB syntax: LAMBDA x : expr
20928            self.write_keyword("LAMBDA");
20929            self.write_space();
20930            for (i, param) in f.parameters.iter().enumerate() {
20931                if i > 0 {
20932                    self.write(", ");
20933                }
20934                self.generate_identifier(param)?;
20935            }
20936            self.write(" : ");
20937        } else {
20938            // Standard syntax: x -> expr or (x, y) -> expr
20939            if f.parameters.len() == 1 {
20940                self.generate_identifier(&f.parameters[0])?;
20941            } else {
20942                self.write("(");
20943                for (i, param) in f.parameters.iter().enumerate() {
20944                    if i > 0 {
20945                        self.write(", ");
20946                    }
20947                    self.generate_identifier(param)?;
20948                }
20949                self.write(")");
20950            }
20951            self.write(" -> ");
20952        }
20953        self.generate_expression(&f.body)
20954    }
20955
20956    fn generate_named_argument(&mut self, f: &NamedArgument) -> Result<()> {
20957        self.generate_identifier(&f.name)?;
20958        match f.separator {
20959            NamedArgSeparator::DArrow => self.write(" => "),
20960            NamedArgSeparator::ColonEq => self.write(" := "),
20961            NamedArgSeparator::Eq => self.write(" = "),
20962        }
20963        self.generate_expression(&f.value)
20964    }
20965
20966    fn generate_table_argument(&mut self, f: &TableArgument) -> Result<()> {
20967        self.write_keyword(&f.prefix);
20968        self.write(" ");
20969        self.generate_expression(&f.this)
20970    }
20971
20972    fn generate_parameter(&mut self, f: &Parameter) -> Result<()> {
20973        match f.style {
20974            ParameterStyle::Question => self.write("?"),
20975            ParameterStyle::Dollar => {
20976                self.write("$");
20977                if let Some(idx) = f.index {
20978                    self.write(&idx.to_string());
20979                } else if let Some(ref name) = f.name {
20980                    // Session variable like $x or $query_id
20981                    self.write(name);
20982                }
20983            }
20984            ParameterStyle::DollarBrace => {
20985                // Template variable like ${x} or ${hiveconf:name} (Databricks, Hive)
20986                self.write("${");
20987                if let Some(ref name) = f.name {
20988                    self.write(name);
20989                }
20990                if let Some(ref expr) = f.expression {
20991                    self.write(":");
20992                    self.write(expr);
20993                }
20994                self.write("}");
20995            }
20996            ParameterStyle::Colon => {
20997                self.write(":");
20998                if let Some(idx) = f.index {
20999                    self.write(&idx.to_string());
21000                } else if let Some(ref name) = f.name {
21001                    self.write(name);
21002                }
21003            }
21004            ParameterStyle::At => {
21005                self.write("@");
21006                if let Some(ref name) = f.name {
21007                    if f.string_quoted {
21008                        self.write("'");
21009                        self.write(name);
21010                        self.write("'");
21011                    } else if f.quoted {
21012                        self.write("\"");
21013                        self.write(name);
21014                        self.write("\"");
21015                    } else {
21016                        self.write(name);
21017                    }
21018                }
21019            }
21020            ParameterStyle::DoubleAt => {
21021                self.write("@@");
21022                if let Some(ref name) = f.name {
21023                    self.write(name);
21024                }
21025            }
21026            ParameterStyle::DoubleDollar => {
21027                self.write("$$");
21028                if let Some(ref name) = f.name {
21029                    self.write(name);
21030                }
21031            }
21032            ParameterStyle::Percent => {
21033                if let Some(ref name) = f.name {
21034                    // %(name)s format
21035                    self.write("%(");
21036                    self.write(name);
21037                    self.write(")s");
21038                } else {
21039                    // %s format
21040                    self.write("%s");
21041                }
21042            }
21043            ParameterStyle::Brace => {
21044                // Spark/Databricks widget template variable: {name}
21045                // ClickHouse query parameter may include kind: {name: Type}
21046                self.write("{");
21047                if let Some(ref name) = f.name {
21048                    self.write(name);
21049                }
21050                if let Some(ref expr) = f.expression {
21051                    self.write(": ");
21052                    self.write(expr);
21053                }
21054                self.write("}");
21055            }
21056        }
21057        Ok(())
21058    }
21059
21060    fn generate_placeholder(&mut self, f: &Placeholder) -> Result<()> {
21061        self.write("?");
21062        if let Some(idx) = f.index {
21063            self.write(&idx.to_string());
21064        }
21065        Ok(())
21066    }
21067
21068    fn generate_sql_comment(&mut self, f: &SqlComment) -> Result<()> {
21069        if f.is_block {
21070            self.write("/*");
21071            self.write(&f.text);
21072            self.write("*/");
21073        } else {
21074            self.write("--");
21075            self.write(&f.text);
21076        }
21077        Ok(())
21078    }
21079
21080    // Additional predicate generators
21081
21082    fn generate_similar_to(&mut self, f: &SimilarToExpr) -> Result<()> {
21083        self.generate_expression(&f.this)?;
21084        if f.not {
21085            self.write_space();
21086            self.write_keyword("NOT");
21087        }
21088        self.write_space();
21089        self.write_keyword("SIMILAR TO");
21090        self.write_space();
21091        self.generate_expression(&f.pattern)?;
21092        if let Some(ref escape) = f.escape {
21093            self.write_space();
21094            self.write_keyword("ESCAPE");
21095            self.write_space();
21096            self.generate_expression(escape)?;
21097        }
21098        Ok(())
21099    }
21100
21101    fn generate_quantified(&mut self, name: &str, f: &QuantifiedExpr) -> Result<()> {
21102        self.generate_expression(&f.this)?;
21103        self.write_space();
21104        // Output comparison operator if present
21105        if let Some(op) = &f.op {
21106            match op {
21107                QuantifiedOp::Eq => self.write("="),
21108                QuantifiedOp::Neq => self.write("<>"),
21109                QuantifiedOp::Lt => self.write("<"),
21110                QuantifiedOp::Lte => self.write("<="),
21111                QuantifiedOp::Gt => self.write(">"),
21112                QuantifiedOp::Gte => self.write(">="),
21113            }
21114            self.write_space();
21115        }
21116        self.write_keyword(name);
21117
21118        // If the child is a Subquery, it provides its own parens — output with space
21119        if matches!(&f.subquery, Expression::Subquery(_)) {
21120            self.write_space();
21121            self.generate_expression(&f.subquery)?;
21122        } else {
21123            self.write("(");
21124
21125            let is_statement = matches!(
21126                &f.subquery,
21127                Expression::Select(_)
21128                    | Expression::Union(_)
21129                    | Expression::Intersect(_)
21130                    | Expression::Except(_)
21131            );
21132
21133            if self.config.pretty && is_statement {
21134                self.write_newline();
21135                self.indent_level += 1;
21136                self.write_indent();
21137            }
21138            self.generate_expression(&f.subquery)?;
21139            if self.config.pretty && is_statement {
21140                self.write_newline();
21141                self.indent_level -= 1;
21142                self.write_indent();
21143            }
21144            self.write(")");
21145        }
21146        Ok(())
21147    }
21148
21149    fn generate_overlaps(&mut self, f: &OverlapsExpr) -> Result<()> {
21150        // Check if this is a simple binary form (this OVERLAPS expression)
21151        if let (Some(this), Some(expr)) = (&f.this, &f.expression) {
21152            self.generate_expression(this)?;
21153            self.write_space();
21154            self.write_keyword("OVERLAPS");
21155            self.write_space();
21156            self.generate_expression(expr)?;
21157        } else if let (Some(ls), Some(le), Some(rs), Some(re)) =
21158            (&f.left_start, &f.left_end, &f.right_start, &f.right_end)
21159        {
21160            // Full ANSI form: (a, b) OVERLAPS (c, d)
21161            self.write("(");
21162            self.generate_expression(ls)?;
21163            self.write(", ");
21164            self.generate_expression(le)?;
21165            self.write(")");
21166            self.write_space();
21167            self.write_keyword("OVERLAPS");
21168            self.write_space();
21169            self.write("(");
21170            self.generate_expression(rs)?;
21171            self.write(", ");
21172            self.generate_expression(re)?;
21173            self.write(")");
21174        }
21175        Ok(())
21176    }
21177
21178    // Type conversion generators
21179
21180    fn generate_try_cast(&mut self, cast: &Cast) -> Result<()> {
21181        use crate::dialects::DialectType;
21182
21183        // SingleStore uses !:> syntax for try cast
21184        if matches!(self.config.dialect, Some(DialectType::SingleStore)) {
21185            self.generate_expression(&cast.this)?;
21186            self.write(" !:> ");
21187            self.generate_data_type(&cast.to)?;
21188            return Ok(());
21189        }
21190
21191        // Teradata uses TRYCAST (no underscore)
21192        if matches!(self.config.dialect, Some(DialectType::Teradata)) {
21193            self.write_keyword("TRYCAST");
21194            self.write("(");
21195            self.generate_expression(&cast.this)?;
21196            self.write_space();
21197            self.write_keyword("AS");
21198            self.write_space();
21199            self.generate_data_type(&cast.to)?;
21200            self.write(")");
21201            return Ok(());
21202        }
21203
21204        // Dialects without TRY_CAST: generate as regular CAST
21205        let keyword = if matches!(
21206            self.config.dialect,
21207            Some(DialectType::Hive)
21208                | Some(DialectType::MySQL)
21209                | Some(DialectType::SQLite)
21210                | Some(DialectType::Oracle)
21211                | Some(DialectType::ClickHouse)
21212                | Some(DialectType::Redshift)
21213                | Some(DialectType::PostgreSQL)
21214                | Some(DialectType::StarRocks)
21215                | Some(DialectType::Doris)
21216        ) {
21217            "CAST"
21218        } else {
21219            "TRY_CAST"
21220        };
21221
21222        self.write_keyword(keyword);
21223        self.write("(");
21224        self.generate_expression(&cast.this)?;
21225        self.write_space();
21226        self.write_keyword("AS");
21227        self.write_space();
21228        self.generate_data_type(&cast.to)?;
21229
21230        // Output FORMAT clause if present
21231        if let Some(format) = &cast.format {
21232            self.write_space();
21233            self.write_keyword("FORMAT");
21234            self.write_space();
21235            self.generate_expression(format)?;
21236        }
21237
21238        self.write(")");
21239        Ok(())
21240    }
21241
21242    fn generate_safe_cast(&mut self, cast: &Cast) -> Result<()> {
21243        self.write_keyword("SAFE_CAST");
21244        self.write("(");
21245        self.generate_expression(&cast.this)?;
21246        self.write_space();
21247        self.write_keyword("AS");
21248        self.write_space();
21249        self.generate_data_type(&cast.to)?;
21250
21251        // Output FORMAT clause if present
21252        if let Some(format) = &cast.format {
21253            self.write_space();
21254            self.write_keyword("FORMAT");
21255            self.write_space();
21256            self.generate_expression(format)?;
21257        }
21258
21259        self.write(")");
21260        Ok(())
21261    }
21262
21263    // Array/struct/map access generators
21264
21265    fn generate_subscript(&mut self, s: &Subscript) -> Result<()> {
21266        // Wrap the base expression in parentheses when it uses arrow syntax (->)
21267        // which has lower precedence than bracket subscript ([]).
21268        // E.g., (t.v -> '$.a')[s.x] instead of t.v -> '$.a'[s.x]
21269        let needs_parens = matches!(&s.this, Expression::JsonExtract(ref f) if f.arrow_syntax);
21270        if needs_parens {
21271            self.write("(");
21272        }
21273        self.generate_expression(&s.this)?;
21274        if needs_parens {
21275            self.write(")");
21276        }
21277        self.write("[");
21278        self.generate_expression(&s.index)?;
21279        self.write("]");
21280        Ok(())
21281    }
21282
21283    fn generate_dot_access(&mut self, d: &DotAccess) -> Result<()> {
21284        self.generate_expression(&d.this)?;
21285        // Snowflake uses : (colon) for first-level struct/object field access on CAST/column expressions
21286        // e.g., CAST(col AS OBJECT(fld1 OBJECT(fld2 INT))):fld1.fld2
21287        let use_colon = matches!(self.config.dialect, Some(DialectType::Snowflake))
21288            && matches!(
21289                &d.this,
21290                Expression::Cast(_) | Expression::SafeCast(_) | Expression::TryCast(_)
21291            );
21292        if use_colon {
21293            self.write(":");
21294        } else {
21295            self.write(".");
21296        }
21297        self.generate_identifier(&d.field)
21298    }
21299
21300    fn generate_method_call(&mut self, m: &MethodCall) -> Result<()> {
21301        self.generate_expression(&m.this)?;
21302        self.write(".");
21303        // Method names after a dot should not be quoted based on reserved keywords
21304        // Only quote if explicitly marked as quoted in the AST
21305        if m.method.quoted {
21306            let q = self.config.identifier_quote;
21307            self.write(&format!("{}{}{}", q, m.method.name, q));
21308        } else {
21309            self.write(&m.method.name);
21310        }
21311        self.write("(");
21312        for (i, arg) in m.args.iter().enumerate() {
21313            if i > 0 {
21314                self.write(", ");
21315            }
21316            self.generate_expression(arg)?;
21317        }
21318        self.write(")");
21319        Ok(())
21320    }
21321
21322    fn generate_array_slice(&mut self, s: &ArraySlice) -> Result<()> {
21323        // Check if we need to wrap the inner expression in parentheses
21324        // JSON arrow expressions have lower precedence than array subscript
21325        let needs_parens = matches!(
21326            &s.this,
21327            Expression::JsonExtract(f) if f.arrow_syntax
21328        ) || matches!(
21329            &s.this,
21330            Expression::JsonExtractScalar(f) if f.arrow_syntax
21331        );
21332
21333        if needs_parens {
21334            self.write("(");
21335        }
21336        self.generate_expression(&s.this)?;
21337        if needs_parens {
21338            self.write(")");
21339        }
21340        self.write("[");
21341        if let Some(start) = &s.start {
21342            self.generate_expression(start)?;
21343        }
21344        self.write(":");
21345        if let Some(end) = &s.end {
21346            self.generate_expression(end)?;
21347        }
21348        self.write("]");
21349        Ok(())
21350    }
21351
21352    fn generate_binary_op(&mut self, op: &BinaryOp, operator: &str) -> Result<()> {
21353        // Generate left expression, but skip trailing comments if they're already in left_comments
21354        // to avoid duplication (comments are captured as both expr.trailing_comments
21355        // and BinaryOp.left_comments during parsing)
21356        match &op.left {
21357            Expression::Column(col) => {
21358                // Generate column with trailing comments but skip them if they're
21359                // already captured in BinaryOp.left_comments to avoid duplication
21360                if let Some(table) = &col.table {
21361                    self.generate_identifier(table)?;
21362                    self.write(".");
21363                }
21364                self.generate_identifier(&col.name)?;
21365                // Oracle-style join marker (+)
21366                if col.join_mark && self.config.supports_column_join_marks {
21367                    self.write(" (+)");
21368                }
21369                // Output column trailing comments if they're not already in left_comments
21370                if op.left_comments.is_empty() {
21371                    for comment in &col.trailing_comments {
21372                        self.write_space();
21373                        self.write_formatted_comment(comment);
21374                    }
21375                }
21376            }
21377            Expression::Add(inner_op)
21378            | Expression::Sub(inner_op)
21379            | Expression::Mul(inner_op)
21380            | Expression::Div(inner_op)
21381            | Expression::Concat(inner_op) => {
21382                // Generate binary op without its trailing comments
21383                self.generate_binary_op_no_trailing(inner_op, match &op.left {
21384                    Expression::Add(_) => "+",
21385                    Expression::Sub(_) => "-",
21386                    Expression::Mul(_) => "*",
21387                    Expression::Div(_) => "/",
21388                    Expression::Concat(_) => "||",
21389                    _ => unreachable!("op.left variant already matched by outer arm as Add/Sub/Mul/Div/Concat"),
21390                })?;
21391            }
21392            _ => {
21393                self.generate_expression(&op.left)?;
21394            }
21395        }
21396        // Output comments after left operand
21397        for comment in &op.left_comments {
21398            self.write_space();
21399            self.write_formatted_comment(comment);
21400        }
21401        if self.config.pretty
21402            && matches!(self.config.dialect, Some(DialectType::Snowflake))
21403            && (operator == "AND" || operator == "OR")
21404        {
21405            self.write_newline();
21406            self.write_indent();
21407            self.write_keyword(operator);
21408        } else {
21409            self.write_space();
21410            if operator.chars().all(|c| c.is_alphabetic()) {
21411                self.write_keyword(operator);
21412            } else {
21413                self.write(operator);
21414            }
21415        }
21416        // Output comments after operator (before right operand)
21417        for comment in &op.operator_comments {
21418            self.write_space();
21419            self.write_formatted_comment(comment);
21420        }
21421        self.write_space();
21422        self.generate_expression(&op.right)?;
21423        // Output trailing comments after right operand
21424        for comment in &op.trailing_comments {
21425            self.write_space();
21426            self.write_formatted_comment(comment);
21427        }
21428        Ok(())
21429    }
21430
21431    fn generate_connector_op(&mut self, op: &BinaryOp, connector: ConnectorOperator) -> Result<()> {
21432        let keyword = connector.keyword();
21433        let Some(terms) = self.flatten_connector_terms(op, connector) else {
21434            return self.generate_binary_op(op, keyword);
21435        };
21436
21437        self.generate_expression(terms[0])?;
21438        for term in terms.iter().skip(1) {
21439            if self.config.pretty && matches!(self.config.dialect, Some(DialectType::Snowflake)) {
21440                self.write_newline();
21441                self.write_indent();
21442                self.write_keyword(keyword);
21443            } else {
21444                self.write_space();
21445                self.write_keyword(keyword);
21446            }
21447            self.write_space();
21448            self.generate_expression(term)?;
21449        }
21450
21451        Ok(())
21452    }
21453
21454    fn flatten_connector_terms<'a>(
21455        &self,
21456        root: &'a BinaryOp,
21457        connector: ConnectorOperator,
21458    ) -> Option<Vec<&'a Expression>> {
21459        if !root.left_comments.is_empty()
21460            || !root.operator_comments.is_empty()
21461            || !root.trailing_comments.is_empty()
21462        {
21463            return None;
21464        }
21465
21466        let mut terms = Vec::new();
21467        let mut stack: Vec<&Expression> = vec![&root.right, &root.left];
21468
21469        while let Some(expr) = stack.pop() {
21470            match (connector, expr) {
21471                (ConnectorOperator::And, Expression::And(inner))
21472                    if inner.left_comments.is_empty()
21473                        && inner.operator_comments.is_empty()
21474                        && inner.trailing_comments.is_empty() =>
21475                {
21476                    stack.push(&inner.right);
21477                    stack.push(&inner.left);
21478                }
21479                (ConnectorOperator::Or, Expression::Or(inner))
21480                    if inner.left_comments.is_empty()
21481                        && inner.operator_comments.is_empty()
21482                        && inner.trailing_comments.is_empty() =>
21483                {
21484                    stack.push(&inner.right);
21485                    stack.push(&inner.left);
21486                }
21487                _ => terms.push(expr),
21488            }
21489        }
21490
21491        if terms.len() > 1 {
21492            Some(terms)
21493        } else {
21494            None
21495        }
21496    }
21497
21498    /// Generate LIKE/ILIKE operation with optional ESCAPE clause
21499    fn generate_like_op(&mut self, op: &LikeOp, operator: &str) -> Result<()> {
21500        self.generate_expression(&op.left)?;
21501        self.write_space();
21502        // Drill backtick-quotes ILIKE
21503        if operator == "ILIKE" && matches!(self.config.dialect, Some(DialectType::Drill)) {
21504            self.write("`ILIKE`");
21505        } else {
21506            self.write_keyword(operator);
21507        }
21508        if let Some(quantifier) = &op.quantifier {
21509            self.write_space();
21510            self.write_keyword(quantifier);
21511            // Match Python sqlglot behavior:
21512            // ANY + Paren (single value): no space → ILIKE ANY('%a%')
21513            // ANY + Tuple (multiple values): space → LIKE ANY ('a', 'b')
21514            // ALL + anything: always space → LIKE ALL ('%a%'), LIKE ALL ('a', 'b')
21515            let is_any =
21516                quantifier.eq_ignore_ascii_case("ANY") || quantifier.eq_ignore_ascii_case("SOME");
21517            if !(is_any && matches!(&op.right, Expression::Paren(_))) {
21518                self.write_space();
21519            }
21520        } else {
21521            self.write_space();
21522        }
21523        self.generate_expression(&op.right)?;
21524        if let Some(escape) = &op.escape {
21525            self.write_space();
21526            self.write_keyword("ESCAPE");
21527            self.write_space();
21528            self.generate_expression(escape)?;
21529        }
21530        Ok(())
21531    }
21532
21533    /// Generate null-safe equality
21534    /// MySQL uses <=>, other dialects use IS NOT DISTINCT FROM
21535    fn generate_null_safe_eq(&mut self, op: &BinaryOp) -> Result<()> {
21536        use crate::dialects::DialectType;
21537        self.generate_expression(&op.left)?;
21538        self.write_space();
21539        if matches!(self.config.dialect, Some(DialectType::MySQL)) {
21540            self.write("<=>");
21541        } else {
21542            self.write_keyword("IS NOT DISTINCT FROM");
21543        }
21544        self.write_space();
21545        self.generate_expression(&op.right)?;
21546        Ok(())
21547    }
21548
21549    /// Generate IS DISTINCT FROM (null-safe inequality)
21550    fn generate_null_safe_neq(&mut self, op: &BinaryOp) -> Result<()> {
21551        self.generate_expression(&op.left)?;
21552        self.write_space();
21553        self.write_keyword("IS DISTINCT FROM");
21554        self.write_space();
21555        self.generate_expression(&op.right)?;
21556        Ok(())
21557    }
21558
21559    /// Generate binary op without trailing comments (used when nested inside another binary op)
21560    fn generate_binary_op_no_trailing(&mut self, op: &BinaryOp, operator: &str) -> Result<()> {
21561        // Generate left expression, but skip trailing comments
21562        match &op.left {
21563            Expression::Column(col) => {
21564                if let Some(table) = &col.table {
21565                    self.generate_identifier(table)?;
21566                    self.write(".");
21567                }
21568                self.generate_identifier(&col.name)?;
21569                // Oracle-style join marker (+)
21570                if col.join_mark && self.config.supports_column_join_marks {
21571                    self.write(" (+)");
21572                }
21573            }
21574            Expression::Add(inner_op)
21575            | Expression::Sub(inner_op)
21576            | Expression::Mul(inner_op)
21577            | Expression::Div(inner_op)
21578            | Expression::Concat(inner_op) => {
21579                self.generate_binary_op_no_trailing(inner_op, match &op.left {
21580                    Expression::Add(_) => "+",
21581                    Expression::Sub(_) => "-",
21582                    Expression::Mul(_) => "*",
21583                    Expression::Div(_) => "/",
21584                    Expression::Concat(_) => "||",
21585                    _ => unreachable!("op.left variant already matched by outer arm as Add/Sub/Mul/Div/Concat"),
21586                })?;
21587            }
21588            _ => {
21589                self.generate_expression(&op.left)?;
21590            }
21591        }
21592        // Output left_comments
21593        for comment in &op.left_comments {
21594            self.write_space();
21595            self.write_formatted_comment(comment);
21596        }
21597        self.write_space();
21598        if operator.chars().all(|c| c.is_alphabetic()) {
21599            self.write_keyword(operator);
21600        } else {
21601            self.write(operator);
21602        }
21603        // Output operator_comments
21604        for comment in &op.operator_comments {
21605            self.write_space();
21606            self.write_formatted_comment(comment);
21607        }
21608        self.write_space();
21609        // Generate right expression, but skip trailing comments if it's a Column
21610        // (the parent's left_comments will output them)
21611        match &op.right {
21612            Expression::Column(col) => {
21613                if let Some(table) = &col.table {
21614                    self.generate_identifier(table)?;
21615                    self.write(".");
21616                }
21617                self.generate_identifier(&col.name)?;
21618                // Oracle-style join marker (+)
21619                if col.join_mark && self.config.supports_column_join_marks {
21620                    self.write(" (+)");
21621                }
21622            }
21623            _ => {
21624                self.generate_expression(&op.right)?;
21625            }
21626        }
21627        // Skip trailing_comments - parent will handle them via its left_comments
21628        Ok(())
21629    }
21630
21631    fn generate_unary_op(&mut self, op: &UnaryOp, operator: &str) -> Result<()> {
21632        if operator.chars().all(|c| c.is_alphabetic()) {
21633            self.write_keyword(operator);
21634            self.write_space();
21635        } else {
21636            self.write(operator);
21637            // Add space between consecutive unary operators (e.g., "- -5" not "--5")
21638            if matches!(&op.this, Expression::Neg(_) | Expression::BitwiseNot(_)) {
21639                self.write_space();
21640            }
21641        }
21642        self.generate_expression(&op.this)
21643    }
21644
21645    fn generate_in(&mut self, in_expr: &In) -> Result<()> {
21646        // Generic mode supports two styles for negated IN:
21647        // - Prefix: NOT a IN (...)
21648        // - Infix:  a NOT IN (...)
21649        let is_generic =
21650            self.config.dialect.is_none() || self.config.dialect == Some(DialectType::Generic);
21651        let use_prefix_not =
21652            in_expr.not && is_generic && self.config.not_in_style == NotInStyle::Prefix;
21653        if use_prefix_not {
21654            self.write_keyword("NOT");
21655            self.write_space();
21656        }
21657        self.generate_expression(&in_expr.this)?;
21658        if in_expr.global {
21659            self.write_space();
21660            self.write_keyword("GLOBAL");
21661        }
21662        if in_expr.not && !use_prefix_not {
21663            self.write_space();
21664            self.write_keyword("NOT");
21665        }
21666        self.write_space();
21667        self.write_keyword("IN");
21668
21669        // BigQuery: IN UNNEST(expr)
21670        if let Some(unnest_expr) = &in_expr.unnest {
21671            self.write_space();
21672            self.write_keyword("UNNEST");
21673            self.write("(");
21674            self.generate_expression(unnest_expr)?;
21675            self.write(")");
21676            return Ok(());
21677        }
21678
21679        if let Some(query) = &in_expr.query {
21680            // Check if this is a bare identifier (PIVOT FOR foo IN y_enum)
21681            // vs a subquery (col IN (SELECT ...))
21682            let is_bare = in_expr.expressions.is_empty()
21683                && !matches!(
21684                    query,
21685                    Expression::Select(_)
21686                        | Expression::Union(_)
21687                        | Expression::Intersect(_)
21688                        | Expression::Except(_)
21689                        | Expression::Subquery(_)
21690                );
21691            if is_bare {
21692                // Bare identifier: no parentheses
21693                self.write_space();
21694                self.generate_expression(query)?;
21695            } else {
21696                // Subquery: with parentheses
21697                self.write(" (");
21698                let is_statement = matches!(
21699                    query,
21700                    Expression::Select(_)
21701                        | Expression::Union(_)
21702                        | Expression::Intersect(_)
21703                        | Expression::Except(_)
21704                        | Expression::Subquery(_)
21705                );
21706                if self.config.pretty && is_statement {
21707                    self.write_newline();
21708                    self.indent_level += 1;
21709                    self.write_indent();
21710                }
21711                self.generate_expression(query)?;
21712                if self.config.pretty && is_statement {
21713                    self.write_newline();
21714                    self.indent_level -= 1;
21715                    self.write_indent();
21716                }
21717                self.write(")");
21718            }
21719        } else {
21720            // DuckDB: IN without parentheses for single expression that is NOT a literal
21721            // (array/list membership like 'red' IN tbl.flags)
21722            // ClickHouse: IN without parentheses for single non-array expressions
21723            let is_duckdb = matches!(
21724                self.config.dialect,
21725                Some(crate::dialects::DialectType::DuckDB)
21726            );
21727            let is_clickhouse = matches!(
21728                self.config.dialect,
21729                Some(crate::dialects::DialectType::ClickHouse)
21730            );
21731            let single_expr = in_expr.expressions.len() == 1;
21732            if is_clickhouse && single_expr {
21733                if let Expression::Array(arr) = &in_expr.expressions[0] {
21734                    // ClickHouse: x IN [1, 2] -> x IN (1, 2)
21735                    self.write(" (");
21736                    for (i, expr) in arr.expressions.iter().enumerate() {
21737                        if i > 0 {
21738                            self.write(", ");
21739                        }
21740                        self.generate_expression(expr)?;
21741                    }
21742                    self.write(")");
21743                } else {
21744                    self.write_space();
21745                    self.generate_expression(&in_expr.expressions[0])?;
21746                }
21747            } else {
21748                let is_bare_ref = single_expr
21749                    && matches!(
21750                        &in_expr.expressions[0],
21751                        Expression::Column(_) | Expression::Identifier(_) | Expression::Dot(_)
21752                    );
21753                if (is_duckdb && is_bare_ref) || (in_expr.is_field && single_expr) {
21754                    // Bare field reference (no parens in source): IN identifier
21755                    // Also DuckDB: IN without parentheses for array/list membership
21756                    self.write_space();
21757                    self.generate_expression(&in_expr.expressions[0])?;
21758                } else {
21759                    // Standard IN (list)
21760                    self.write(" (");
21761                    for (i, expr) in in_expr.expressions.iter().enumerate() {
21762                        if i > 0 {
21763                            self.write(", ");
21764                        }
21765                        self.generate_expression(expr)?;
21766                    }
21767                    self.write(")");
21768                }
21769            }
21770        }
21771
21772        Ok(())
21773    }
21774
21775    fn generate_between(&mut self, between: &Between) -> Result<()> {
21776        // Generic mode: normalize NOT BETWEEN to prefix form: NOT a BETWEEN b AND c
21777        let use_prefix_not = between.not
21778            && (self.config.dialect.is_none() || self.config.dialect == Some(DialectType::Generic));
21779        if use_prefix_not {
21780            self.write_keyword("NOT");
21781            self.write_space();
21782        }
21783        self.generate_expression(&between.this)?;
21784        if between.not && !use_prefix_not {
21785            self.write_space();
21786            self.write_keyword("NOT");
21787        }
21788        self.write_space();
21789        self.write_keyword("BETWEEN");
21790        // Emit SYMMETRIC/ASYMMETRIC if present
21791        if let Some(sym) = between.symmetric {
21792            if sym {
21793                self.write(" SYMMETRIC");
21794            } else {
21795                self.write(" ASYMMETRIC");
21796            }
21797        }
21798        self.write_space();
21799        self.generate_expression(&between.low)?;
21800        self.write_space();
21801        self.write_keyword("AND");
21802        self.write_space();
21803        self.generate_expression(&between.high)
21804    }
21805
21806    fn generate_is_null(&mut self, is_null: &IsNull) -> Result<()> {
21807        // Generic mode: normalize IS NOT NULL to prefix form: NOT x IS NULL
21808        let use_prefix_not = is_null.not
21809            && (self.config.dialect.is_none()
21810                || self.config.dialect == Some(DialectType::Generic)
21811                || is_null.postfix_form);
21812        if use_prefix_not {
21813            // NOT x IS NULL (generic normalization and NOTNULL postfix form)
21814            self.write_keyword("NOT");
21815            self.write_space();
21816            self.generate_expression(&is_null.this)?;
21817            self.write_space();
21818            self.write_keyword("IS");
21819            self.write_space();
21820            self.write_keyword("NULL");
21821        } else {
21822            self.generate_expression(&is_null.this)?;
21823            self.write_space();
21824            self.write_keyword("IS");
21825            if is_null.not {
21826                self.write_space();
21827                self.write_keyword("NOT");
21828            }
21829            self.write_space();
21830            self.write_keyword("NULL");
21831        }
21832        Ok(())
21833    }
21834
21835    fn generate_is_true(&mut self, is_true: &IsTrueFalse) -> Result<()> {
21836        self.generate_expression(&is_true.this)?;
21837        self.write_space();
21838        self.write_keyword("IS");
21839        if is_true.not {
21840            self.write_space();
21841            self.write_keyword("NOT");
21842        }
21843        self.write_space();
21844        self.write_keyword("TRUE");
21845        Ok(())
21846    }
21847
21848    fn generate_is_false(&mut self, is_false: &IsTrueFalse) -> Result<()> {
21849        self.generate_expression(&is_false.this)?;
21850        self.write_space();
21851        self.write_keyword("IS");
21852        if is_false.not {
21853            self.write_space();
21854            self.write_keyword("NOT");
21855        }
21856        self.write_space();
21857        self.write_keyword("FALSE");
21858        Ok(())
21859    }
21860
21861    fn generate_is_json(&mut self, is_json: &IsJson) -> Result<()> {
21862        self.generate_expression(&is_json.this)?;
21863        self.write_space();
21864        self.write_keyword("IS");
21865        if is_json.negated {
21866            self.write_space();
21867            self.write_keyword("NOT");
21868        }
21869        self.write_space();
21870        self.write_keyword("JSON");
21871
21872        // Output JSON type if specified (VALUE, SCALAR, OBJECT, ARRAY)
21873        if let Some(ref json_type) = is_json.json_type {
21874            self.write_space();
21875            self.write_keyword(json_type);
21876        }
21877
21878        // Output key uniqueness constraint if specified
21879        match &is_json.unique_keys {
21880            Some(JsonUniqueKeys::With) => {
21881                self.write_space();
21882                self.write_keyword("WITH UNIQUE KEYS");
21883            }
21884            Some(JsonUniqueKeys::Without) => {
21885                self.write_space();
21886                self.write_keyword("WITHOUT UNIQUE KEYS");
21887            }
21888            Some(JsonUniqueKeys::Shorthand) => {
21889                self.write_space();
21890                self.write_keyword("UNIQUE KEYS");
21891            }
21892            None => {}
21893        }
21894
21895        Ok(())
21896    }
21897
21898    fn generate_is(&mut self, is_expr: &BinaryOp) -> Result<()> {
21899        self.generate_expression(&is_expr.left)?;
21900        self.write_space();
21901        self.write_keyword("IS");
21902        self.write_space();
21903        self.generate_expression(&is_expr.right)
21904    }
21905
21906    fn generate_exists(&mut self, exists: &Exists) -> Result<()> {
21907        if exists.not {
21908            self.write_keyword("NOT");
21909            self.write_space();
21910        }
21911        self.write_keyword("EXISTS");
21912        self.write("(");
21913        let is_statement = matches!(
21914            &exists.this,
21915            Expression::Select(_)
21916                | Expression::Union(_)
21917                | Expression::Intersect(_)
21918                | Expression::Except(_)
21919        );
21920        if self.config.pretty && is_statement {
21921            self.write_newline();
21922            self.indent_level += 1;
21923            self.write_indent();
21924            self.generate_expression(&exists.this)?;
21925            self.write_newline();
21926            self.indent_level -= 1;
21927            self.write_indent();
21928            self.write(")");
21929        } else {
21930            self.generate_expression(&exists.this)?;
21931            self.write(")");
21932        }
21933        Ok(())
21934    }
21935
21936    fn generate_member_of(&mut self, op: &BinaryOp) -> Result<()> {
21937        self.generate_expression(&op.left)?;
21938        self.write_space();
21939        self.write_keyword("MEMBER OF");
21940        self.write("(");
21941        self.generate_expression(&op.right)?;
21942        self.write(")");
21943        Ok(())
21944    }
21945
21946    fn generate_subquery(&mut self, subquery: &Subquery) -> Result<()> {
21947        if subquery.lateral {
21948            self.write_keyword("LATERAL");
21949            self.write_space();
21950        }
21951
21952        // If the inner expression is a Paren wrapping a statement, don't add extra parentheses
21953        // This handles cases like ((SELECT 1)) LIMIT 1 where we wrap Paren in Subquery
21954        // to carry the LIMIT modifier without adding more parens
21955        let skip_outer_parens = if let Expression::Paren(ref p) = &subquery.this {
21956            matches!(
21957                &p.this,
21958                Expression::Select(_)
21959                    | Expression::Union(_)
21960                    | Expression::Intersect(_)
21961                    | Expression::Except(_)
21962                    | Expression::Subquery(_)
21963            )
21964        } else {
21965            false
21966        };
21967
21968        // Check if inner expression is a statement for pretty formatting
21969        let is_statement = matches!(
21970            &subquery.this,
21971            Expression::Select(_)
21972                | Expression::Union(_)
21973                | Expression::Intersect(_)
21974                | Expression::Except(_)
21975                | Expression::Merge(_)
21976        );
21977
21978        if !skip_outer_parens {
21979            self.write("(");
21980            if self.config.pretty && is_statement {
21981                self.write_newline();
21982                self.indent_level += 1;
21983                self.write_indent();
21984            }
21985        }
21986        self.generate_expression(&subquery.this)?;
21987
21988        // Generate ORDER BY, LIMIT, OFFSET based on modifiers_inside flag
21989        if subquery.modifiers_inside {
21990            // Generate modifiers INSIDE the parentheses: (SELECT ... LIMIT 1)
21991            if let Some(order_by) = &subquery.order_by {
21992                self.write_space();
21993                self.write_keyword("ORDER BY");
21994                self.write_space();
21995                for (i, ord) in order_by.expressions.iter().enumerate() {
21996                    if i > 0 {
21997                        self.write(", ");
21998                    }
21999                    self.generate_ordered(ord)?;
22000                }
22001            }
22002
22003            if let Some(limit) = &subquery.limit {
22004                self.write_space();
22005                self.write_keyword("LIMIT");
22006                self.write_space();
22007                self.generate_expression(&limit.this)?;
22008                if limit.percent {
22009                    self.write_space();
22010                    self.write_keyword("PERCENT");
22011                }
22012            }
22013
22014            if let Some(offset) = &subquery.offset {
22015                self.write_space();
22016                self.write_keyword("OFFSET");
22017                self.write_space();
22018                self.generate_expression(&offset.this)?;
22019            }
22020        }
22021
22022        if !skip_outer_parens {
22023            if self.config.pretty && is_statement {
22024                self.write_newline();
22025                self.indent_level -= 1;
22026                self.write_indent();
22027            }
22028            self.write(")");
22029        }
22030
22031        // Generate modifiers OUTSIDE the parentheses: (SELECT ...) LIMIT 1
22032        if !subquery.modifiers_inside {
22033            if let Some(order_by) = &subquery.order_by {
22034                self.write_space();
22035                self.write_keyword("ORDER BY");
22036                self.write_space();
22037                for (i, ord) in order_by.expressions.iter().enumerate() {
22038                    if i > 0 {
22039                        self.write(", ");
22040                    }
22041                    self.generate_ordered(ord)?;
22042                }
22043            }
22044
22045            if let Some(limit) = &subquery.limit {
22046                self.write_space();
22047                self.write_keyword("LIMIT");
22048                self.write_space();
22049                self.generate_expression(&limit.this)?;
22050                if limit.percent {
22051                    self.write_space();
22052                    self.write_keyword("PERCENT");
22053                }
22054            }
22055
22056            if let Some(offset) = &subquery.offset {
22057                self.write_space();
22058                self.write_keyword("OFFSET");
22059                self.write_space();
22060                self.generate_expression(&offset.this)?;
22061            }
22062
22063            // Generate DISTRIBUTE BY (Hive/Spark)
22064            if let Some(distribute_by) = &subquery.distribute_by {
22065                self.write_space();
22066                self.write_keyword("DISTRIBUTE BY");
22067                self.write_space();
22068                for (i, expr) in distribute_by.expressions.iter().enumerate() {
22069                    if i > 0 {
22070                        self.write(", ");
22071                    }
22072                    self.generate_expression(expr)?;
22073                }
22074            }
22075
22076            // Generate SORT BY (Hive/Spark)
22077            if let Some(sort_by) = &subquery.sort_by {
22078                self.write_space();
22079                self.write_keyword("SORT BY");
22080                self.write_space();
22081                for (i, ord) in sort_by.expressions.iter().enumerate() {
22082                    if i > 0 {
22083                        self.write(", ");
22084                    }
22085                    self.generate_ordered(ord)?;
22086                }
22087            }
22088
22089            // Generate CLUSTER BY (Hive/Spark)
22090            if let Some(cluster_by) = &subquery.cluster_by {
22091                self.write_space();
22092                self.write_keyword("CLUSTER BY");
22093                self.write_space();
22094                for (i, ord) in cluster_by.expressions.iter().enumerate() {
22095                    if i > 0 {
22096                        self.write(", ");
22097                    }
22098                    self.generate_ordered(ord)?;
22099                }
22100            }
22101        }
22102
22103        if let Some(alias) = &subquery.alias {
22104            self.write_space();
22105            // Oracle doesn't use AS for subquery aliases
22106            let skip_as = matches!(
22107                self.config.dialect,
22108                Some(crate::dialects::DialectType::Oracle)
22109            );
22110            if !skip_as {
22111                self.write_keyword("AS");
22112                self.write_space();
22113            }
22114            self.generate_identifier(alias)?;
22115            if !subquery.column_aliases.is_empty() {
22116                self.write("(");
22117                for (i, col) in subquery.column_aliases.iter().enumerate() {
22118                    if i > 0 {
22119                        self.write(", ");
22120                    }
22121                    self.generate_identifier(col)?;
22122                }
22123                self.write(")");
22124            }
22125        }
22126        // Output trailing comments
22127        for comment in &subquery.trailing_comments {
22128            self.write(" ");
22129            self.write_formatted_comment(comment);
22130        }
22131        Ok(())
22132    }
22133
22134    fn generate_pivot(&mut self, pivot: &Pivot) -> Result<()> {
22135        // Generate WITH clause if present
22136        if let Some(ref with) = pivot.with {
22137            self.generate_with(with)?;
22138            self.write_space();
22139        }
22140
22141        let direction = if pivot.unpivot { "UNPIVOT" } else { "PIVOT" };
22142
22143        // Check for Redshift UNPIVOT in FROM clause:
22144        // UNPIVOT expr [AS val AT attr]
22145        // This is when unpivot=true, expressions is empty, fields is empty, and this is not Null
22146        let is_redshift_unpivot = pivot.unpivot
22147            && pivot.expressions.is_empty()
22148            && pivot.fields.is_empty()
22149            && pivot.using.is_empty()
22150            && pivot.into.is_none()
22151            && !matches!(&pivot.this, Expression::Null(_));
22152
22153        if is_redshift_unpivot {
22154            // Redshift UNPIVOT: UNPIVOT expr [AS alias]
22155            self.write_keyword("UNPIVOT");
22156            self.write_space();
22157            self.generate_expression(&pivot.this)?;
22158            // Alias - for Redshift it can be "val AT attr" format
22159            if let Some(alias) = &pivot.alias {
22160                self.write_space();
22161                self.write_keyword("AS");
22162                self.write_space();
22163                // The alias might contain " AT " for the attr part
22164                self.write(&alias.name);
22165            }
22166            return Ok(());
22167        }
22168
22169        // Check if this is a DuckDB simplified pivot (has `using` or `into`, or no `fields`)
22170        let is_simplified = !pivot.using.is_empty()
22171            || pivot.into.is_some()
22172            || (pivot.fields.is_empty()
22173                && !pivot.expressions.is_empty()
22174                && !matches!(&pivot.this, Expression::Null(_)));
22175
22176        if is_simplified {
22177            // DuckDB simplified syntax:
22178            //   PIVOT table ON cols [IN (...)] USING agg [AS alias], ... [GROUP BY ...]
22179            //   UNPIVOT table ON cols INTO NAME col VALUE col
22180            self.write_keyword(direction);
22181            self.write_space();
22182            self.generate_expression(&pivot.this)?;
22183
22184            if !pivot.expressions.is_empty() {
22185                self.write_space();
22186                self.write_keyword("ON");
22187                self.write_space();
22188                for (i, expr) in pivot.expressions.iter().enumerate() {
22189                    if i > 0 {
22190                        self.write(", ");
22191                    }
22192                    self.generate_expression(expr)?;
22193                }
22194            }
22195
22196            // INTO (for UNPIVOT)
22197            if let Some(into) = &pivot.into {
22198                self.write_space();
22199                self.write_keyword("INTO");
22200                self.write_space();
22201                self.generate_expression(into)?;
22202            }
22203
22204            // USING (for PIVOT)
22205            if !pivot.using.is_empty() {
22206                self.write_space();
22207                self.write_keyword("USING");
22208                self.write_space();
22209                for (i, expr) in pivot.using.iter().enumerate() {
22210                    if i > 0 {
22211                        self.write(", ");
22212                    }
22213                    self.generate_expression(expr)?;
22214                }
22215            }
22216
22217            // GROUP BY
22218            if let Some(group) = &pivot.group {
22219                self.write_space();
22220                self.generate_expression(group)?;
22221            }
22222        } else {
22223            // Standard syntax:
22224            //   table PIVOT(agg [AS alias], ... FOR col IN (val [AS alias], ...) [GROUP BY ...])
22225            //   table UNPIVOT(value_col FOR name_col IN (col1, col2, ...))
22226            // Only output the table expression if it's not a Null (null is used when PIVOT comes after JOIN ON)
22227            if !matches!(&pivot.this, Expression::Null(_)) {
22228                self.generate_expression(&pivot.this)?;
22229                self.write_space();
22230            }
22231            self.write_keyword(direction);
22232            self.write("(");
22233
22234            // Aggregation expressions
22235            for (i, expr) in pivot.expressions.iter().enumerate() {
22236                if i > 0 {
22237                    self.write(", ");
22238                }
22239                self.generate_expression(expr)?;
22240            }
22241
22242            // FOR...IN fields
22243            if !pivot.fields.is_empty() {
22244                if !pivot.expressions.is_empty() {
22245                    self.write_space();
22246                }
22247                self.write_keyword("FOR");
22248                self.write_space();
22249                for (i, field) in pivot.fields.iter().enumerate() {
22250                    if i > 0 {
22251                        self.write_space();
22252                    }
22253                    // field is an In expression: column IN (values)
22254                    self.generate_expression(field)?;
22255                }
22256            }
22257
22258            // DEFAULT ON NULL
22259            if let Some(default_val) = &pivot.default_on_null {
22260                self.write_space();
22261                self.write_keyword("DEFAULT ON NULL");
22262                self.write(" (");
22263                self.generate_expression(default_val)?;
22264                self.write(")");
22265            }
22266
22267            // GROUP BY inside PIVOT parens
22268            if let Some(group) = &pivot.group {
22269                self.write_space();
22270                self.generate_expression(group)?;
22271            }
22272
22273            self.write(")");
22274        }
22275
22276        // Alias
22277        if let Some(alias) = &pivot.alias {
22278            self.write_space();
22279            self.write_keyword("AS");
22280            self.write_space();
22281            self.generate_identifier(alias)?;
22282        }
22283
22284        Ok(())
22285    }
22286
22287    fn generate_unpivot(&mut self, unpivot: &Unpivot) -> Result<()> {
22288        self.generate_expression(&unpivot.this)?;
22289        self.write_space();
22290        self.write_keyword("UNPIVOT");
22291        // Output INCLUDE NULLS or EXCLUDE NULLS if specified
22292        if let Some(include) = unpivot.include_nulls {
22293            self.write_space();
22294            if include {
22295                self.write_keyword("INCLUDE NULLS");
22296            } else {
22297                self.write_keyword("EXCLUDE NULLS");
22298            }
22299            self.write_space();
22300        }
22301        self.write("(");
22302        if unpivot.value_column_parenthesized {
22303            self.write("(");
22304        }
22305        self.generate_identifier(&unpivot.value_column)?;
22306        // Output additional value columns if present
22307        for extra_col in &unpivot.extra_value_columns {
22308            self.write(", ");
22309            self.generate_identifier(extra_col)?;
22310        }
22311        if unpivot.value_column_parenthesized {
22312            self.write(")");
22313        }
22314        self.write_space();
22315        self.write_keyword("FOR");
22316        self.write_space();
22317        self.generate_identifier(&unpivot.name_column)?;
22318        self.write_space();
22319        self.write_keyword("IN");
22320        self.write(" (");
22321        for (i, col) in unpivot.columns.iter().enumerate() {
22322            if i > 0 {
22323                self.write(", ");
22324            }
22325            self.generate_expression(col)?;
22326        }
22327        self.write("))");
22328        if let Some(alias) = &unpivot.alias {
22329            self.write_space();
22330            self.write_keyword("AS");
22331            self.write_space();
22332            self.generate_identifier(alias)?;
22333        }
22334        Ok(())
22335    }
22336
22337    fn generate_values(&mut self, values: &Values) -> Result<()> {
22338        self.write_keyword("VALUES");
22339        for (i, row) in values.expressions.iter().enumerate() {
22340            if i > 0 {
22341                self.write(",");
22342            }
22343            self.write(" (");
22344            for (j, expr) in row.expressions.iter().enumerate() {
22345                if j > 0 {
22346                    self.write(", ");
22347                }
22348                self.generate_expression(expr)?;
22349            }
22350            self.write(")");
22351        }
22352        if let Some(alias) = &values.alias {
22353            self.write_space();
22354            self.write_keyword("AS");
22355            self.write_space();
22356            self.generate_identifier(alias)?;
22357            if !values.column_aliases.is_empty() {
22358                self.write("(");
22359                for (i, col) in values.column_aliases.iter().enumerate() {
22360                    if i > 0 {
22361                        self.write(", ");
22362                    }
22363                    self.generate_identifier(col)?;
22364                }
22365                self.write(")");
22366            }
22367        }
22368        Ok(())
22369    }
22370
22371    fn generate_array(&mut self, arr: &Array) -> Result<()> {
22372        // Apply struct name inheritance for target dialects that need it
22373        let needs_inheritance = matches!(
22374            self.config.dialect,
22375            Some(DialectType::DuckDB)
22376                | Some(DialectType::Spark)
22377                | Some(DialectType::Databricks)
22378                | Some(DialectType::Hive)
22379                | Some(DialectType::Snowflake)
22380                | Some(DialectType::Presto)
22381                | Some(DialectType::Trino)
22382        );
22383        let propagated: Vec<Expression>;
22384        let expressions = if needs_inheritance && arr.expressions.len() > 1 {
22385            propagated = Self::inherit_struct_field_names(&arr.expressions);
22386            &propagated
22387        } else {
22388            &arr.expressions
22389        };
22390
22391        // Generic mode: ARRAY(1, 2, 3) with parentheses
22392        // Dialect mode: ARRAY[1, 2, 3] with brackets (or just [1, 2, 3] if array_bracket_only)
22393        let use_parens =
22394            self.config.dialect.is_none() || self.config.dialect == Some(DialectType::Generic);
22395        if !self.config.array_bracket_only {
22396            self.write_keyword("ARRAY");
22397        }
22398        if use_parens {
22399            self.write("(");
22400        } else {
22401            self.write("[");
22402        }
22403        for (i, expr) in expressions.iter().enumerate() {
22404            if i > 0 {
22405                self.write(", ");
22406            }
22407            self.generate_expression(expr)?;
22408        }
22409        if use_parens {
22410            self.write(")");
22411        } else {
22412            self.write("]");
22413        }
22414        Ok(())
22415    }
22416
22417    fn generate_tuple(&mut self, tuple: &Tuple) -> Result<()> {
22418        // Special case: Tuple(function/expr, TableAlias) pattern for table functions with typed aliases
22419        // Used for PostgreSQL functions like JSON_TO_RECORDSET: FUNC(args) AS alias(col1 type1, col2 type2)
22420        if tuple.expressions.len() == 2 {
22421            if let Expression::TableAlias(_) = &tuple.expressions[1] {
22422                // First element is the function/expression, second is the TableAlias
22423                self.generate_expression(&tuple.expressions[0])?;
22424                self.write_space();
22425                self.write_keyword("AS");
22426                self.write_space();
22427                self.generate_expression(&tuple.expressions[1])?;
22428                return Ok(());
22429            }
22430        }
22431
22432        // In pretty mode, format long tuples with each element on a new line
22433        // Only expand if total width exceeds threshold
22434        let expand_tuple = if self.config.pretty && tuple.expressions.len() > 1 {
22435            let mut expr_strings: Vec<String> = Vec::with_capacity(tuple.expressions.len());
22436            for expr in &tuple.expressions {
22437                expr_strings.push(self.generate_to_string(expr)?);
22438            }
22439            self.too_wide(&expr_strings)
22440        } else {
22441            false
22442        };
22443
22444        if expand_tuple {
22445            self.write("(");
22446            self.write_newline();
22447            self.indent_level += 1;
22448            for (i, expr) in tuple.expressions.iter().enumerate() {
22449                if i > 0 {
22450                    self.write(",");
22451                    self.write_newline();
22452                }
22453                self.write_indent();
22454                self.generate_expression(expr)?;
22455            }
22456            self.indent_level -= 1;
22457            self.write_newline();
22458            self.write_indent();
22459            self.write(")");
22460        } else {
22461            self.write("(");
22462            for (i, expr) in tuple.expressions.iter().enumerate() {
22463                if i > 0 {
22464                    self.write(", ");
22465                }
22466                self.generate_expression(expr)?;
22467            }
22468            self.write(")");
22469        }
22470        Ok(())
22471    }
22472
22473    fn generate_pipe_operator(&mut self, pipe: &PipeOperator) -> Result<()> {
22474        self.generate_expression(&pipe.this)?;
22475        self.write(" |> ");
22476        self.generate_expression(&pipe.expression)?;
22477        Ok(())
22478    }
22479
22480    fn generate_ordered(&mut self, ordered: &Ordered) -> Result<()> {
22481        self.generate_expression(&ordered.this)?;
22482        if ordered.desc {
22483            self.write_space();
22484            self.write_keyword("DESC");
22485        } else if ordered.explicit_asc {
22486            self.write_space();
22487            self.write_keyword("ASC");
22488        }
22489        if let Some(nulls_first) = ordered.nulls_first {
22490            // Determine if we should skip outputting NULLS FIRST/LAST when it's the default
22491            // for the dialect. Different dialects have different NULL ordering defaults:
22492            //
22493            // nulls_are_large (Oracle, Postgres, Snowflake, etc.):
22494            //   - ASC: NULLS LAST is default (omit NULLS LAST for ASC)
22495            //   - DESC: NULLS FIRST is default (omit NULLS FIRST for DESC)
22496            //
22497            // nulls_are_small (Spark, Hive, BigQuery, most others):
22498            //   - ASC: NULLS FIRST is default
22499            //   - DESC: NULLS LAST is default
22500            //
22501            // nulls_are_last (DuckDB, Presto, Trino, Dremio, etc.):
22502            //   - NULLS LAST is always the default regardless of sort direction
22503            let is_asc = !ordered.desc;
22504            let is_nulls_are_large = matches!(
22505                self.config.dialect,
22506                Some(DialectType::Oracle)
22507                    | Some(DialectType::PostgreSQL)
22508                    | Some(DialectType::Redshift)
22509                    | Some(DialectType::Snowflake)
22510            );
22511            let is_nulls_are_last = matches!(
22512                self.config.dialect,
22513                Some(DialectType::Dremio)
22514                    | Some(DialectType::DuckDB)
22515                    | Some(DialectType::Presto)
22516                    | Some(DialectType::Trino)
22517                    | Some(DialectType::Athena)
22518                    | Some(DialectType::ClickHouse)
22519                    | Some(DialectType::Drill)
22520                    | Some(DialectType::Exasol)
22521            );
22522
22523            // Check if the NULLS ordering matches the default for this dialect
22524            let is_default_nulls = if is_nulls_are_large {
22525                // For nulls_are_large: ASC + NULLS LAST or DESC + NULLS FIRST is default
22526                (is_asc && !nulls_first) || (!is_asc && nulls_first)
22527            } else if is_nulls_are_last {
22528                // For nulls_are_last: NULLS LAST is always default
22529                !nulls_first
22530            } else {
22531                false
22532            };
22533
22534            if !is_default_nulls {
22535                self.write_space();
22536                self.write_keyword("NULLS");
22537                self.write_space();
22538                self.write_keyword(if nulls_first { "FIRST" } else { "LAST" });
22539            }
22540        }
22541        // WITH FILL clause (ClickHouse)
22542        if let Some(ref with_fill) = ordered.with_fill {
22543            self.write_space();
22544            self.generate_with_fill(with_fill)?;
22545        }
22546        Ok(())
22547    }
22548
22549    /// Write a ClickHouse type string, wrapping in Nullable unless in map key context.
22550    fn write_clickhouse_type(&mut self, type_str: &str) {
22551        if self.clickhouse_nullable_depth < 0 {
22552            // Map key context: don't wrap in Nullable
22553            self.write(type_str);
22554        } else {
22555            self.write(&format!("Nullable({})", type_str));
22556        }
22557    }
22558
22559    fn generate_data_type(&mut self, dt: &DataType) -> Result<()> {
22560        use crate::dialects::DialectType;
22561
22562        match dt {
22563            DataType::Boolean => {
22564                // Dialect-specific boolean type mappings
22565                match self.config.dialect {
22566                    Some(DialectType::TSQL) => self.write_keyword("BIT"),
22567                    Some(DialectType::MySQL) => self.write_keyword("BOOLEAN"), // alias for TINYINT(1)
22568                    Some(DialectType::Oracle) => {
22569                        // Oracle 23c+ supports BOOLEAN, older versions use NUMBER(1)
22570                        self.write_keyword("NUMBER(1)")
22571                    }
22572                    Some(DialectType::ClickHouse) => self.write("Bool"), // ClickHouse uses Bool (case-sensitive)
22573                    _ => self.write_keyword("BOOLEAN"),
22574                }
22575            }
22576            DataType::TinyInt { length } => {
22577                // PostgreSQL, Oracle, and Exasol don't have TINYINT, use SMALLINT
22578                // Dremio maps TINYINT to INT
22579                // ClickHouse maps TINYINT to Int8
22580                match self.config.dialect {
22581                    Some(DialectType::PostgreSQL)
22582                    | Some(DialectType::Redshift)
22583                    | Some(DialectType::Oracle)
22584                    | Some(DialectType::Exasol) => {
22585                        self.write_keyword("SMALLINT");
22586                    }
22587                    Some(DialectType::Teradata) => {
22588                        // Teradata uses BYTEINT for smallest integer
22589                        self.write_keyword("BYTEINT");
22590                    }
22591                    Some(DialectType::Dremio) => {
22592                        // Dremio maps TINYINT to INT
22593                        self.write_keyword("INT");
22594                    }
22595                    Some(DialectType::ClickHouse) => {
22596                        self.write_clickhouse_type("Int8");
22597                    }
22598                    _ => {
22599                        self.write_keyword("TINYINT");
22600                    }
22601                }
22602                if let Some(n) = length {
22603                    if !matches!(
22604                        self.config.dialect,
22605                        Some(DialectType::Dremio) | Some(DialectType::ClickHouse)
22606                    ) {
22607                        self.write(&format!("({})", n));
22608                    }
22609                }
22610            }
22611            DataType::SmallInt { length } => {
22612                // Dremio maps SMALLINT to INT, SQLite/Drill maps SMALLINT to INTEGER
22613                match self.config.dialect {
22614                    Some(DialectType::Dremio) => {
22615                        self.write_keyword("INT");
22616                    }
22617                    Some(DialectType::SQLite) | Some(DialectType::Drill) => {
22618                        self.write_keyword("INTEGER");
22619                    }
22620                    Some(DialectType::BigQuery) => {
22621                        self.write_keyword("INT64");
22622                    }
22623                    Some(DialectType::ClickHouse) => {
22624                        self.write_clickhouse_type("Int16");
22625                    }
22626                    _ => {
22627                        self.write_keyword("SMALLINT");
22628                        if let Some(n) = length {
22629                            self.write(&format!("({})", n));
22630                        }
22631                    }
22632                }
22633            }
22634            DataType::Int {
22635                length,
22636                integer_spelling: _,
22637            } => {
22638                // BigQuery uses INT64 for INT
22639                if matches!(self.config.dialect, Some(DialectType::BigQuery)) {
22640                    self.write_keyword("INT64");
22641                } else if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
22642                    self.write_clickhouse_type("Int32");
22643                } else {
22644                    // TSQL, Presto, Trino, SQLite, Redshift use INTEGER as the canonical form
22645                    let use_integer = match self.config.dialect {
22646                        Some(DialectType::TSQL)
22647                        | Some(DialectType::Fabric)
22648                        | Some(DialectType::Presto)
22649                        | Some(DialectType::Trino)
22650                        | Some(DialectType::SQLite)
22651                        | Some(DialectType::Redshift) => true,
22652                        _ => false,
22653                    };
22654                    if use_integer {
22655                        self.write_keyword("INTEGER");
22656                    } else {
22657                        self.write_keyword("INT");
22658                    }
22659                    if let Some(n) = length {
22660                        self.write(&format!("({})", n));
22661                    }
22662                }
22663            }
22664            DataType::BigInt { length } => {
22665                // Dialect-specific bigint type mappings
22666                match self.config.dialect {
22667                    Some(DialectType::Oracle) => {
22668                        // Oracle doesn't have BIGINT, uses INT
22669                        self.write_keyword("INT");
22670                    }
22671                    Some(DialectType::ClickHouse) => {
22672                        self.write_clickhouse_type("Int64");
22673                    }
22674                    _ => {
22675                        self.write_keyword("BIGINT");
22676                        if let Some(n) = length {
22677                            self.write(&format!("({})", n));
22678                        }
22679                    }
22680                }
22681            }
22682            DataType::Float {
22683                precision,
22684                scale,
22685                real_spelling,
22686            } => {
22687                // Dialect-specific float type mappings
22688                // If real_spelling is true, preserve REAL; otherwise use dialect default
22689                // Spark/Hive don't support REAL, always use FLOAT
22690                if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
22691                    self.write_clickhouse_type("Float32");
22692                } else if *real_spelling
22693                    && !matches!(
22694                        self.config.dialect,
22695                        Some(DialectType::Spark)
22696                            | Some(DialectType::Databricks)
22697                            | Some(DialectType::Hive)
22698                            | Some(DialectType::Snowflake)
22699                            | Some(DialectType::MySQL)
22700                            | Some(DialectType::BigQuery)
22701                    )
22702                {
22703                    self.write_keyword("REAL")
22704                } else {
22705                    match self.config.dialect {
22706                        Some(DialectType::PostgreSQL) => self.write_keyword("REAL"),
22707                        Some(DialectType::BigQuery) => self.write_keyword("FLOAT64"),
22708                        _ => self.write_keyword("FLOAT"),
22709                    }
22710                }
22711                // MySQL supports FLOAT(precision) or FLOAT(precision, scale)
22712                // Spark/Hive don't support FLOAT(precision)
22713                if !matches!(
22714                    self.config.dialect,
22715                    Some(DialectType::Spark)
22716                        | Some(DialectType::Databricks)
22717                        | Some(DialectType::Hive)
22718                        | Some(DialectType::Presto)
22719                        | Some(DialectType::Trino)
22720                ) {
22721                    if let Some(p) = precision {
22722                        self.write(&format!("({}", p));
22723                        if let Some(s) = scale {
22724                            self.write(&format!(", {})", s));
22725                        } else {
22726                            self.write(")");
22727                        }
22728                    }
22729                }
22730            }
22731            DataType::Double { precision, scale } => {
22732                // Dialect-specific double type mappings
22733                match self.config.dialect {
22734                    Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
22735                        self.write_keyword("FLOAT")
22736                    } // SQL Server/Fabric FLOAT is double
22737                    Some(DialectType::Oracle) => self.write_keyword("DOUBLE PRECISION"),
22738                    Some(DialectType::ClickHouse) => self.write_clickhouse_type("Float64"),
22739                    Some(DialectType::BigQuery) => self.write_keyword("FLOAT64"),
22740                    Some(DialectType::SQLite) => self.write_keyword("REAL"),
22741                    Some(DialectType::PostgreSQL)
22742                    | Some(DialectType::Redshift)
22743                    | Some(DialectType::Teradata)
22744                    | Some(DialectType::Materialize) => self.write_keyword("DOUBLE PRECISION"),
22745                    _ => self.write_keyword("DOUBLE"),
22746                }
22747                // MySQL supports DOUBLE(precision, scale)
22748                if let Some(p) = precision {
22749                    self.write(&format!("({}", p));
22750                    if let Some(s) = scale {
22751                        self.write(&format!(", {})", s));
22752                    } else {
22753                        self.write(")");
22754                    }
22755                }
22756            }
22757            DataType::Decimal { precision, scale } => {
22758                // Dialect-specific decimal type mappings
22759                match self.config.dialect {
22760                    Some(DialectType::ClickHouse) => {
22761                        self.write("Decimal");
22762                        if let Some(p) = precision {
22763                            self.write(&format!("({}", p));
22764                            if let Some(s) = scale {
22765                                self.write(&format!(", {}", s));
22766                            }
22767                            self.write(")");
22768                        }
22769                    }
22770                    Some(DialectType::Oracle) => {
22771                        // Oracle uses NUMBER instead of DECIMAL
22772                        self.write_keyword("NUMBER");
22773                        if let Some(p) = precision {
22774                            self.write(&format!("({}", p));
22775                            if let Some(s) = scale {
22776                                self.write(&format!(", {}", s));
22777                            }
22778                            self.write(")");
22779                        }
22780                    }
22781                    Some(DialectType::BigQuery) => {
22782                        // BigQuery uses NUMERIC instead of DECIMAL
22783                        self.write_keyword("NUMERIC");
22784                        if let Some(p) = precision {
22785                            self.write(&format!("({}", p));
22786                            if let Some(s) = scale {
22787                                self.write(&format!(", {}", s));
22788                            }
22789                            self.write(")");
22790                        }
22791                    }
22792                    _ => {
22793                        self.write_keyword("DECIMAL");
22794                        if let Some(p) = precision {
22795                            self.write(&format!("({}", p));
22796                            if let Some(s) = scale {
22797                                self.write(&format!(", {}", s));
22798                            }
22799                            self.write(")");
22800                        }
22801                    }
22802                }
22803            }
22804            DataType::Char { length } => {
22805                // Dialect-specific char type mappings
22806                match self.config.dialect {
22807                    Some(DialectType::DuckDB) | Some(DialectType::SQLite) => {
22808                        // DuckDB/SQLite maps CHAR to TEXT
22809                        self.write_keyword("TEXT");
22810                    }
22811                    Some(DialectType::Hive)
22812                    | Some(DialectType::Spark)
22813                    | Some(DialectType::Databricks) => {
22814                        // Hive/Spark/Databricks maps CHAR to STRING (when no length)
22815                        // CHAR(n) with explicit length is kept as CHAR(n) for Spark/Databricks
22816                        if length.is_some()
22817                            && !matches!(self.config.dialect, Some(DialectType::Hive))
22818                        {
22819                            self.write_keyword("CHAR");
22820                            if let Some(n) = length {
22821                                self.write(&format!("({})", n));
22822                            }
22823                        } else {
22824                            self.write_keyword("STRING");
22825                        }
22826                    }
22827                    Some(DialectType::Dremio) => {
22828                        // Dremio maps CHAR to VARCHAR
22829                        self.write_keyword("VARCHAR");
22830                        if let Some(n) = length {
22831                            self.write(&format!("({})", n));
22832                        }
22833                    }
22834                    _ => {
22835                        self.write_keyword("CHAR");
22836                        if let Some(n) = length {
22837                            self.write(&format!("({})", n));
22838                        }
22839                    }
22840                }
22841            }
22842            DataType::VarChar {
22843                length,
22844                parenthesized_length,
22845            } => {
22846                // Dialect-specific varchar type mappings
22847                match self.config.dialect {
22848                    Some(DialectType::Oracle) => {
22849                        self.write_keyword("VARCHAR2");
22850                        if let Some(n) = length {
22851                            self.write(&format!("({})", n));
22852                        }
22853                    }
22854                    Some(DialectType::DuckDB) => {
22855                        // DuckDB maps VARCHAR to TEXT, preserving length
22856                        self.write_keyword("TEXT");
22857                        if let Some(n) = length {
22858                            self.write(&format!("({})", n));
22859                        }
22860                    }
22861                    Some(DialectType::SQLite) => {
22862                        // SQLite maps VARCHAR to TEXT, preserving length
22863                        self.write_keyword("TEXT");
22864                        if let Some(n) = length {
22865                            self.write(&format!("({})", n));
22866                        }
22867                    }
22868                    Some(DialectType::MySQL) if length.is_none() => {
22869                        // MySQL requires VARCHAR to have a size - if it doesn't, use TEXT
22870                        self.write_keyword("TEXT");
22871                    }
22872                    Some(DialectType::Hive)
22873                    | Some(DialectType::Spark)
22874                    | Some(DialectType::Databricks)
22875                        if length.is_none() =>
22876                    {
22877                        // Hive/Spark/Databricks: VARCHAR without length → STRING
22878                        self.write_keyword("STRING");
22879                    }
22880                    _ => {
22881                        self.write_keyword("VARCHAR");
22882                        if let Some(n) = length {
22883                            // Hive uses VARCHAR((n)) with extra parentheses in STRUCT definitions
22884                            if *parenthesized_length {
22885                                self.write(&format!("(({}))", n));
22886                            } else {
22887                                self.write(&format!("({})", n));
22888                            }
22889                        }
22890                    }
22891                }
22892            }
22893            DataType::Text => {
22894                // Dialect-specific text type mappings
22895                match self.config.dialect {
22896                    Some(DialectType::Oracle) => self.write_keyword("CLOB"),
22897                    Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
22898                        self.write_keyword("VARCHAR(MAX)")
22899                    }
22900                    Some(DialectType::BigQuery) => self.write_keyword("STRING"),
22901                    Some(DialectType::Snowflake)
22902                    | Some(DialectType::Dremio)
22903                    | Some(DialectType::Drill) => self.write_keyword("VARCHAR"),
22904                    Some(DialectType::Exasol) => self.write_keyword("LONG VARCHAR"),
22905                    Some(DialectType::Presto)
22906                    | Some(DialectType::Trino)
22907                    | Some(DialectType::Athena) => self.write_keyword("VARCHAR"),
22908                    Some(DialectType::Spark)
22909                    | Some(DialectType::Databricks)
22910                    | Some(DialectType::Hive) => self.write_keyword("STRING"),
22911                    Some(DialectType::Redshift) => self.write_keyword("VARCHAR(MAX)"),
22912                    Some(DialectType::StarRocks) | Some(DialectType::Doris) => {
22913                        self.write_keyword("STRING")
22914                    }
22915                    Some(DialectType::ClickHouse) => self.write_clickhouse_type("String"),
22916                    _ => self.write_keyword("TEXT"),
22917                }
22918            }
22919            DataType::TextWithLength { length } => {
22920                // TEXT(n) - dialect-specific type with length
22921                match self.config.dialect {
22922                    Some(DialectType::Oracle) => self.write(&format!("CLOB({})", length)),
22923                    Some(DialectType::Hive)
22924                    | Some(DialectType::Spark)
22925                    | Some(DialectType::Databricks) => {
22926                        self.write(&format!("VARCHAR({})", length));
22927                    }
22928                    Some(DialectType::Redshift) => self.write(&format!("VARCHAR({})", length)),
22929                    Some(DialectType::BigQuery) => self.write(&format!("STRING({})", length)),
22930                    Some(DialectType::Snowflake)
22931                    | Some(DialectType::Presto)
22932                    | Some(DialectType::Trino)
22933                    | Some(DialectType::Athena)
22934                    | Some(DialectType::Drill)
22935                    | Some(DialectType::Dremio) => {
22936                        self.write(&format!("VARCHAR({})", length));
22937                    }
22938                    Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
22939                        self.write(&format!("VARCHAR({})", length))
22940                    }
22941                    Some(DialectType::StarRocks) | Some(DialectType::Doris) => {
22942                        self.write(&format!("STRING({})", length))
22943                    }
22944                    Some(DialectType::ClickHouse) => self.write_clickhouse_type("String"),
22945                    _ => self.write(&format!("TEXT({})", length)),
22946                }
22947            }
22948            DataType::String { length } => {
22949                // STRING type with optional length (BigQuery STRING(n))
22950                match self.config.dialect {
22951                    Some(DialectType::ClickHouse) => {
22952                        // ClickHouse uses String with specific casing
22953                        self.write("String");
22954                        if let Some(n) = length {
22955                            self.write(&format!("({})", n));
22956                        }
22957                    }
22958                    Some(DialectType::BigQuery)
22959                    | Some(DialectType::Hive)
22960                    | Some(DialectType::Spark)
22961                    | Some(DialectType::Databricks)
22962                    | Some(DialectType::StarRocks)
22963                    | Some(DialectType::Doris) => {
22964                        self.write_keyword("STRING");
22965                        if let Some(n) = length {
22966                            self.write(&format!("({})", n));
22967                        }
22968                    }
22969                    Some(DialectType::PostgreSQL) => {
22970                        // PostgreSQL doesn't have STRING - use VARCHAR or TEXT
22971                        if let Some(n) = length {
22972                            self.write_keyword("VARCHAR");
22973                            self.write(&format!("({})", n));
22974                        } else {
22975                            self.write_keyword("TEXT");
22976                        }
22977                    }
22978                    Some(DialectType::Redshift) => {
22979                        // Redshift: STRING -> VARCHAR(MAX)
22980                        if let Some(n) = length {
22981                            self.write_keyword("VARCHAR");
22982                            self.write(&format!("({})", n));
22983                        } else {
22984                            self.write_keyword("VARCHAR(MAX)");
22985                        }
22986                    }
22987                    Some(DialectType::MySQL) => {
22988                        // MySQL doesn't have STRING - use VARCHAR or TEXT
22989                        if let Some(n) = length {
22990                            self.write_keyword("VARCHAR");
22991                            self.write(&format!("({})", n));
22992                        } else {
22993                            self.write_keyword("TEXT");
22994                        }
22995                    }
22996                    Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
22997                        // TSQL: STRING -> VARCHAR(MAX)
22998                        if let Some(n) = length {
22999                            self.write_keyword("VARCHAR");
23000                            self.write(&format!("({})", n));
23001                        } else {
23002                            self.write_keyword("VARCHAR(MAX)");
23003                        }
23004                    }
23005                    Some(DialectType::Oracle) => {
23006                        // Oracle: STRING -> CLOB
23007                        self.write_keyword("CLOB");
23008                    }
23009                    Some(DialectType::DuckDB) | Some(DialectType::Materialize) => {
23010                        // DuckDB/Materialize uses TEXT for string types
23011                        self.write_keyword("TEXT");
23012                        if let Some(n) = length {
23013                            self.write(&format!("({})", n));
23014                        }
23015                    }
23016                    Some(DialectType::Presto)
23017                    | Some(DialectType::Trino)
23018                    | Some(DialectType::Drill)
23019                    | Some(DialectType::Dremio) => {
23020                        // Presto/Trino/Drill use VARCHAR for string types
23021                        self.write_keyword("VARCHAR");
23022                        if let Some(n) = length {
23023                            self.write(&format!("({})", n));
23024                        }
23025                    }
23026                    Some(DialectType::Snowflake) => {
23027                        // Snowflake: STRING stays as STRING (identity/DDL)
23028                        // CAST context STRING -> VARCHAR is handled in generate_cast
23029                        self.write_keyword("STRING");
23030                        if let Some(n) = length {
23031                            self.write(&format!("({})", n));
23032                        }
23033                    }
23034                    _ => {
23035                        // Default: output STRING with optional length
23036                        self.write_keyword("STRING");
23037                        if let Some(n) = length {
23038                            self.write(&format!("({})", n));
23039                        }
23040                    }
23041                }
23042            }
23043            DataType::Binary { length } => {
23044                // Dialect-specific binary type mappings
23045                match self.config.dialect {
23046                    Some(DialectType::PostgreSQL) | Some(DialectType::Materialize) => {
23047                        self.write_keyword("BYTEA");
23048                        if let Some(n) = length {
23049                            self.write(&format!("({})", n));
23050                        }
23051                    }
23052                    Some(DialectType::Redshift) => {
23053                        self.write_keyword("VARBYTE");
23054                        if let Some(n) = length {
23055                            self.write(&format!("({})", n));
23056                        }
23057                    }
23058                    Some(DialectType::DuckDB)
23059                    | Some(DialectType::SQLite)
23060                    | Some(DialectType::Oracle) => {
23061                        // DuckDB/SQLite/Oracle maps BINARY to BLOB
23062                        self.write_keyword("BLOB");
23063                        if let Some(n) = length {
23064                            self.write(&format!("({})", n));
23065                        }
23066                    }
23067                    Some(DialectType::Presto)
23068                    | Some(DialectType::Trino)
23069                    | Some(DialectType::Athena)
23070                    | Some(DialectType::Drill)
23071                    | Some(DialectType::Dremio) => {
23072                        // These dialects map BINARY to VARBINARY
23073                        self.write_keyword("VARBINARY");
23074                        if let Some(n) = length {
23075                            self.write(&format!("({})", n));
23076                        }
23077                    }
23078                    Some(DialectType::ClickHouse) => {
23079                        // ClickHouse: wrap BINARY in Nullable (unless map key context)
23080                        if self.clickhouse_nullable_depth < 0 {
23081                            self.write("BINARY");
23082                        } else {
23083                            self.write("Nullable(BINARY");
23084                        }
23085                        if let Some(n) = length {
23086                            self.write(&format!("({})", n));
23087                        }
23088                        if self.clickhouse_nullable_depth >= 0 {
23089                            self.write(")");
23090                        }
23091                    }
23092                    _ => {
23093                        self.write_keyword("BINARY");
23094                        if let Some(n) = length {
23095                            self.write(&format!("({})", n));
23096                        }
23097                    }
23098                }
23099            }
23100            DataType::VarBinary { length } => {
23101                // Dialect-specific varbinary type mappings
23102                match self.config.dialect {
23103                    Some(DialectType::PostgreSQL) | Some(DialectType::Materialize) => {
23104                        self.write_keyword("BYTEA");
23105                        if let Some(n) = length {
23106                            self.write(&format!("({})", n));
23107                        }
23108                    }
23109                    Some(DialectType::Redshift) => {
23110                        self.write_keyword("VARBYTE");
23111                        if let Some(n) = length {
23112                            self.write(&format!("({})", n));
23113                        }
23114                    }
23115                    Some(DialectType::DuckDB)
23116                    | Some(DialectType::SQLite)
23117                    | Some(DialectType::Oracle) => {
23118                        // DuckDB/SQLite/Oracle maps VARBINARY to BLOB
23119                        self.write_keyword("BLOB");
23120                        if let Some(n) = length {
23121                            self.write(&format!("({})", n));
23122                        }
23123                    }
23124                    Some(DialectType::Exasol) => {
23125                        // Exasol maps VARBINARY to VARCHAR
23126                        self.write_keyword("VARCHAR");
23127                    }
23128                    Some(DialectType::Spark)
23129                    | Some(DialectType::Hive)
23130                    | Some(DialectType::Databricks) => {
23131                        // Spark/Hive use BINARY instead of VARBINARY
23132                        self.write_keyword("BINARY");
23133                        if let Some(n) = length {
23134                            self.write(&format!("({})", n));
23135                        }
23136                    }
23137                    Some(DialectType::ClickHouse) => {
23138                        // ClickHouse maps VARBINARY to String (wrapped in Nullable unless map key)
23139                        self.write_clickhouse_type("String");
23140                    }
23141                    _ => {
23142                        self.write_keyword("VARBINARY");
23143                        if let Some(n) = length {
23144                            self.write(&format!("({})", n));
23145                        }
23146                    }
23147                }
23148            }
23149            DataType::Blob => {
23150                // Dialect-specific blob type mappings
23151                match self.config.dialect {
23152                    Some(DialectType::PostgreSQL) => self.write_keyword("BYTEA"),
23153                    Some(DialectType::Redshift) => self.write_keyword("VARBYTE"),
23154                    Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
23155                        self.write_keyword("VARBINARY")
23156                    }
23157                    Some(DialectType::BigQuery) => self.write_keyword("BYTES"),
23158                    Some(DialectType::Exasol) => self.write_keyword("VARCHAR"),
23159                    Some(DialectType::Presto)
23160                    | Some(DialectType::Trino)
23161                    | Some(DialectType::Athena) => self.write_keyword("VARBINARY"),
23162                    Some(DialectType::DuckDB) => {
23163                        // Python sqlglot: BLOB -> VARBINARY for DuckDB (base TYPE_MAPPING)
23164                        // DuckDB identity works via: BLOB -> transform VarBinary -> generator BLOB
23165                        self.write_keyword("VARBINARY");
23166                    }
23167                    Some(DialectType::Spark)
23168                    | Some(DialectType::Databricks)
23169                    | Some(DialectType::Hive) => self.write_keyword("BINARY"),
23170                    Some(DialectType::ClickHouse) => {
23171                        // BLOB maps to Nullable(String) in ClickHouse, even in column defs
23172                        // where we normally suppress Nullable wrapping (clickhouse_nullable_depth = -1).
23173                        // This matches Python sqlglot behavior.
23174                        self.write("Nullable(String)");
23175                    }
23176                    _ => self.write_keyword("BLOB"),
23177                }
23178            }
23179            DataType::Bit { length } => {
23180                // Dialect-specific bit type mappings
23181                match self.config.dialect {
23182                    Some(DialectType::Dremio)
23183                    | Some(DialectType::Spark)
23184                    | Some(DialectType::Databricks)
23185                    | Some(DialectType::Hive)
23186                    | Some(DialectType::Snowflake)
23187                    | Some(DialectType::BigQuery)
23188                    | Some(DialectType::Presto)
23189                    | Some(DialectType::Trino)
23190                    | Some(DialectType::ClickHouse)
23191                    | Some(DialectType::Redshift) => {
23192                        // These dialects don't support BIT type, use BOOLEAN
23193                        self.write_keyword("BOOLEAN");
23194                    }
23195                    _ => {
23196                        self.write_keyword("BIT");
23197                        if let Some(n) = length {
23198                            self.write(&format!("({})", n));
23199                        }
23200                    }
23201                }
23202            }
23203            DataType::VarBit { length } => {
23204                self.write_keyword("VARBIT");
23205                if let Some(n) = length {
23206                    self.write(&format!("({})", n));
23207                }
23208            }
23209            DataType::Date => self.write_keyword("DATE"),
23210            DataType::Time {
23211                precision,
23212                timezone,
23213            } => {
23214                if *timezone {
23215                    // Dialect-specific TIME WITH TIME ZONE output
23216                    match self.config.dialect {
23217                        Some(DialectType::DuckDB) => {
23218                            // DuckDB: TIMETZ (drops precision)
23219                            self.write_keyword("TIMETZ");
23220                        }
23221                        Some(DialectType::PostgreSQL) => {
23222                            // PostgreSQL: TIMETZ or TIMETZ(p)
23223                            self.write_keyword("TIMETZ");
23224                            if let Some(p) = precision {
23225                                self.write(&format!("({})", p));
23226                            }
23227                        }
23228                        _ => {
23229                            // Presto/Trino/Redshift/others: TIME(p) WITH TIME ZONE
23230                            self.write_keyword("TIME");
23231                            if let Some(p) = precision {
23232                                self.write(&format!("({})", p));
23233                            }
23234                            self.write_keyword(" WITH TIME ZONE");
23235                        }
23236                    }
23237                } else {
23238                    // Spark/Hive/Databricks: TIME -> TIMESTAMP (TIME not supported)
23239                    if matches!(
23240                        self.config.dialect,
23241                        Some(DialectType::Spark)
23242                            | Some(DialectType::Databricks)
23243                            | Some(DialectType::Hive)
23244                    ) {
23245                        self.write_keyword("TIMESTAMP");
23246                    } else {
23247                        self.write_keyword("TIME");
23248                        if let Some(p) = precision {
23249                            self.write(&format!("({})", p));
23250                        }
23251                    }
23252                }
23253            }
23254            DataType::Timestamp {
23255                precision,
23256                timezone,
23257            } => {
23258                // Dialect-specific timestamp type mappings
23259                match self.config.dialect {
23260                    Some(DialectType::ClickHouse) => {
23261                        self.write("DateTime");
23262                        if let Some(p) = precision {
23263                            self.write(&format!("({})", p));
23264                        }
23265                    }
23266                    Some(DialectType::TSQL) => {
23267                        if *timezone {
23268                            self.write_keyword("DATETIMEOFFSET");
23269                        } else {
23270                            self.write_keyword("DATETIME2");
23271                        }
23272                        if let Some(p) = precision {
23273                            self.write(&format!("({})", p));
23274                        }
23275                    }
23276                    Some(DialectType::MySQL) => {
23277                        // MySQL: TIMESTAMP stays as TIMESTAMP in DDL; CAST mapping handled separately
23278                        self.write_keyword("TIMESTAMP");
23279                        if let Some(p) = precision {
23280                            self.write(&format!("({})", p));
23281                        }
23282                    }
23283                    Some(DialectType::Doris) | Some(DialectType::StarRocks) => {
23284                        // Doris/StarRocks: TIMESTAMP -> DATETIME
23285                        self.write_keyword("DATETIME");
23286                        if let Some(p) = precision {
23287                            self.write(&format!("({})", p));
23288                        }
23289                    }
23290                    Some(DialectType::BigQuery) => {
23291                        // BigQuery: TIMESTAMP is always UTC, DATETIME is timezone-naive
23292                        if *timezone {
23293                            self.write_keyword("TIMESTAMP");
23294                        } else {
23295                            self.write_keyword("DATETIME");
23296                        }
23297                    }
23298                    Some(DialectType::DuckDB) => {
23299                        // DuckDB: TIMESTAMPTZ shorthand
23300                        if *timezone {
23301                            self.write_keyword("TIMESTAMPTZ");
23302                        } else {
23303                            self.write_keyword("TIMESTAMP");
23304                            if let Some(p) = precision {
23305                                self.write(&format!("({})", p));
23306                            }
23307                        }
23308                    }
23309                    _ => {
23310                        if *timezone && !self.config.tz_to_with_time_zone {
23311                            // Use TIMESTAMPTZ shorthand when dialect doesn't prefer WITH TIME ZONE
23312                            self.write_keyword("TIMESTAMPTZ");
23313                            if let Some(p) = precision {
23314                                self.write(&format!("({})", p));
23315                            }
23316                        } else {
23317                            self.write_keyword("TIMESTAMP");
23318                            if let Some(p) = precision {
23319                                self.write(&format!("({})", p));
23320                            }
23321                            if *timezone {
23322                                self.write_space();
23323                                self.write_keyword("WITH TIME ZONE");
23324                            }
23325                        }
23326                    }
23327                }
23328            }
23329            DataType::Interval { unit, to } => {
23330                self.write_keyword("INTERVAL");
23331                if let Some(u) = unit {
23332                    self.write_space();
23333                    self.write_keyword(u);
23334                }
23335                // Handle range intervals like DAY TO HOUR
23336                if let Some(t) = to {
23337                    self.write_space();
23338                    self.write_keyword("TO");
23339                    self.write_space();
23340                    self.write_keyword(t);
23341                }
23342            }
23343            DataType::Json => {
23344                // Dialect-specific JSON type mappings
23345                match self.config.dialect {
23346                    Some(DialectType::Oracle) => self.write_keyword("JSON"), // Oracle 21c+
23347                    Some(DialectType::TSQL) => self.write_keyword("NVARCHAR(MAX)"), // No native JSON type
23348                    Some(DialectType::MySQL) => self.write_keyword("JSON"),
23349                    Some(DialectType::Snowflake) => self.write_keyword("VARIANT"),
23350                    _ => self.write_keyword("JSON"),
23351                }
23352            }
23353            DataType::JsonB => {
23354                // JSONB is PostgreSQL specific, but Doris also supports it
23355                match self.config.dialect {
23356                    Some(DialectType::PostgreSQL) => self.write_keyword("JSONB"),
23357                    Some(DialectType::Doris) => self.write_keyword("JSONB"),
23358                    Some(DialectType::Snowflake) => self.write_keyword("VARIANT"),
23359                    Some(DialectType::TSQL) => self.write_keyword("NVARCHAR(MAX)"),
23360                    Some(DialectType::DuckDB) => self.write_keyword("JSON"), // DuckDB maps JSONB to JSON
23361                    _ => self.write_keyword("JSON"), // Fall back to JSON for other dialects
23362                }
23363            }
23364            DataType::Uuid => {
23365                // Dialect-specific UUID type mappings
23366                match self.config.dialect {
23367                    Some(DialectType::TSQL) => self.write_keyword("UNIQUEIDENTIFIER"),
23368                    Some(DialectType::MySQL) => self.write_keyword("CHAR(36)"),
23369                    Some(DialectType::Oracle) => self.write_keyword("RAW(16)"),
23370                    Some(DialectType::BigQuery)
23371                    | Some(DialectType::Spark)
23372                    | Some(DialectType::Databricks) => self.write_keyword("STRING"),
23373                    _ => self.write_keyword("UUID"),
23374                }
23375            }
23376            DataType::Array {
23377                element_type,
23378                dimension,
23379            } => {
23380                // Dialect-specific array syntax
23381                match self.config.dialect {
23382                    Some(DialectType::PostgreSQL)
23383                    | Some(DialectType::Redshift)
23384                    | Some(DialectType::DuckDB) => {
23385                        // PostgreSQL uses TYPE[] or TYPE[N] syntax
23386                        self.generate_data_type(element_type)?;
23387                        if let Some(dim) = dimension {
23388                            self.write(&format!("[{}]", dim));
23389                        } else {
23390                            self.write("[]");
23391                        }
23392                    }
23393                    Some(DialectType::BigQuery) => {
23394                        self.write_keyword("ARRAY<");
23395                        self.generate_data_type(element_type)?;
23396                        self.write(">");
23397                    }
23398                    Some(DialectType::Snowflake)
23399                    | Some(DialectType::Presto)
23400                    | Some(DialectType::Trino)
23401                    | Some(DialectType::ClickHouse) => {
23402                        // These dialects use Array(TYPE) parentheses syntax
23403                        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
23404                            self.write("Array(");
23405                        } else {
23406                            self.write_keyword("ARRAY(");
23407                        }
23408                        self.generate_data_type(element_type)?;
23409                        self.write(")");
23410                    }
23411                    Some(DialectType::TSQL)
23412                    | Some(DialectType::MySQL)
23413                    | Some(DialectType::Oracle) => {
23414                        // These dialects don't have native array types
23415                        // Fall back to JSON or use native workarounds
23416                        match self.config.dialect {
23417                            Some(DialectType::MySQL) => self.write_keyword("JSON"),
23418                            Some(DialectType::TSQL) => self.write_keyword("NVARCHAR(MAX)"),
23419                            _ => self.write_keyword("JSON"),
23420                        }
23421                    }
23422                    _ => {
23423                        // Default: use angle bracket syntax (ARRAY<T>)
23424                        self.write_keyword("ARRAY<");
23425                        self.generate_data_type(element_type)?;
23426                        self.write(">");
23427                    }
23428                }
23429            }
23430            DataType::List { element_type } => {
23431                // Materialize: element_type LIST (postfix syntax)
23432                self.generate_data_type(element_type)?;
23433                self.write_keyword(" LIST");
23434            }
23435            DataType::Map {
23436                key_type,
23437                value_type,
23438            } => {
23439                // Use parentheses for Snowflake and RisingWave, bracket syntax for Materialize, angle brackets for others
23440                match self.config.dialect {
23441                    Some(DialectType::Materialize) => {
23442                        // Materialize: MAP[key_type => value_type]
23443                        self.write_keyword("MAP[");
23444                        self.generate_data_type(key_type)?;
23445                        self.write(" => ");
23446                        self.generate_data_type(value_type)?;
23447                        self.write("]");
23448                    }
23449                    Some(DialectType::Snowflake)
23450                    | Some(DialectType::RisingWave)
23451                    | Some(DialectType::DuckDB)
23452                    | Some(DialectType::Presto)
23453                    | Some(DialectType::Trino)
23454                    | Some(DialectType::Athena) => {
23455                        self.write_keyword("MAP(");
23456                        self.generate_data_type(key_type)?;
23457                        self.write(", ");
23458                        self.generate_data_type(value_type)?;
23459                        self.write(")");
23460                    }
23461                    Some(DialectType::ClickHouse) => {
23462                        // ClickHouse: Map(key_type, value_type) with parenthesized syntax
23463                        // Key types must NOT be wrapped in Nullable
23464                        self.write("Map(");
23465                        self.clickhouse_nullable_depth = -1; // suppress Nullable for key
23466                        self.generate_data_type(key_type)?;
23467                        self.clickhouse_nullable_depth = 0;
23468                        self.write(", ");
23469                        self.generate_data_type(value_type)?;
23470                        self.write(")");
23471                    }
23472                    _ => {
23473                        self.write_keyword("MAP<");
23474                        self.generate_data_type(key_type)?;
23475                        self.write(", ");
23476                        self.generate_data_type(value_type)?;
23477                        self.write(">");
23478                    }
23479                }
23480            }
23481            DataType::Vector {
23482                element_type,
23483                dimension,
23484            } => {
23485                if matches!(self.config.dialect, Some(DialectType::SingleStore)) {
23486                    // SingleStore format: VECTOR(dimension, type_alias)
23487                    self.write_keyword("VECTOR(");
23488                    if let Some(dim) = dimension {
23489                        self.write(&dim.to_string());
23490                    }
23491                    // Map type back to SingleStore alias
23492                    let type_alias = element_type.as_ref().and_then(|et| match et.as_ref() {
23493                        DataType::TinyInt { .. } => Some("I8"),
23494                        DataType::SmallInt { .. } => Some("I16"),
23495                        DataType::Int { .. } => Some("I32"),
23496                        DataType::BigInt { .. } => Some("I64"),
23497                        DataType::Float { .. } => Some("F32"),
23498                        DataType::Double { .. } => Some("F64"),
23499                        _ => None,
23500                    });
23501                    if let Some(alias) = type_alias {
23502                        if dimension.is_some() {
23503                            self.write(", ");
23504                        }
23505                        self.write(alias);
23506                    }
23507                    self.write(")");
23508                } else {
23509                    // Snowflake format: VECTOR(type, dimension)
23510                    self.write_keyword("VECTOR(");
23511                    if let Some(ref et) = element_type {
23512                        self.generate_data_type(et)?;
23513                        if dimension.is_some() {
23514                            self.write(", ");
23515                        }
23516                    }
23517                    if let Some(dim) = dimension {
23518                        self.write(&dim.to_string());
23519                    }
23520                    self.write(")");
23521                }
23522            }
23523            DataType::Object { fields, modifier } => {
23524                self.write_keyword("OBJECT(");
23525                for (i, (name, dt, not_null)) in fields.iter().enumerate() {
23526                    if i > 0 {
23527                        self.write(", ");
23528                    }
23529                    self.write(name);
23530                    self.write(" ");
23531                    self.generate_data_type(dt)?;
23532                    if *not_null {
23533                        self.write_keyword(" NOT NULL");
23534                    }
23535                }
23536                self.write(")");
23537                if let Some(mod_str) = modifier {
23538                    self.write(" ");
23539                    self.write_keyword(mod_str);
23540                }
23541            }
23542            DataType::Struct { fields, nested } => {
23543                // Dialect-specific struct type mappings
23544                match self.config.dialect {
23545                    Some(DialectType::Snowflake) => {
23546                        // Snowflake maps STRUCT to OBJECT
23547                        self.write_keyword("OBJECT(");
23548                        for (i, field) in fields.iter().enumerate() {
23549                            if i > 0 {
23550                                self.write(", ");
23551                            }
23552                            if !field.name.is_empty() {
23553                                self.write(&field.name);
23554                                self.write(" ");
23555                            }
23556                            self.generate_data_type(&field.data_type)?;
23557                        }
23558                        self.write(")");
23559                    }
23560                    Some(DialectType::Presto) | Some(DialectType::Trino) => {
23561                        // Presto/Trino use ROW(name TYPE, ...) syntax
23562                        self.write_keyword("ROW(");
23563                        for (i, field) in fields.iter().enumerate() {
23564                            if i > 0 {
23565                                self.write(", ");
23566                            }
23567                            if !field.name.is_empty() {
23568                                self.write(&field.name);
23569                                self.write(" ");
23570                            }
23571                            self.generate_data_type(&field.data_type)?;
23572                        }
23573                        self.write(")");
23574                    }
23575                    Some(DialectType::DuckDB) => {
23576                        // DuckDB uses parenthesized syntax: STRUCT(name TYPE, ...)
23577                        self.write_keyword("STRUCT(");
23578                        for (i, field) in fields.iter().enumerate() {
23579                            if i > 0 {
23580                                self.write(", ");
23581                            }
23582                            if !field.name.is_empty() {
23583                                self.write(&field.name);
23584                                self.write(" ");
23585                            }
23586                            self.generate_data_type(&field.data_type)?;
23587                        }
23588                        self.write(")");
23589                    }
23590                    Some(DialectType::ClickHouse) => {
23591                        // ClickHouse uses Tuple(name TYPE, ...) for struct types
23592                        self.write("Tuple(");
23593                        for (i, field) in fields.iter().enumerate() {
23594                            if i > 0 {
23595                                self.write(", ");
23596                            }
23597                            if !field.name.is_empty() {
23598                                self.write(&field.name);
23599                                self.write(" ");
23600                            }
23601                            self.generate_data_type(&field.data_type)?;
23602                        }
23603                        self.write(")");
23604                    }
23605                    Some(DialectType::SingleStore) => {
23606                        // SingleStore uses RECORD(name TYPE, ...) for struct types
23607                        self.write_keyword("RECORD(");
23608                        for (i, field) in fields.iter().enumerate() {
23609                            if i > 0 {
23610                                self.write(", ");
23611                            }
23612                            if !field.name.is_empty() {
23613                                self.write(&field.name);
23614                                self.write(" ");
23615                            }
23616                            self.generate_data_type(&field.data_type)?;
23617                        }
23618                        self.write(")");
23619                    }
23620                    _ => {
23621                        // Hive/Spark always use angle bracket syntax: STRUCT<name: TYPE>
23622                        let force_angle_brackets = matches!(
23623                            self.config.dialect,
23624                            Some(DialectType::Hive)
23625                                | Some(DialectType::Spark)
23626                                | Some(DialectType::Databricks)
23627                        );
23628                        if *nested && !force_angle_brackets {
23629                            self.write_keyword("STRUCT(");
23630                            for (i, field) in fields.iter().enumerate() {
23631                                if i > 0 {
23632                                    self.write(", ");
23633                                }
23634                                if !field.name.is_empty() {
23635                                    self.write(&field.name);
23636                                    self.write(" ");
23637                                }
23638                                self.generate_data_type(&field.data_type)?;
23639                            }
23640                            self.write(")");
23641                        } else {
23642                            self.write_keyword("STRUCT<");
23643                            for (i, field) in fields.iter().enumerate() {
23644                                if i > 0 {
23645                                    self.write(", ");
23646                                }
23647                                if !field.name.is_empty() {
23648                                    // Named field: name TYPE (with configurable separator for Hive)
23649                                    self.write(&field.name);
23650                                    self.write(self.config.struct_field_sep);
23651                                }
23652                                // For anonymous fields, just output the type
23653                                self.generate_data_type(&field.data_type)?;
23654                                // Spark/Databricks: Output COMMENT clause if present
23655                                if let Some(comment) = &field.comment {
23656                                    self.write(" COMMENT '");
23657                                    self.write(comment);
23658                                    self.write("'");
23659                                }
23660                                // BigQuery: Output OPTIONS clause if present
23661                                if !field.options.is_empty() {
23662                                    self.write(" ");
23663                                    self.generate_options_clause(&field.options)?;
23664                                }
23665                            }
23666                            self.write(">");
23667                        }
23668                    }
23669                }
23670            }
23671            DataType::Enum {
23672                values,
23673                assignments,
23674            } => {
23675                // DuckDB ENUM type: ENUM('RED', 'GREEN', 'BLUE')
23676                // ClickHouse: Enum('hello' = 1, 'world' = 2)
23677                if self.config.dialect == Some(DialectType::ClickHouse) {
23678                    self.write("Enum(");
23679                } else {
23680                    self.write_keyword("ENUM(");
23681                }
23682                for (i, val) in values.iter().enumerate() {
23683                    if i > 0 {
23684                        self.write(", ");
23685                    }
23686                    self.write("'");
23687                    self.write(val);
23688                    self.write("'");
23689                    if let Some(Some(assignment)) = assignments.get(i) {
23690                        self.write(" = ");
23691                        self.write(assignment);
23692                    }
23693                }
23694                self.write(")");
23695            }
23696            DataType::Set { values } => {
23697                // MySQL SET type: SET('a', 'b', 'c')
23698                self.write_keyword("SET(");
23699                for (i, val) in values.iter().enumerate() {
23700                    if i > 0 {
23701                        self.write(", ");
23702                    }
23703                    self.write("'");
23704                    self.write(val);
23705                    self.write("'");
23706                }
23707                self.write(")");
23708            }
23709            DataType::Union { fields } => {
23710                // DuckDB UNION type: UNION(num INT, str TEXT)
23711                self.write_keyword("UNION(");
23712                for (i, (name, dt)) in fields.iter().enumerate() {
23713                    if i > 0 {
23714                        self.write(", ");
23715                    }
23716                    if !name.is_empty() {
23717                        self.write(name);
23718                        self.write(" ");
23719                    }
23720                    self.generate_data_type(dt)?;
23721                }
23722                self.write(")");
23723            }
23724            DataType::Nullable { inner } => {
23725                // ClickHouse: Nullable(T), other dialects: just the inner type
23726                if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
23727                    self.write("Nullable(");
23728                    // Suppress inner Nullable wrapping to prevent Nullable(Nullable(...))
23729                    let saved_depth = self.clickhouse_nullable_depth;
23730                    self.clickhouse_nullable_depth = -1;
23731                    self.generate_data_type(inner)?;
23732                    self.clickhouse_nullable_depth = saved_depth;
23733                    self.write(")");
23734                } else {
23735                    // Map ClickHouse-specific custom type names to standard types
23736                    match inner.as_ref() {
23737                        DataType::Custom { name } if name.eq_ignore_ascii_case("DATETIME") => {
23738                            self.generate_data_type(&DataType::Timestamp {
23739                                precision: None,
23740                                timezone: false,
23741                            })?;
23742                        }
23743                        _ => {
23744                            self.generate_data_type(inner)?;
23745                        }
23746                    }
23747                }
23748            }
23749            DataType::Custom { name } => {
23750                // Handle dialect-specific type transformations
23751                let name_upper = name.to_ascii_uppercase();
23752                match self.config.dialect {
23753                    Some(DialectType::ClickHouse) => {
23754                        let (base_upper, suffix) = if let Some(idx) = name.find('(') {
23755                            (name_upper[..idx].to_string(), &name[idx..])
23756                        } else {
23757                            (name_upper.clone(), "")
23758                        };
23759                        let mapped = match base_upper.as_str() {
23760                            "DATETIME" | "TIMESTAMPTZ" | "TIMESTAMP" | "TIMESTAMPNTZ"
23761                            | "SMALLDATETIME" | "DATETIME2" => "DateTime",
23762                            "DATETIME64" => "DateTime64",
23763                            "DATE32" => "Date32",
23764                            "INT" => "Int32",
23765                            "MEDIUMINT" => "Int32",
23766                            "INT8" => "Int8",
23767                            "INT16" => "Int16",
23768                            "INT32" => "Int32",
23769                            "INT64" => "Int64",
23770                            "INT128" => "Int128",
23771                            "INT256" => "Int256",
23772                            "UINT8" => "UInt8",
23773                            "UINT16" => "UInt16",
23774                            "UINT32" => "UInt32",
23775                            "UINT64" => "UInt64",
23776                            "UINT128" => "UInt128",
23777                            "UINT256" => "UInt256",
23778                            "FLOAT32" => "Float32",
23779                            "FLOAT64" => "Float64",
23780                            "DECIMAL32" => "Decimal32",
23781                            "DECIMAL64" => "Decimal64",
23782                            "DECIMAL128" => "Decimal128",
23783                            "DECIMAL256" => "Decimal256",
23784                            "ENUM" => "Enum",
23785                            "ENUM8" => "Enum8",
23786                            "ENUM16" => "Enum16",
23787                            "FIXEDSTRING" => "FixedString",
23788                            "NESTED" => "Nested",
23789                            "LOWCARDINALITY" => "LowCardinality",
23790                            "NULLABLE" => "Nullable",
23791                            "IPV4" => "IPv4",
23792                            "IPV6" => "IPv6",
23793                            "POINT" => "Point",
23794                            "RING" => "Ring",
23795                            "LINESTRING" => "LineString",
23796                            "MULTILINESTRING" => "MultiLineString",
23797                            "POLYGON" => "Polygon",
23798                            "MULTIPOLYGON" => "MultiPolygon",
23799                            "AGGREGATEFUNCTION" => "AggregateFunction",
23800                            "SIMPLEAGGREGATEFUNCTION" => "SimpleAggregateFunction",
23801                            "DYNAMIC" => "Dynamic",
23802                            _ => "",
23803                        };
23804                        if mapped.is_empty() {
23805                            self.write(name);
23806                        } else {
23807                            self.write(mapped);
23808                            self.write(suffix);
23809                        }
23810                    }
23811                    Some(DialectType::MySQL)
23812                        if name_upper == "TIMESTAMPTZ" || name_upper == "TIMESTAMPLTZ" =>
23813                    {
23814                        // MySQL doesn't support TIMESTAMPTZ/TIMESTAMPLTZ, use TIMESTAMP
23815                        self.write_keyword("TIMESTAMP");
23816                    }
23817                    Some(DialectType::TSQL) if name_upper == "VARIANT" => {
23818                        self.write_keyword("SQL_VARIANT");
23819                    }
23820                    Some(DialectType::DuckDB) if name_upper == "DECFLOAT" => {
23821                        self.write_keyword("DECIMAL(38, 5)");
23822                    }
23823                    Some(DialectType::Exasol) => {
23824                        // Exasol type mappings for custom types
23825                        match name_upper.as_str() {
23826                            // Binary types → VARCHAR
23827                            "LONGBLOB" | "MEDIUMBLOB" | "TINYBLOB" => self.write_keyword("VARCHAR"),
23828                            // Text types → VARCHAR (TEXT → LONG VARCHAR is handled by DataType::Text)
23829                            "LONGTEXT" | "MEDIUMTEXT" | "TINYTEXT" => self.write_keyword("VARCHAR"),
23830                            // Integer types
23831                            "MEDIUMINT" => self.write_keyword("INT"),
23832                            // Decimal types → DECIMAL
23833                            "DECIMAL32" | "DECIMAL64" | "DECIMAL128" | "DECIMAL256" => {
23834                                self.write_keyword("DECIMAL")
23835                            }
23836                            // Timestamp types
23837                            "DATETIME" => self.write_keyword("TIMESTAMP"),
23838                            "TIMESTAMPLTZ" => self.write_keyword("TIMESTAMP WITH LOCAL TIME ZONE"),
23839                            _ => self.write(name),
23840                        }
23841                    }
23842                    Some(DialectType::Dremio) => {
23843                        // Dremio type mappings for custom types
23844                        match name_upper.as_str() {
23845                            "TIMESTAMPNTZ" | "DATETIME" => self.write_keyword("TIMESTAMP"),
23846                            "ARRAY" => self.write_keyword("LIST"),
23847                            "NCHAR" => self.write_keyword("VARCHAR"),
23848                            _ => self.write(name),
23849                        }
23850                    }
23851                    // Map dialect-specific custom types to standard SQL types for other dialects
23852                    _ => {
23853                        // Extract base name and args for types with parenthesized args (e.g., DATETIME2(3))
23854                        let (base_upper, _args_str) = if let Some(idx) = name_upper.find('(') {
23855                            (name_upper[..idx].to_string(), Some(&name[idx..]))
23856                        } else {
23857                            (name_upper.clone(), None)
23858                        };
23859
23860                        match base_upper.as_str() {
23861                            "INT64"
23862                                if !matches!(self.config.dialect, Some(DialectType::BigQuery)) =>
23863                            {
23864                                self.write_keyword("BIGINT");
23865                            }
23866                            "FLOAT64"
23867                                if !matches!(self.config.dialect, Some(DialectType::BigQuery)) =>
23868                            {
23869                                self.write_keyword("DOUBLE");
23870                            }
23871                            "BOOL"
23872                                if !matches!(self.config.dialect, Some(DialectType::BigQuery)) =>
23873                            {
23874                                self.write_keyword("BOOLEAN");
23875                            }
23876                            "BYTES"
23877                                if matches!(
23878                                    self.config.dialect,
23879                                    Some(DialectType::Spark)
23880                                        | Some(DialectType::Hive)
23881                                        | Some(DialectType::Databricks)
23882                                ) =>
23883                            {
23884                                self.write_keyword("BINARY");
23885                            }
23886                            "BYTES"
23887                                if !matches!(self.config.dialect, Some(DialectType::BigQuery)) =>
23888                            {
23889                                self.write_keyword("VARBINARY");
23890                            }
23891                            // TSQL DATETIME2/SMALLDATETIME -> TIMESTAMP
23892                            "DATETIME2" | "SMALLDATETIME"
23893                                if !matches!(
23894                                    self.config.dialect,
23895                                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
23896                                ) =>
23897                            {
23898                                // PostgreSQL preserves precision, others don't
23899                                if matches!(
23900                                    self.config.dialect,
23901                                    Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
23902                                ) {
23903                                    self.write_keyword("TIMESTAMP");
23904                                    if let Some(args) = _args_str {
23905                                        self.write(args);
23906                                    }
23907                                } else {
23908                                    self.write_keyword("TIMESTAMP");
23909                                }
23910                            }
23911                            // TSQL DATETIMEOFFSET -> TIMESTAMPTZ
23912                            "DATETIMEOFFSET"
23913                                if !matches!(
23914                                    self.config.dialect,
23915                                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
23916                                ) =>
23917                            {
23918                                if matches!(
23919                                    self.config.dialect,
23920                                    Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
23921                                ) {
23922                                    self.write_keyword("TIMESTAMPTZ");
23923                                    if let Some(args) = _args_str {
23924                                        self.write(args);
23925                                    }
23926                                } else {
23927                                    self.write_keyword("TIMESTAMPTZ");
23928                                }
23929                            }
23930                            // TSQL UNIQUEIDENTIFIER -> UUID or STRING
23931                            "UNIQUEIDENTIFIER"
23932                                if !matches!(
23933                                    self.config.dialect,
23934                                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
23935                                ) =>
23936                            {
23937                                match self.config.dialect {
23938                                    Some(DialectType::Spark)
23939                                    | Some(DialectType::Databricks)
23940                                    | Some(DialectType::Hive) => self.write_keyword("STRING"),
23941                                    _ => self.write_keyword("UUID"),
23942                                }
23943                            }
23944                            // TSQL BIT -> BOOLEAN for most dialects
23945                            "BIT"
23946                                if !matches!(
23947                                    self.config.dialect,
23948                                    Some(DialectType::TSQL)
23949                                        | Some(DialectType::Fabric)
23950                                        | Some(DialectType::PostgreSQL)
23951                                        | Some(DialectType::MySQL)
23952                                        | Some(DialectType::DuckDB)
23953                                ) =>
23954                            {
23955                                self.write_keyword("BOOLEAN");
23956                            }
23957                            // TSQL NVARCHAR -> VARCHAR (with default size 30 for some dialects)
23958                            "NVARCHAR"
23959                                if !matches!(
23960                                    self.config.dialect,
23961                                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
23962                                ) =>
23963                            {
23964                                match self.config.dialect {
23965                                    Some(DialectType::Oracle) => {
23966                                        // Oracle: NVARCHAR -> NVARCHAR2
23967                                        self.write_keyword("NVARCHAR2");
23968                                        if let Some(args) = _args_str {
23969                                            self.write(args);
23970                                        }
23971                                    }
23972                                    Some(DialectType::BigQuery) => {
23973                                        // BigQuery: NVARCHAR -> STRING
23974                                        self.write_keyword("STRING");
23975                                    }
23976                                    Some(DialectType::SQLite) | Some(DialectType::DuckDB) => {
23977                                        self.write_keyword("TEXT");
23978                                        if let Some(args) = _args_str {
23979                                            self.write(args);
23980                                        }
23981                                    }
23982                                    Some(DialectType::Hive) => {
23983                                        // Hive: NVARCHAR -> STRING
23984                                        self.write_keyword("STRING");
23985                                    }
23986                                    Some(DialectType::Spark) | Some(DialectType::Databricks) => {
23987                                        if _args_str.is_some() {
23988                                            self.write_keyword("VARCHAR");
23989                                            self.write(_args_str.unwrap());
23990                                        } else {
23991                                            self.write_keyword("STRING");
23992                                        }
23993                                    }
23994                                    _ => {
23995                                        self.write_keyword("VARCHAR");
23996                                        if let Some(args) = _args_str {
23997                                            self.write(args);
23998                                        }
23999                                    }
24000                                }
24001                            }
24002                            // NCHAR -> CHAR (NCHAR for Oracle/TSQL, STRING for BigQuery/Hive)
24003                            "NCHAR"
24004                                if !matches!(
24005                                    self.config.dialect,
24006                                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
24007                                ) =>
24008                            {
24009                                match self.config.dialect {
24010                                    Some(DialectType::Oracle) => {
24011                                        // Oracle natively supports NCHAR
24012                                        self.write_keyword("NCHAR");
24013                                        if let Some(args) = _args_str {
24014                                            self.write(args);
24015                                        }
24016                                    }
24017                                    Some(DialectType::BigQuery) => {
24018                                        // BigQuery: NCHAR -> STRING
24019                                        self.write_keyword("STRING");
24020                                    }
24021                                    Some(DialectType::Hive) => {
24022                                        // Hive: NCHAR -> STRING
24023                                        self.write_keyword("STRING");
24024                                    }
24025                                    Some(DialectType::SQLite) | Some(DialectType::DuckDB) => {
24026                                        self.write_keyword("TEXT");
24027                                        if let Some(args) = _args_str {
24028                                            self.write(args);
24029                                        }
24030                                    }
24031                                    Some(DialectType::Spark) | Some(DialectType::Databricks) => {
24032                                        if _args_str.is_some() {
24033                                            self.write_keyword("CHAR");
24034                                            self.write(_args_str.unwrap());
24035                                        } else {
24036                                            self.write_keyword("STRING");
24037                                        }
24038                                    }
24039                                    _ => {
24040                                        self.write_keyword("CHAR");
24041                                        if let Some(args) = _args_str {
24042                                            self.write(args);
24043                                        }
24044                                    }
24045                                }
24046                            }
24047                            // MySQL text variant types -> map to appropriate target type
24048                            // For MySQL/SingleStore: keep original name (column definitions), CAST handling is in generate_cast
24049                            "LONGTEXT" | "MEDIUMTEXT" | "TINYTEXT" => match self.config.dialect {
24050                                Some(DialectType::MySQL)
24051                                | Some(DialectType::SingleStore)
24052                                | Some(DialectType::TiDB) => self.write_keyword(&base_upper),
24053                                Some(DialectType::Spark)
24054                                | Some(DialectType::Databricks)
24055                                | Some(DialectType::Hive) => self.write_keyword("TEXT"),
24056                                Some(DialectType::BigQuery) => self.write_keyword("STRING"),
24057                                Some(DialectType::Presto)
24058                                | Some(DialectType::Trino)
24059                                | Some(DialectType::Athena) => self.write_keyword("VARCHAR"),
24060                                Some(DialectType::Snowflake)
24061                                | Some(DialectType::Redshift)
24062                                | Some(DialectType::Dremio) => self.write_keyword("VARCHAR"),
24063                                _ => self.write_keyword("TEXT"),
24064                            },
24065                            // MySQL blob variant types -> map to appropriate target type
24066                            // For MySQL/SingleStore: keep original name (column definitions), CAST handling is in generate_cast
24067                            "LONGBLOB" | "MEDIUMBLOB" | "TINYBLOB" => match self.config.dialect {
24068                                Some(DialectType::MySQL)
24069                                | Some(DialectType::SingleStore)
24070                                | Some(DialectType::TiDB) => self.write_keyword(&base_upper),
24071                                Some(DialectType::Spark)
24072                                | Some(DialectType::Databricks)
24073                                | Some(DialectType::Hive) => self.write_keyword("BLOB"),
24074                                Some(DialectType::DuckDB) => self.write_keyword("VARBINARY"),
24075                                Some(DialectType::BigQuery) => self.write_keyword("BYTES"),
24076                                Some(DialectType::Presto)
24077                                | Some(DialectType::Trino)
24078                                | Some(DialectType::Athena) => self.write_keyword("VARBINARY"),
24079                                Some(DialectType::Snowflake)
24080                                | Some(DialectType::Redshift)
24081                                | Some(DialectType::Dremio) => self.write_keyword("VARBINARY"),
24082                                _ => self.write_keyword("BLOB"),
24083                            },
24084                            // LONGVARCHAR -> TEXT for SQLite, VARCHAR for others
24085                            "LONGVARCHAR" => match self.config.dialect {
24086                                Some(DialectType::SQLite) => self.write_keyword("TEXT"),
24087                                _ => self.write_keyword("VARCHAR"),
24088                            },
24089                            // DATETIME -> TIMESTAMP for most, DATETIME for MySQL/Doris/StarRocks/Snowflake
24090                            "DATETIME" => {
24091                                match self.config.dialect {
24092                                    Some(DialectType::MySQL)
24093                                    | Some(DialectType::Doris)
24094                                    | Some(DialectType::StarRocks)
24095                                    | Some(DialectType::TSQL)
24096                                    | Some(DialectType::Fabric)
24097                                    | Some(DialectType::BigQuery)
24098                                    | Some(DialectType::SQLite)
24099                                    | Some(DialectType::Snowflake) => {
24100                                        self.write_keyword("DATETIME");
24101                                        if let Some(args) = _args_str {
24102                                            self.write(args);
24103                                        }
24104                                    }
24105                                    Some(_) => {
24106                                        // Only map to TIMESTAMP when we have a specific target dialect
24107                                        self.write_keyword("TIMESTAMP");
24108                                        if let Some(args) = _args_str {
24109                                            self.write(args);
24110                                        }
24111                                    }
24112                                    None => {
24113                                        // No dialect - preserve original
24114                                        self.write(name);
24115                                    }
24116                                }
24117                            }
24118                            // VARCHAR2/NVARCHAR2 (Oracle) -> VARCHAR for non-Oracle targets
24119                            "VARCHAR2"
24120                                if !matches!(self.config.dialect, Some(DialectType::Oracle)) =>
24121                            {
24122                                match self.config.dialect {
24123                                    Some(DialectType::DuckDB) | Some(DialectType::SQLite) => {
24124                                        self.write_keyword("TEXT");
24125                                    }
24126                                    Some(DialectType::Hive)
24127                                    | Some(DialectType::Spark)
24128                                    | Some(DialectType::Databricks)
24129                                    | Some(DialectType::BigQuery)
24130                                    | Some(DialectType::ClickHouse)
24131                                    | Some(DialectType::StarRocks)
24132                                    | Some(DialectType::Doris) => {
24133                                        self.write_keyword("STRING");
24134                                    }
24135                                    _ => {
24136                                        self.write_keyword("VARCHAR");
24137                                        if let Some(args) = _args_str {
24138                                            self.write(args);
24139                                        }
24140                                    }
24141                                }
24142                            }
24143                            "NVARCHAR2"
24144                                if !matches!(self.config.dialect, Some(DialectType::Oracle)) =>
24145                            {
24146                                match self.config.dialect {
24147                                    Some(DialectType::DuckDB) | Some(DialectType::SQLite) => {
24148                                        self.write_keyword("TEXT");
24149                                    }
24150                                    Some(DialectType::Hive)
24151                                    | Some(DialectType::Spark)
24152                                    | Some(DialectType::Databricks)
24153                                    | Some(DialectType::BigQuery)
24154                                    | Some(DialectType::ClickHouse)
24155                                    | Some(DialectType::StarRocks)
24156                                    | Some(DialectType::Doris) => {
24157                                        self.write_keyword("STRING");
24158                                    }
24159                                    _ => {
24160                                        self.write_keyword("VARCHAR");
24161                                        if let Some(args) = _args_str {
24162                                            self.write(args);
24163                                        }
24164                                    }
24165                                }
24166                            }
24167                            _ => self.write(name),
24168                        }
24169                    }
24170                }
24171            }
24172            DataType::Geometry { subtype, srid } => {
24173                // Dialect-specific geometry type mappings
24174                match self.config.dialect {
24175                    Some(DialectType::MySQL) => {
24176                        // MySQL uses POINT SRID 4326 syntax for specific types
24177                        if let Some(sub) = subtype {
24178                            self.write_keyword(sub);
24179                            if let Some(s) = srid {
24180                                self.write(" SRID ");
24181                                self.write(&s.to_string());
24182                            }
24183                        } else {
24184                            self.write_keyword("GEOMETRY");
24185                        }
24186                    }
24187                    Some(DialectType::BigQuery) => {
24188                        // BigQuery only supports GEOGRAPHY, not GEOMETRY
24189                        self.write_keyword("GEOGRAPHY");
24190                    }
24191                    Some(DialectType::Teradata) => {
24192                        // Teradata uses ST_GEOMETRY
24193                        self.write_keyword("ST_GEOMETRY");
24194                        if subtype.is_some() || srid.is_some() {
24195                            self.write("(");
24196                            if let Some(sub) = subtype {
24197                                self.write_keyword(sub);
24198                            }
24199                            if let Some(s) = srid {
24200                                if subtype.is_some() {
24201                                    self.write(", ");
24202                                }
24203                                self.write(&s.to_string());
24204                            }
24205                            self.write(")");
24206                        }
24207                    }
24208                    _ => {
24209                        // PostgreSQL, Snowflake, DuckDB use GEOMETRY(subtype, srid) syntax
24210                        self.write_keyword("GEOMETRY");
24211                        if subtype.is_some() || srid.is_some() {
24212                            self.write("(");
24213                            if let Some(sub) = subtype {
24214                                self.write_keyword(sub);
24215                            }
24216                            if let Some(s) = srid {
24217                                if subtype.is_some() {
24218                                    self.write(", ");
24219                                }
24220                                self.write(&s.to_string());
24221                            }
24222                            self.write(")");
24223                        }
24224                    }
24225                }
24226            }
24227            DataType::Geography { subtype, srid } => {
24228                // Dialect-specific geography type mappings
24229                match self.config.dialect {
24230                    Some(DialectType::MySQL) => {
24231                        // MySQL doesn't have native GEOGRAPHY, use GEOMETRY with SRID 4326
24232                        if let Some(sub) = subtype {
24233                            self.write_keyword(sub);
24234                        } else {
24235                            self.write_keyword("GEOMETRY");
24236                        }
24237                        // Geography implies SRID 4326 (WGS84)
24238                        let effective_srid = srid.unwrap_or(4326);
24239                        self.write(" SRID ");
24240                        self.write(&effective_srid.to_string());
24241                    }
24242                    Some(DialectType::BigQuery) => {
24243                        // BigQuery uses simple GEOGRAPHY without parameters
24244                        self.write_keyword("GEOGRAPHY");
24245                    }
24246                    Some(DialectType::Snowflake) => {
24247                        // Snowflake uses GEOGRAPHY without parameters
24248                        self.write_keyword("GEOGRAPHY");
24249                    }
24250                    _ => {
24251                        // PostgreSQL uses GEOGRAPHY(subtype, srid) syntax
24252                        self.write_keyword("GEOGRAPHY");
24253                        if subtype.is_some() || srid.is_some() {
24254                            self.write("(");
24255                            if let Some(sub) = subtype {
24256                                self.write_keyword(sub);
24257                            }
24258                            if let Some(s) = srid {
24259                                if subtype.is_some() {
24260                                    self.write(", ");
24261                                }
24262                                self.write(&s.to_string());
24263                            }
24264                            self.write(")");
24265                        }
24266                    }
24267                }
24268            }
24269            DataType::CharacterSet { name } => {
24270                // For MySQL CONVERT USING - output as CHAR CHARACTER SET name
24271                self.write_keyword("CHAR CHARACTER SET ");
24272                self.write(name);
24273            }
24274            _ => self.write("UNKNOWN"),
24275        }
24276        Ok(())
24277    }
24278
24279    // === Helper methods ===
24280
24281    #[inline]
24282    fn write(&mut self, s: &str) {
24283        self.output.push_str(s);
24284    }
24285
24286    #[inline]
24287    fn write_space(&mut self) {
24288        self.output.push(' ');
24289    }
24290
24291    #[inline]
24292    fn write_keyword(&mut self, keyword: &str) {
24293        if self.config.uppercase_keywords {
24294            self.output.push_str(keyword);
24295        } else {
24296            for b in keyword.bytes() {
24297                self.output.push(b.to_ascii_lowercase() as char);
24298            }
24299        }
24300    }
24301
24302    /// Write a function name respecting the normalize_functions config setting
24303    fn write_func_name(&mut self, name: &str) {
24304        let normalized = self.normalize_func_name(name);
24305        self.output.push_str(normalized.as_ref());
24306    }
24307
24308    /// Convert strptime format string to Exasol format string
24309    /// Exasol TIME_MAPPING (reverse of Python sqlglot):
24310    /// %Y -> YYYY, %y -> YY, %m -> MM, %d -> DD, %H -> HH, %M -> MI, %S -> SS, %a -> DY
24311    fn convert_strptime_to_exasol_format(format: &str) -> String {
24312        let mut result = String::new();
24313        let chars: Vec<char> = format.chars().collect();
24314        let mut i = 0;
24315        while i < chars.len() {
24316            if chars[i] == '%' && i + 1 < chars.len() {
24317                let spec = chars[i + 1];
24318                let exasol_spec = match spec {
24319                    'Y' => "YYYY",
24320                    'y' => "YY",
24321                    'm' => "MM",
24322                    'd' => "DD",
24323                    'H' => "HH",
24324                    'M' => "MI",
24325                    'S' => "SS",
24326                    'a' => "DY",    // abbreviated weekday name
24327                    'A' => "DAY",   // full weekday name
24328                    'b' => "MON",   // abbreviated month name
24329                    'B' => "MONTH", // full month name
24330                    'I' => "H12",   // 12-hour format
24331                    'u' => "ID",    // ISO weekday (1-7)
24332                    'V' => "IW",    // ISO week number
24333                    'G' => "IYYY",  // ISO year
24334                    'W' => "UW",    // Week number (Monday as first day)
24335                    'U' => "UW",    // Week number (Sunday as first day)
24336                    'z' => "Z",     // timezone offset
24337                    _ => {
24338                        // Unknown specifier, keep as-is
24339                        result.push('%');
24340                        result.push(spec);
24341                        i += 2;
24342                        continue;
24343                    }
24344                };
24345                result.push_str(exasol_spec);
24346                i += 2;
24347            } else {
24348                result.push(chars[i]);
24349                i += 1;
24350            }
24351        }
24352        result
24353    }
24354
24355    /// Convert strptime format string to PostgreSQL/Redshift format string
24356    /// PostgreSQL INVERSE_TIME_MAPPING from Python sqlglot:
24357    /// %Y -> YYYY, %y -> YY, %m -> MM, %d -> DD, %H -> HH24, %M -> MI, %S -> SS, %f -> US, etc.
24358    fn convert_strptime_to_postgres_format(format: &str) -> String {
24359        let mut result = String::new();
24360        let chars: Vec<char> = format.chars().collect();
24361        let mut i = 0;
24362        while i < chars.len() {
24363            if chars[i] == '%' && i + 1 < chars.len() {
24364                // Check for %-d, %-m, etc. (non-padded, 3-char sequence)
24365                if chars[i + 1] == '-' && i + 2 < chars.len() {
24366                    let spec = chars[i + 2];
24367                    let pg_spec = match spec {
24368                        'd' => "FMDD",
24369                        'm' => "FMMM",
24370                        'H' => "FMHH24",
24371                        'M' => "FMMI",
24372                        'S' => "FMSS",
24373                        _ => {
24374                            result.push('%');
24375                            result.push('-');
24376                            result.push(spec);
24377                            i += 3;
24378                            continue;
24379                        }
24380                    };
24381                    result.push_str(pg_spec);
24382                    i += 3;
24383                    continue;
24384                }
24385                let spec = chars[i + 1];
24386                let pg_spec = match spec {
24387                    'Y' => "YYYY",
24388                    'y' => "YY",
24389                    'm' => "MM",
24390                    'd' => "DD",
24391                    'H' => "HH24",
24392                    'I' => "HH12",
24393                    'M' => "MI",
24394                    'S' => "SS",
24395                    'f' => "US",      // microseconds
24396                    'u' => "D",       // day of week (1=Monday)
24397                    'j' => "DDD",     // day of year
24398                    'z' => "OF",      // UTC offset
24399                    'Z' => "TZ",      // timezone name
24400                    'A' => "TMDay",   // full weekday name
24401                    'a' => "TMDy",    // abbreviated weekday name
24402                    'b' => "TMMon",   // abbreviated month name
24403                    'B' => "TMMonth", // full month name
24404                    'U' => "WW",      // week number
24405                    _ => {
24406                        // Unknown specifier, keep as-is
24407                        result.push('%');
24408                        result.push(spec);
24409                        i += 2;
24410                        continue;
24411                    }
24412                };
24413                result.push_str(pg_spec);
24414                i += 2;
24415            } else {
24416                result.push(chars[i]);
24417                i += 1;
24418            }
24419        }
24420        result
24421    }
24422
24423    /// Write a LIMIT expression value, evaluating constant expressions if limit_only_literals is set
24424    fn write_limit_expr(&mut self, expr: &Expression) -> Result<()> {
24425        if self.config.limit_only_literals {
24426            if let Some(value) = Self::try_evaluate_constant(expr) {
24427                self.write(&value.to_string());
24428                return Ok(());
24429            }
24430        }
24431        self.generate_expression(expr)
24432    }
24433
24434    /// Format a comment with proper spacing.
24435    /// Converts `/*text*/` to `/* text */` (adding internal spaces if not present).
24436    /// Python SQLGlot normalizes comment format to have spaces inside block comments.
24437    fn write_formatted_comment(&mut self, comment: &str) {
24438        // Normalize all comments to block comment format /* ... */
24439        // This matches Python sqlglot behavior which always outputs block comments
24440        let content = if comment.starts_with("/*") && comment.ends_with("*/") {
24441            // Already block comment - extract inner content
24442            // Preserve internal whitespace, but ensure at least one space padding
24443            &comment[2..comment.len() - 2]
24444        } else if comment.starts_with("--") {
24445            // Line comment - extract content after --
24446            // Preserve internal whitespace (e.g., "--       x" -> "/*       x */")
24447            &comment[2..]
24448        } else {
24449            // Raw content (no delimiters)
24450            comment
24451        };
24452        // Skip empty comments (e.g., bare "--" with no content)
24453        if content.trim().is_empty() {
24454            return;
24455        }
24456        // Escape nested block comment markers to prevent premature closure or unintended nesting.
24457        // This matches Python sqlglot's sanitize_comment behavior.
24458        let sanitized = content.replace("*/", "* /").replace("/*", "/ *");
24459        let content = &sanitized;
24460        // Ensure at least one space after /* and before */
24461        self.output.push_str("/*");
24462        if !content.starts_with(' ') {
24463            self.output.push(' ');
24464        }
24465        self.output.push_str(content);
24466        if !content.ends_with(' ') {
24467            self.output.push(' ');
24468        }
24469        self.output.push_str("*/");
24470    }
24471
24472    /// Escape a raw block content (from dollar-quoted string) for single-quoted output.
24473    /// Escapes single quotes with backslash, and for Snowflake also escapes backslashes.
24474    fn escape_block_for_single_quote(&self, block: &str) -> String {
24475        let escape_backslash = matches!(
24476            self.config.dialect,
24477            Some(crate::dialects::DialectType::Snowflake)
24478        );
24479        let mut escaped = String::with_capacity(block.len() + 4);
24480        for ch in block.chars() {
24481            if ch == '\'' {
24482                escaped.push('\\');
24483                escaped.push('\'');
24484            } else if escape_backslash && ch == '\\' {
24485                escaped.push('\\');
24486                escaped.push('\\');
24487            } else {
24488                escaped.push(ch);
24489            }
24490        }
24491        escaped
24492    }
24493
24494    fn write_newline(&mut self) {
24495        self.output.push('\n');
24496    }
24497
24498    fn write_indent(&mut self) {
24499        for _ in 0..self.indent_level {
24500            self.output.push_str(self.config.indent);
24501        }
24502    }
24503
24504    // === SQLGlot-style pretty printing helpers ===
24505
24506    /// Returns the separator string for pretty printing.
24507    /// Check if the total length of arguments exceeds max_text_width.
24508    /// Used for dynamic line breaking in expressions() formatting.
24509    fn too_wide(&self, args: &[String]) -> bool {
24510        args.iter().map(|s| s.len()).sum::<usize>() > self.config.max_text_width
24511    }
24512
24513    /// Generate an expression to a string using a temporary non-pretty generator.
24514    /// Useful for width calculations before deciding on formatting.
24515    fn generate_to_string(&self, expr: &Expression) -> Result<String> {
24516        let config = GeneratorConfig {
24517            pretty: false,
24518            dialect: self.config.dialect,
24519            ..Default::default()
24520        };
24521        let mut gen = Generator::with_config(config);
24522        gen.generate_expression(expr)?;
24523        Ok(gen.output)
24524    }
24525
24526    /// Writes a clause with a single condition (WHERE, HAVING, QUALIFY).
24527    /// In pretty mode: newline + indented keyword + newline + indented condition
24528    fn write_clause_condition(&mut self, keyword: &str, condition: &Expression) -> Result<()> {
24529        if self.config.pretty {
24530            self.write_newline();
24531            self.write_indent();
24532            self.write_keyword(keyword);
24533            self.write_newline();
24534            self.indent_level += 1;
24535            self.write_indent();
24536            self.generate_expression(condition)?;
24537            self.indent_level -= 1;
24538        } else {
24539            self.write_space();
24540            self.write_keyword(keyword);
24541            self.write_space();
24542            self.generate_expression(condition)?;
24543        }
24544        Ok(())
24545    }
24546
24547    /// Writes a clause with a list of expressions (GROUP BY, DISTRIBUTE BY, CLUSTER BY).
24548    /// In pretty mode: each expression on new line with indentation
24549    fn write_clause_expressions(&mut self, keyword: &str, exprs: &[Expression]) -> Result<()> {
24550        if exprs.is_empty() {
24551            return Ok(());
24552        }
24553
24554        if self.config.pretty {
24555            self.write_newline();
24556            self.write_indent();
24557            self.write_keyword(keyword);
24558            self.write_newline();
24559            self.indent_level += 1;
24560            for (i, expr) in exprs.iter().enumerate() {
24561                if i > 0 {
24562                    self.write(",");
24563                    self.write_newline();
24564                }
24565                self.write_indent();
24566                self.generate_expression(expr)?;
24567            }
24568            self.indent_level -= 1;
24569        } else {
24570            self.write_space();
24571            self.write_keyword(keyword);
24572            self.write_space();
24573            for (i, expr) in exprs.iter().enumerate() {
24574                if i > 0 {
24575                    self.write(", ");
24576                }
24577                self.generate_expression(expr)?;
24578            }
24579        }
24580        Ok(())
24581    }
24582
24583    /// Writes ORDER BY / SORT BY clause with Ordered expressions
24584    fn write_order_clause(&mut self, keyword: &str, orderings: &[Ordered]) -> Result<()> {
24585        if orderings.is_empty() {
24586            return Ok(());
24587        }
24588
24589        if self.config.pretty {
24590            self.write_newline();
24591            self.write_indent();
24592            self.write_keyword(keyword);
24593            self.write_newline();
24594            self.indent_level += 1;
24595            for (i, ordered) in orderings.iter().enumerate() {
24596                if i > 0 {
24597                    self.write(",");
24598                    self.write_newline();
24599                }
24600                self.write_indent();
24601                self.generate_ordered(ordered)?;
24602            }
24603            self.indent_level -= 1;
24604        } else {
24605            self.write_space();
24606            self.write_keyword(keyword);
24607            self.write_space();
24608            for (i, ordered) in orderings.iter().enumerate() {
24609                if i > 0 {
24610                    self.write(", ");
24611                }
24612                self.generate_ordered(ordered)?;
24613            }
24614        }
24615        Ok(())
24616    }
24617
24618    /// Writes WINDOW clause with named window definitions
24619    fn write_window_clause(&mut self, windows: &[NamedWindow]) -> Result<()> {
24620        if windows.is_empty() {
24621            return Ok(());
24622        }
24623
24624        if self.config.pretty {
24625            self.write_newline();
24626            self.write_indent();
24627            self.write_keyword("WINDOW");
24628            self.write_newline();
24629            self.indent_level += 1;
24630            for (i, named_window) in windows.iter().enumerate() {
24631                if i > 0 {
24632                    self.write(",");
24633                    self.write_newline();
24634                }
24635                self.write_indent();
24636                self.generate_identifier(&named_window.name)?;
24637                self.write_space();
24638                self.write_keyword("AS");
24639                self.write(" (");
24640                self.generate_over(&named_window.spec)?;
24641                self.write(")");
24642            }
24643            self.indent_level -= 1;
24644        } else {
24645            self.write_space();
24646            self.write_keyword("WINDOW");
24647            self.write_space();
24648            for (i, named_window) in windows.iter().enumerate() {
24649                if i > 0 {
24650                    self.write(", ");
24651                }
24652                self.generate_identifier(&named_window.name)?;
24653                self.write_space();
24654                self.write_keyword("AS");
24655                self.write(" (");
24656                self.generate_over(&named_window.spec)?;
24657                self.write(")");
24658            }
24659        }
24660        Ok(())
24661    }
24662
24663    // === BATCH-GENERATED STUB METHODS (481 variants) ===
24664    fn generate_ai_agg(&mut self, e: &AIAgg) -> Result<()> {
24665        // AI_AGG(this, expression)
24666        self.write_keyword("AI_AGG");
24667        self.write("(");
24668        self.generate_expression(&e.this)?;
24669        self.write(", ");
24670        self.generate_expression(&e.expression)?;
24671        self.write(")");
24672        Ok(())
24673    }
24674
24675    fn generate_ai_classify(&mut self, e: &AIClassify) -> Result<()> {
24676        // AI_CLASSIFY(input, [categories], [config])
24677        self.write_keyword("AI_CLASSIFY");
24678        self.write("(");
24679        self.generate_expression(&e.this)?;
24680        if let Some(categories) = &e.categories {
24681            self.write(", ");
24682            self.generate_expression(categories)?;
24683        }
24684        if let Some(config) = &e.config {
24685            self.write(", ");
24686            self.generate_expression(config)?;
24687        }
24688        self.write(")");
24689        Ok(())
24690    }
24691
24692    fn generate_add_partition(&mut self, e: &AddPartition) -> Result<()> {
24693        // Python: return f"ADD {exists}{self.sql(expression.this)}{location}"
24694        self.write_keyword("ADD");
24695        self.write_space();
24696        if e.exists {
24697            self.write_keyword("IF NOT EXISTS");
24698            self.write_space();
24699        }
24700        self.generate_expression(&e.this)?;
24701        if let Some(location) = &e.location {
24702            self.write_space();
24703            self.generate_expression(location)?;
24704        }
24705        Ok(())
24706    }
24707
24708    fn generate_algorithm_property(&mut self, e: &AlgorithmProperty) -> Result<()> {
24709        // Python: return f"ALGORITHM={self.sql(expression, 'this')}"
24710        self.write_keyword("ALGORITHM");
24711        self.write("=");
24712        self.generate_expression(&e.this)?;
24713        Ok(())
24714    }
24715
24716    fn generate_aliases(&mut self, e: &Aliases) -> Result<()> {
24717        // Python: return f"{self.sql(expression, 'this')} AS ({self.expressions(expression, flat=True)})"
24718        self.generate_expression(&e.this)?;
24719        self.write_space();
24720        self.write_keyword("AS");
24721        self.write(" (");
24722        for (i, expr) in e.expressions.iter().enumerate() {
24723            if i > 0 {
24724                self.write(", ");
24725            }
24726            self.generate_expression(expr)?;
24727        }
24728        self.write(")");
24729        Ok(())
24730    }
24731
24732    fn generate_allowed_values_property(&mut self, e: &AllowedValuesProperty) -> Result<()> {
24733        // Python: return f"ALLOWED_VALUES {self.expressions(e, flat=True)}"
24734        self.write_keyword("ALLOWED_VALUES");
24735        self.write_space();
24736        for (i, expr) in e.expressions.iter().enumerate() {
24737            if i > 0 {
24738                self.write(", ");
24739            }
24740            self.generate_expression(expr)?;
24741        }
24742        Ok(())
24743    }
24744
24745    fn generate_alter_column(&mut self, e: &AlterColumn) -> Result<()> {
24746        // Python: complex logic based on dtype, default, comment, visible, etc.
24747        self.write_keyword("ALTER COLUMN");
24748        self.write_space();
24749        self.generate_expression(&e.this)?;
24750
24751        if let Some(dtype) = &e.dtype {
24752            self.write_space();
24753            self.write_keyword("SET DATA TYPE");
24754            self.write_space();
24755            self.generate_expression(dtype)?;
24756            if let Some(collate) = &e.collate {
24757                self.write_space();
24758                self.write_keyword("COLLATE");
24759                self.write_space();
24760                self.generate_expression(collate)?;
24761            }
24762            if let Some(using) = &e.using {
24763                self.write_space();
24764                self.write_keyword("USING");
24765                self.write_space();
24766                self.generate_expression(using)?;
24767            }
24768        } else if let Some(default) = &e.default {
24769            self.write_space();
24770            self.write_keyword("SET DEFAULT");
24771            self.write_space();
24772            self.generate_expression(default)?;
24773        } else if let Some(comment) = &e.comment {
24774            self.write_space();
24775            self.write_keyword("COMMENT");
24776            self.write_space();
24777            self.generate_expression(comment)?;
24778        } else if let Some(drop) = &e.drop {
24779            self.write_space();
24780            self.write_keyword("DROP");
24781            self.write_space();
24782            self.generate_expression(drop)?;
24783        } else if let Some(visible) = &e.visible {
24784            self.write_space();
24785            self.generate_expression(visible)?;
24786        } else if let Some(rename_to) = &e.rename_to {
24787            self.write_space();
24788            self.write_keyword("RENAME TO");
24789            self.write_space();
24790            self.generate_expression(rename_to)?;
24791        } else if let Some(allow_null) = &e.allow_null {
24792            self.write_space();
24793            self.generate_expression(allow_null)?;
24794        }
24795        Ok(())
24796    }
24797
24798    fn generate_alter_session(&mut self, e: &AlterSession) -> Result<()> {
24799        // Python: keyword = "UNSET" if expression.args.get("unset") else "SET"; return f"{keyword} {items_sql}"
24800        self.write_keyword("ALTER SESSION");
24801        self.write_space();
24802        if e.unset.is_some() {
24803            self.write_keyword("UNSET");
24804        } else {
24805            self.write_keyword("SET");
24806        }
24807        self.write_space();
24808        for (i, expr) in e.expressions.iter().enumerate() {
24809            if i > 0 {
24810                self.write(", ");
24811            }
24812            self.generate_expression(expr)?;
24813        }
24814        Ok(())
24815    }
24816
24817    fn generate_alter_set(&mut self, e: &AlterSet) -> Result<()> {
24818        // Python (Snowflake): return f"SET{exprs}{file_format}{copy_options}{tag}"
24819        self.write_keyword("SET");
24820
24821        // Generate option (e.g., AUTHORIZATION, LOGGED, UNLOGGED, etc.)
24822        if let Some(opt) = &e.option {
24823            self.write_space();
24824            self.generate_expression(opt)?;
24825        }
24826
24827        // Generate PROPERTIES (for Trino SET PROPERTIES x = y, ...)
24828        // Check if expressions look like property assignments
24829        if !e.expressions.is_empty() {
24830            // Check if this looks like property assignments (for SET PROPERTIES)
24831            let is_properties = e
24832                .expressions
24833                .iter()
24834                .any(|expr| matches!(expr, Expression::Eq(_)));
24835            if is_properties && e.option.is_none() {
24836                self.write_space();
24837                self.write_keyword("PROPERTIES");
24838            }
24839            self.write_space();
24840            for (i, expr) in e.expressions.iter().enumerate() {
24841                if i > 0 {
24842                    self.write(", ");
24843                }
24844                self.generate_expression(expr)?;
24845            }
24846        }
24847
24848        // Generate STAGE_FILE_FORMAT = (...) with space-separated properties
24849        if let Some(file_format) = &e.file_format {
24850            self.write(" ");
24851            self.write_keyword("STAGE_FILE_FORMAT");
24852            self.write(" = (");
24853            self.generate_space_separated_properties(file_format)?;
24854            self.write(")");
24855        }
24856
24857        // Generate STAGE_COPY_OPTIONS = (...) with space-separated properties
24858        if let Some(copy_options) = &e.copy_options {
24859            self.write(" ");
24860            self.write_keyword("STAGE_COPY_OPTIONS");
24861            self.write(" = (");
24862            self.generate_space_separated_properties(copy_options)?;
24863            self.write(")");
24864        }
24865
24866        // Generate TAG ...
24867        if let Some(tag) = &e.tag {
24868            self.write(" ");
24869            self.write_keyword("TAG");
24870            self.write(" ");
24871            self.generate_expression(tag)?;
24872        }
24873
24874        Ok(())
24875    }
24876
24877    /// Generate space-separated properties (for Snowflake STAGE_FILE_FORMAT, etc.)
24878    fn generate_space_separated_properties(&mut self, expr: &Expression) -> Result<()> {
24879        match expr {
24880            Expression::Tuple(t) => {
24881                for (i, prop) in t.expressions.iter().enumerate() {
24882                    if i > 0 {
24883                        self.write(" ");
24884                    }
24885                    self.generate_expression(prop)?;
24886                }
24887            }
24888            _ => {
24889                self.generate_expression(expr)?;
24890            }
24891        }
24892        Ok(())
24893    }
24894
24895    fn generate_alter_sort_key(&mut self, e: &AlterSortKey) -> Result<()> {
24896        // Python: return f"ALTER{compound} SORTKEY {this or expressions}"
24897        self.write_keyword("ALTER");
24898        if e.compound.is_some() {
24899            self.write_space();
24900            self.write_keyword("COMPOUND");
24901        }
24902        self.write_space();
24903        self.write_keyword("SORTKEY");
24904        self.write_space();
24905        if let Some(this) = &e.this {
24906            self.generate_expression(this)?;
24907        } else if !e.expressions.is_empty() {
24908            self.write("(");
24909            for (i, expr) in e.expressions.iter().enumerate() {
24910                if i > 0 {
24911                    self.write(", ");
24912                }
24913                self.generate_expression(expr)?;
24914            }
24915            self.write(")");
24916        }
24917        Ok(())
24918    }
24919
24920    fn generate_analyze(&mut self, e: &Analyze) -> Result<()> {
24921        // Python: return f"ANALYZE{options}{kind}{this}{partition}{mode}{inner_expression}{properties}"
24922        self.write_keyword("ANALYZE");
24923        if !e.options.is_empty() {
24924            self.write_space();
24925            for (i, opt) in e.options.iter().enumerate() {
24926                if i > 0 {
24927                    self.write_space();
24928                }
24929                // Write options as keywords (not identifiers) to avoid quoting reserved words like FULL
24930                if let Expression::Identifier(id) = opt {
24931                    self.write_keyword(&id.name);
24932                } else {
24933                    self.generate_expression(opt)?;
24934                }
24935            }
24936        }
24937        if let Some(kind) = &e.kind {
24938            self.write_space();
24939            self.write_keyword(kind);
24940        }
24941        if let Some(this) = &e.this {
24942            self.write_space();
24943            self.generate_expression(this)?;
24944        }
24945        // Column list: ANALYZE tbl(col1, col2) (PostgreSQL)
24946        if !e.columns.is_empty() {
24947            self.write("(");
24948            for (i, col) in e.columns.iter().enumerate() {
24949                if i > 0 {
24950                    self.write(", ");
24951                }
24952                self.write(col);
24953            }
24954            self.write(")");
24955        }
24956        if let Some(partition) = &e.partition {
24957            self.write_space();
24958            self.generate_expression(partition)?;
24959        }
24960        if let Some(mode) = &e.mode {
24961            self.write_space();
24962            self.generate_expression(mode)?;
24963        }
24964        if let Some(expression) = &e.expression {
24965            self.write_space();
24966            self.generate_expression(expression)?;
24967        }
24968        if !e.properties.is_empty() {
24969            self.write_space();
24970            self.write_keyword(self.config.with_properties_prefix);
24971            self.write(" (");
24972            for (i, prop) in e.properties.iter().enumerate() {
24973                if i > 0 {
24974                    self.write(", ");
24975                }
24976                self.generate_expression(prop)?;
24977            }
24978            self.write(")");
24979        }
24980        Ok(())
24981    }
24982
24983    fn generate_analyze_delete(&mut self, e: &AnalyzeDelete) -> Result<()> {
24984        // Python: return f"DELETE{kind} STATISTICS"
24985        self.write_keyword("DELETE");
24986        if let Some(kind) = &e.kind {
24987            self.write_space();
24988            self.write_keyword(kind);
24989        }
24990        self.write_space();
24991        self.write_keyword("STATISTICS");
24992        Ok(())
24993    }
24994
24995    fn generate_analyze_histogram(&mut self, e: &AnalyzeHistogram) -> Result<()> {
24996        // Python: return f"{this} HISTOGRAM ON {columns}{inner_expression}{update_options}"
24997        // Write `this` (UPDATE or DROP) as keyword to avoid quoting reserved words
24998        if let Expression::Identifier(id) = e.this.as_ref() {
24999            self.write_keyword(&id.name);
25000        } else {
25001            self.generate_expression(&e.this)?;
25002        }
25003        self.write_space();
25004        self.write_keyword("HISTOGRAM ON");
25005        self.write_space();
25006        for (i, expr) in e.expressions.iter().enumerate() {
25007            if i > 0 {
25008                self.write(", ");
25009            }
25010            self.generate_expression(expr)?;
25011        }
25012        if let Some(expression) = &e.expression {
25013            self.write_space();
25014            self.generate_expression(expression)?;
25015        }
25016        if let Some(update_options) = &e.update_options {
25017            self.write_space();
25018            self.generate_expression(update_options)?;
25019            self.write_space();
25020            self.write_keyword("UPDATE");
25021        }
25022        Ok(())
25023    }
25024
25025    fn generate_analyze_list_chained_rows(&mut self, e: &AnalyzeListChainedRows) -> Result<()> {
25026        // Python: return f"LIST CHAINED ROWS{inner_expression}"
25027        self.write_keyword("LIST CHAINED ROWS");
25028        if let Some(expression) = &e.expression {
25029            self.write_space();
25030            self.write_keyword("INTO");
25031            self.write_space();
25032            self.generate_expression(expression)?;
25033        }
25034        Ok(())
25035    }
25036
25037    fn generate_analyze_sample(&mut self, e: &AnalyzeSample) -> Result<()> {
25038        // Python: return f"SAMPLE {sample} {kind}"
25039        self.write_keyword("SAMPLE");
25040        self.write_space();
25041        if let Some(sample) = &e.sample {
25042            self.generate_expression(sample)?;
25043            self.write_space();
25044        }
25045        self.write_keyword(&e.kind);
25046        Ok(())
25047    }
25048
25049    fn generate_analyze_statistics(&mut self, e: &AnalyzeStatistics) -> Result<()> {
25050        // Python: return f"{kind}{option} STATISTICS{this}{columns}"
25051        self.write_keyword(&e.kind);
25052        if let Some(option) = &e.option {
25053            self.write_space();
25054            self.generate_expression(option)?;
25055        }
25056        self.write_space();
25057        self.write_keyword("STATISTICS");
25058        if let Some(this) = &e.this {
25059            self.write_space();
25060            self.generate_expression(this)?;
25061        }
25062        if !e.expressions.is_empty() {
25063            self.write_space();
25064            for (i, expr) in e.expressions.iter().enumerate() {
25065                if i > 0 {
25066                    self.write(", ");
25067                }
25068                self.generate_expression(expr)?;
25069            }
25070        }
25071        Ok(())
25072    }
25073
25074    fn generate_analyze_validate(&mut self, e: &AnalyzeValidate) -> Result<()> {
25075        // Python: return f"VALIDATE {kind}{this}{inner_expression}"
25076        self.write_keyword("VALIDATE");
25077        self.write_space();
25078        self.write_keyword(&e.kind);
25079        if let Some(this) = &e.this {
25080            self.write_space();
25081            // this is a keyword string like "UPDATE", "CASCADE FAST", etc. - write as keywords
25082            if let Expression::Identifier(id) = this.as_ref() {
25083                self.write_keyword(&id.name);
25084            } else {
25085                self.generate_expression(this)?;
25086            }
25087        }
25088        if let Some(expression) = &e.expression {
25089            self.write_space();
25090            self.write_keyword("INTO");
25091            self.write_space();
25092            self.generate_expression(expression)?;
25093        }
25094        Ok(())
25095    }
25096
25097    fn generate_analyze_with(&mut self, e: &AnalyzeWith) -> Result<()> {
25098        // Python: return f"WITH {expressions}"
25099        self.write_keyword("WITH");
25100        self.write_space();
25101        for (i, expr) in e.expressions.iter().enumerate() {
25102            if i > 0 {
25103                self.write(", ");
25104            }
25105            self.generate_expression(expr)?;
25106        }
25107        Ok(())
25108    }
25109
25110    fn generate_anonymous(&mut self, e: &Anonymous) -> Result<()> {
25111        // Anonymous represents a generic function call: FUNC_NAME(args...)
25112        // Python: return self.func(self.sql(expression, "this"), *expression.expressions)
25113        self.generate_expression(&e.this)?;
25114        self.write("(");
25115        for (i, arg) in e.expressions.iter().enumerate() {
25116            if i > 0 {
25117                self.write(", ");
25118            }
25119            self.generate_expression(arg)?;
25120        }
25121        self.write(")");
25122        Ok(())
25123    }
25124
25125    fn generate_anonymous_agg_func(&mut self, e: &AnonymousAggFunc) -> Result<()> {
25126        // Same as Anonymous but for aggregate functions
25127        self.generate_expression(&e.this)?;
25128        self.write("(");
25129        for (i, arg) in e.expressions.iter().enumerate() {
25130            if i > 0 {
25131                self.write(", ");
25132            }
25133            self.generate_expression(arg)?;
25134        }
25135        self.write(")");
25136        Ok(())
25137    }
25138
25139    fn generate_apply(&mut self, e: &Apply) -> Result<()> {
25140        // Python: return f"{this} APPLY({expr})"
25141        self.generate_expression(&e.this)?;
25142        self.write_space();
25143        self.write_keyword("APPLY");
25144        self.write("(");
25145        self.generate_expression(&e.expression)?;
25146        self.write(")");
25147        Ok(())
25148    }
25149
25150    fn generate_approx_percentile_estimate(&mut self, e: &ApproxPercentileEstimate) -> Result<()> {
25151        // APPROX_PERCENTILE_ESTIMATE(this, percentile)
25152        self.write_keyword("APPROX_PERCENTILE_ESTIMATE");
25153        self.write("(");
25154        self.generate_expression(&e.this)?;
25155        if let Some(percentile) = &e.percentile {
25156            self.write(", ");
25157            self.generate_expression(percentile)?;
25158        }
25159        self.write(")");
25160        Ok(())
25161    }
25162
25163    fn generate_approx_quantile(&mut self, e: &ApproxQuantile) -> Result<()> {
25164        // APPROX_QUANTILE(this, quantile[, accuracy][, weight])
25165        self.write_keyword("APPROX_QUANTILE");
25166        self.write("(");
25167        self.generate_expression(&e.this)?;
25168        if let Some(quantile) = &e.quantile {
25169            self.write(", ");
25170            self.generate_expression(quantile)?;
25171        }
25172        if let Some(accuracy) = &e.accuracy {
25173            self.write(", ");
25174            self.generate_expression(accuracy)?;
25175        }
25176        if let Some(weight) = &e.weight {
25177            self.write(", ");
25178            self.generate_expression(weight)?;
25179        }
25180        self.write(")");
25181        Ok(())
25182    }
25183
25184    fn generate_approx_quantiles(&mut self, e: &ApproxQuantiles) -> Result<()> {
25185        // APPROX_QUANTILES(this, expression)
25186        self.write_keyword("APPROX_QUANTILES");
25187        self.write("(");
25188        self.generate_expression(&e.this)?;
25189        if let Some(expression) = &e.expression {
25190            self.write(", ");
25191            self.generate_expression(expression)?;
25192        }
25193        self.write(")");
25194        Ok(())
25195    }
25196
25197    fn generate_approx_top_k(&mut self, e: &ApproxTopK) -> Result<()> {
25198        // APPROX_TOP_K(this[, expression][, counters])
25199        self.write_keyword("APPROX_TOP_K");
25200        self.write("(");
25201        self.generate_expression(&e.this)?;
25202        if let Some(expression) = &e.expression {
25203            self.write(", ");
25204            self.generate_expression(expression)?;
25205        }
25206        if let Some(counters) = &e.counters {
25207            self.write(", ");
25208            self.generate_expression(counters)?;
25209        }
25210        self.write(")");
25211        Ok(())
25212    }
25213
25214    fn generate_approx_top_k_accumulate(&mut self, e: &ApproxTopKAccumulate) -> Result<()> {
25215        // APPROX_TOP_K_ACCUMULATE(this[, expression])
25216        self.write_keyword("APPROX_TOP_K_ACCUMULATE");
25217        self.write("(");
25218        self.generate_expression(&e.this)?;
25219        if let Some(expression) = &e.expression {
25220            self.write(", ");
25221            self.generate_expression(expression)?;
25222        }
25223        self.write(")");
25224        Ok(())
25225    }
25226
25227    fn generate_approx_top_k_combine(&mut self, e: &ApproxTopKCombine) -> Result<()> {
25228        // APPROX_TOP_K_COMBINE(this[, expression])
25229        self.write_keyword("APPROX_TOP_K_COMBINE");
25230        self.write("(");
25231        self.generate_expression(&e.this)?;
25232        if let Some(expression) = &e.expression {
25233            self.write(", ");
25234            self.generate_expression(expression)?;
25235        }
25236        self.write(")");
25237        Ok(())
25238    }
25239
25240    fn generate_approx_top_k_estimate(&mut self, e: &ApproxTopKEstimate) -> Result<()> {
25241        // APPROX_TOP_K_ESTIMATE(this[, expression])
25242        self.write_keyword("APPROX_TOP_K_ESTIMATE");
25243        self.write("(");
25244        self.generate_expression(&e.this)?;
25245        if let Some(expression) = &e.expression {
25246            self.write(", ");
25247            self.generate_expression(expression)?;
25248        }
25249        self.write(")");
25250        Ok(())
25251    }
25252
25253    fn generate_approx_top_sum(&mut self, e: &ApproxTopSum) -> Result<()> {
25254        // APPROX_TOP_SUM(this, expression[, count])
25255        self.write_keyword("APPROX_TOP_SUM");
25256        self.write("(");
25257        self.generate_expression(&e.this)?;
25258        self.write(", ");
25259        self.generate_expression(&e.expression)?;
25260        if let Some(count) = &e.count {
25261            self.write(", ");
25262            self.generate_expression(count)?;
25263        }
25264        self.write(")");
25265        Ok(())
25266    }
25267
25268    fn generate_arg_max(&mut self, e: &ArgMax) -> Result<()> {
25269        // ARG_MAX(this, expression[, count])
25270        self.write_keyword("ARG_MAX");
25271        self.write("(");
25272        self.generate_expression(&e.this)?;
25273        self.write(", ");
25274        self.generate_expression(&e.expression)?;
25275        if let Some(count) = &e.count {
25276            self.write(", ");
25277            self.generate_expression(count)?;
25278        }
25279        self.write(")");
25280        Ok(())
25281    }
25282
25283    fn generate_arg_min(&mut self, e: &ArgMin) -> Result<()> {
25284        // ARG_MIN(this, expression[, count])
25285        self.write_keyword("ARG_MIN");
25286        self.write("(");
25287        self.generate_expression(&e.this)?;
25288        self.write(", ");
25289        self.generate_expression(&e.expression)?;
25290        if let Some(count) = &e.count {
25291            self.write(", ");
25292            self.generate_expression(count)?;
25293        }
25294        self.write(")");
25295        Ok(())
25296    }
25297
25298    fn generate_array_all(&mut self, e: &ArrayAll) -> Result<()> {
25299        // ARRAY_ALL(this, expression)
25300        self.write_keyword("ARRAY_ALL");
25301        self.write("(");
25302        self.generate_expression(&e.this)?;
25303        self.write(", ");
25304        self.generate_expression(&e.expression)?;
25305        self.write(")");
25306        Ok(())
25307    }
25308
25309    fn generate_array_any(&mut self, e: &ArrayAny) -> Result<()> {
25310        // ARRAY_ANY(this, expression) - fallback implementation
25311        self.write_keyword("ARRAY_ANY");
25312        self.write("(");
25313        self.generate_expression(&e.this)?;
25314        self.write(", ");
25315        self.generate_expression(&e.expression)?;
25316        self.write(")");
25317        Ok(())
25318    }
25319
25320    fn generate_array_construct_compact(&mut self, e: &ArrayConstructCompact) -> Result<()> {
25321        // ARRAY_CONSTRUCT_COMPACT(expressions...)
25322        self.write_keyword("ARRAY_CONSTRUCT_COMPACT");
25323        self.write("(");
25324        for (i, expr) in e.expressions.iter().enumerate() {
25325            if i > 0 {
25326                self.write(", ");
25327            }
25328            self.generate_expression(expr)?;
25329        }
25330        self.write(")");
25331        Ok(())
25332    }
25333
25334    fn generate_array_sum(&mut self, e: &ArraySum) -> Result<()> {
25335        // ARRAY_SUM(this[, expression])
25336        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
25337            self.write("arraySum");
25338        } else {
25339            self.write_keyword("ARRAY_SUM");
25340        }
25341        self.write("(");
25342        self.generate_expression(&e.this)?;
25343        if let Some(expression) = &e.expression {
25344            self.write(", ");
25345            self.generate_expression(expression)?;
25346        }
25347        self.write(")");
25348        Ok(())
25349    }
25350
25351    fn generate_at_index(&mut self, e: &AtIndex) -> Result<()> {
25352        // Python: return f"{this} AT {index}"
25353        self.generate_expression(&e.this)?;
25354        self.write_space();
25355        self.write_keyword("AT");
25356        self.write_space();
25357        self.generate_expression(&e.expression)?;
25358        Ok(())
25359    }
25360
25361    fn generate_attach(&mut self, e: &Attach) -> Result<()> {
25362        // Python: return f"ATTACH{exists_sql} {this}{expressions}"
25363        self.write_keyword("ATTACH");
25364        if e.exists {
25365            self.write_space();
25366            self.write_keyword("IF NOT EXISTS");
25367        }
25368        self.write_space();
25369        self.generate_expression(&e.this)?;
25370        if !e.expressions.is_empty() {
25371            self.write(" (");
25372            for (i, expr) in e.expressions.iter().enumerate() {
25373                if i > 0 {
25374                    self.write(", ");
25375                }
25376                self.generate_expression(expr)?;
25377            }
25378            self.write(")");
25379        }
25380        Ok(())
25381    }
25382
25383    fn generate_attach_option(&mut self, e: &AttachOption) -> Result<()> {
25384        // AttachOption: this [expression]
25385        // Python sqlglot: no equals sign, just space-separated
25386        self.generate_expression(&e.this)?;
25387        if let Some(expression) = &e.expression {
25388            self.write_space();
25389            self.generate_expression(expression)?;
25390        }
25391        Ok(())
25392    }
25393
25394    /// Generate the auto_increment keyword and options for a column definition.
25395    /// Different dialects use different syntax: IDENTITY, AUTOINCREMENT, AUTO_INCREMENT,
25396    /// GENERATED AS IDENTITY, etc.
25397    fn generate_auto_increment_keyword(
25398        &mut self,
25399        col: &crate::expressions::ColumnDef,
25400    ) -> Result<()> {
25401        use crate::dialects::DialectType;
25402        if matches!(self.config.dialect, Some(DialectType::Redshift)) {
25403            self.write_keyword("IDENTITY");
25404            if col.auto_increment_start.is_some() || col.auto_increment_increment.is_some() {
25405                self.write("(");
25406                if let Some(ref start) = col.auto_increment_start {
25407                    self.generate_expression(start)?;
25408                } else {
25409                    self.write("0");
25410                }
25411                self.write(", ");
25412                if let Some(ref inc) = col.auto_increment_increment {
25413                    self.generate_expression(inc)?;
25414                } else {
25415                    self.write("1");
25416                }
25417                self.write(")");
25418            }
25419        } else if matches!(
25420            self.config.dialect,
25421            Some(DialectType::Snowflake) | Some(DialectType::SQLite)
25422        ) {
25423            self.write_keyword("AUTOINCREMENT");
25424            if let Some(ref start) = col.auto_increment_start {
25425                self.write_space();
25426                self.write_keyword("START");
25427                self.write_space();
25428                self.generate_expression(start)?;
25429            }
25430            if let Some(ref inc) = col.auto_increment_increment {
25431                self.write_space();
25432                self.write_keyword("INCREMENT");
25433                self.write_space();
25434                self.generate_expression(inc)?;
25435            }
25436            if let Some(order) = col.auto_increment_order {
25437                self.write_space();
25438                if order {
25439                    self.write_keyword("ORDER");
25440                } else {
25441                    self.write_keyword("NOORDER");
25442                }
25443            }
25444        } else if matches!(self.config.dialect, Some(DialectType::PostgreSQL)) {
25445            self.write_keyword("GENERATED BY DEFAULT AS IDENTITY");
25446            if col.auto_increment_start.is_some() || col.auto_increment_increment.is_some() {
25447                self.write(" (");
25448                let mut first = true;
25449                if let Some(ref start) = col.auto_increment_start {
25450                    self.write_keyword("START WITH");
25451                    self.write_space();
25452                    self.generate_expression(start)?;
25453                    first = false;
25454                }
25455                if let Some(ref inc) = col.auto_increment_increment {
25456                    if !first {
25457                        self.write_space();
25458                    }
25459                    self.write_keyword("INCREMENT BY");
25460                    self.write_space();
25461                    self.generate_expression(inc)?;
25462                }
25463                self.write(")");
25464            }
25465        } else if matches!(self.config.dialect, Some(DialectType::Databricks)) {
25466            // IDENTITY(start, increment) -> GENERATED BY DEFAULT AS IDENTITY
25467            // Plain IDENTITY/AUTO_INCREMENT -> GENERATED ALWAYS AS IDENTITY
25468            if col.auto_increment_start.is_some() || col.auto_increment_increment.is_some() {
25469                self.write_keyword("GENERATED BY DEFAULT AS IDENTITY");
25470            } else {
25471                self.write_keyword("GENERATED ALWAYS AS IDENTITY");
25472            }
25473            if col.auto_increment_start.is_some() || col.auto_increment_increment.is_some() {
25474                self.write(" (");
25475                let mut first = true;
25476                if let Some(ref start) = col.auto_increment_start {
25477                    self.write_keyword("START WITH");
25478                    self.write_space();
25479                    self.generate_expression(start)?;
25480                    first = false;
25481                }
25482                if let Some(ref inc) = col.auto_increment_increment {
25483                    if !first {
25484                        self.write_space();
25485                    }
25486                    self.write_keyword("INCREMENT BY");
25487                    self.write_space();
25488                    self.generate_expression(inc)?;
25489                }
25490                self.write(")");
25491            }
25492        } else if matches!(
25493            self.config.dialect,
25494            Some(DialectType::TSQL) | Some(DialectType::Fabric)
25495        ) {
25496            self.write_keyword("IDENTITY");
25497            if col.auto_increment_start.is_some() || col.auto_increment_increment.is_some() {
25498                self.write("(");
25499                if let Some(ref start) = col.auto_increment_start {
25500                    self.generate_expression(start)?;
25501                } else {
25502                    self.write("0");
25503                }
25504                self.write(", ");
25505                if let Some(ref inc) = col.auto_increment_increment {
25506                    self.generate_expression(inc)?;
25507                } else {
25508                    self.write("1");
25509                }
25510                self.write(")");
25511            }
25512        } else {
25513            self.write_keyword("AUTO_INCREMENT");
25514            if let Some(ref start) = col.auto_increment_start {
25515                self.write_space();
25516                self.write_keyword("START");
25517                self.write_space();
25518                self.generate_expression(start)?;
25519            }
25520            if let Some(ref inc) = col.auto_increment_increment {
25521                self.write_space();
25522                self.write_keyword("INCREMENT");
25523                self.write_space();
25524                self.generate_expression(inc)?;
25525            }
25526            if let Some(order) = col.auto_increment_order {
25527                self.write_space();
25528                if order {
25529                    self.write_keyword("ORDER");
25530                } else {
25531                    self.write_keyword("NOORDER");
25532                }
25533            }
25534        }
25535        Ok(())
25536    }
25537
25538    fn generate_auto_increment_property(&mut self, e: &AutoIncrementProperty) -> Result<()> {
25539        // AUTO_INCREMENT=value
25540        self.write_keyword("AUTO_INCREMENT");
25541        self.write("=");
25542        self.generate_expression(&e.this)?;
25543        Ok(())
25544    }
25545
25546    fn generate_auto_refresh_property(&mut self, e: &AutoRefreshProperty) -> Result<()> {
25547        // AUTO_REFRESH=value
25548        self.write_keyword("AUTO_REFRESH");
25549        self.write("=");
25550        self.generate_expression(&e.this)?;
25551        Ok(())
25552    }
25553
25554    fn generate_backup_property(&mut self, e: &BackupProperty) -> Result<()> {
25555        // BACKUP YES|NO (Redshift syntax uses space, not equals)
25556        self.write_keyword("BACKUP");
25557        self.write_space();
25558        self.generate_expression(&e.this)?;
25559        Ok(())
25560    }
25561
25562    fn generate_base64_decode_binary(&mut self, e: &Base64DecodeBinary) -> Result<()> {
25563        // BASE64_DECODE_BINARY(this[, alphabet])
25564        self.write_keyword("BASE64_DECODE_BINARY");
25565        self.write("(");
25566        self.generate_expression(&e.this)?;
25567        if let Some(alphabet) = &e.alphabet {
25568            self.write(", ");
25569            self.generate_expression(alphabet)?;
25570        }
25571        self.write(")");
25572        Ok(())
25573    }
25574
25575    fn generate_base64_decode_string(&mut self, e: &Base64DecodeString) -> Result<()> {
25576        // BASE64_DECODE_STRING(this[, alphabet])
25577        self.write_keyword("BASE64_DECODE_STRING");
25578        self.write("(");
25579        self.generate_expression(&e.this)?;
25580        if let Some(alphabet) = &e.alphabet {
25581            self.write(", ");
25582            self.generate_expression(alphabet)?;
25583        }
25584        self.write(")");
25585        Ok(())
25586    }
25587
25588    fn generate_base64_encode(&mut self, e: &Base64Encode) -> Result<()> {
25589        // BASE64_ENCODE(this[, max_line_length][, alphabet])
25590        self.write_keyword("BASE64_ENCODE");
25591        self.write("(");
25592        self.generate_expression(&e.this)?;
25593        if let Some(max_line_length) = &e.max_line_length {
25594            self.write(", ");
25595            self.generate_expression(max_line_length)?;
25596        }
25597        if let Some(alphabet) = &e.alphabet {
25598            self.write(", ");
25599            self.generate_expression(alphabet)?;
25600        }
25601        self.write(")");
25602        Ok(())
25603    }
25604
25605    fn generate_block_compression_property(&mut self, e: &BlockCompressionProperty) -> Result<()> {
25606        // BLOCKCOMPRESSION=... (complex Teradata property)
25607        self.write_keyword("BLOCKCOMPRESSION");
25608        self.write("=");
25609        if let Some(autotemp) = &e.autotemp {
25610            self.write_keyword("AUTOTEMP");
25611            self.write("(");
25612            self.generate_expression(autotemp)?;
25613            self.write(")");
25614        }
25615        if let Some(always) = &e.always {
25616            self.generate_expression(always)?;
25617        }
25618        if let Some(default) = &e.default {
25619            self.generate_expression(default)?;
25620        }
25621        if let Some(manual) = &e.manual {
25622            self.generate_expression(manual)?;
25623        }
25624        if let Some(never) = &e.never {
25625            self.generate_expression(never)?;
25626        }
25627        Ok(())
25628    }
25629
25630    fn generate_booland(&mut self, e: &Booland) -> Result<()> {
25631        // Python: return f"(({self.sql(expression, 'this')}) AND ({self.sql(expression, 'expression')}))"
25632        self.write("((");
25633        self.generate_expression(&e.this)?;
25634        self.write(") ");
25635        self.write_keyword("AND");
25636        self.write(" (");
25637        self.generate_expression(&e.expression)?;
25638        self.write("))");
25639        Ok(())
25640    }
25641
25642    fn generate_boolor(&mut self, e: &Boolor) -> Result<()> {
25643        // Python: return f"(({self.sql(expression, 'this')}) OR ({self.sql(expression, 'expression')}))"
25644        self.write("((");
25645        self.generate_expression(&e.this)?;
25646        self.write(") ");
25647        self.write_keyword("OR");
25648        self.write(" (");
25649        self.generate_expression(&e.expression)?;
25650        self.write("))");
25651        Ok(())
25652    }
25653
25654    fn generate_build_property(&mut self, e: &BuildProperty) -> Result<()> {
25655        // BUILD value (e.g., BUILD IMMEDIATE, BUILD DEFERRED)
25656        self.write_keyword("BUILD");
25657        self.write_space();
25658        self.generate_expression(&e.this)?;
25659        Ok(())
25660    }
25661
25662    fn generate_byte_string(&mut self, e: &ByteString) -> Result<()> {
25663        // Byte string literal like B'...' or X'...'
25664        self.generate_expression(&e.this)?;
25665        Ok(())
25666    }
25667
25668    fn generate_case_specific_column_constraint(
25669        &mut self,
25670        e: &CaseSpecificColumnConstraint,
25671    ) -> Result<()> {
25672        // CASESPECIFIC or NOT CASESPECIFIC (Teradata)
25673        if e.not_.is_some() {
25674            self.write_keyword("NOT");
25675            self.write_space();
25676        }
25677        self.write_keyword("CASESPECIFIC");
25678        Ok(())
25679    }
25680
25681    fn generate_cast_to_str_type(&mut self, e: &CastToStrType) -> Result<()> {
25682        // Cast to string type (dialect-specific)
25683        self.write_keyword("CAST");
25684        self.write("(");
25685        self.generate_expression(&e.this)?;
25686        if self.config.dialect == Some(DialectType::ClickHouse) {
25687            // ClickHouse: CAST(expr, 'type_string')
25688            self.write(", ");
25689        } else {
25690            self.write_space();
25691            self.write_keyword("AS");
25692            self.write_space();
25693        }
25694        if let Some(to) = &e.to {
25695            self.generate_expression(to)?;
25696        }
25697        self.write(")");
25698        Ok(())
25699    }
25700
25701    fn generate_changes(&mut self, e: &Changes) -> Result<()> {
25702        // CHANGES (INFORMATION => value) AT|BEFORE (...) END (...)
25703        // Python: f"CHANGES ({information}){at_before}{end}"
25704        self.write_keyword("CHANGES");
25705        self.write(" (");
25706        if let Some(information) = &e.information {
25707            self.write_keyword("INFORMATION");
25708            self.write(" => ");
25709            self.generate_expression(information)?;
25710        }
25711        self.write(")");
25712        // at_before and end are HistoricalData expressions that generate their own keywords
25713        if let Some(at_before) = &e.at_before {
25714            self.write(" ");
25715            self.generate_expression(at_before)?;
25716        }
25717        if let Some(end) = &e.end {
25718            self.write(" ");
25719            self.generate_expression(end)?;
25720        }
25721        Ok(())
25722    }
25723
25724    fn generate_character_set_column_constraint(
25725        &mut self,
25726        e: &CharacterSetColumnConstraint,
25727    ) -> Result<()> {
25728        // CHARACTER SET charset_name
25729        self.write_keyword("CHARACTER SET");
25730        self.write_space();
25731        self.generate_expression(&e.this)?;
25732        Ok(())
25733    }
25734
25735    fn generate_character_set_property(&mut self, e: &CharacterSetProperty) -> Result<()> {
25736        // [DEFAULT] CHARACTER SET=value
25737        if e.default.is_some() {
25738            self.write_keyword("DEFAULT");
25739            self.write_space();
25740        }
25741        self.write_keyword("CHARACTER SET");
25742        self.write("=");
25743        self.generate_expression(&e.this)?;
25744        Ok(())
25745    }
25746
25747    fn generate_check_column_constraint(&mut self, e: &CheckColumnConstraint) -> Result<()> {
25748        // Python: return f"CHECK ({self.sql(expression, 'this')}){enforced}"
25749        self.write_keyword("CHECK");
25750        self.write(" (");
25751        self.generate_expression(&e.this)?;
25752        self.write(")");
25753        if e.enforced.is_some() {
25754            self.write_space();
25755            self.write_keyword("ENFORCED");
25756        }
25757        Ok(())
25758    }
25759
25760    fn generate_assume_column_constraint(&mut self, e: &AssumeColumnConstraint) -> Result<()> {
25761        // Python: return f"ASSUME ({self.sql(e, 'this')})"
25762        self.write_keyword("ASSUME");
25763        self.write(" (");
25764        self.generate_expression(&e.this)?;
25765        self.write(")");
25766        Ok(())
25767    }
25768
25769    fn generate_check_json(&mut self, e: &CheckJson) -> Result<()> {
25770        // CHECK_JSON(this)
25771        self.write_keyword("CHECK_JSON");
25772        self.write("(");
25773        self.generate_expression(&e.this)?;
25774        self.write(")");
25775        Ok(())
25776    }
25777
25778    fn generate_check_xml(&mut self, e: &CheckXml) -> Result<()> {
25779        // CHECK_XML(this)
25780        self.write_keyword("CHECK_XML");
25781        self.write("(");
25782        self.generate_expression(&e.this)?;
25783        self.write(")");
25784        Ok(())
25785    }
25786
25787    fn generate_checksum_property(&mut self, e: &ChecksumProperty) -> Result<()> {
25788        // CHECKSUM=[ON|OFF|DEFAULT]
25789        self.write_keyword("CHECKSUM");
25790        self.write("=");
25791        if e.on.is_some() {
25792            self.write_keyword("ON");
25793        } else if e.default.is_some() {
25794            self.write_keyword("DEFAULT");
25795        } else {
25796            self.write_keyword("OFF");
25797        }
25798        Ok(())
25799    }
25800
25801    fn generate_clone(&mut self, e: &Clone) -> Result<()> {
25802        // Python: return f"{shallow}{keyword} {this}"
25803        if e.shallow.is_some() {
25804            self.write_keyword("SHALLOW");
25805            self.write_space();
25806        }
25807        if e.copy.is_some() {
25808            self.write_keyword("COPY");
25809        } else {
25810            self.write_keyword("CLONE");
25811        }
25812        self.write_space();
25813        self.generate_expression(&e.this)?;
25814        Ok(())
25815    }
25816
25817    fn generate_cluster_by(&mut self, e: &ClusterBy) -> Result<()> {
25818        // CLUSTER BY (expressions)
25819        self.write_keyword("CLUSTER BY");
25820        self.write(" (");
25821        for (i, ord) in e.expressions.iter().enumerate() {
25822            if i > 0 {
25823                self.write(", ");
25824            }
25825            self.generate_ordered(ord)?;
25826        }
25827        self.write(")");
25828        Ok(())
25829    }
25830
25831    fn generate_cluster_by_columns_property(&mut self, e: &ClusterByColumnsProperty) -> Result<()> {
25832        // BigQuery table property: CLUSTER BY col1, col2
25833        self.write_keyword("CLUSTER BY");
25834        self.write_space();
25835        for (i, col) in e.columns.iter().enumerate() {
25836            if i > 0 {
25837                self.write(", ");
25838            }
25839            self.generate_identifier(col)?;
25840        }
25841        Ok(())
25842    }
25843
25844    fn generate_clustered_by_property(&mut self, e: &ClusteredByProperty) -> Result<()> {
25845        // Python: return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS"
25846        self.write_keyword("CLUSTERED BY");
25847        self.write(" (");
25848        for (i, expr) in e.expressions.iter().enumerate() {
25849            if i > 0 {
25850                self.write(", ");
25851            }
25852            self.generate_expression(expr)?;
25853        }
25854        self.write(")");
25855        if let Some(sorted_by) = &e.sorted_by {
25856            self.write_space();
25857            self.write_keyword("SORTED BY");
25858            self.write(" (");
25859            // Unwrap Tuple to avoid double parentheses
25860            if let Expression::Tuple(t) = sorted_by.as_ref() {
25861                for (i, expr) in t.expressions.iter().enumerate() {
25862                    if i > 0 {
25863                        self.write(", ");
25864                    }
25865                    self.generate_expression(expr)?;
25866                }
25867            } else {
25868                self.generate_expression(sorted_by)?;
25869            }
25870            self.write(")");
25871        }
25872        if let Some(buckets) = &e.buckets {
25873            self.write_space();
25874            self.write_keyword("INTO");
25875            self.write_space();
25876            self.generate_expression(buckets)?;
25877            self.write_space();
25878            self.write_keyword("BUCKETS");
25879        }
25880        Ok(())
25881    }
25882
25883    fn generate_collate_property(&mut self, e: &CollateProperty) -> Result<()> {
25884        // [DEFAULT] COLLATE [=] value
25885        // BigQuery uses space: DEFAULT COLLATE 'en'
25886        // Others use equals: COLLATE='en'
25887        if e.default.is_some() {
25888            self.write_keyword("DEFAULT");
25889            self.write_space();
25890        }
25891        self.write_keyword("COLLATE");
25892        // BigQuery uses space between COLLATE and value
25893        match self.config.dialect {
25894            Some(DialectType::BigQuery) => self.write_space(),
25895            _ => self.write("="),
25896        }
25897        self.generate_expression(&e.this)?;
25898        Ok(())
25899    }
25900
25901    fn generate_column_constraint(&mut self, e: &ColumnConstraint) -> Result<()> {
25902        // ColumnConstraint is an enum
25903        match e {
25904            ColumnConstraint::NotNull => {
25905                self.write_keyword("NOT NULL");
25906            }
25907            ColumnConstraint::Null => {
25908                self.write_keyword("NULL");
25909            }
25910            ColumnConstraint::Unique => {
25911                self.write_keyword("UNIQUE");
25912            }
25913            ColumnConstraint::PrimaryKey => {
25914                self.write_keyword("PRIMARY KEY");
25915            }
25916            ColumnConstraint::Default(expr) => {
25917                self.write_keyword("DEFAULT");
25918                self.write_space();
25919                self.generate_expression(expr)?;
25920            }
25921            ColumnConstraint::Check(expr) => {
25922                self.write_keyword("CHECK");
25923                self.write(" (");
25924                self.generate_expression(expr)?;
25925                self.write(")");
25926            }
25927            ColumnConstraint::References(fk_ref) => {
25928                if fk_ref.has_foreign_key_keywords {
25929                    self.write_keyword("FOREIGN KEY");
25930                    self.write_space();
25931                }
25932                self.write_keyword("REFERENCES");
25933                self.write_space();
25934                self.generate_table(&fk_ref.table)?;
25935                if !fk_ref.columns.is_empty() {
25936                    self.write(" (");
25937                    for (i, col) in fk_ref.columns.iter().enumerate() {
25938                        if i > 0 {
25939                            self.write(", ");
25940                        }
25941                        self.generate_identifier(col)?;
25942                    }
25943                    self.write(")");
25944                }
25945            }
25946            ColumnConstraint::GeneratedAsIdentity(gen) => {
25947                self.write_keyword("GENERATED");
25948                self.write_space();
25949                if gen.always {
25950                    self.write_keyword("ALWAYS");
25951                } else {
25952                    self.write_keyword("BY DEFAULT");
25953                    if gen.on_null {
25954                        self.write_space();
25955                        self.write_keyword("ON NULL");
25956                    }
25957                }
25958                self.write_space();
25959                self.write_keyword("AS IDENTITY");
25960            }
25961            ColumnConstraint::Collate(collation) => {
25962                self.write_keyword("COLLATE");
25963                self.write_space();
25964                self.generate_identifier(collation)?;
25965            }
25966            ColumnConstraint::Comment(comment) => {
25967                self.write_keyword("COMMENT");
25968                self.write(" '");
25969                self.write(comment);
25970                self.write("'");
25971            }
25972            ColumnConstraint::ComputedColumn(cc) => {
25973                self.generate_computed_column_inline(cc)?;
25974            }
25975            ColumnConstraint::GeneratedAsRow(gar) => {
25976                self.generate_generated_as_row_inline(gar)?;
25977            }
25978            ColumnConstraint::Tags(tags) => {
25979                self.write_keyword("TAG");
25980                self.write(" (");
25981                for (i, expr) in tags.expressions.iter().enumerate() {
25982                    if i > 0 {
25983                        self.write(", ");
25984                    }
25985                    self.generate_expression(expr)?;
25986                }
25987                self.write(")");
25988            }
25989            ColumnConstraint::Path(path_expr) => {
25990                self.write_keyword("PATH");
25991                self.write_space();
25992                self.generate_expression(path_expr)?;
25993            }
25994        }
25995        Ok(())
25996    }
25997
25998    fn generate_column_position(&mut self, e: &ColumnPosition) -> Result<()> {
25999        // ColumnPosition is an enum
26000        match e {
26001            ColumnPosition::First => {
26002                self.write_keyword("FIRST");
26003            }
26004            ColumnPosition::After(ident) => {
26005                self.write_keyword("AFTER");
26006                self.write_space();
26007                self.generate_identifier(ident)?;
26008            }
26009        }
26010        Ok(())
26011    }
26012
26013    fn generate_column_prefix(&mut self, e: &ColumnPrefix) -> Result<()> {
26014        // column(prefix)
26015        self.generate_expression(&e.this)?;
26016        self.write("(");
26017        self.generate_expression(&e.expression)?;
26018        self.write(")");
26019        Ok(())
26020    }
26021
26022    fn generate_columns(&mut self, e: &Columns) -> Result<()> {
26023        // If unpack is true, this came from * COLUMNS(pattern)
26024        // DuckDB syntax: * COLUMNS(c ILIKE '%suffix') or COLUMNS(pattern)
26025        if let Some(ref unpack) = e.unpack {
26026            if let Expression::Boolean(b) = unpack.as_ref() {
26027                if b.value {
26028                    self.write("*");
26029                }
26030            }
26031        }
26032        self.write_keyword("COLUMNS");
26033        self.write("(");
26034        self.generate_expression(&e.this)?;
26035        self.write(")");
26036        Ok(())
26037    }
26038
26039    fn generate_combined_agg_func(&mut self, e: &CombinedAggFunc) -> Result<()> {
26040        // Combined aggregate: FUNC(args) combined
26041        self.generate_expression(&e.this)?;
26042        self.write("(");
26043        for (i, expr) in e.expressions.iter().enumerate() {
26044            if i > 0 {
26045                self.write(", ");
26046            }
26047            self.generate_expression(expr)?;
26048        }
26049        self.write(")");
26050        Ok(())
26051    }
26052
26053    fn generate_combined_parameterized_agg(&mut self, e: &CombinedParameterizedAgg) -> Result<()> {
26054        // Combined parameterized aggregate: FUNC(params)(expressions)
26055        self.generate_expression(&e.this)?;
26056        self.write("(");
26057        for (i, param) in e.params.iter().enumerate() {
26058            if i > 0 {
26059                self.write(", ");
26060            }
26061            self.generate_expression(param)?;
26062        }
26063        self.write(")(");
26064        for (i, expr) in e.expressions.iter().enumerate() {
26065            if i > 0 {
26066                self.write(", ");
26067            }
26068            self.generate_expression(expr)?;
26069        }
26070        self.write(")");
26071        Ok(())
26072    }
26073
26074    fn generate_commit(&mut self, e: &Commit) -> Result<()> {
26075        // COMMIT [TRANSACTION [transaction_name]] [WITH (DELAYED_DURABILITY = ON|OFF)] [AND [NO] CHAIN]
26076        self.write_keyword("COMMIT");
26077
26078        // TSQL always uses COMMIT TRANSACTION
26079        if e.this.is_none()
26080            && matches!(
26081                self.config.dialect,
26082                Some(DialectType::TSQL) | Some(DialectType::Fabric)
26083            )
26084        {
26085            self.write_space();
26086            self.write_keyword("TRANSACTION");
26087        }
26088
26089        // Check if this has TRANSACTION keyword or transaction name
26090        if let Some(this) = &e.this {
26091            // Check if it's just the "TRANSACTION" marker or an actual transaction name
26092            let is_transaction_marker = matches!(
26093                this.as_ref(),
26094                Expression::Identifier(id) if id.name == "TRANSACTION"
26095            );
26096
26097            self.write_space();
26098            self.write_keyword("TRANSACTION");
26099
26100            // If it's a real transaction name, output it
26101            if !is_transaction_marker {
26102                self.write_space();
26103                self.generate_expression(this)?;
26104            }
26105        }
26106
26107        // Output WITH (DELAYED_DURABILITY = ON|OFF) for TSQL
26108        if let Some(durability) = &e.durability {
26109            self.write_space();
26110            self.write_keyword("WITH");
26111            self.write(" (");
26112            self.write_keyword("DELAYED_DURABILITY");
26113            self.write(" = ");
26114            if let Expression::Boolean(BooleanLiteral { value: true }) = durability.as_ref() {
26115                self.write_keyword("ON");
26116            } else {
26117                self.write_keyword("OFF");
26118            }
26119            self.write(")");
26120        }
26121
26122        // Output AND [NO] CHAIN
26123        if let Some(chain) = &e.chain {
26124            self.write_space();
26125            if let Expression::Boolean(BooleanLiteral { value: false }) = chain.as_ref() {
26126                self.write_keyword("AND NO CHAIN");
26127            } else {
26128                self.write_keyword("AND CHAIN");
26129            }
26130        }
26131        Ok(())
26132    }
26133
26134    fn generate_comprehension(&mut self, e: &Comprehension) -> Result<()> {
26135        // Python-style comprehension: [expr FOR var[, pos] IN iterator IF condition]
26136        self.write("[");
26137        self.generate_expression(&e.this)?;
26138        self.write_space();
26139        self.write_keyword("FOR");
26140        self.write_space();
26141        self.generate_expression(&e.expression)?;
26142        // Handle optional position variable (for enumerate-like syntax)
26143        if let Some(pos) = &e.position {
26144            self.write(", ");
26145            self.generate_expression(pos)?;
26146        }
26147        if let Some(iterator) = &e.iterator {
26148            self.write_space();
26149            self.write_keyword("IN");
26150            self.write_space();
26151            self.generate_expression(iterator)?;
26152        }
26153        if let Some(condition) = &e.condition {
26154            self.write_space();
26155            self.write_keyword("IF");
26156            self.write_space();
26157            self.generate_expression(condition)?;
26158        }
26159        self.write("]");
26160        Ok(())
26161    }
26162
26163    fn generate_compress(&mut self, e: &Compress) -> Result<()> {
26164        // COMPRESS(this[, method])
26165        self.write_keyword("COMPRESS");
26166        self.write("(");
26167        self.generate_expression(&e.this)?;
26168        if let Some(method) = &e.method {
26169            self.write(", '");
26170            self.write(method);
26171            self.write("'");
26172        }
26173        self.write(")");
26174        Ok(())
26175    }
26176
26177    fn generate_compress_column_constraint(&mut self, e: &CompressColumnConstraint) -> Result<()> {
26178        // Python: return f"COMPRESS {this}"
26179        self.write_keyword("COMPRESS");
26180        if let Some(this) = &e.this {
26181            self.write_space();
26182            self.generate_expression(this)?;
26183        }
26184        Ok(())
26185    }
26186
26187    fn generate_computed_column_constraint(&mut self, e: &ComputedColumnConstraint) -> Result<()> {
26188        // Python: return f"AS {this}{persisted}"
26189        self.write_keyword("AS");
26190        self.write_space();
26191        self.generate_expression(&e.this)?;
26192        if e.not_null.is_some() {
26193            self.write_space();
26194            self.write_keyword("PERSISTED NOT NULL");
26195        } else if e.persisted.is_some() {
26196            self.write_space();
26197            self.write_keyword("PERSISTED");
26198        }
26199        Ok(())
26200    }
26201
26202    /// Generate a ComputedColumn constraint inline within a column definition.
26203    /// Handles MySQL/PostgreSQL: GENERATED ALWAYS AS (expr) STORED|VIRTUAL
26204    /// Handles TSQL: AS (expr) [PERSISTED] [NOT NULL]
26205    fn generate_computed_column_inline(&mut self, cc: &ComputedColumn) -> Result<()> {
26206        let computed_expr = if matches!(
26207            self.config.dialect,
26208            Some(DialectType::TSQL) | Some(DialectType::Fabric)
26209        ) {
26210            match &*cc.expression {
26211                Expression::Year(y) if !matches!(&y.this, Expression::Cast(c) if matches!(c.to, DataType::Date)) =>
26212                {
26213                    let wrapped = Expression::Cast(Box::new(Cast {
26214                        this: y.this.clone(),
26215                        to: DataType::Date,
26216                        trailing_comments: Vec::new(),
26217                        double_colon_syntax: false,
26218                        format: None,
26219                        default: None,
26220                        inferred_type: None,
26221                    }));
26222                    Expression::Year(Box::new(UnaryFunc::new(wrapped)))
26223                }
26224                Expression::Function(f)
26225                    if f.name.eq_ignore_ascii_case("YEAR")
26226                        && f.args.len() == 1
26227                        && !matches!(&f.args[0], Expression::Cast(c) if matches!(c.to, DataType::Date)) =>
26228                {
26229                    let wrapped = Expression::Cast(Box::new(Cast {
26230                        this: f.args[0].clone(),
26231                        to: DataType::Date,
26232                        trailing_comments: Vec::new(),
26233                        double_colon_syntax: false,
26234                        format: None,
26235                        default: None,
26236                        inferred_type: None,
26237                    }));
26238                    Expression::Function(Box::new(Function::new("YEAR".to_string(), vec![wrapped])))
26239                }
26240                _ => *cc.expression.clone(),
26241            }
26242        } else {
26243            *cc.expression.clone()
26244        };
26245
26246        match cc.persistence_kind.as_deref() {
26247            Some("STORED") | Some("VIRTUAL") => {
26248                // MySQL/PostgreSQL: GENERATED ALWAYS AS (expr) STORED|VIRTUAL
26249                self.write_keyword("GENERATED ALWAYS AS");
26250                self.write(" (");
26251                self.generate_expression(&computed_expr)?;
26252                self.write(")");
26253                self.write_space();
26254                if cc.persisted {
26255                    self.write_keyword("STORED");
26256                } else {
26257                    self.write_keyword("VIRTUAL");
26258                }
26259            }
26260            Some("PERSISTED") => {
26261                // TSQL/SingleStore: AS (expr) PERSISTED [TYPE] [NOT NULL]
26262                self.write_keyword("AS");
26263                self.write(" (");
26264                self.generate_expression(&computed_expr)?;
26265                self.write(")");
26266                self.write_space();
26267                self.write_keyword("PERSISTED");
26268                // Output data type if present (SingleStore: PERSISTED TYPE NOT NULL)
26269                if let Some(ref dt) = cc.data_type {
26270                    self.write_space();
26271                    self.generate_data_type(dt)?;
26272                }
26273                if cc.not_null {
26274                    self.write_space();
26275                    self.write_keyword("NOT NULL");
26276                }
26277            }
26278            _ => {
26279                // Spark/Databricks/Hive: GENERATED ALWAYS AS (expr)
26280                // TSQL computed column without PERSISTED: AS (expr)
26281                if matches!(
26282                    self.config.dialect,
26283                    Some(DialectType::Spark)
26284                        | Some(DialectType::Databricks)
26285                        | Some(DialectType::Hive)
26286                ) {
26287                    self.write_keyword("GENERATED ALWAYS AS");
26288                    self.write(" (");
26289                    self.generate_expression(&computed_expr)?;
26290                    self.write(")");
26291                } else if matches!(
26292                    self.config.dialect,
26293                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
26294                ) {
26295                    self.write_keyword("AS");
26296                    let omit_parens = matches!(computed_expr, Expression::Year(_))
26297                        || matches!(&computed_expr, Expression::Function(f) if f.name.eq_ignore_ascii_case("YEAR"));
26298                    if omit_parens {
26299                        self.write_space();
26300                        self.generate_expression(&computed_expr)?;
26301                    } else {
26302                        self.write(" (");
26303                        self.generate_expression(&computed_expr)?;
26304                        self.write(")");
26305                    }
26306                } else {
26307                    self.write_keyword("AS");
26308                    self.write(" (");
26309                    self.generate_expression(&computed_expr)?;
26310                    self.write(")");
26311                }
26312            }
26313        }
26314        Ok(())
26315    }
26316
26317    /// Generate a GeneratedAsRow constraint inline within a column definition.
26318    /// TSQL temporal: GENERATED ALWAYS AS ROW START|END [HIDDEN]
26319    fn generate_generated_as_row_inline(&mut self, gar: &GeneratedAsRow) -> Result<()> {
26320        self.write_keyword("GENERATED ALWAYS AS ROW ");
26321        if gar.start {
26322            self.write_keyword("START");
26323        } else {
26324            self.write_keyword("END");
26325        }
26326        if gar.hidden {
26327            self.write_space();
26328            self.write_keyword("HIDDEN");
26329        }
26330        Ok(())
26331    }
26332
26333    /// Generate just the SYSTEM_VERSIONING=ON(...) content without WITH() wrapper.
26334    fn generate_system_versioning_content(
26335        &mut self,
26336        e: &WithSystemVersioningProperty,
26337    ) -> Result<()> {
26338        let mut parts = Vec::new();
26339
26340        if let Some(this) = &e.this {
26341            let mut s = String::from("HISTORY_TABLE=");
26342            let mut gen = Generator::with_arc_config(self.config.clone());
26343            gen.generate_expression(this)?;
26344            s.push_str(&gen.output);
26345            parts.push(s);
26346        }
26347
26348        if let Some(data_consistency) = &e.data_consistency {
26349            let mut s = String::from("DATA_CONSISTENCY_CHECK=");
26350            let mut gen = Generator::with_arc_config(self.config.clone());
26351            gen.generate_expression(data_consistency)?;
26352            s.push_str(&gen.output);
26353            parts.push(s);
26354        }
26355
26356        if let Some(retention_period) = &e.retention_period {
26357            let mut s = String::from("HISTORY_RETENTION_PERIOD=");
26358            let mut gen = Generator::with_arc_config(self.config.clone());
26359            gen.generate_expression(retention_period)?;
26360            s.push_str(&gen.output);
26361            parts.push(s);
26362        }
26363
26364        self.write_keyword("SYSTEM_VERSIONING");
26365        self.write("=");
26366
26367        if !parts.is_empty() {
26368            self.write_keyword("ON");
26369            self.write("(");
26370            self.write(&parts.join(", "));
26371            self.write(")");
26372        } else if e.on.is_some() {
26373            self.write_keyword("ON");
26374        } else {
26375            self.write_keyword("OFF");
26376        }
26377
26378        Ok(())
26379    }
26380
26381    fn generate_conditional_insert(&mut self, e: &ConditionalInsert) -> Result<()> {
26382        // Conditional INSERT for multi-table inserts
26383        // Output: [WHEN cond THEN | ELSE] INTO table [(cols)] [VALUES (...)]
26384        if e.else_.is_some() {
26385            self.write_keyword("ELSE");
26386            self.write_space();
26387        } else if let Some(expression) = &e.expression {
26388            self.write_keyword("WHEN");
26389            self.write_space();
26390            self.generate_expression(expression)?;
26391            self.write_space();
26392            self.write_keyword("THEN");
26393            self.write_space();
26394        }
26395
26396        // Handle Insert expression specially - output "INTO table (cols) VALUES (...)"
26397        // without the "INSERT " prefix
26398        if let Expression::Insert(insert) = e.this.as_ref() {
26399            self.write_keyword("INTO");
26400            self.write_space();
26401            self.generate_table(&insert.table)?;
26402
26403            // Optional column list
26404            if !insert.columns.is_empty() {
26405                self.write(" (");
26406                for (i, col) in insert.columns.iter().enumerate() {
26407                    if i > 0 {
26408                        self.write(", ");
26409                    }
26410                    self.generate_identifier(col)?;
26411                }
26412                self.write(")");
26413            }
26414
26415            // Optional VALUES clause
26416            if !insert.values.is_empty() {
26417                self.write_space();
26418                self.write_keyword("VALUES");
26419                for (row_idx, row) in insert.values.iter().enumerate() {
26420                    if row_idx > 0 {
26421                        self.write(", ");
26422                    }
26423                    self.write(" (");
26424                    for (i, val) in row.iter().enumerate() {
26425                        if i > 0 {
26426                            self.write(", ");
26427                        }
26428                        self.generate_expression(val)?;
26429                    }
26430                    self.write(")");
26431                }
26432            }
26433        } else {
26434            // Fallback for non-Insert expressions
26435            self.generate_expression(&e.this)?;
26436        }
26437        Ok(())
26438    }
26439
26440    fn generate_constraint(&mut self, e: &Constraint) -> Result<()> {
26441        // Python: return f"CONSTRAINT {this} {expressions}"
26442        self.write_keyword("CONSTRAINT");
26443        self.write_space();
26444        self.generate_expression(&e.this)?;
26445        if !e.expressions.is_empty() {
26446            self.write_space();
26447            for (i, expr) in e.expressions.iter().enumerate() {
26448                if i > 0 {
26449                    self.write_space();
26450                }
26451                self.generate_expression(expr)?;
26452            }
26453        }
26454        Ok(())
26455    }
26456
26457    fn generate_convert_timezone(&mut self, e: &ConvertTimezone) -> Result<()> {
26458        // CONVERT_TIMEZONE([source_tz,] target_tz, timestamp)
26459        self.write_keyword("CONVERT_TIMEZONE");
26460        self.write("(");
26461        let mut first = true;
26462        if let Some(source_tz) = &e.source_tz {
26463            self.generate_expression(source_tz)?;
26464            first = false;
26465        }
26466        if let Some(target_tz) = &e.target_tz {
26467            if !first {
26468                self.write(", ");
26469            }
26470            self.generate_expression(target_tz)?;
26471            first = false;
26472        }
26473        if let Some(timestamp) = &e.timestamp {
26474            if !first {
26475                self.write(", ");
26476            }
26477            self.generate_expression(timestamp)?;
26478        }
26479        self.write(")");
26480        Ok(())
26481    }
26482
26483    fn generate_convert_to_charset(&mut self, e: &ConvertToCharset) -> Result<()> {
26484        // CONVERT(this USING dest)
26485        self.write_keyword("CONVERT");
26486        self.write("(");
26487        self.generate_expression(&e.this)?;
26488        if let Some(dest) = &e.dest {
26489            self.write_space();
26490            self.write_keyword("USING");
26491            self.write_space();
26492            self.generate_expression(dest)?;
26493        }
26494        self.write(")");
26495        Ok(())
26496    }
26497
26498    fn generate_copy(&mut self, e: &CopyStmt) -> Result<()> {
26499        self.write_keyword("COPY");
26500        if e.is_into {
26501            self.write_space();
26502            self.write_keyword("INTO");
26503        }
26504        self.write_space();
26505
26506        // Generate target table or query (or stage for COPY INTO @stage)
26507        if let Expression::Literal(lit) = &e.this {
26508            if let Literal::String(s) = lit.as_ref() {
26509                if s.starts_with('@') {
26510                    self.write(s);
26511                } else {
26512                    self.generate_expression(&e.this)?;
26513                }
26514            }
26515        } else {
26516            self.generate_expression(&e.this)?;
26517        }
26518
26519        // FROM or TO based on kind
26520        if e.kind {
26521            // kind=true means FROM (loading into table)
26522            if self.config.pretty {
26523                self.write_newline();
26524            } else {
26525                self.write_space();
26526            }
26527            self.write_keyword("FROM");
26528            self.write_space();
26529        } else if !e.files.is_empty() {
26530            // kind=false means TO (exporting)
26531            if self.config.pretty {
26532                self.write_newline();
26533            } else {
26534                self.write_space();
26535            }
26536            self.write_keyword("TO");
26537            self.write_space();
26538        }
26539
26540        // Generate source/destination files
26541        for (i, file) in e.files.iter().enumerate() {
26542            if i > 0 {
26543                self.write_space();
26544            }
26545            // For stage references (strings starting with @), output without quotes
26546            if let Expression::Literal(lit) = file {
26547                if let Literal::String(s) = lit.as_ref() {
26548                    if s.starts_with('@') {
26549                        self.write(s);
26550                    } else {
26551                        self.generate_expression(file)?;
26552                    }
26553                }
26554            } else if let Expression::Identifier(id) = file {
26555                // Backtick-quoted file path (Databricks style: `s3://link`)
26556                if id.quoted {
26557                    self.write("`");
26558                    self.write(&id.name);
26559                    self.write("`");
26560                } else {
26561                    self.generate_expression(file)?;
26562                }
26563            } else {
26564                self.generate_expression(file)?;
26565            }
26566        }
26567
26568        // Generate credentials if present (Snowflake style - not wrapped in WITH)
26569        if !e.with_wrapped {
26570            if let Some(ref creds) = e.credentials {
26571                if let Some(ref storage) = creds.storage {
26572                    if self.config.pretty {
26573                        self.write_newline();
26574                    } else {
26575                        self.write_space();
26576                    }
26577                    self.write_keyword("STORAGE_INTEGRATION");
26578                    self.write(" = ");
26579                    self.write(storage);
26580                }
26581                if creds.credentials.is_empty() {
26582                    // Empty credentials: CREDENTIALS = ()
26583                    if self.config.pretty {
26584                        self.write_newline();
26585                    } else {
26586                        self.write_space();
26587                    }
26588                    self.write_keyword("CREDENTIALS");
26589                    self.write(" = ()");
26590                } else {
26591                    if self.config.pretty {
26592                        self.write_newline();
26593                    } else {
26594                        self.write_space();
26595                    }
26596                    self.write_keyword("CREDENTIALS");
26597                    // Check if this is Redshift-style (single value with empty key)
26598                    // vs Snowflake-style (multiple key=value pairs)
26599                    if creds.credentials.len() == 1 && creds.credentials[0].0.is_empty() {
26600                        // Redshift style: CREDENTIALS 'value'
26601                        self.write(" '");
26602                        self.write(&creds.credentials[0].1);
26603                        self.write("'");
26604                    } else {
26605                        // Snowflake style: CREDENTIALS = (KEY='value' ...)
26606                        self.write(" = (");
26607                        for (i, (k, v)) in creds.credentials.iter().enumerate() {
26608                            if i > 0 {
26609                                self.write_space();
26610                            }
26611                            self.write(k);
26612                            self.write("='");
26613                            self.write(v);
26614                            self.write("'");
26615                        }
26616                        self.write(")");
26617                    }
26618                }
26619                if let Some(ref encryption) = creds.encryption {
26620                    self.write_space();
26621                    self.write_keyword("ENCRYPTION");
26622                    self.write(" = ");
26623                    self.write(encryption);
26624                }
26625            }
26626        }
26627
26628        // Generate parameters
26629        if !e.params.is_empty() {
26630            if e.with_wrapped {
26631                // DuckDB/PostgreSQL/TSQL WITH (...) format
26632                self.write_space();
26633                self.write_keyword("WITH");
26634                self.write(" (");
26635                for (i, param) in e.params.iter().enumerate() {
26636                    if i > 0 {
26637                        self.write(", ");
26638                    }
26639                    self.generate_copy_param_with_format(param)?;
26640                }
26641                self.write(")");
26642            } else {
26643                // Snowflake/Redshift format: KEY = VALUE or KEY VALUE (space separated, no WITH wrapper)
26644                // For Redshift: IAM_ROLE value, CREDENTIALS 'value', REGION 'value', FORMAT type
26645                // For Snowflake: KEY = VALUE
26646                for param in &e.params {
26647                    if self.config.pretty {
26648                        self.write_newline();
26649                    } else {
26650                        self.write_space();
26651                    }
26652                    // Preserve original case of parameter name (important for Redshift COPY options)
26653                    self.write(&param.name);
26654                    if let Some(ref value) = param.value {
26655                        // Use = only if it was present in the original (param.eq)
26656                        if param.eq {
26657                            self.write(" = ");
26658                        } else {
26659                            self.write(" ");
26660                        }
26661                        if !param.values.is_empty() {
26662                            self.write("(");
26663                            for (i, v) in param.values.iter().enumerate() {
26664                                if i > 0 {
26665                                    self.write_space();
26666                                }
26667                                self.generate_copy_nested_param(v)?;
26668                            }
26669                            self.write(")");
26670                        } else {
26671                            // For COPY parameter values, output identifiers without quoting
26672                            self.generate_copy_param_value(value)?;
26673                        }
26674                    } else if !param.values.is_empty() {
26675                        // For varlen options like FORMAT_OPTIONS, COPY_OPTIONS - no = before (
26676                        if param.eq {
26677                            self.write(" = (");
26678                        } else {
26679                            self.write(" (");
26680                        }
26681                        // Determine separator for values inside parentheses:
26682                        // - Snowflake FILE_FORMAT = (TYPE=CSV FIELD_DELIMITER='|') → space-separated (has = before parens)
26683                        // - Databricks FORMAT_OPTIONS ('opt1'='true', 'opt2'='test') → comma-separated (no = before parens)
26684                        // - Simple value lists like FILES = ('file1', 'file2') → comma-separated
26685                        let is_key_value_pairs = param
26686                            .values
26687                            .first()
26688                            .map_or(false, |v| matches!(v, Expression::Eq(_)));
26689                        let sep = if is_key_value_pairs && param.eq {
26690                            " "
26691                        } else {
26692                            ", "
26693                        };
26694                        for (i, v) in param.values.iter().enumerate() {
26695                            if i > 0 {
26696                                self.write(sep);
26697                            }
26698                            self.generate_copy_nested_param(v)?;
26699                        }
26700                        self.write(")");
26701                    }
26702                }
26703            }
26704        }
26705
26706        Ok(())
26707    }
26708
26709    /// Generate a COPY parameter in WITH (...) format
26710    /// Handles both KEY = VALUE (TSQL) and KEY VALUE (DuckDB/PostgreSQL) formats
26711    fn generate_copy_param_with_format(&mut self, param: &CopyParameter) -> Result<()> {
26712        self.write_keyword(&param.name);
26713        if !param.values.is_empty() {
26714            // Nested values: CREDENTIAL = (IDENTITY='...', SECRET='...')
26715            self.write(" = (");
26716            for (i, v) in param.values.iter().enumerate() {
26717                if i > 0 {
26718                    self.write(", ");
26719                }
26720                self.generate_copy_nested_param(v)?;
26721            }
26722            self.write(")");
26723        } else if let Some(ref value) = param.value {
26724            if param.eq {
26725                self.write(" = ");
26726            } else {
26727                self.write(" ");
26728            }
26729            self.generate_expression(value)?;
26730        }
26731        Ok(())
26732    }
26733
26734    /// Generate nested parameter for COPY statements (KEY=VALUE without spaces)
26735    fn generate_copy_nested_param(&mut self, expr: &Expression) -> Result<()> {
26736        match expr {
26737            Expression::Eq(eq) => {
26738                // Generate key
26739                match &eq.left {
26740                    Expression::Column(c) => self.write(&c.name.name),
26741                    _ => self.generate_expression(&eq.left)?,
26742                }
26743                self.write("=");
26744                // Generate value
26745                match &eq.right {
26746                    Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
26747                        let Literal::String(s) = lit.as_ref() else {
26748                            unreachable!()
26749                        };
26750                        self.write("'");
26751                        self.write(s);
26752                        self.write("'");
26753                    }
26754                    Expression::Tuple(t) => {
26755                        // For lists like NULL_IF=('', 'str1')
26756                        self.write("(");
26757                        if self.config.pretty {
26758                            self.write_newline();
26759                            self.indent_level += 1;
26760                            for (i, item) in t.expressions.iter().enumerate() {
26761                                if i > 0 {
26762                                    self.write(", ");
26763                                }
26764                                self.write_indent();
26765                                self.generate_expression(item)?;
26766                            }
26767                            self.write_newline();
26768                            self.indent_level -= 1;
26769                        } else {
26770                            for (i, item) in t.expressions.iter().enumerate() {
26771                                if i > 0 {
26772                                    self.write(", ");
26773                                }
26774                                self.generate_expression(item)?;
26775                            }
26776                        }
26777                        self.write(")");
26778                    }
26779                    _ => self.generate_expression(&eq.right)?,
26780                }
26781                Ok(())
26782            }
26783            Expression::Column(c) => {
26784                // Standalone keyword like COMPRESSION
26785                self.write(&c.name.name);
26786                Ok(())
26787            }
26788            _ => self.generate_expression(expr),
26789        }
26790    }
26791
26792    /// Generate a COPY parameter value, outputting identifiers/columns without quoting
26793    /// This is needed for Redshift-style COPY params like: IAM_ROLE default, FORMAT orc
26794    fn generate_copy_param_value(&mut self, expr: &Expression) -> Result<()> {
26795        match expr {
26796            Expression::Column(c) => {
26797                // Output identifier, preserving quotes if originally quoted
26798                if c.name.quoted {
26799                    self.write("\"");
26800                    self.write(&c.name.name);
26801                    self.write("\"");
26802                } else {
26803                    self.write(&c.name.name);
26804                }
26805                Ok(())
26806            }
26807            Expression::Identifier(id) => {
26808                // Output identifier, preserving quotes if originally quoted
26809                if id.quoted {
26810                    self.write("\"");
26811                    self.write(&id.name);
26812                    self.write("\"");
26813                } else {
26814                    self.write(&id.name);
26815                }
26816                Ok(())
26817            }
26818            Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
26819                let Literal::String(s) = lit.as_ref() else {
26820                    unreachable!()
26821                };
26822                // Output string with quotes
26823                self.write("'");
26824                self.write(s);
26825                self.write("'");
26826                Ok(())
26827            }
26828            _ => self.generate_expression(expr),
26829        }
26830    }
26831
26832    fn generate_copy_parameter(&mut self, e: &CopyParameter) -> Result<()> {
26833        self.write_keyword(&e.name);
26834        if let Some(ref value) = e.value {
26835            if e.eq {
26836                self.write(" = ");
26837            } else {
26838                self.write(" ");
26839            }
26840            self.generate_expression(value)?;
26841        }
26842        if !e.values.is_empty() {
26843            if e.eq {
26844                self.write(" = ");
26845            } else {
26846                self.write(" ");
26847            }
26848            self.write("(");
26849            for (i, v) in e.values.iter().enumerate() {
26850                if i > 0 {
26851                    self.write(", ");
26852                }
26853                self.generate_expression(v)?;
26854            }
26855            self.write(")");
26856        }
26857        Ok(())
26858    }
26859
26860    fn generate_corr(&mut self, e: &Corr) -> Result<()> {
26861        // CORR(this, expression)
26862        self.write_keyword("CORR");
26863        self.write("(");
26864        self.generate_expression(&e.this)?;
26865        self.write(", ");
26866        self.generate_expression(&e.expression)?;
26867        self.write(")");
26868        Ok(())
26869    }
26870
26871    fn generate_cosine_distance(&mut self, e: &CosineDistance) -> Result<()> {
26872        // COSINE_DISTANCE(this, expression)
26873        self.write_keyword("COSINE_DISTANCE");
26874        self.write("(");
26875        self.generate_expression(&e.this)?;
26876        self.write(", ");
26877        self.generate_expression(&e.expression)?;
26878        self.write(")");
26879        Ok(())
26880    }
26881
26882    fn generate_covar_pop(&mut self, e: &CovarPop) -> Result<()> {
26883        // COVAR_POP(this, expression)
26884        self.write_keyword("COVAR_POP");
26885        self.write("(");
26886        self.generate_expression(&e.this)?;
26887        self.write(", ");
26888        self.generate_expression(&e.expression)?;
26889        self.write(")");
26890        Ok(())
26891    }
26892
26893    fn generate_covar_samp(&mut self, e: &CovarSamp) -> Result<()> {
26894        // COVAR_SAMP(this, expression)
26895        self.write_keyword("COVAR_SAMP");
26896        self.write("(");
26897        self.generate_expression(&e.this)?;
26898        self.write(", ");
26899        self.generate_expression(&e.expression)?;
26900        self.write(")");
26901        Ok(())
26902    }
26903
26904    fn generate_credentials(&mut self, e: &Credentials) -> Result<()> {
26905        // CREDENTIALS (key1='value1', key2='value2')
26906        self.write_keyword("CREDENTIALS");
26907        self.write(" (");
26908        for (i, (key, value)) in e.credentials.iter().enumerate() {
26909            if i > 0 {
26910                self.write(", ");
26911            }
26912            self.write(key);
26913            self.write("='");
26914            self.write(value);
26915            self.write("'");
26916        }
26917        self.write(")");
26918        Ok(())
26919    }
26920
26921    fn generate_credentials_property(&mut self, e: &CredentialsProperty) -> Result<()> {
26922        // CREDENTIALS=(expressions)
26923        self.write_keyword("CREDENTIALS");
26924        self.write("=(");
26925        for (i, expr) in e.expressions.iter().enumerate() {
26926            if i > 0 {
26927                self.write(", ");
26928            }
26929            self.generate_expression(expr)?;
26930        }
26931        self.write(")");
26932        Ok(())
26933    }
26934
26935    fn generate_cte(&mut self, e: &Cte) -> Result<()> {
26936        use crate::dialects::DialectType;
26937
26938        // Python: return f"{alias_sql}{key_expressions} AS {materialized or ''}{self.wrap(expression)}"
26939        // Output: alias [(col1, col2, ...)] AS [MATERIALIZED|NOT MATERIALIZED] (subquery)
26940        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) && !e.alias_first {
26941            self.generate_expression(&e.this)?;
26942            self.write_space();
26943            self.write_keyword("AS");
26944            self.write_space();
26945            self.generate_identifier(&e.alias)?;
26946            return Ok(());
26947        }
26948        self.write(&e.alias.name);
26949
26950        // BigQuery doesn't support column aliases in CTE definitions
26951        let skip_cte_columns = matches!(self.config.dialect, Some(DialectType::BigQuery));
26952
26953        if !e.columns.is_empty() && !skip_cte_columns {
26954            self.write("(");
26955            for (i, col) in e.columns.iter().enumerate() {
26956                if i > 0 {
26957                    self.write(", ");
26958                }
26959                self.write(&col.name);
26960            }
26961            self.write(")");
26962        }
26963        // USING KEY (columns) for DuckDB recursive CTEs
26964        if !e.key_expressions.is_empty() {
26965            self.write_space();
26966            self.write_keyword("USING KEY");
26967            self.write(" (");
26968            for (i, key) in e.key_expressions.iter().enumerate() {
26969                if i > 0 {
26970                    self.write(", ");
26971                }
26972                self.write(&key.name);
26973            }
26974            self.write(")");
26975        }
26976        self.write_space();
26977        self.write_keyword("AS");
26978        self.write_space();
26979        if let Some(materialized) = e.materialized {
26980            if materialized {
26981                self.write_keyword("MATERIALIZED");
26982            } else {
26983                self.write_keyword("NOT MATERIALIZED");
26984            }
26985            self.write_space();
26986        }
26987        self.write("(");
26988        self.generate_expression(&e.this)?;
26989        self.write(")");
26990        Ok(())
26991    }
26992
26993    fn generate_cube(&mut self, e: &Cube) -> Result<()> {
26994        // Python: return f"CUBE {self.wrap(expressions)}" if expressions else "WITH CUBE"
26995        if e.expressions.is_empty() {
26996            self.write_keyword("WITH CUBE");
26997        } else {
26998            self.write_keyword("CUBE");
26999            self.write("(");
27000            for (i, expr) in e.expressions.iter().enumerate() {
27001                if i > 0 {
27002                    self.write(", ");
27003                }
27004                self.generate_expression(expr)?;
27005            }
27006            self.write(")");
27007        }
27008        Ok(())
27009    }
27010
27011    fn generate_current_datetime(&mut self, e: &CurrentDatetime) -> Result<()> {
27012        // CURRENT_DATETIME or CURRENT_DATETIME(timezone)
27013        self.write_keyword("CURRENT_DATETIME");
27014        if let Some(this) = &e.this {
27015            self.write("(");
27016            self.generate_expression(this)?;
27017            self.write(")");
27018        }
27019        Ok(())
27020    }
27021
27022    fn generate_current_schema(&mut self, _e: &CurrentSchema) -> Result<()> {
27023        // CURRENT_SCHEMA - no arguments
27024        self.write_keyword("CURRENT_SCHEMA");
27025        Ok(())
27026    }
27027
27028    fn generate_current_schemas(&mut self, e: &CurrentSchemas) -> Result<()> {
27029        // CURRENT_SCHEMAS(include_implicit)
27030        self.write_keyword("CURRENT_SCHEMAS");
27031        self.write("(");
27032        // Snowflake: drop the argument (CURRENT_SCHEMAS() takes no args)
27033        if !matches!(
27034            self.config.dialect,
27035            Some(crate::dialects::DialectType::Snowflake)
27036        ) {
27037            if let Some(this) = &e.this {
27038                self.generate_expression(this)?;
27039            }
27040        }
27041        self.write(")");
27042        Ok(())
27043    }
27044
27045    fn generate_current_user(&mut self, e: &CurrentUser) -> Result<()> {
27046        // CURRENT_USER or CURRENT_USER()
27047        self.write_keyword("CURRENT_USER");
27048        // Some dialects always need parens: Snowflake, Spark, Hive, DuckDB, BigQuery, MySQL, Databricks
27049        let needs_parens = e.this.is_some()
27050            || matches!(
27051                self.config.dialect,
27052                Some(DialectType::Snowflake)
27053                    | Some(DialectType::Spark)
27054                    | Some(DialectType::Hive)
27055                    | Some(DialectType::DuckDB)
27056                    | Some(DialectType::BigQuery)
27057                    | Some(DialectType::MySQL)
27058                    | Some(DialectType::Databricks)
27059            );
27060        if needs_parens {
27061            self.write("()");
27062        }
27063        Ok(())
27064    }
27065
27066    fn generate_d_pipe(&mut self, e: &DPipe) -> Result<()> {
27067        // In Solr, || is OR, not string concatenation (DPIPE_IS_STRING_CONCAT = False)
27068        if self.config.dialect == Some(DialectType::Solr) {
27069            self.generate_expression(&e.this)?;
27070            self.write(" ");
27071            self.write_keyword("OR");
27072            self.write(" ");
27073            self.generate_expression(&e.expression)?;
27074        } else if self.config.dialect == Some(DialectType::MySQL) {
27075            self.generate_mysql_concat_from_dpipe(e)?;
27076        } else {
27077            // String concatenation: this || expression
27078            self.generate_expression(&e.this)?;
27079            self.write(" || ");
27080            self.generate_expression(&e.expression)?;
27081        }
27082        Ok(())
27083    }
27084
27085    fn generate_data_blocksize_property(&mut self, e: &DataBlocksizeProperty) -> Result<()> {
27086        // DATABLOCKSIZE=... (Teradata)
27087        self.write_keyword("DATABLOCKSIZE");
27088        self.write("=");
27089        if let Some(size) = e.size {
27090            self.write(&size.to_string());
27091            if let Some(units) = &e.units {
27092                self.write_space();
27093                self.generate_expression(units)?;
27094            }
27095        } else if e.minimum.is_some() {
27096            self.write_keyword("MINIMUM");
27097        } else if e.maximum.is_some() {
27098            self.write_keyword("MAXIMUM");
27099        } else if e.default.is_some() {
27100            self.write_keyword("DEFAULT");
27101        }
27102        Ok(())
27103    }
27104
27105    fn generate_data_deletion_property(&mut self, e: &DataDeletionProperty) -> Result<()> {
27106        // DATA_DELETION=ON or DATA_DELETION=OFF or DATA_DELETION=ON(FILTER_COLUMN=col, RETENTION_PERIOD=...)
27107        self.write_keyword("DATA_DELETION");
27108        self.write("=");
27109
27110        let is_on = matches!(&*e.on, Expression::Boolean(BooleanLiteral { value: true }));
27111        let has_options = e.filter_column.is_some() || e.retention_period.is_some();
27112
27113        if is_on {
27114            self.write_keyword("ON");
27115            if has_options {
27116                self.write("(");
27117                let mut first = true;
27118                if let Some(filter_column) = &e.filter_column {
27119                    self.write_keyword("FILTER_COLUMN");
27120                    self.write("=");
27121                    self.generate_expression(filter_column)?;
27122                    first = false;
27123                }
27124                if let Some(retention_period) = &e.retention_period {
27125                    if !first {
27126                        self.write(", ");
27127                    }
27128                    self.write_keyword("RETENTION_PERIOD");
27129                    self.write("=");
27130                    self.generate_expression(retention_period)?;
27131                }
27132                self.write(")");
27133            }
27134        } else {
27135            self.write_keyword("OFF");
27136        }
27137        Ok(())
27138    }
27139
27140    /// Generate a Date function expression
27141    /// For Exasol: {d'value'} -> TO_DATE('value')
27142    /// For other dialects: DATE('value')
27143    fn generate_date_func(&mut self, e: &UnaryFunc) -> Result<()> {
27144        use crate::dialects::DialectType;
27145        use crate::expressions::Literal;
27146
27147        match self.config.dialect {
27148            // Exasol uses TO_DATE for Date expressions
27149            Some(DialectType::Exasol) => {
27150                self.write_keyword("TO_DATE");
27151                self.write("(");
27152                // Extract the string value from the expression if it's a string literal
27153                match &e.this {
27154                    Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
27155                        let Literal::String(s) = lit.as_ref() else {
27156                            unreachable!()
27157                        };
27158                        self.write("'");
27159                        self.write(s);
27160                        self.write("'");
27161                    }
27162                    _ => {
27163                        self.generate_expression(&e.this)?;
27164                    }
27165                }
27166                self.write(")");
27167            }
27168            // Standard: DATE(value)
27169            _ => {
27170                self.write_keyword("DATE");
27171                self.write("(");
27172                self.generate_expression(&e.this)?;
27173                self.write(")");
27174            }
27175        }
27176        Ok(())
27177    }
27178
27179    fn generate_date_bin(&mut self, e: &DateBin) -> Result<()> {
27180        // DATE_BIN(interval, timestamp[, origin])
27181        self.write_keyword("DATE_BIN");
27182        self.write("(");
27183        self.generate_expression(&e.this)?;
27184        self.write(", ");
27185        self.generate_expression(&e.expression)?;
27186        if let Some(origin) = &e.origin {
27187            self.write(", ");
27188            self.generate_expression(origin)?;
27189        }
27190        self.write(")");
27191        Ok(())
27192    }
27193
27194    fn generate_date_format_column_constraint(
27195        &mut self,
27196        e: &DateFormatColumnConstraint,
27197    ) -> Result<()> {
27198        // FORMAT 'format_string' (Teradata)
27199        self.write_keyword("FORMAT");
27200        self.write_space();
27201        self.generate_expression(&e.this)?;
27202        Ok(())
27203    }
27204
27205    fn generate_date_from_parts(&mut self, e: &DateFromParts) -> Result<()> {
27206        // DATE_FROM_PARTS(year, month, day) or DATEFROMPARTS(year, month, day)
27207        self.write_keyword("DATE_FROM_PARTS");
27208        self.write("(");
27209        let mut first = true;
27210        if let Some(year) = &e.year {
27211            self.generate_expression(year)?;
27212            first = false;
27213        }
27214        if let Some(month) = &e.month {
27215            if !first {
27216                self.write(", ");
27217            }
27218            self.generate_expression(month)?;
27219            first = false;
27220        }
27221        if let Some(day) = &e.day {
27222            if !first {
27223                self.write(", ");
27224            }
27225            self.generate_expression(day)?;
27226        }
27227        self.write(")");
27228        Ok(())
27229    }
27230
27231    fn generate_datetime(&mut self, e: &Datetime) -> Result<()> {
27232        // DATETIME(this) or DATETIME(this, expression)
27233        self.write_keyword("DATETIME");
27234        self.write("(");
27235        self.generate_expression(&e.this)?;
27236        if let Some(expr) = &e.expression {
27237            self.write(", ");
27238            self.generate_expression(expr)?;
27239        }
27240        self.write(")");
27241        Ok(())
27242    }
27243
27244    fn generate_datetime_add(&mut self, e: &DatetimeAdd) -> Result<()> {
27245        // DATETIME_ADD(this, expression, unit)
27246        self.write_keyword("DATETIME_ADD");
27247        self.write("(");
27248        self.generate_expression(&e.this)?;
27249        self.write(", ");
27250        self.generate_expression(&e.expression)?;
27251        if let Some(unit) = &e.unit {
27252            self.write(", ");
27253            self.write_keyword(unit);
27254        }
27255        self.write(")");
27256        Ok(())
27257    }
27258
27259    fn generate_datetime_diff(&mut self, e: &DatetimeDiff) -> Result<()> {
27260        // DATETIME_DIFF(this, expression, unit)
27261        self.write_keyword("DATETIME_DIFF");
27262        self.write("(");
27263        self.generate_expression(&e.this)?;
27264        self.write(", ");
27265        self.generate_expression(&e.expression)?;
27266        if let Some(unit) = &e.unit {
27267            self.write(", ");
27268            self.write_keyword(unit);
27269        }
27270        self.write(")");
27271        Ok(())
27272    }
27273
27274    fn generate_datetime_sub(&mut self, e: &DatetimeSub) -> Result<()> {
27275        // DATETIME_SUB(this, expression, unit)
27276        self.write_keyword("DATETIME_SUB");
27277        self.write("(");
27278        self.generate_expression(&e.this)?;
27279        self.write(", ");
27280        self.generate_expression(&e.expression)?;
27281        if let Some(unit) = &e.unit {
27282            self.write(", ");
27283            self.write_keyword(unit);
27284        }
27285        self.write(")");
27286        Ok(())
27287    }
27288
27289    fn generate_datetime_trunc(&mut self, e: &DatetimeTrunc) -> Result<()> {
27290        // DATETIME_TRUNC(this, unit, zone)
27291        self.write_keyword("DATETIME_TRUNC");
27292        self.write("(");
27293        self.generate_expression(&e.this)?;
27294        self.write(", ");
27295        self.write_keyword(&e.unit);
27296        if let Some(zone) = &e.zone {
27297            self.write(", ");
27298            self.generate_expression(zone)?;
27299        }
27300        self.write(")");
27301        Ok(())
27302    }
27303
27304    fn generate_dayname(&mut self, e: &Dayname) -> Result<()> {
27305        // DAYNAME(this)
27306        self.write_keyword("DAYNAME");
27307        self.write("(");
27308        self.generate_expression(&e.this)?;
27309        self.write(")");
27310        Ok(())
27311    }
27312
27313    fn generate_declare(&mut self, e: &Declare) -> Result<()> {
27314        // DECLARE [OR REPLACE] var1 AS type1, var2 AS type2, ...
27315        self.write_keyword("DECLARE");
27316        self.write_space();
27317        if e.replace {
27318            self.write_keyword("OR");
27319            self.write_space();
27320            self.write_keyword("REPLACE");
27321            self.write_space();
27322        }
27323        for (i, expr) in e.expressions.iter().enumerate() {
27324            if i > 0 {
27325                self.write(", ");
27326            }
27327            self.generate_expression(expr)?;
27328        }
27329        Ok(())
27330    }
27331
27332    fn generate_declare_item(&mut self, e: &DeclareItem) -> Result<()> {
27333        use crate::dialects::DialectType;
27334
27335        // variable TYPE [DEFAULT default]
27336        self.generate_expression(&e.this)?;
27337        // BigQuery multi-variable: DECLARE X, Y, Z INT64
27338        for name in &e.additional_names {
27339            self.write(", ");
27340            self.generate_expression(name)?;
27341        }
27342        if let Some(kind) = &e.kind {
27343            self.write_space();
27344            // BigQuery uses: DECLARE x INT64 DEFAULT value (no AS)
27345            // TSQL: Always includes AS (normalization)
27346            // Others: Include AS if present in original
27347            match self.config.dialect {
27348                Some(DialectType::BigQuery) => {
27349                    self.write(kind);
27350                }
27351                Some(DialectType::TSQL) => {
27352                    // TSQL DECLARE: no AS keyword (sqlglot convention)
27353                    // Normalize INT to INTEGER for simple declarations
27354                    // Complex TABLE declarations (with CLUSTERED/INDEX) are preserved as-is
27355                    let is_complex_table = kind.starts_with("TABLE")
27356                        && (kind.contains("CLUSTERED") || kind.contains("INDEX"));
27357                    if is_complex_table {
27358                        self.write(kind);
27359                    } else if kind == "INT" {
27360                        self.write("INTEGER");
27361                    } else if kind.starts_with("TABLE") {
27362                        // Normalize INT to INTEGER inside simple TABLE column definitions
27363                        let normalized = kind
27364                            .replace(" INT ", " INTEGER ")
27365                            .replace(" INT,", " INTEGER,")
27366                            .replace(" INT)", " INTEGER)")
27367                            .replace("(INT ", "(INTEGER ");
27368                        self.write(&normalized);
27369                    } else {
27370                        self.write(kind);
27371                    }
27372                }
27373                _ => {
27374                    if e.has_as {
27375                        self.write_keyword("AS");
27376                        self.write_space();
27377                    }
27378                    self.write(kind);
27379                }
27380            }
27381        }
27382        if let Some(default) = &e.default {
27383            // BigQuery uses DEFAULT, others use =
27384            match self.config.dialect {
27385                Some(DialectType::BigQuery) => {
27386                    self.write_space();
27387                    self.write_keyword("DEFAULT");
27388                    self.write_space();
27389                }
27390                _ => {
27391                    self.write(" = ");
27392                }
27393            }
27394            self.generate_expression(default)?;
27395        }
27396        Ok(())
27397    }
27398
27399    fn generate_decode_case(&mut self, e: &DecodeCase) -> Result<()> {
27400        // DECODE(expr, search1, result1, search2, result2, ..., default)
27401        self.write_keyword("DECODE");
27402        self.write("(");
27403        for (i, expr) in e.expressions.iter().enumerate() {
27404            if i > 0 {
27405                self.write(", ");
27406            }
27407            self.generate_expression(expr)?;
27408        }
27409        self.write(")");
27410        Ok(())
27411    }
27412
27413    fn generate_decompress_binary(&mut self, e: &DecompressBinary) -> Result<()> {
27414        // DECOMPRESS(expr, 'method')
27415        self.write_keyword("DECOMPRESS");
27416        self.write("(");
27417        self.generate_expression(&e.this)?;
27418        self.write(", '");
27419        self.write(&e.method);
27420        self.write("')");
27421        Ok(())
27422    }
27423
27424    fn generate_decompress_string(&mut self, e: &DecompressString) -> Result<()> {
27425        // DECOMPRESS(expr, 'method')
27426        self.write_keyword("DECOMPRESS");
27427        self.write("(");
27428        self.generate_expression(&e.this)?;
27429        self.write(", '");
27430        self.write(&e.method);
27431        self.write("')");
27432        Ok(())
27433    }
27434
27435    fn generate_decrypt(&mut self, e: &Decrypt) -> Result<()> {
27436        // DECRYPT(value, passphrase [, aad [, algorithm]])
27437        self.write_keyword("DECRYPT");
27438        self.write("(");
27439        self.generate_expression(&e.this)?;
27440        if let Some(passphrase) = &e.passphrase {
27441            self.write(", ");
27442            self.generate_expression(passphrase)?;
27443        }
27444        if let Some(aad) = &e.aad {
27445            self.write(", ");
27446            self.generate_expression(aad)?;
27447        }
27448        if let Some(method) = &e.encryption_method {
27449            self.write(", ");
27450            self.generate_expression(method)?;
27451        }
27452        self.write(")");
27453        Ok(())
27454    }
27455
27456    fn generate_decrypt_raw(&mut self, e: &DecryptRaw) -> Result<()> {
27457        // DECRYPT_RAW(value, key [, iv [, aad [, algorithm]]])
27458        self.write_keyword("DECRYPT_RAW");
27459        self.write("(");
27460        self.generate_expression(&e.this)?;
27461        if let Some(key) = &e.key {
27462            self.write(", ");
27463            self.generate_expression(key)?;
27464        }
27465        if let Some(iv) = &e.iv {
27466            self.write(", ");
27467            self.generate_expression(iv)?;
27468        }
27469        if let Some(aad) = &e.aad {
27470            self.write(", ");
27471            self.generate_expression(aad)?;
27472        }
27473        if let Some(method) = &e.encryption_method {
27474            self.write(", ");
27475            self.generate_expression(method)?;
27476        }
27477        self.write(")");
27478        Ok(())
27479    }
27480
27481    fn generate_definer_property(&mut self, e: &DefinerProperty) -> Result<()> {
27482        // DEFINER = user
27483        self.write_keyword("DEFINER");
27484        self.write(" = ");
27485        self.generate_expression(&e.this)?;
27486        Ok(())
27487    }
27488
27489    fn generate_detach(&mut self, e: &Detach) -> Result<()> {
27490        // Python: DETACH[DATABASE IF EXISTS] this
27491        self.write_keyword("DETACH");
27492        if e.exists {
27493            self.write_keyword(" DATABASE IF EXISTS");
27494        }
27495        self.write_space();
27496        self.generate_expression(&e.this)?;
27497        Ok(())
27498    }
27499
27500    fn generate_dict_property(&mut self, e: &DictProperty) -> Result<()> {
27501        let property_name = match e.this.as_ref() {
27502            Expression::Identifier(id) => id.name.as_str(),
27503            Expression::Var(v) => v.this.as_str(),
27504            _ => "DICTIONARY",
27505        };
27506        self.write_keyword(property_name);
27507        self.write("(");
27508        self.write(&e.kind);
27509        if let Some(settings) = &e.settings {
27510            self.write("(");
27511            if let Expression::Tuple(t) = settings.as_ref() {
27512                if self.config.pretty && !t.expressions.is_empty() {
27513                    self.write_newline();
27514                    self.indent_level += 1;
27515                    for (i, pair) in t.expressions.iter().enumerate() {
27516                        if i > 0 {
27517                            self.write(",");
27518                            self.write_newline();
27519                        }
27520                        self.write_indent();
27521                        if let Expression::Tuple(pair_tuple) = pair {
27522                            if let Some(k) = pair_tuple.expressions.first() {
27523                                self.generate_expression(k)?;
27524                            }
27525                            if let Some(v) = pair_tuple.expressions.get(1) {
27526                                self.write(" ");
27527                                self.generate_expression(v)?;
27528                            }
27529                        } else {
27530                            self.generate_expression(pair)?;
27531                        }
27532                    }
27533                    self.indent_level -= 1;
27534                    self.write_newline();
27535                    self.write_indent();
27536                } else {
27537                    for (i, pair) in t.expressions.iter().enumerate() {
27538                        if i > 0 {
27539                            // ClickHouse dict properties are space-separated, not comma-separated
27540                            self.write(" ");
27541                        }
27542                        if let Expression::Tuple(pair_tuple) = pair {
27543                            if let Some(k) = pair_tuple.expressions.first() {
27544                                self.generate_expression(k)?;
27545                            }
27546                            if let Some(v) = pair_tuple.expressions.get(1) {
27547                                self.write(" ");
27548                                self.generate_expression(v)?;
27549                            }
27550                        } else {
27551                            self.generate_expression(pair)?;
27552                        }
27553                    }
27554                }
27555            } else {
27556                self.generate_expression(settings)?;
27557            }
27558            self.write(")");
27559        } else {
27560            // No settings but kind had parens (e.g., SOURCE(NULL()), LAYOUT(FLAT()))
27561            self.write("()");
27562        }
27563        self.write(")");
27564        Ok(())
27565    }
27566
27567    fn generate_dict_range(&mut self, e: &DictRange) -> Result<()> {
27568        let property_name = match e.this.as_ref() {
27569            Expression::Identifier(id) => id.name.as_str(),
27570            Expression::Var(v) => v.this.as_str(),
27571            _ => "RANGE",
27572        };
27573        self.write_keyword(property_name);
27574        self.write("(");
27575        if let Some(min) = &e.min {
27576            self.write_keyword("MIN");
27577            self.write_space();
27578            self.generate_expression(min)?;
27579        }
27580        if let Some(max) = &e.max {
27581            self.write_space();
27582            self.write_keyword("MAX");
27583            self.write_space();
27584            self.generate_expression(max)?;
27585        }
27586        self.write(")");
27587        Ok(())
27588    }
27589
27590    fn generate_directory(&mut self, e: &Directory) -> Result<()> {
27591        // Python: {local}DIRECTORY {this}{row_format}
27592        if e.local.is_some() {
27593            self.write_keyword("LOCAL ");
27594        }
27595        self.write_keyword("DIRECTORY");
27596        self.write_space();
27597        self.generate_expression(&e.this)?;
27598        if let Some(row_format) = &e.row_format {
27599            self.write_space();
27600            self.generate_expression(row_format)?;
27601        }
27602        Ok(())
27603    }
27604
27605    fn generate_dist_key_property(&mut self, e: &DistKeyProperty) -> Result<()> {
27606        // Redshift: DISTKEY(column)
27607        self.write_keyword("DISTKEY");
27608        self.write("(");
27609        self.generate_expression(&e.this)?;
27610        self.write(")");
27611        Ok(())
27612    }
27613
27614    fn generate_dist_style_property(&mut self, e: &DistStyleProperty) -> Result<()> {
27615        // Redshift: DISTSTYLE KEY|ALL|EVEN|AUTO
27616        self.write_keyword("DISTSTYLE");
27617        self.write_space();
27618        self.generate_expression(&e.this)?;
27619        Ok(())
27620    }
27621
27622    fn generate_distribute_by(&mut self, e: &DistributeBy) -> Result<()> {
27623        // Python: "DISTRIBUTE BY" expressions
27624        self.write_keyword("DISTRIBUTE BY");
27625        self.write_space();
27626        for (i, expr) in e.expressions.iter().enumerate() {
27627            if i > 0 {
27628                self.write(", ");
27629            }
27630            self.generate_expression(expr)?;
27631        }
27632        Ok(())
27633    }
27634
27635    fn generate_distributed_by_property(&mut self, e: &DistributedByProperty) -> Result<()> {
27636        // Python: DISTRIBUTED BY kind (expressions) BUCKETS buckets order
27637        self.write_keyword("DISTRIBUTED BY");
27638        self.write_space();
27639        self.write(&e.kind);
27640        if !e.expressions.is_empty() {
27641            self.write(" (");
27642            for (i, expr) in e.expressions.iter().enumerate() {
27643                if i > 0 {
27644                    self.write(", ");
27645                }
27646                self.generate_expression(expr)?;
27647            }
27648            self.write(")");
27649        }
27650        if let Some(buckets) = &e.buckets {
27651            self.write_space();
27652            self.write_keyword("BUCKETS");
27653            self.write_space();
27654            self.generate_expression(buckets)?;
27655        }
27656        if let Some(order) = &e.order {
27657            self.write_space();
27658            self.generate_expression(order)?;
27659        }
27660        Ok(())
27661    }
27662
27663    fn generate_dot_product(&mut self, e: &DotProduct) -> Result<()> {
27664        // DOT_PRODUCT(vector1, vector2)
27665        self.write_keyword("DOT_PRODUCT");
27666        self.write("(");
27667        self.generate_expression(&e.this)?;
27668        self.write(", ");
27669        self.generate_expression(&e.expression)?;
27670        self.write(")");
27671        Ok(())
27672    }
27673
27674    fn generate_drop_partition(&mut self, e: &DropPartition) -> Result<()> {
27675        // Python: DROP{IF EXISTS }expressions
27676        self.write_keyword("DROP");
27677        if e.exists {
27678            self.write_keyword(" IF EXISTS ");
27679        } else {
27680            self.write_space();
27681        }
27682        for (i, expr) in e.expressions.iter().enumerate() {
27683            if i > 0 {
27684                self.write(", ");
27685            }
27686            self.generate_expression(expr)?;
27687        }
27688        Ok(())
27689    }
27690
27691    fn generate_duplicate_key_property(&mut self, e: &DuplicateKeyProperty) -> Result<()> {
27692        // Python: DUPLICATE KEY (expressions)
27693        self.write_keyword("DUPLICATE KEY");
27694        self.write(" (");
27695        for (i, expr) in e.expressions.iter().enumerate() {
27696            if i > 0 {
27697                self.write(", ");
27698            }
27699            self.generate_expression(expr)?;
27700        }
27701        self.write(")");
27702        Ok(())
27703    }
27704
27705    fn generate_elt(&mut self, e: &Elt) -> Result<()> {
27706        // ELT(index, str1, str2, ...)
27707        self.write_keyword("ELT");
27708        self.write("(");
27709        self.generate_expression(&e.this)?;
27710        for expr in &e.expressions {
27711            self.write(", ");
27712            self.generate_expression(expr)?;
27713        }
27714        self.write(")");
27715        Ok(())
27716    }
27717
27718    fn generate_encode(&mut self, e: &Encode) -> Result<()> {
27719        // ENCODE(string, charset)
27720        self.write_keyword("ENCODE");
27721        self.write("(");
27722        self.generate_expression(&e.this)?;
27723        if let Some(charset) = &e.charset {
27724            self.write(", ");
27725            self.generate_expression(charset)?;
27726        }
27727        self.write(")");
27728        Ok(())
27729    }
27730
27731    fn generate_encode_property(&mut self, e: &EncodeProperty) -> Result<()> {
27732        // Python: [KEY ]ENCODE this [properties]
27733        if e.key.is_some() {
27734            self.write_keyword("KEY ");
27735        }
27736        self.write_keyword("ENCODE");
27737        self.write_space();
27738        self.generate_expression(&e.this)?;
27739        if !e.properties.is_empty() {
27740            self.write(" (");
27741            for (i, prop) in e.properties.iter().enumerate() {
27742                if i > 0 {
27743                    self.write(", ");
27744                }
27745                self.generate_expression(prop)?;
27746            }
27747            self.write(")");
27748        }
27749        Ok(())
27750    }
27751
27752    fn generate_encrypt(&mut self, e: &Encrypt) -> Result<()> {
27753        // ENCRYPT(value, passphrase [, aad [, algorithm]])
27754        self.write_keyword("ENCRYPT");
27755        self.write("(");
27756        self.generate_expression(&e.this)?;
27757        if let Some(passphrase) = &e.passphrase {
27758            self.write(", ");
27759            self.generate_expression(passphrase)?;
27760        }
27761        if let Some(aad) = &e.aad {
27762            self.write(", ");
27763            self.generate_expression(aad)?;
27764        }
27765        if let Some(method) = &e.encryption_method {
27766            self.write(", ");
27767            self.generate_expression(method)?;
27768        }
27769        self.write(")");
27770        Ok(())
27771    }
27772
27773    fn generate_encrypt_raw(&mut self, e: &EncryptRaw) -> Result<()> {
27774        // ENCRYPT_RAW(value, key [, iv [, aad [, algorithm]]])
27775        self.write_keyword("ENCRYPT_RAW");
27776        self.write("(");
27777        self.generate_expression(&e.this)?;
27778        if let Some(key) = &e.key {
27779            self.write(", ");
27780            self.generate_expression(key)?;
27781        }
27782        if let Some(iv) = &e.iv {
27783            self.write(", ");
27784            self.generate_expression(iv)?;
27785        }
27786        if let Some(aad) = &e.aad {
27787            self.write(", ");
27788            self.generate_expression(aad)?;
27789        }
27790        if let Some(method) = &e.encryption_method {
27791            self.write(", ");
27792            self.generate_expression(method)?;
27793        }
27794        self.write(")");
27795        Ok(())
27796    }
27797
27798    fn generate_engine_property(&mut self, e: &EngineProperty) -> Result<()> {
27799        // MySQL: ENGINE = InnoDB
27800        self.write_keyword("ENGINE");
27801        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
27802            self.write("=");
27803        } else {
27804            self.write(" = ");
27805        }
27806        self.generate_expression(&e.this)?;
27807        Ok(())
27808    }
27809
27810    fn generate_enviroment_property(&mut self, e: &EnviromentProperty) -> Result<()> {
27811        // ENVIRONMENT (expressions)
27812        self.write_keyword("ENVIRONMENT");
27813        self.write(" (");
27814        for (i, expr) in e.expressions.iter().enumerate() {
27815            if i > 0 {
27816                self.write(", ");
27817            }
27818            self.generate_expression(expr)?;
27819        }
27820        self.write(")");
27821        Ok(())
27822    }
27823
27824    fn generate_ephemeral_column_constraint(
27825        &mut self,
27826        e: &EphemeralColumnConstraint,
27827    ) -> Result<()> {
27828        // MySQL: EPHEMERAL [expr]
27829        self.write_keyword("EPHEMERAL");
27830        if let Some(this) = &e.this {
27831            self.write_space();
27832            self.generate_expression(this)?;
27833        }
27834        Ok(())
27835    }
27836
27837    fn generate_equal_null(&mut self, e: &EqualNull) -> Result<()> {
27838        // Snowflake: EQUAL_NULL(a, b)
27839        self.write_keyword("EQUAL_NULL");
27840        self.write("(");
27841        self.generate_expression(&e.this)?;
27842        self.write(", ");
27843        self.generate_expression(&e.expression)?;
27844        self.write(")");
27845        Ok(())
27846    }
27847
27848    fn generate_euclidean_distance(&mut self, e: &EuclideanDistance) -> Result<()> {
27849        use crate::dialects::DialectType;
27850
27851        // PostgreSQL uses <-> operator syntax
27852        match self.config.dialect {
27853            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift) => {
27854                self.generate_expression(&e.this)?;
27855                self.write(" <-> ");
27856                self.generate_expression(&e.expression)?;
27857            }
27858            _ => {
27859                // Other dialects use EUCLIDEAN_DISTANCE function
27860                self.write_keyword("EUCLIDEAN_DISTANCE");
27861                self.write("(");
27862                self.generate_expression(&e.this)?;
27863                self.write(", ");
27864                self.generate_expression(&e.expression)?;
27865                self.write(")");
27866            }
27867        }
27868        Ok(())
27869    }
27870
27871    fn generate_execute_as_property(&mut self, e: &ExecuteAsProperty) -> Result<()> {
27872        // EXECUTE AS CALLER|OWNER|user
27873        self.write_keyword("EXECUTE AS");
27874        self.write_space();
27875        self.generate_expression(&e.this)?;
27876        Ok(())
27877    }
27878
27879    fn generate_export(&mut self, e: &Export) -> Result<()> {
27880        // BigQuery: EXPORT DATA [WITH CONNECTION connection] OPTIONS (...) AS query
27881        self.write_keyword("EXPORT DATA");
27882        if let Some(connection) = &e.connection {
27883            self.write_space();
27884            self.write_keyword("WITH CONNECTION");
27885            self.write_space();
27886            self.generate_expression(connection)?;
27887        }
27888        if !e.options.is_empty() {
27889            self.write_space();
27890            self.generate_options_clause(&e.options)?;
27891        }
27892        self.write_space();
27893        self.write_keyword("AS");
27894        self.write_space();
27895        self.generate_expression(&e.this)?;
27896        Ok(())
27897    }
27898
27899    fn generate_external_property(&mut self, e: &ExternalProperty) -> Result<()> {
27900        // EXTERNAL [this]
27901        self.write_keyword("EXTERNAL");
27902        if let Some(this) = &e.this {
27903            self.write_space();
27904            self.generate_expression(this)?;
27905        }
27906        Ok(())
27907    }
27908
27909    fn generate_fallback_property(&mut self, e: &FallbackProperty) -> Result<()> {
27910        // Python: {no}FALLBACK{protection}
27911        if e.no.is_some() {
27912            self.write_keyword("NO ");
27913        }
27914        self.write_keyword("FALLBACK");
27915        if e.protection.is_some() {
27916            self.write_keyword(" PROTECTION");
27917        }
27918        Ok(())
27919    }
27920
27921    fn generate_farm_fingerprint(&mut self, e: &FarmFingerprint) -> Result<()> {
27922        // BigQuery: FARM_FINGERPRINT(value)
27923        self.write_keyword("FARM_FINGERPRINT");
27924        self.write("(");
27925        for (i, expr) in e.expressions.iter().enumerate() {
27926            if i > 0 {
27927                self.write(", ");
27928            }
27929            self.generate_expression(expr)?;
27930        }
27931        self.write(")");
27932        Ok(())
27933    }
27934
27935    fn generate_features_at_time(&mut self, e: &FeaturesAtTime) -> Result<()> {
27936        // BigQuery ML: FEATURES_AT_TIME(feature_view, time, [num_rows], [ignore_feature_nulls])
27937        self.write_keyword("FEATURES_AT_TIME");
27938        self.write("(");
27939        self.generate_expression(&e.this)?;
27940        if let Some(time) = &e.time {
27941            self.write(", ");
27942            self.generate_expression(time)?;
27943        }
27944        if let Some(num_rows) = &e.num_rows {
27945            self.write(", ");
27946            self.generate_expression(num_rows)?;
27947        }
27948        if let Some(ignore_nulls) = &e.ignore_feature_nulls {
27949            self.write(", ");
27950            self.generate_expression(ignore_nulls)?;
27951        }
27952        self.write(")");
27953        Ok(())
27954    }
27955
27956    fn generate_fetch(&mut self, e: &Fetch) -> Result<()> {
27957        // For dialects that prefer LIMIT, convert simple FETCH to LIMIT
27958        let use_limit = !e.percent
27959            && !e.with_ties
27960            && e.count.is_some()
27961            && matches!(
27962                self.config.dialect,
27963                Some(DialectType::Spark)
27964                    | Some(DialectType::Hive)
27965                    | Some(DialectType::DuckDB)
27966                    | Some(DialectType::SQLite)
27967                    | Some(DialectType::MySQL)
27968                    | Some(DialectType::BigQuery)
27969                    | Some(DialectType::Databricks)
27970                    | Some(DialectType::StarRocks)
27971                    | Some(DialectType::Doris)
27972                    | Some(DialectType::Athena)
27973                    | Some(DialectType::ClickHouse)
27974            );
27975
27976        if use_limit {
27977            self.write_keyword("LIMIT");
27978            self.write_space();
27979            self.generate_expression(e.count.as_ref().unwrap())?;
27980            return Ok(());
27981        }
27982
27983        // Python: FETCH direction count limit_options
27984        self.write_keyword("FETCH");
27985        if !e.direction.is_empty() {
27986            self.write_space();
27987            self.write_keyword(&e.direction);
27988        }
27989        if let Some(count) = &e.count {
27990            self.write_space();
27991            self.generate_expression(count)?;
27992        }
27993        // Generate PERCENT, ROWS, WITH TIES/ONLY
27994        if e.percent {
27995            self.write_keyword(" PERCENT");
27996        }
27997        if e.rows {
27998            self.write_keyword(" ROWS");
27999        }
28000        if e.with_ties {
28001            self.write_keyword(" WITH TIES");
28002        } else if e.rows {
28003            self.write_keyword(" ONLY");
28004        } else {
28005            self.write_keyword(" ROWS ONLY");
28006        }
28007        Ok(())
28008    }
28009
28010    fn generate_file_format_property(&mut self, e: &FileFormatProperty) -> Result<()> {
28011        // For Hive format: STORED AS this or STORED AS INPUTFORMAT x OUTPUTFORMAT y
28012        // For Spark/Databricks without hive_format: USING this
28013        // For Snowflake/others: FILE_FORMAT = this or FILE_FORMAT = (expressions)
28014        if e.hive_format.is_some() {
28015            // Hive format: STORED AS ...
28016            self.write_keyword("STORED AS");
28017            self.write_space();
28018            if let Some(this) = &e.this {
28019                // Uppercase the format name (e.g., parquet -> PARQUET)
28020                if let Expression::Identifier(id) = this.as_ref() {
28021                    self.write_keyword(&id.name.to_ascii_uppercase());
28022                } else {
28023                    self.generate_expression(this)?;
28024                }
28025            }
28026        } else if matches!(self.config.dialect, Some(DialectType::Hive)) {
28027            // Hive: STORED AS format
28028            self.write_keyword("STORED AS");
28029            self.write_space();
28030            if let Some(this) = &e.this {
28031                if let Expression::Identifier(id) = this.as_ref() {
28032                    self.write_keyword(&id.name.to_ascii_uppercase());
28033                } else {
28034                    self.generate_expression(this)?;
28035                }
28036            }
28037        } else if matches!(
28038            self.config.dialect,
28039            Some(DialectType::Spark) | Some(DialectType::Databricks)
28040        ) {
28041            // Spark/Databricks: USING format (e.g., USING DELTA)
28042            self.write_keyword("USING");
28043            self.write_space();
28044            if let Some(this) = &e.this {
28045                self.generate_expression(this)?;
28046            }
28047        } else {
28048            // Snowflake/standard format
28049            self.write_keyword("FILE_FORMAT");
28050            self.write(" = ");
28051            if let Some(this) = &e.this {
28052                self.generate_expression(this)?;
28053            } else if !e.expressions.is_empty() {
28054                self.write("(");
28055                for (i, expr) in e.expressions.iter().enumerate() {
28056                    if i > 0 {
28057                        self.write(", ");
28058                    }
28059                    self.generate_expression(expr)?;
28060                }
28061                self.write(")");
28062            }
28063        }
28064        Ok(())
28065    }
28066
28067    fn generate_filter(&mut self, e: &Filter) -> Result<()> {
28068        // agg_func FILTER(WHERE condition)
28069        self.generate_expression(&e.this)?;
28070        self.write_space();
28071        self.write_keyword("FILTER");
28072        self.write("(");
28073        self.write_keyword("WHERE");
28074        self.write_space();
28075        self.generate_expression(&e.expression)?;
28076        self.write(")");
28077        Ok(())
28078    }
28079
28080    fn generate_float64(&mut self, e: &Float64) -> Result<()> {
28081        // FLOAT64(this) or FLOAT64(this, expression)
28082        self.write_keyword("FLOAT64");
28083        self.write("(");
28084        self.generate_expression(&e.this)?;
28085        if let Some(expr) = &e.expression {
28086            self.write(", ");
28087            self.generate_expression(expr)?;
28088        }
28089        self.write(")");
28090        Ok(())
28091    }
28092
28093    fn generate_for_in(&mut self, e: &ForIn) -> Result<()> {
28094        // FOR this DO expression
28095        self.write_keyword("FOR");
28096        self.write_space();
28097        self.generate_expression(&e.this)?;
28098        self.write_space();
28099        self.write_keyword("DO");
28100        self.write_space();
28101        self.generate_expression(&e.expression)?;
28102        Ok(())
28103    }
28104
28105    fn generate_foreign_key(&mut self, e: &ForeignKey) -> Result<()> {
28106        // FOREIGN KEY (cols) REFERENCES table(cols) ON DELETE action ON UPDATE action
28107        self.write_keyword("FOREIGN KEY");
28108        if !e.expressions.is_empty() {
28109            self.write(" (");
28110            for (i, expr) in e.expressions.iter().enumerate() {
28111                if i > 0 {
28112                    self.write(", ");
28113                }
28114                self.generate_expression(expr)?;
28115            }
28116            self.write(")");
28117        }
28118        if let Some(reference) = &e.reference {
28119            self.write_space();
28120            self.generate_expression(reference)?;
28121        }
28122        if let Some(delete) = &e.delete {
28123            self.write_space();
28124            self.write_keyword("ON DELETE");
28125            self.write_space();
28126            self.generate_expression(delete)?;
28127        }
28128        if let Some(update) = &e.update {
28129            self.write_space();
28130            self.write_keyword("ON UPDATE");
28131            self.write_space();
28132            self.generate_expression(update)?;
28133        }
28134        if !e.options.is_empty() {
28135            self.write_space();
28136            for (i, opt) in e.options.iter().enumerate() {
28137                if i > 0 {
28138                    self.write_space();
28139                }
28140                self.generate_expression(opt)?;
28141            }
28142        }
28143        Ok(())
28144    }
28145
28146    fn generate_format(&mut self, e: &Format) -> Result<()> {
28147        // FORMAT(this, expressions...)
28148        self.write_keyword("FORMAT");
28149        self.write("(");
28150        self.generate_expression(&e.this)?;
28151        for expr in &e.expressions {
28152            self.write(", ");
28153            self.generate_expression(expr)?;
28154        }
28155        self.write(")");
28156        Ok(())
28157    }
28158
28159    fn generate_format_phrase(&mut self, e: &FormatPhrase) -> Result<()> {
28160        // Teradata: column (FORMAT 'format_string')
28161        self.generate_expression(&e.this)?;
28162        self.write(" (");
28163        self.write_keyword("FORMAT");
28164        self.write(" '");
28165        self.write(&e.format);
28166        self.write("')");
28167        Ok(())
28168    }
28169
28170    fn generate_freespace_property(&mut self, e: &FreespaceProperty) -> Result<()> {
28171        // Python: FREESPACE=this[PERCENT]
28172        self.write_keyword("FREESPACE");
28173        self.write("=");
28174        self.generate_expression(&e.this)?;
28175        if e.percent.is_some() {
28176            self.write_keyword(" PERCENT");
28177        }
28178        Ok(())
28179    }
28180
28181    fn generate_from(&mut self, e: &From) -> Result<()> {
28182        // Python: return f"{self.seg('FROM')} {self.sql(expression, 'this')}"
28183        self.write_keyword("FROM");
28184        self.write_space();
28185
28186        // BigQuery, Hive, Spark, Databricks, SQLite, and ClickHouse prefer explicit CROSS JOIN over comma syntax
28187        // But keep commas when TABLESAMPLE is present
28188        // Also keep commas when the source dialect is Generic/None and target is one of these dialects
28189        use crate::dialects::DialectType;
28190        let has_tablesample = e
28191            .expressions
28192            .iter()
28193            .any(|expr| matches!(expr, Expression::TableSample(_)));
28194        let is_cross_join_dialect = matches!(
28195            self.config.dialect,
28196            Some(DialectType::BigQuery)
28197                | Some(DialectType::Hive)
28198                | Some(DialectType::Spark)
28199                | Some(DialectType::Databricks)
28200                | Some(DialectType::SQLite)
28201                | Some(DialectType::ClickHouse)
28202        );
28203        let source_is_same_as_target2 = self.config.source_dialect.is_some()
28204            && self.config.source_dialect == self.config.dialect;
28205        let source_is_cross_join_dialect2 = matches!(
28206            self.config.source_dialect,
28207            Some(DialectType::BigQuery)
28208                | Some(DialectType::Hive)
28209                | Some(DialectType::Spark)
28210                | Some(DialectType::Databricks)
28211                | Some(DialectType::SQLite)
28212                | Some(DialectType::ClickHouse)
28213        );
28214        let use_cross_join = !has_tablesample
28215            && is_cross_join_dialect
28216            && (source_is_same_as_target2
28217                || source_is_cross_join_dialect2
28218                || self.config.source_dialect.is_none());
28219
28220        // Snowflake wraps standalone VALUES in FROM clause with parentheses
28221        let wrap_values_in_parens = matches!(self.config.dialect, Some(DialectType::Snowflake));
28222
28223        for (i, expr) in e.expressions.iter().enumerate() {
28224            if i > 0 {
28225                if use_cross_join {
28226                    self.write(" CROSS JOIN ");
28227                } else {
28228                    self.write(", ");
28229                }
28230            }
28231            if wrap_values_in_parens && matches!(expr, Expression::Values(_)) {
28232                self.write("(");
28233                self.generate_expression(expr)?;
28234                self.write(")");
28235            } else {
28236                self.generate_expression(expr)?;
28237            }
28238            // Output leading comments that were on the table name before FROM
28239            // (e.g., FROM \n/* comment */\n tbl PIVOT(...) -> ... PIVOT(...) /* comment */)
28240            let leading = Self::extract_table_leading_comments(expr);
28241            for comment in &leading {
28242                self.write_space();
28243                self.write_formatted_comment(comment);
28244            }
28245        }
28246        Ok(())
28247    }
28248
28249    /// Extract leading_comments from a table expression (possibly wrapped in PIVOT/UNPIVOT)
28250    fn extract_table_leading_comments(expr: &Expression) -> Vec<String> {
28251        match expr {
28252            Expression::Table(t) => t.leading_comments.clone(),
28253            Expression::Pivot(p) => {
28254                if let Expression::Table(t) = &p.this {
28255                    t.leading_comments.clone()
28256                } else {
28257                    Vec::new()
28258                }
28259            }
28260            _ => Vec::new(),
28261        }
28262    }
28263
28264    fn generate_from_base(&mut self, e: &FromBase) -> Result<()> {
28265        // FROM_BASE(this, expression) - convert from base N
28266        self.write_keyword("FROM_BASE");
28267        self.write("(");
28268        self.generate_expression(&e.this)?;
28269        self.write(", ");
28270        self.generate_expression(&e.expression)?;
28271        self.write(")");
28272        Ok(())
28273    }
28274
28275    fn generate_from_time_zone(&mut self, e: &FromTimeZone) -> Result<()> {
28276        // this AT TIME ZONE zone AT TIME ZONE 'UTC'
28277        self.generate_expression(&e.this)?;
28278        if let Some(zone) = &e.zone {
28279            self.write_space();
28280            self.write_keyword("AT TIME ZONE");
28281            self.write_space();
28282            self.generate_expression(zone)?;
28283            self.write_space();
28284            self.write_keyword("AT TIME ZONE");
28285            self.write(" 'UTC'");
28286        }
28287        Ok(())
28288    }
28289
28290    fn generate_gap_fill(&mut self, e: &GapFill) -> Result<()> {
28291        // GAP_FILL(this, ts_column, bucket_width, ...)
28292        self.write_keyword("GAP_FILL");
28293        self.write("(");
28294        self.generate_expression(&e.this)?;
28295        if let Some(ts_column) = &e.ts_column {
28296            self.write(", ");
28297            self.generate_expression(ts_column)?;
28298        }
28299        if let Some(bucket_width) = &e.bucket_width {
28300            self.write(", ");
28301            self.generate_expression(bucket_width)?;
28302        }
28303        if let Some(partitioning_columns) = &e.partitioning_columns {
28304            self.write(", ");
28305            self.generate_expression(partitioning_columns)?;
28306        }
28307        if let Some(value_columns) = &e.value_columns {
28308            self.write(", ");
28309            self.generate_expression(value_columns)?;
28310        }
28311        self.write(")");
28312        Ok(())
28313    }
28314
28315    fn generate_generate_date_array(&mut self, e: &GenerateDateArray) -> Result<()> {
28316        // GENERATE_DATE_ARRAY(start, end, step)
28317        self.write_keyword("GENERATE_DATE_ARRAY");
28318        self.write("(");
28319        let mut first = true;
28320        if let Some(start) = &e.start {
28321            self.generate_expression(start)?;
28322            first = false;
28323        }
28324        if let Some(end) = &e.end {
28325            if !first {
28326                self.write(", ");
28327            }
28328            self.generate_expression(end)?;
28329            first = false;
28330        }
28331        if let Some(step) = &e.step {
28332            if !first {
28333                self.write(", ");
28334            }
28335            self.generate_expression(step)?;
28336        }
28337        self.write(")");
28338        Ok(())
28339    }
28340
28341    fn generate_generate_embedding(&mut self, e: &GenerateEmbedding) -> Result<()> {
28342        // ML.GENERATE_EMBEDDING(model, content, params)
28343        self.write_keyword("ML.GENERATE_EMBEDDING");
28344        self.write("(");
28345        self.generate_expression(&e.this)?;
28346        self.write(", ");
28347        self.generate_expression(&e.expression)?;
28348        if let Some(params) = &e.params_struct {
28349            self.write(", ");
28350            self.generate_expression(params)?;
28351        }
28352        self.write(")");
28353        Ok(())
28354    }
28355
28356    fn generate_generate_series(&mut self, e: &GenerateSeries) -> Result<()> {
28357        // Dialect-specific function name
28358        let fn_name = match self.config.dialect {
28359            Some(DialectType::Presto)
28360            | Some(DialectType::Trino)
28361            | Some(DialectType::Athena)
28362            | Some(DialectType::Spark)
28363            | Some(DialectType::Databricks)
28364            | Some(DialectType::Hive) => "SEQUENCE",
28365            _ => "GENERATE_SERIES",
28366        };
28367        self.write_keyword(fn_name);
28368        self.write("(");
28369        let mut first = true;
28370        if let Some(start) = &e.start {
28371            self.generate_expression(start)?;
28372            first = false;
28373        }
28374        if let Some(end) = &e.end {
28375            if !first {
28376                self.write(", ");
28377            }
28378            self.generate_expression(end)?;
28379            first = false;
28380        }
28381        if let Some(step) = &e.step {
28382            if !first {
28383                self.write(", ");
28384            }
28385            // For Presto/Trino: convert WEEK intervals to DAY multiples
28386            // e.g., INTERVAL '1' WEEK -> (1 * INTERVAL '7' DAY)
28387            if matches!(
28388                self.config.dialect,
28389                Some(DialectType::Presto) | Some(DialectType::Trino) | Some(DialectType::Athena)
28390            ) {
28391                if let Some(converted) = self.convert_week_interval_to_day(step) {
28392                    self.generate_expression(&converted)?;
28393                } else {
28394                    self.generate_expression(step)?;
28395                }
28396            } else {
28397                self.generate_expression(step)?;
28398            }
28399        }
28400        self.write(")");
28401        Ok(())
28402    }
28403
28404    /// Convert a WEEK interval to a DAY-based multiplication expression for Presto/Trino.
28405    /// INTERVAL N WEEK -> (N * INTERVAL '7' DAY)
28406    fn convert_week_interval_to_day(&self, expr: &Expression) -> Option<Expression> {
28407        use crate::expressions::*;
28408        if let Expression::Interval(ref iv) = expr {
28409            // Check for structured WEEK unit
28410            let (is_week, count_str) = if let Some(IntervalUnitSpec::Simple {
28411                unit: IntervalUnit::Week,
28412                ..
28413            }) = &iv.unit
28414            {
28415                // Value is in iv.this
28416                let count = match &iv.this {
28417                    Some(Expression::Literal(lit)) => match lit.as_ref() {
28418                        Literal::String(s) | Literal::Number(s) => s.clone(),
28419                        _ => return None,
28420                    },
28421                    _ => return None,
28422                };
28423                (true, count)
28424            } else if iv.unit.is_none() {
28425                // Check for string-encoded interval like "1 WEEK"
28426                if let Some(Expression::Literal(lit)) = &iv.this {
28427                    if let Literal::String(s) = lit.as_ref() {
28428                        let parts: Vec<&str> = s.trim().splitn(2, char::is_whitespace).collect();
28429                        if parts.len() == 2 && parts[1].eq_ignore_ascii_case("WEEK") {
28430                            (true, parts[0].to_string())
28431                        } else {
28432                            (false, String::new())
28433                        }
28434                    } else {
28435                        (false, String::new())
28436                    }
28437                } else {
28438                    (false, String::new())
28439                }
28440            } else {
28441                (false, String::new())
28442            };
28443
28444            if is_week {
28445                // Build: (N * INTERVAL '7' DAY)
28446                let count_expr = Expression::Literal(Box::new(Literal::Number(count_str)));
28447                let day_interval = Expression::Interval(Box::new(Interval {
28448                    this: Some(Expression::Literal(Box::new(Literal::String(
28449                        "7".to_string(),
28450                    )))),
28451                    unit: Some(IntervalUnitSpec::Simple {
28452                        unit: IntervalUnit::Day,
28453                        use_plural: false,
28454                    }),
28455                }));
28456                let mul = Expression::Mul(Box::new(BinaryOp {
28457                    left: count_expr,
28458                    right: day_interval,
28459                    left_comments: vec![],
28460                    operator_comments: vec![],
28461                    trailing_comments: vec![],
28462                    inferred_type: None,
28463                }));
28464                return Some(Expression::Paren(Box::new(Paren {
28465                    this: mul,
28466                    trailing_comments: vec![],
28467                })));
28468            }
28469        }
28470        None
28471    }
28472
28473    fn generate_generate_timestamp_array(&mut self, e: &GenerateTimestampArray) -> Result<()> {
28474        // GENERATE_TIMESTAMP_ARRAY(start, end, step)
28475        self.write_keyword("GENERATE_TIMESTAMP_ARRAY");
28476        self.write("(");
28477        let mut first = true;
28478        if let Some(start) = &e.start {
28479            self.generate_expression(start)?;
28480            first = false;
28481        }
28482        if let Some(end) = &e.end {
28483            if !first {
28484                self.write(", ");
28485            }
28486            self.generate_expression(end)?;
28487            first = false;
28488        }
28489        if let Some(step) = &e.step {
28490            if !first {
28491                self.write(", ");
28492            }
28493            self.generate_expression(step)?;
28494        }
28495        self.write(")");
28496        Ok(())
28497    }
28498
28499    fn generate_generated_as_identity_column_constraint(
28500        &mut self,
28501        e: &GeneratedAsIdentityColumnConstraint,
28502    ) -> Result<()> {
28503        use crate::dialects::DialectType;
28504
28505        // For Snowflake, use AUTOINCREMENT START x INCREMENT y syntax
28506        if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
28507            self.write_keyword("AUTOINCREMENT");
28508            if let Some(start) = &e.start {
28509                self.write_keyword(" START ");
28510                self.generate_expression(start)?;
28511            }
28512            if let Some(increment) = &e.increment {
28513                self.write_keyword(" INCREMENT ");
28514                self.generate_expression(increment)?;
28515            }
28516            return Ok(());
28517        }
28518
28519        // Python: GENERATED [ALWAYS|BY DEFAULT [ON NULL]] AS IDENTITY [(start, increment, ...)]
28520        self.write_keyword("GENERATED");
28521        if let Some(this) = &e.this {
28522            // Check if it's a truthy boolean expression
28523            if let Expression::Boolean(b) = this.as_ref() {
28524                if b.value {
28525                    self.write_keyword(" ALWAYS");
28526                } else {
28527                    self.write_keyword(" BY DEFAULT");
28528                    if e.on_null.is_some() {
28529                        self.write_keyword(" ON NULL");
28530                    }
28531                }
28532            } else {
28533                self.write_keyword(" ALWAYS");
28534            }
28535        }
28536        self.write_keyword(" AS IDENTITY");
28537        // Add sequence options if any
28538        let has_options = e.start.is_some()
28539            || e.increment.is_some()
28540            || e.minvalue.is_some()
28541            || e.maxvalue.is_some();
28542        if has_options {
28543            self.write(" (");
28544            let mut first = true;
28545            if let Some(start) = &e.start {
28546                self.write_keyword("START WITH ");
28547                self.generate_expression(start)?;
28548                first = false;
28549            }
28550            if let Some(increment) = &e.increment {
28551                if !first {
28552                    self.write(" ");
28553                }
28554                self.write_keyword("INCREMENT BY ");
28555                self.generate_expression(increment)?;
28556                first = false;
28557            }
28558            if let Some(minvalue) = &e.minvalue {
28559                if !first {
28560                    self.write(" ");
28561                }
28562                self.write_keyword("MINVALUE ");
28563                self.generate_expression(minvalue)?;
28564                first = false;
28565            }
28566            if let Some(maxvalue) = &e.maxvalue {
28567                if !first {
28568                    self.write(" ");
28569                }
28570                self.write_keyword("MAXVALUE ");
28571                self.generate_expression(maxvalue)?;
28572            }
28573            self.write(")");
28574        }
28575        Ok(())
28576    }
28577
28578    fn generate_generated_as_row_column_constraint(
28579        &mut self,
28580        e: &GeneratedAsRowColumnConstraint,
28581    ) -> Result<()> {
28582        // Python: GENERATED ALWAYS AS ROW START|END [HIDDEN]
28583        self.write_keyword("GENERATED ALWAYS AS ROW ");
28584        if e.start.is_some() {
28585            self.write_keyword("START");
28586        } else {
28587            self.write_keyword("END");
28588        }
28589        if e.hidden.is_some() {
28590            self.write_keyword(" HIDDEN");
28591        }
28592        Ok(())
28593    }
28594
28595    fn generate_get(&mut self, e: &Get) -> Result<()> {
28596        // GET this target properties
28597        self.write_keyword("GET");
28598        self.write_space();
28599        self.generate_expression(&e.this)?;
28600        if let Some(target) = &e.target {
28601            self.write_space();
28602            self.generate_expression(target)?;
28603        }
28604        for prop in &e.properties {
28605            self.write_space();
28606            self.generate_expression(prop)?;
28607        }
28608        Ok(())
28609    }
28610
28611    fn generate_get_extract(&mut self, e: &GetExtract) -> Result<()> {
28612        // GetExtract generates bracket access: this[expression]
28613        self.generate_expression(&e.this)?;
28614        self.write("[");
28615        self.generate_expression(&e.expression)?;
28616        self.write("]");
28617        Ok(())
28618    }
28619
28620    fn generate_getbit(&mut self, e: &Getbit) -> Result<()> {
28621        // GETBIT(this, expression) or GET_BIT(this, expression)
28622        self.write_keyword("GETBIT");
28623        self.write("(");
28624        self.generate_expression(&e.this)?;
28625        self.write(", ");
28626        self.generate_expression(&e.expression)?;
28627        self.write(")");
28628        Ok(())
28629    }
28630
28631    fn generate_grant_principal(&mut self, e: &GrantPrincipal) -> Result<()> {
28632        // [ROLE|GROUP|SHARE] name (e.g., "ROLE admin", "GROUP qa_users", "SHARE s1", or just "user1")
28633        if e.is_role {
28634            self.write_keyword("ROLE");
28635            self.write_space();
28636        } else if e.is_group {
28637            self.write_keyword("GROUP");
28638            self.write_space();
28639        } else if e.is_share {
28640            self.write_keyword("SHARE");
28641            self.write_space();
28642        }
28643        self.write(&e.name.name);
28644        Ok(())
28645    }
28646
28647    fn generate_grant_privilege(&mut self, e: &GrantPrivilege) -> Result<()> {
28648        // privilege(columns) or just privilege
28649        self.generate_expression(&e.this)?;
28650        if !e.expressions.is_empty() {
28651            self.write("(");
28652            for (i, expr) in e.expressions.iter().enumerate() {
28653                if i > 0 {
28654                    self.write(", ");
28655                }
28656                self.generate_expression(expr)?;
28657            }
28658            self.write(")");
28659        }
28660        Ok(())
28661    }
28662
28663    fn generate_group(&mut self, e: &Group) -> Result<()> {
28664        // Python handles GROUP BY ALL/DISTINCT modifiers and grouping expressions
28665        self.write_keyword("GROUP BY");
28666        // Handle ALL/DISTINCT modifier: Some(true) = ALL, Some(false) = DISTINCT
28667        match e.all {
28668            Some(true) => {
28669                self.write_space();
28670                self.write_keyword("ALL");
28671            }
28672            Some(false) => {
28673                self.write_space();
28674                self.write_keyword("DISTINCT");
28675            }
28676            None => {}
28677        }
28678        if !e.expressions.is_empty() {
28679            self.write_space();
28680            for (i, expr) in e.expressions.iter().enumerate() {
28681                if i > 0 {
28682                    self.write(", ");
28683                }
28684                self.generate_expression(expr)?;
28685            }
28686        }
28687        // Handle CUBE, ROLLUP, GROUPING SETS
28688        if let Some(cube) = &e.cube {
28689            if !e.expressions.is_empty() {
28690                self.write(", ");
28691            } else {
28692                self.write_space();
28693            }
28694            self.generate_expression(cube)?;
28695        }
28696        if let Some(rollup) = &e.rollup {
28697            if !e.expressions.is_empty() || e.cube.is_some() {
28698                self.write(", ");
28699            } else {
28700                self.write_space();
28701            }
28702            self.generate_expression(rollup)?;
28703        }
28704        if let Some(grouping_sets) = &e.grouping_sets {
28705            if !e.expressions.is_empty() || e.cube.is_some() || e.rollup.is_some() {
28706                self.write(", ");
28707            } else {
28708                self.write_space();
28709            }
28710            self.generate_expression(grouping_sets)?;
28711        }
28712        if let Some(totals) = &e.totals {
28713            self.write_space();
28714            self.write_keyword("WITH TOTALS");
28715            self.generate_expression(totals)?;
28716        }
28717        Ok(())
28718    }
28719
28720    fn generate_group_by(&mut self, e: &GroupBy) -> Result<()> {
28721        // GROUP BY expressions
28722        self.write_keyword("GROUP BY");
28723        // Handle ALL/DISTINCT modifier: Some(true) = ALL, Some(false) = DISTINCT
28724        match e.all {
28725            Some(true) => {
28726                self.write_space();
28727                self.write_keyword("ALL");
28728            }
28729            Some(false) => {
28730                self.write_space();
28731                self.write_keyword("DISTINCT");
28732            }
28733            None => {}
28734        }
28735
28736        // Check for trailing WITH CUBE or WITH ROLLUP (Hive/MySQL syntax)
28737        // These are represented as Cube/Rollup expressions with empty expressions at the end
28738        let mut trailing_cube = false;
28739        let mut trailing_rollup = false;
28740        let mut regular_expressions: Vec<&Expression> = Vec::new();
28741
28742        for expr in &e.expressions {
28743            match expr {
28744                Expression::Cube(c) if c.expressions.is_empty() => {
28745                    trailing_cube = true;
28746                }
28747                Expression::Rollup(r) if r.expressions.is_empty() => {
28748                    trailing_rollup = true;
28749                }
28750                _ => {
28751                    regular_expressions.push(expr);
28752                }
28753            }
28754        }
28755
28756        // In pretty mode, put columns on separate lines
28757        if self.config.pretty {
28758            self.write_newline();
28759            self.indent_level += 1;
28760            for (i, expr) in regular_expressions.iter().enumerate() {
28761                if i > 0 {
28762                    self.write(",");
28763                    self.write_newline();
28764                }
28765                self.write_indent();
28766                self.generate_expression(expr)?;
28767            }
28768            self.indent_level -= 1;
28769        } else {
28770            self.write_space();
28771            for (i, expr) in regular_expressions.iter().enumerate() {
28772                if i > 0 {
28773                    self.write(", ");
28774                }
28775                self.generate_expression(expr)?;
28776            }
28777        }
28778
28779        // Output trailing WITH CUBE or WITH ROLLUP
28780        if trailing_cube {
28781            self.write_space();
28782            self.write_keyword("WITH CUBE");
28783        } else if trailing_rollup {
28784            self.write_space();
28785            self.write_keyword("WITH ROLLUP");
28786        }
28787
28788        // ClickHouse: WITH TOTALS
28789        if e.totals {
28790            self.write_space();
28791            self.write_keyword("WITH TOTALS");
28792        }
28793
28794        Ok(())
28795    }
28796
28797    fn generate_grouping(&mut self, e: &Grouping) -> Result<()> {
28798        // GROUPING(col1, col2, ...)
28799        self.write_keyword("GROUPING");
28800        self.write("(");
28801        for (i, expr) in e.expressions.iter().enumerate() {
28802            if i > 0 {
28803                self.write(", ");
28804            }
28805            self.generate_expression(expr)?;
28806        }
28807        self.write(")");
28808        Ok(())
28809    }
28810
28811    fn generate_grouping_id(&mut self, e: &GroupingId) -> Result<()> {
28812        // GROUPING_ID(col1, col2, ...)
28813        self.write_keyword("GROUPING_ID");
28814        self.write("(");
28815        for (i, expr) in e.expressions.iter().enumerate() {
28816            if i > 0 {
28817                self.write(", ");
28818            }
28819            self.generate_expression(expr)?;
28820        }
28821        self.write(")");
28822        Ok(())
28823    }
28824
28825    fn generate_grouping_sets(&mut self, e: &GroupingSets) -> Result<()> {
28826        // Python: return f"GROUPING SETS {self.wrap(grouping_sets)}"
28827        self.write_keyword("GROUPING SETS");
28828        self.write(" (");
28829        for (i, expr) in e.expressions.iter().enumerate() {
28830            if i > 0 {
28831                self.write(", ");
28832            }
28833            self.generate_expression(expr)?;
28834        }
28835        self.write(")");
28836        Ok(())
28837    }
28838
28839    fn generate_hash_agg(&mut self, e: &HashAgg) -> Result<()> {
28840        // HASH_AGG(this, expressions...)
28841        self.write_keyword("HASH_AGG");
28842        self.write("(");
28843        self.generate_expression(&e.this)?;
28844        for expr in &e.expressions {
28845            self.write(", ");
28846            self.generate_expression(expr)?;
28847        }
28848        self.write(")");
28849        Ok(())
28850    }
28851
28852    fn generate_having(&mut self, e: &Having) -> Result<()> {
28853        // Python: return f"{self.seg('HAVING')}{self.sep()}{this}"
28854        self.write_keyword("HAVING");
28855        self.write_space();
28856        self.generate_expression(&e.this)?;
28857        Ok(())
28858    }
28859
28860    fn generate_having_max(&mut self, e: &HavingMax) -> Result<()> {
28861        // Python: this HAVING MAX|MIN expression
28862        self.generate_expression(&e.this)?;
28863        self.write_space();
28864        self.write_keyword("HAVING");
28865        self.write_space();
28866        if e.max.is_some() {
28867            self.write_keyword("MAX");
28868        } else {
28869            self.write_keyword("MIN");
28870        }
28871        self.write_space();
28872        self.generate_expression(&e.expression)?;
28873        Ok(())
28874    }
28875
28876    fn generate_heredoc(&mut self, e: &Heredoc) -> Result<()> {
28877        use crate::dialects::DialectType;
28878        // DuckDB: convert dollar-tagged strings to single-quoted
28879        if matches!(self.config.dialect, Some(DialectType::DuckDB)) {
28880            // Extract the string content and output as single-quoted
28881            if let Expression::Literal(ref lit) = *e.this {
28882                if let Literal::String(ref s) = lit.as_ref() {
28883                    return self.generate_string_literal(s);
28884                }
28885            }
28886        }
28887        // PostgreSQL: preserve dollar-quoting
28888        if matches!(
28889            self.config.dialect,
28890            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
28891        ) {
28892            self.write("$");
28893            if let Some(tag) = &e.tag {
28894                self.generate_expression(tag)?;
28895            }
28896            self.write("$");
28897            self.generate_expression(&e.this)?;
28898            self.write("$");
28899            if let Some(tag) = &e.tag {
28900                self.generate_expression(tag)?;
28901            }
28902            self.write("$");
28903            return Ok(());
28904        }
28905        // Default: output as dollar-tagged
28906        self.write("$");
28907        if let Some(tag) = &e.tag {
28908            self.generate_expression(tag)?;
28909        }
28910        self.write("$");
28911        self.generate_expression(&e.this)?;
28912        self.write("$");
28913        if let Some(tag) = &e.tag {
28914            self.generate_expression(tag)?;
28915        }
28916        self.write("$");
28917        Ok(())
28918    }
28919
28920    fn generate_hex_encode(&mut self, e: &HexEncode) -> Result<()> {
28921        // HEX_ENCODE(this)
28922        self.write_keyword("HEX_ENCODE");
28923        self.write("(");
28924        self.generate_expression(&e.this)?;
28925        self.write(")");
28926        Ok(())
28927    }
28928
28929    fn generate_historical_data(&mut self, e: &HistoricalData) -> Result<()> {
28930        // Python: this (kind => expression)
28931        // Write the keyword (AT/BEFORE/END) directly to avoid quoting it as a reserved word
28932        match e.this.as_ref() {
28933            Expression::Identifier(id) => self.write(&id.name),
28934            other => self.generate_expression(other)?,
28935        }
28936        self.write(" (");
28937        self.write(&e.kind);
28938        self.write(" => ");
28939        self.generate_expression(&e.expression)?;
28940        self.write(")");
28941        Ok(())
28942    }
28943
28944    fn generate_hll(&mut self, e: &Hll) -> Result<()> {
28945        // HLL(this, expressions...)
28946        self.write_keyword("HLL");
28947        self.write("(");
28948        self.generate_expression(&e.this)?;
28949        for expr in &e.expressions {
28950            self.write(", ");
28951            self.generate_expression(expr)?;
28952        }
28953        self.write(")");
28954        Ok(())
28955    }
28956
28957    fn generate_in_out_column_constraint(&mut self, e: &InOutColumnConstraint) -> Result<()> {
28958        // Python: IN|OUT|IN OUT
28959        if e.input_.is_some() && e.output.is_some() {
28960            self.write_keyword("IN OUT");
28961        } else if e.input_.is_some() {
28962            self.write_keyword("IN");
28963        } else if e.output.is_some() {
28964            self.write_keyword("OUT");
28965        }
28966        Ok(())
28967    }
28968
28969    fn generate_include_property(&mut self, e: &IncludeProperty) -> Result<()> {
28970        // Python: INCLUDE this [column_def] [AS alias]
28971        self.write_keyword("INCLUDE");
28972        self.write_space();
28973        self.generate_expression(&e.this)?;
28974        if let Some(column_def) = &e.column_def {
28975            self.write_space();
28976            self.generate_expression(column_def)?;
28977        }
28978        if let Some(alias) = &e.alias {
28979            self.write_space();
28980            self.write_keyword("AS");
28981            self.write_space();
28982            self.write(alias);
28983        }
28984        Ok(())
28985    }
28986
28987    fn generate_index(&mut self, e: &Index) -> Result<()> {
28988        // [UNIQUE] [PRIMARY] [AMP] INDEX [name] [ON table] (params)
28989        if e.unique {
28990            self.write_keyword("UNIQUE");
28991            self.write_space();
28992        }
28993        if e.primary.is_some() {
28994            self.write_keyword("PRIMARY");
28995            self.write_space();
28996        }
28997        if e.amp.is_some() {
28998            self.write_keyword("AMP");
28999            self.write_space();
29000        }
29001        if e.table.is_none() {
29002            self.write_keyword("INDEX");
29003            self.write_space();
29004        }
29005        if let Some(name) = &e.this {
29006            self.generate_expression(name)?;
29007            self.write_space();
29008        }
29009        if let Some(table) = &e.table {
29010            self.write_keyword("ON");
29011            self.write_space();
29012            self.generate_expression(table)?;
29013        }
29014        if !e.params.is_empty() {
29015            self.write("(");
29016            for (i, param) in e.params.iter().enumerate() {
29017                if i > 0 {
29018                    self.write(", ");
29019                }
29020                self.generate_expression(param)?;
29021            }
29022            self.write(")");
29023        }
29024        Ok(())
29025    }
29026
29027    fn generate_index_column_constraint(&mut self, e: &IndexColumnConstraint) -> Result<()> {
29028        // Python: kind INDEX [this] [USING index_type] (expressions) [options]
29029        if let Some(kind) = &e.kind {
29030            self.write(kind);
29031            self.write_space();
29032        }
29033        self.write_keyword("INDEX");
29034        if let Some(this) = &e.this {
29035            self.write_space();
29036            self.generate_expression(this)?;
29037        }
29038        if let Some(index_type) = &e.index_type {
29039            self.write_space();
29040            self.write_keyword("USING");
29041            self.write_space();
29042            self.generate_expression(index_type)?;
29043        }
29044        if !e.expressions.is_empty() {
29045            self.write(" (");
29046            for (i, expr) in e.expressions.iter().enumerate() {
29047                if i > 0 {
29048                    self.write(", ");
29049                }
29050                self.generate_expression(expr)?;
29051            }
29052            self.write(")");
29053        }
29054        for opt in &e.options {
29055            self.write_space();
29056            self.generate_expression(opt)?;
29057        }
29058        Ok(())
29059    }
29060
29061    fn generate_index_constraint_option(&mut self, e: &IndexConstraintOption) -> Result<()> {
29062        // Python: KEY_BLOCK_SIZE = x | USING x | WITH PARSER x | COMMENT x | visible | engine_attr | secondary_engine_attr
29063        if let Some(key_block_size) = &e.key_block_size {
29064            self.write_keyword("KEY_BLOCK_SIZE");
29065            self.write(" = ");
29066            self.generate_expression(key_block_size)?;
29067        } else if let Some(using) = &e.using {
29068            self.write_keyword("USING");
29069            self.write_space();
29070            self.generate_expression(using)?;
29071        } else if let Some(parser) = &e.parser {
29072            self.write_keyword("WITH PARSER");
29073            self.write_space();
29074            self.generate_expression(parser)?;
29075        } else if let Some(comment) = &e.comment {
29076            self.write_keyword("COMMENT");
29077            self.write_space();
29078            self.generate_expression(comment)?;
29079        } else if let Some(visible) = &e.visible {
29080            self.generate_expression(visible)?;
29081        } else if let Some(engine_attr) = &e.engine_attr {
29082            self.write_keyword("ENGINE_ATTRIBUTE");
29083            self.write(" = ");
29084            self.generate_expression(engine_attr)?;
29085        } else if let Some(secondary_engine_attr) = &e.secondary_engine_attr {
29086            self.write_keyword("SECONDARY_ENGINE_ATTRIBUTE");
29087            self.write(" = ");
29088            self.generate_expression(secondary_engine_attr)?;
29089        }
29090        Ok(())
29091    }
29092
29093    fn generate_index_parameters(&mut self, e: &IndexParameters) -> Result<()> {
29094        // Python: [USING using] (columns) [PARTITION BY partition_by] [where] [INCLUDE (include)] [WITH (with_storage)] [USING INDEX TABLESPACE tablespace]
29095        if let Some(using) = &e.using {
29096            self.write_keyword("USING");
29097            self.write_space();
29098            self.generate_expression(using)?;
29099        }
29100        if !e.columns.is_empty() {
29101            self.write("(");
29102            for (i, col) in e.columns.iter().enumerate() {
29103                if i > 0 {
29104                    self.write(", ");
29105                }
29106                self.generate_expression(col)?;
29107            }
29108            self.write(")");
29109        }
29110        if let Some(partition_by) = &e.partition_by {
29111            self.write_space();
29112            self.write_keyword("PARTITION BY");
29113            self.write_space();
29114            self.generate_expression(partition_by)?;
29115        }
29116        if let Some(where_) = &e.where_ {
29117            self.write_space();
29118            self.generate_expression(where_)?;
29119        }
29120        if let Some(include) = &e.include {
29121            self.write_space();
29122            self.write_keyword("INCLUDE");
29123            self.write(" (");
29124            self.generate_expression(include)?;
29125            self.write(")");
29126        }
29127        if let Some(with_storage) = &e.with_storage {
29128            self.write_space();
29129            self.write_keyword("WITH");
29130            self.write(" (");
29131            self.generate_expression(with_storage)?;
29132            self.write(")");
29133        }
29134        if let Some(tablespace) = &e.tablespace {
29135            self.write_space();
29136            self.write_keyword("USING INDEX TABLESPACE");
29137            self.write_space();
29138            self.generate_expression(tablespace)?;
29139        }
29140        Ok(())
29141    }
29142
29143    fn generate_index_table_hint(&mut self, e: &IndexTableHint) -> Result<()> {
29144        // Python: this INDEX [FOR target] (expressions)
29145        // Write hint type (USE/IGNORE/FORCE) as keyword, not through generate_expression
29146        // to avoid quoting reserved keywords like IGNORE, FORCE, JOIN
29147        if let Expression::Identifier(id) = &*e.this {
29148            self.write_keyword(&id.name);
29149        } else {
29150            self.generate_expression(&e.this)?;
29151        }
29152        self.write_space();
29153        self.write_keyword("INDEX");
29154        if let Some(target) = &e.target {
29155            self.write_space();
29156            self.write_keyword("FOR");
29157            self.write_space();
29158            if let Expression::Identifier(id) = &**target {
29159                self.write_keyword(&id.name);
29160            } else {
29161                self.generate_expression(target)?;
29162            }
29163        }
29164        // Always output parentheses (even if empty, e.g. USE INDEX ())
29165        self.write(" (");
29166        for (i, expr) in e.expressions.iter().enumerate() {
29167            if i > 0 {
29168                self.write(", ");
29169            }
29170            self.generate_expression(expr)?;
29171        }
29172        self.write(")");
29173        Ok(())
29174    }
29175
29176    fn generate_inherits_property(&mut self, e: &InheritsProperty) -> Result<()> {
29177        // INHERITS (table1, table2, ...)
29178        self.write_keyword("INHERITS");
29179        self.write(" (");
29180        for (i, expr) in e.expressions.iter().enumerate() {
29181            if i > 0 {
29182                self.write(", ");
29183            }
29184            self.generate_expression(expr)?;
29185        }
29186        self.write(")");
29187        Ok(())
29188    }
29189
29190    fn generate_input_model_property(&mut self, e: &InputModelProperty) -> Result<()> {
29191        // INPUT(model)
29192        self.write_keyword("INPUT");
29193        self.write("(");
29194        self.generate_expression(&e.this)?;
29195        self.write(")");
29196        Ok(())
29197    }
29198
29199    fn generate_input_output_format(&mut self, e: &InputOutputFormat) -> Result<()> {
29200        // Python: INPUTFORMAT input_format OUTPUTFORMAT output_format
29201        if let Some(input_format) = &e.input_format {
29202            self.write_keyword("INPUTFORMAT");
29203            self.write_space();
29204            self.generate_expression(input_format)?;
29205        }
29206        if let Some(output_format) = &e.output_format {
29207            if e.input_format.is_some() {
29208                self.write(" ");
29209            }
29210            self.write_keyword("OUTPUTFORMAT");
29211            self.write_space();
29212            self.generate_expression(output_format)?;
29213        }
29214        Ok(())
29215    }
29216
29217    fn generate_install(&mut self, e: &Install) -> Result<()> {
29218        // [FORCE] INSTALL extension [FROM source]
29219        if e.force.is_some() {
29220            self.write_keyword("FORCE");
29221            self.write_space();
29222        }
29223        self.write_keyword("INSTALL");
29224        self.write_space();
29225        self.generate_expression(&e.this)?;
29226        if let Some(from) = &e.from_ {
29227            self.write_space();
29228            self.write_keyword("FROM");
29229            self.write_space();
29230            self.generate_expression(from)?;
29231        }
29232        Ok(())
29233    }
29234
29235    fn generate_interval_op(&mut self, e: &IntervalOp) -> Result<()> {
29236        // INTERVAL 'expression' unit
29237        self.write_keyword("INTERVAL");
29238        self.write_space();
29239        // When a unit is specified and the expression is a number,
29240        self.generate_expression(&e.expression)?;
29241        if let Some(unit) = &e.unit {
29242            self.write_space();
29243            self.write(unit);
29244        }
29245        Ok(())
29246    }
29247
29248    fn generate_interval_span(&mut self, e: &IntervalSpan) -> Result<()> {
29249        // unit TO unit (e.g., HOUR TO SECOND)
29250        self.write(&format!("{:?}", e.this).to_ascii_uppercase());
29251        self.write_space();
29252        self.write_keyword("TO");
29253        self.write_space();
29254        self.write(&format!("{:?}", e.expression).to_ascii_uppercase());
29255        Ok(())
29256    }
29257
29258    fn generate_into_clause(&mut self, e: &IntoClause) -> Result<()> {
29259        // INTO [TEMPORARY|UNLOGGED] table
29260        self.write_keyword("INTO");
29261        if e.temporary {
29262            self.write_keyword(" TEMPORARY");
29263        }
29264        if e.unlogged.is_some() {
29265            self.write_keyword(" UNLOGGED");
29266        }
29267        if let Some(this) = &e.this {
29268            self.write_space();
29269            self.generate_expression(this)?;
29270        }
29271        if !e.expressions.is_empty() {
29272            self.write(" (");
29273            for (i, expr) in e.expressions.iter().enumerate() {
29274                if i > 0 {
29275                    self.write(", ");
29276                }
29277                self.generate_expression(expr)?;
29278            }
29279            self.write(")");
29280        }
29281        Ok(())
29282    }
29283
29284    fn generate_introducer(&mut self, e: &Introducer) -> Result<()> {
29285        // Python: this expression (e.g., _utf8 'string')
29286        self.generate_expression(&e.this)?;
29287        self.write_space();
29288        self.generate_expression(&e.expression)?;
29289        Ok(())
29290    }
29291
29292    fn generate_isolated_loading_property(&mut self, e: &IsolatedLoadingProperty) -> Result<()> {
29293        // Python: WITH [NO] [CONCURRENT] ISOLATED LOADING [target]
29294        self.write_keyword("WITH");
29295        if e.no.is_some() {
29296            self.write_keyword(" NO");
29297        }
29298        if e.concurrent.is_some() {
29299            self.write_keyword(" CONCURRENT");
29300        }
29301        self.write_keyword(" ISOLATED LOADING");
29302        if let Some(target) = &e.target {
29303            self.write_space();
29304            self.generate_expression(target)?;
29305        }
29306        Ok(())
29307    }
29308
29309    fn generate_json(&mut self, e: &JSON) -> Result<()> {
29310        // Python: JSON [this] [WITHOUT|WITH] [UNIQUE KEYS]
29311        self.write_keyword("JSON");
29312        if let Some(this) = &e.this {
29313            self.write_space();
29314            self.generate_expression(this)?;
29315        }
29316        if let Some(with_) = &e.with_ {
29317            // Check if it's a truthy boolean
29318            if let Expression::Boolean(b) = with_.as_ref() {
29319                if b.value {
29320                    self.write_keyword(" WITH");
29321                } else {
29322                    self.write_keyword(" WITHOUT");
29323                }
29324            }
29325        }
29326        if e.unique {
29327            self.write_keyword(" UNIQUE KEYS");
29328        }
29329        Ok(())
29330    }
29331
29332    fn generate_json_array(&mut self, e: &JSONArray) -> Result<()> {
29333        // Python: return self.func("JSON_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})")
29334        self.write_keyword("JSON_ARRAY");
29335        self.write("(");
29336        for (i, expr) in e.expressions.iter().enumerate() {
29337            if i > 0 {
29338                self.write(", ");
29339            }
29340            self.generate_expression(expr)?;
29341        }
29342        if let Some(null_handling) = &e.null_handling {
29343            self.write_space();
29344            self.generate_expression(null_handling)?;
29345        }
29346        if let Some(return_type) = &e.return_type {
29347            self.write_space();
29348            self.write_keyword("RETURNING");
29349            self.write_space();
29350            self.generate_expression(return_type)?;
29351        }
29352        if e.strict.is_some() {
29353            self.write_space();
29354            self.write_keyword("STRICT");
29355        }
29356        self.write(")");
29357        Ok(())
29358    }
29359
29360    fn generate_json_array_agg_struct(&mut self, e: &JSONArrayAgg) -> Result<()> {
29361        // JSON_ARRAYAGG(this [ORDER BY ...] [NULL ON NULL | ABSENT ON NULL] [RETURNING type] [STRICT])
29362        self.write_keyword("JSON_ARRAYAGG");
29363        self.write("(");
29364        self.generate_expression(&e.this)?;
29365        if let Some(order) = &e.order {
29366            self.write_space();
29367            // Order is stored as an OrderBy expression
29368            if let Expression::OrderBy(ob) = order.as_ref() {
29369                self.write_keyword("ORDER BY");
29370                self.write_space();
29371                for (i, ord) in ob.expressions.iter().enumerate() {
29372                    if i > 0 {
29373                        self.write(", ");
29374                    }
29375                    self.generate_ordered(ord)?;
29376                }
29377            } else {
29378                // Fallback: generate the expression directly
29379                self.generate_expression(order)?;
29380            }
29381        }
29382        if let Some(null_handling) = &e.null_handling {
29383            self.write_space();
29384            self.generate_expression(null_handling)?;
29385        }
29386        if let Some(return_type) = &e.return_type {
29387            self.write_space();
29388            self.write_keyword("RETURNING");
29389            self.write_space();
29390            self.generate_expression(return_type)?;
29391        }
29392        if e.strict.is_some() {
29393            self.write_space();
29394            self.write_keyword("STRICT");
29395        }
29396        self.write(")");
29397        Ok(())
29398    }
29399
29400    fn generate_json_object_agg_struct(&mut self, e: &JSONObjectAgg) -> Result<()> {
29401        // JSON_OBJECTAGG(key: value [NULL ON NULL | ABSENT ON NULL] [WITH UNIQUE KEYS] [RETURNING type])
29402        self.write_keyword("JSON_OBJECTAGG");
29403        self.write("(");
29404        for (i, expr) in e.expressions.iter().enumerate() {
29405            if i > 0 {
29406                self.write(", ");
29407            }
29408            self.generate_expression(expr)?;
29409        }
29410        if let Some(null_handling) = &e.null_handling {
29411            self.write_space();
29412            self.generate_expression(null_handling)?;
29413        }
29414        if let Some(unique_keys) = &e.unique_keys {
29415            self.write_space();
29416            if let Expression::Boolean(b) = unique_keys.as_ref() {
29417                if b.value {
29418                    self.write_keyword("WITH UNIQUE KEYS");
29419                } else {
29420                    self.write_keyword("WITHOUT UNIQUE KEYS");
29421                }
29422            }
29423        }
29424        if let Some(return_type) = &e.return_type {
29425            self.write_space();
29426            self.write_keyword("RETURNING");
29427            self.write_space();
29428            self.generate_expression(return_type)?;
29429        }
29430        self.write(")");
29431        Ok(())
29432    }
29433
29434    fn generate_json_array_append(&mut self, e: &JSONArrayAppend) -> Result<()> {
29435        // JSON_ARRAY_APPEND(this, path, value, ...)
29436        self.write_keyword("JSON_ARRAY_APPEND");
29437        self.write("(");
29438        self.generate_expression(&e.this)?;
29439        for expr in &e.expressions {
29440            self.write(", ");
29441            self.generate_expression(expr)?;
29442        }
29443        self.write(")");
29444        Ok(())
29445    }
29446
29447    fn generate_json_array_contains(&mut self, e: &JSONArrayContains) -> Result<()> {
29448        // JSON_ARRAY_CONTAINS(this, expression)
29449        self.write_keyword("JSON_ARRAY_CONTAINS");
29450        self.write("(");
29451        self.generate_expression(&e.this)?;
29452        self.write(", ");
29453        self.generate_expression(&e.expression)?;
29454        self.write(")");
29455        Ok(())
29456    }
29457
29458    fn generate_json_array_insert(&mut self, e: &JSONArrayInsert) -> Result<()> {
29459        // JSON_ARRAY_INSERT(this, path, value, ...)
29460        self.write_keyword("JSON_ARRAY_INSERT");
29461        self.write("(");
29462        self.generate_expression(&e.this)?;
29463        for expr in &e.expressions {
29464            self.write(", ");
29465            self.generate_expression(expr)?;
29466        }
29467        self.write(")");
29468        Ok(())
29469    }
29470
29471    fn generate_jsonb_exists(&mut self, e: &JSONBExists) -> Result<()> {
29472        // JSONB_EXISTS(this, path)
29473        self.write_keyword("JSONB_EXISTS");
29474        self.write("(");
29475        self.generate_expression(&e.this)?;
29476        if let Some(path) = &e.path {
29477            self.write(", ");
29478            self.generate_expression(path)?;
29479        }
29480        self.write(")");
29481        Ok(())
29482    }
29483
29484    fn generate_jsonb_extract_scalar(&mut self, e: &JSONBExtractScalar) -> Result<()> {
29485        // JSONB_EXTRACT_SCALAR(this, expression)
29486        self.write_keyword("JSONB_EXTRACT_SCALAR");
29487        self.write("(");
29488        self.generate_expression(&e.this)?;
29489        self.write(", ");
29490        self.generate_expression(&e.expression)?;
29491        self.write(")");
29492        Ok(())
29493    }
29494
29495    fn generate_jsonb_object_agg(&mut self, e: &JSONBObjectAgg) -> Result<()> {
29496        // JSONB_OBJECT_AGG(this, expression)
29497        self.write_keyword("JSONB_OBJECT_AGG");
29498        self.write("(");
29499        self.generate_expression(&e.this)?;
29500        self.write(", ");
29501        self.generate_expression(&e.expression)?;
29502        self.write(")");
29503        Ok(())
29504    }
29505
29506    fn generate_json_column_def(&mut self, e: &JSONColumnDef) -> Result<()> {
29507        // Python: NESTED PATH path schema | this kind PATH path [FOR ORDINALITY]
29508        if let Some(nested_schema) = &e.nested_schema {
29509            self.write_keyword("NESTED");
29510            if let Some(path) = &e.path {
29511                self.write_space();
29512                self.write_keyword("PATH");
29513                self.write_space();
29514                self.generate_expression(path)?;
29515            }
29516            self.write_space();
29517            self.generate_expression(nested_schema)?;
29518        } else {
29519            if let Some(this) = &e.this {
29520                self.generate_expression(this)?;
29521            }
29522            if let Some(kind) = &e.kind {
29523                self.write_space();
29524                self.write(kind);
29525            }
29526            if let Some(path) = &e.path {
29527                self.write_space();
29528                self.write_keyword("PATH");
29529                self.write_space();
29530                self.generate_expression(path)?;
29531            }
29532            if e.ordinality.is_some() {
29533                self.write_keyword(" FOR ORDINALITY");
29534            }
29535        }
29536        Ok(())
29537    }
29538
29539    fn generate_json_exists(&mut self, e: &JSONExists) -> Result<()> {
29540        // JSON_EXISTS(this, path PASSING vars ON ERROR/EMPTY condition)
29541        self.write_keyword("JSON_EXISTS");
29542        self.write("(");
29543        self.generate_expression(&e.this)?;
29544        if let Some(path) = &e.path {
29545            self.write(", ");
29546            self.generate_expression(path)?;
29547        }
29548        if let Some(passing) = &e.passing {
29549            self.write_space();
29550            self.write_keyword("PASSING");
29551            self.write_space();
29552            self.generate_expression(passing)?;
29553        }
29554        if let Some(on_condition) = &e.on_condition {
29555            self.write_space();
29556            self.generate_expression(on_condition)?;
29557        }
29558        self.write(")");
29559        Ok(())
29560    }
29561
29562    fn generate_json_cast(&mut self, e: &JSONCast) -> Result<()> {
29563        self.generate_expression(&e.this)?;
29564        self.write(".:");
29565        // If the data type has nested type parameters (like Array(JSON), Map(String, Int)),
29566        // wrap the entire type string in double quotes.
29567        // This matches Python sqlglot's ClickHouse _json_cast_sql behavior.
29568        if Self::data_type_has_nested_expressions(&e.to) {
29569            // Generate the data type to a temporary string buffer, then wrap in quotes
29570            let saved = std::mem::take(&mut self.output);
29571            self.generate_data_type(&e.to)?;
29572            let type_sql = std::mem::replace(&mut self.output, saved);
29573            self.write("\"");
29574            self.write(&type_sql);
29575            self.write("\"");
29576        } else {
29577            self.generate_data_type(&e.to)?;
29578        }
29579        Ok(())
29580    }
29581
29582    /// Check if a DataType has nested type expressions (sub-types).
29583    /// This corresponds to Python sqlglot's `to.expressions` being non-empty.
29584    fn data_type_has_nested_expressions(dt: &DataType) -> bool {
29585        matches!(
29586            dt,
29587            DataType::Array { .. } | DataType::Map { .. } | DataType::Struct { .. }
29588        )
29589    }
29590
29591    fn generate_json_extract_array(&mut self, e: &JSONExtractArray) -> Result<()> {
29592        // JSON_EXTRACT_ARRAY(this, expression)
29593        self.write_keyword("JSON_EXTRACT_ARRAY");
29594        self.write("(");
29595        self.generate_expression(&e.this)?;
29596        if let Some(expr) = &e.expression {
29597            self.write(", ");
29598            self.generate_expression(expr)?;
29599        }
29600        self.write(")");
29601        Ok(())
29602    }
29603
29604    fn generate_json_extract_quote(&mut self, e: &JSONExtractQuote) -> Result<()> {
29605        // Snowflake: KEEP [OMIT] QUOTES [SCALAR_ONLY] for JSON extraction
29606        if let Some(option) = &e.option {
29607            self.generate_expression(option)?;
29608            self.write_space();
29609        }
29610        self.write_keyword("QUOTES");
29611        if e.scalar.is_some() {
29612            self.write_keyword(" SCALAR_ONLY");
29613        }
29614        Ok(())
29615    }
29616
29617    fn generate_json_extract_scalar(&mut self, e: &JSONExtractScalar) -> Result<()> {
29618        // JSON_EXTRACT_SCALAR(this, expression)
29619        self.write_keyword("JSON_EXTRACT_SCALAR");
29620        self.write("(");
29621        self.generate_expression(&e.this)?;
29622        self.write(", ");
29623        self.generate_expression(&e.expression)?;
29624        self.write(")");
29625        Ok(())
29626    }
29627
29628    fn generate_json_extract_path(&mut self, e: &JSONExtract) -> Result<()> {
29629        // For variant_extract (Snowflake/Databricks colon syntax like a:field)
29630        // Databricks uses col:path syntax, Snowflake uses GET_PATH(col, 'path')
29631        // Otherwise output JSON_EXTRACT(this, expression)
29632        if e.variant_extract.is_some() {
29633            use crate::dialects::DialectType;
29634            if matches!(self.config.dialect, Some(DialectType::Databricks)) {
29635                // Databricks: output col:path syntax (e.g., c1:price, c1:price.foo, c1:price.bar[1])
29636                // Keys that are not safe identifiers (contain hyphens, spaces, etc.) must use
29637                // bracket notation: c:["x-y"] instead of c:x-y
29638                self.generate_expression(&e.this)?;
29639                self.write(":");
29640                match e.expression.as_ref() {
29641                    Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
29642                        let Literal::String(s) = lit.as_ref() else {
29643                            unreachable!()
29644                        };
29645                        self.write_databricks_json_path(s);
29646                    }
29647                    _ => {
29648                        // Fallback: generate as-is (shouldn't happen in typical cases)
29649                        self.generate_expression(&e.expression)?;
29650                    }
29651                }
29652            } else {
29653                // Snowflake and others: use GET_PATH(col, 'path')
29654                self.write_keyword("GET_PATH");
29655                self.write("(");
29656                self.generate_expression(&e.this)?;
29657                self.write(", ");
29658                self.generate_expression(&e.expression)?;
29659                self.write(")");
29660            }
29661        } else {
29662            self.write_keyword("JSON_EXTRACT");
29663            self.write("(");
29664            self.generate_expression(&e.this)?;
29665            self.write(", ");
29666            self.generate_expression(&e.expression)?;
29667            for expr in &e.expressions {
29668                self.write(", ");
29669                self.generate_expression(expr)?;
29670            }
29671            self.write(")");
29672        }
29673        Ok(())
29674    }
29675
29676    /// Write a Databricks JSON colon-path, using bracket notation for keys
29677    /// that are not safe identifiers (e.g., contain hyphens, spaces, etc.)
29678    /// Safe identifier regex: ^[_a-zA-Z]\w*$
29679    fn write_databricks_json_path(&mut self, path: &str) {
29680        // If the path already starts with bracket notation (e.g., '["fr\'uit"]'),
29681        // it was already formatted by the parser - output as-is
29682        if path.starts_with("[\"") || path.starts_with("['") {
29683            self.write(path);
29684            return;
29685        }
29686        // Split the path into segments at '.' boundaries, but preserve bracket subscripts
29687        // e.g., "price.items[0].name" -> ["price", "items[0]", "name"]
29688        // e.g., "x-y" -> ["x-y"]
29689        let mut first = true;
29690        for segment in path.split('.') {
29691            if !first {
29692                self.write(".");
29693            }
29694            first = false;
29695            // Check if there's a bracket subscript in this segment: "items[0]"
29696            if let Some(bracket_pos) = segment.find('[') {
29697                let key = &segment[..bracket_pos];
29698                let subscript = &segment[bracket_pos..];
29699                if key.is_empty() {
29700                    // Bracket notation at start of segment (e.g., already formatted)
29701                    self.write(segment);
29702                } else if Self::is_safe_json_path_key(key) {
29703                    self.write(key);
29704                    self.write(subscript);
29705                } else {
29706                    self.write("[\"");
29707                    self.write(key);
29708                    self.write("\"]");
29709                    self.write(subscript);
29710                }
29711            } else if Self::is_safe_json_path_key(segment) {
29712                self.write(segment);
29713            } else {
29714                self.write("[\"");
29715                self.write(segment);
29716                self.write("\"]");
29717            }
29718        }
29719    }
29720
29721    /// Check if a JSON path key is a safe identifier that doesn't need bracket quoting.
29722    /// Matches Python sqlglot's SAFE_IDENTIFIER_RE: ^[_a-zA-Z]\w*$
29723    fn is_safe_json_path_key(key: &str) -> bool {
29724        if key.is_empty() {
29725            return false;
29726        }
29727        let mut chars = key.chars();
29728        let first = chars.next().unwrap();
29729        if first != '_' && !first.is_ascii_alphabetic() {
29730            return false;
29731        }
29732        chars.all(|c| c == '_' || c.is_ascii_alphanumeric())
29733    }
29734
29735    fn generate_json_format(&mut self, e: &JSONFormat) -> Result<()> {
29736        // Output: {expr} FORMAT JSON
29737        // This wraps an expression with FORMAT JSON suffix (Oracle JSON function syntax)
29738        if let Some(this) = &e.this {
29739            self.generate_expression(this)?;
29740            self.write_space();
29741        }
29742        self.write_keyword("FORMAT JSON");
29743        Ok(())
29744    }
29745
29746    fn generate_json_key_value(&mut self, e: &JSONKeyValue) -> Result<()> {
29747        // key: value (for JSON objects)
29748        self.generate_expression(&e.this)?;
29749        self.write(": ");
29750        self.generate_expression(&e.expression)?;
29751        Ok(())
29752    }
29753
29754    fn generate_json_keys(&mut self, e: &JSONKeys) -> Result<()> {
29755        // JSON_KEYS(this, expression, expressions...)
29756        self.write_keyword("JSON_KEYS");
29757        self.write("(");
29758        self.generate_expression(&e.this)?;
29759        if let Some(expr) = &e.expression {
29760            self.write(", ");
29761            self.generate_expression(expr)?;
29762        }
29763        for expr in &e.expressions {
29764            self.write(", ");
29765            self.generate_expression(expr)?;
29766        }
29767        self.write(")");
29768        Ok(())
29769    }
29770
29771    fn generate_json_keys_at_depth(&mut self, e: &JSONKeysAtDepth) -> Result<()> {
29772        // JSON_KEYS(this, expression)
29773        self.write_keyword("JSON_KEYS");
29774        self.write("(");
29775        self.generate_expression(&e.this)?;
29776        if let Some(expr) = &e.expression {
29777            self.write(", ");
29778            self.generate_expression(expr)?;
29779        }
29780        self.write(")");
29781        Ok(())
29782    }
29783
29784    fn generate_json_path_expr(&mut self, e: &JSONPath) -> Result<()> {
29785        // JSONPath expression: generates a quoted path like '$.foo' or '$[0]'
29786        // The path components are concatenated without spaces
29787        let mut path_str = String::new();
29788        for expr in &e.expressions {
29789            match expr {
29790                Expression::JSONPathRoot(_) => {
29791                    path_str.push('$');
29792                }
29793                Expression::JSONPathKey(k) => {
29794                    // .key or ."key" (quote if key has special characters)
29795                    if let Expression::Literal(lit) = k.this.as_ref() {
29796                        if let crate::expressions::Literal::String(s) = lit.as_ref() {
29797                            path_str.push('.');
29798                            // Quote the key if it contains non-alphanumeric characters (hyphens, spaces, etc.)
29799                            let needs_quoting = s.chars().any(|c| !c.is_alphanumeric() && c != '_');
29800                            if needs_quoting {
29801                                path_str.push('"');
29802                                path_str.push_str(s);
29803                                path_str.push('"');
29804                            } else {
29805                                path_str.push_str(s);
29806                            }
29807                        }
29808                    }
29809                }
29810                Expression::JSONPathSubscript(s) => {
29811                    // [index]
29812                    if let Expression::Literal(lit) = s.this.as_ref() {
29813                        if let crate::expressions::Literal::Number(n) = lit.as_ref() {
29814                            path_str.push('[');
29815                            path_str.push_str(n);
29816                            path_str.push(']');
29817                        }
29818                    }
29819                }
29820                _ => {
29821                    // For other path parts, try to generate them
29822                    let mut temp_gen = Self::with_arc_config(self.config.clone());
29823                    temp_gen.generate_expression(expr)?;
29824                    path_str.push_str(&temp_gen.output);
29825                }
29826            }
29827        }
29828        // Output as quoted string
29829        self.write("'");
29830        self.write(&path_str);
29831        self.write("'");
29832        Ok(())
29833    }
29834
29835    fn generate_json_path_filter(&mut self, e: &JSONPathFilter) -> Result<()> {
29836        // JSON path filter: ?(predicate)
29837        self.write("?(");
29838        self.generate_expression(&e.this)?;
29839        self.write(")");
29840        Ok(())
29841    }
29842
29843    fn generate_json_path_key(&mut self, e: &JSONPathKey) -> Result<()> {
29844        // JSON path key: .key or ["key"]
29845        self.write(".");
29846        self.generate_expression(&e.this)?;
29847        Ok(())
29848    }
29849
29850    fn generate_json_path_recursive(&mut self, e: &JSONPathRecursive) -> Result<()> {
29851        // JSON path recursive descent: ..
29852        self.write("..");
29853        if let Some(this) = &e.this {
29854            self.generate_expression(this)?;
29855        }
29856        Ok(())
29857    }
29858
29859    fn generate_json_path_root(&mut self) -> Result<()> {
29860        // JSON path root: $
29861        self.write("$");
29862        Ok(())
29863    }
29864
29865    fn generate_json_path_script(&mut self, e: &JSONPathScript) -> Result<()> {
29866        // JSON path script: (expression)
29867        self.write("(");
29868        self.generate_expression(&e.this)?;
29869        self.write(")");
29870        Ok(())
29871    }
29872
29873    fn generate_json_path_selector(&mut self, e: &JSONPathSelector) -> Result<()> {
29874        // JSON path selector: *
29875        self.generate_expression(&e.this)?;
29876        Ok(())
29877    }
29878
29879    fn generate_json_path_slice(&mut self, e: &JSONPathSlice) -> Result<()> {
29880        // JSON path slice: [start:end:step]
29881        self.write("[");
29882        if let Some(start) = &e.start {
29883            self.generate_expression(start)?;
29884        }
29885        self.write(":");
29886        if let Some(end) = &e.end {
29887            self.generate_expression(end)?;
29888        }
29889        if let Some(step) = &e.step {
29890            self.write(":");
29891            self.generate_expression(step)?;
29892        }
29893        self.write("]");
29894        Ok(())
29895    }
29896
29897    fn generate_json_path_subscript(&mut self, e: &JSONPathSubscript) -> Result<()> {
29898        // JSON path subscript: [index] or [*]
29899        self.write("[");
29900        self.generate_expression(&e.this)?;
29901        self.write("]");
29902        Ok(())
29903    }
29904
29905    fn generate_json_path_union(&mut self, e: &JSONPathUnion) -> Result<()> {
29906        // JSON path union: [key1, key2, ...]
29907        self.write("[");
29908        for (i, expr) in e.expressions.iter().enumerate() {
29909            if i > 0 {
29910                self.write(", ");
29911            }
29912            self.generate_expression(expr)?;
29913        }
29914        self.write("]");
29915        Ok(())
29916    }
29917
29918    fn generate_json_remove(&mut self, e: &JSONRemove) -> Result<()> {
29919        // JSON_REMOVE(this, path1, path2, ...)
29920        self.write_keyword("JSON_REMOVE");
29921        self.write("(");
29922        self.generate_expression(&e.this)?;
29923        for expr in &e.expressions {
29924            self.write(", ");
29925            self.generate_expression(expr)?;
29926        }
29927        self.write(")");
29928        Ok(())
29929    }
29930
29931    fn generate_json_schema(&mut self, e: &JSONSchema) -> Result<()> {
29932        // COLUMNS(col1 type, col2 type, ...)
29933        // When pretty printing and content is too wide, format with each column on a separate line
29934        self.write_keyword("COLUMNS");
29935        self.write("(");
29936
29937        if self.config.pretty && !e.expressions.is_empty() {
29938            // First, generate all expressions into strings to check width
29939            let mut expr_strings: Vec<String> = Vec::with_capacity(e.expressions.len());
29940            for expr in &e.expressions {
29941                let mut temp_gen = Generator::with_arc_config(self.config.clone());
29942                temp_gen.generate_expression(expr)?;
29943                expr_strings.push(temp_gen.output);
29944            }
29945
29946            // Check if total width exceeds max_text_width
29947            if self.too_wide(&expr_strings) {
29948                // Pretty print: each column on its own line
29949                self.write_newline();
29950                self.indent_level += 1;
29951                for (i, expr_str) in expr_strings.iter().enumerate() {
29952                    if i > 0 {
29953                        self.write(",");
29954                        self.write_newline();
29955                    }
29956                    self.write_indent();
29957                    self.write(expr_str);
29958                }
29959                self.write_newline();
29960                self.indent_level -= 1;
29961                self.write_indent();
29962            } else {
29963                // Compact: all on one line
29964                for (i, expr_str) in expr_strings.iter().enumerate() {
29965                    if i > 0 {
29966                        self.write(", ");
29967                    }
29968                    self.write(expr_str);
29969                }
29970            }
29971        } else {
29972            // Non-pretty mode: compact format
29973            for (i, expr) in e.expressions.iter().enumerate() {
29974                if i > 0 {
29975                    self.write(", ");
29976                }
29977                self.generate_expression(expr)?;
29978            }
29979        }
29980        self.write(")");
29981        Ok(())
29982    }
29983
29984    fn generate_json_set(&mut self, e: &JSONSet) -> Result<()> {
29985        // JSON_SET(this, path, value, ...)
29986        self.write_keyword("JSON_SET");
29987        self.write("(");
29988        self.generate_expression(&e.this)?;
29989        for expr in &e.expressions {
29990            self.write(", ");
29991            self.generate_expression(expr)?;
29992        }
29993        self.write(")");
29994        Ok(())
29995    }
29996
29997    fn generate_json_strip_nulls(&mut self, e: &JSONStripNulls) -> Result<()> {
29998        // JSON_STRIP_NULLS(this, expression)
29999        self.write_keyword("JSON_STRIP_NULLS");
30000        self.write("(");
30001        self.generate_expression(&e.this)?;
30002        if let Some(expr) = &e.expression {
30003            self.write(", ");
30004            self.generate_expression(expr)?;
30005        }
30006        self.write(")");
30007        Ok(())
30008    }
30009
30010    fn generate_json_table(&mut self, e: &JSONTable) -> Result<()> {
30011        // JSON_TABLE(this, path [error_handling] [empty_handling] schema)
30012        self.write_keyword("JSON_TABLE");
30013        self.write("(");
30014        self.generate_expression(&e.this)?;
30015        if let Some(path) = &e.path {
30016            self.write(", ");
30017            self.generate_expression(path)?;
30018        }
30019        if let Some(error_handling) = &e.error_handling {
30020            self.write_space();
30021            self.generate_expression(error_handling)?;
30022        }
30023        if let Some(empty_handling) = &e.empty_handling {
30024            self.write_space();
30025            self.generate_expression(empty_handling)?;
30026        }
30027        if let Some(schema) = &e.schema {
30028            self.write_space();
30029            self.generate_expression(schema)?;
30030        }
30031        self.write(")");
30032        Ok(())
30033    }
30034
30035    fn generate_json_type(&mut self, e: &JSONType) -> Result<()> {
30036        // JSON_TYPE(this)
30037        self.write_keyword("JSON_TYPE");
30038        self.write("(");
30039        self.generate_expression(&e.this)?;
30040        self.write(")");
30041        Ok(())
30042    }
30043
30044    fn generate_json_value(&mut self, e: &JSONValue) -> Result<()> {
30045        // JSON_VALUE(this, path RETURNING type ON condition)
30046        self.write_keyword("JSON_VALUE");
30047        self.write("(");
30048        self.generate_expression(&e.this)?;
30049        if let Some(path) = &e.path {
30050            self.write(", ");
30051            self.generate_expression(path)?;
30052        }
30053        if let Some(returning) = &e.returning {
30054            self.write_space();
30055            self.write_keyword("RETURNING");
30056            self.write_space();
30057            self.generate_expression(returning)?;
30058        }
30059        if let Some(on_condition) = &e.on_condition {
30060            self.write_space();
30061            self.generate_expression(on_condition)?;
30062        }
30063        self.write(")");
30064        Ok(())
30065    }
30066
30067    fn generate_json_value_array(&mut self, e: &JSONValueArray) -> Result<()> {
30068        // JSON_VALUE_ARRAY(this)
30069        self.write_keyword("JSON_VALUE_ARRAY");
30070        self.write("(");
30071        self.generate_expression(&e.this)?;
30072        self.write(")");
30073        Ok(())
30074    }
30075
30076    fn generate_jarowinkler_similarity(&mut self, e: &JarowinklerSimilarity) -> Result<()> {
30077        // JAROWINKLER_SIMILARITY(str1, str2)
30078        self.write_keyword("JAROWINKLER_SIMILARITY");
30079        self.write("(");
30080        self.generate_expression(&e.this)?;
30081        self.write(", ");
30082        self.generate_expression(&e.expression)?;
30083        self.write(")");
30084        Ok(())
30085    }
30086
30087    fn generate_join_hint(&mut self, e: &JoinHint) -> Result<()> {
30088        // Python: this(expressions)
30089        self.generate_expression(&e.this)?;
30090        self.write("(");
30091        for (i, expr) in e.expressions.iter().enumerate() {
30092            if i > 0 {
30093                self.write(", ");
30094            }
30095            self.generate_expression(expr)?;
30096        }
30097        self.write(")");
30098        Ok(())
30099    }
30100
30101    fn generate_journal_property(&mut self, e: &JournalProperty) -> Result<()> {
30102        // Python: {no}{local}{dual}{before}{after}JOURNAL
30103        if e.no.is_some() {
30104            self.write_keyword("NO ");
30105        }
30106        if let Some(local) = &e.local {
30107            self.generate_expression(local)?;
30108            self.write_space();
30109        }
30110        if e.dual.is_some() {
30111            self.write_keyword("DUAL ");
30112        }
30113        if e.before.is_some() {
30114            self.write_keyword("BEFORE ");
30115        }
30116        if e.after.is_some() {
30117            self.write_keyword("AFTER ");
30118        }
30119        self.write_keyword("JOURNAL");
30120        Ok(())
30121    }
30122
30123    fn generate_language_property(&mut self, e: &LanguageProperty) -> Result<()> {
30124        // LANGUAGE language_name
30125        self.write_keyword("LANGUAGE");
30126        self.write_space();
30127        self.generate_expression(&e.this)?;
30128        Ok(())
30129    }
30130
30131    fn generate_lateral(&mut self, e: &Lateral) -> Result<()> {
30132        // Python: handles LATERAL VIEW (Hive/Spark) and regular LATERAL
30133        if e.view.is_some() {
30134            // LATERAL VIEW [OUTER] expression [alias] [AS columns]
30135            self.write_keyword("LATERAL VIEW");
30136            if e.outer.is_some() {
30137                self.write_space();
30138                self.write_keyword("OUTER");
30139            }
30140            self.write_space();
30141            self.generate_expression(&e.this)?;
30142            if let Some(alias) = &e.alias {
30143                self.write_space();
30144                self.write(alias);
30145            }
30146        } else {
30147            // LATERAL subquery/function [WITH ORDINALITY] [AS alias(columns)]
30148            self.write_keyword("LATERAL");
30149            self.write_space();
30150            self.generate_expression(&e.this)?;
30151            if e.ordinality.is_some() {
30152                self.write_space();
30153                self.write_keyword("WITH ORDINALITY");
30154            }
30155            if let Some(alias) = &e.alias {
30156                self.write_space();
30157                self.write_keyword("AS");
30158                self.write_space();
30159                self.write(alias);
30160                if !e.column_aliases.is_empty() {
30161                    self.write("(");
30162                    for (i, col) in e.column_aliases.iter().enumerate() {
30163                        if i > 0 {
30164                            self.write(", ");
30165                        }
30166                        self.write(col);
30167                    }
30168                    self.write(")");
30169                }
30170            }
30171        }
30172        Ok(())
30173    }
30174
30175    fn generate_like_property(&mut self, e: &LikeProperty) -> Result<()> {
30176        // Python: LIKE this [options]
30177        self.write_keyword("LIKE");
30178        self.write_space();
30179        self.generate_expression(&e.this)?;
30180        for expr in &e.expressions {
30181            self.write_space();
30182            self.generate_expression(expr)?;
30183        }
30184        Ok(())
30185    }
30186
30187    fn generate_limit(&mut self, e: &Limit) -> Result<()> {
30188        self.write_keyword("LIMIT");
30189        self.write_space();
30190        self.write_limit_expr(&e.this)?;
30191        if e.percent {
30192            self.write_space();
30193            self.write_keyword("PERCENT");
30194        }
30195        // Emit any comments that were captured from before the LIMIT keyword
30196        for comment in &e.comments {
30197            self.write(" ");
30198            self.write_formatted_comment(comment);
30199        }
30200        Ok(())
30201    }
30202
30203    fn generate_limit_options(&mut self, e: &LimitOptions) -> Result<()> {
30204        // Python: [PERCENT][ROWS][WITH TIES|ONLY]
30205        if e.percent.is_some() {
30206            self.write_keyword(" PERCENT");
30207        }
30208        if e.rows.is_some() {
30209            self.write_keyword(" ROWS");
30210        }
30211        if e.with_ties.is_some() {
30212            self.write_keyword(" WITH TIES");
30213        } else if e.rows.is_some() {
30214            self.write_keyword(" ONLY");
30215        }
30216        Ok(())
30217    }
30218
30219    fn generate_list(&mut self, e: &List) -> Result<()> {
30220        use crate::dialects::DialectType;
30221        let is_materialize = matches!(self.config.dialect, Some(DialectType::Materialize));
30222
30223        // Check if this is a subquery-based list (LIST(SELECT ...))
30224        if e.expressions.len() == 1 {
30225            if let Expression::Select(_) = &e.expressions[0] {
30226                self.write_keyword("LIST");
30227                self.write("(");
30228                self.generate_expression(&e.expressions[0])?;
30229                self.write(")");
30230                return Ok(());
30231            }
30232        }
30233
30234        // For Materialize, output as LIST[expr, expr, ...]
30235        if is_materialize {
30236            self.write_keyword("LIST");
30237            self.write("[");
30238            for (i, expr) in e.expressions.iter().enumerate() {
30239                if i > 0 {
30240                    self.write(", ");
30241                }
30242                self.generate_expression(expr)?;
30243            }
30244            self.write("]");
30245        } else {
30246            // For other dialects, output as LIST(expr, expr, ...)
30247            self.write_keyword("LIST");
30248            self.write("(");
30249            for (i, expr) in e.expressions.iter().enumerate() {
30250                if i > 0 {
30251                    self.write(", ");
30252                }
30253                self.generate_expression(expr)?;
30254            }
30255            self.write(")");
30256        }
30257        Ok(())
30258    }
30259
30260    fn generate_tomap(&mut self, e: &ToMap) -> Result<()> {
30261        // Check if this is a subquery-based map (MAP(SELECT ...))
30262        if let Expression::Select(_) = &*e.this {
30263            self.write_keyword("MAP");
30264            self.write("(");
30265            self.generate_expression(&e.this)?;
30266            self.write(")");
30267            return Ok(());
30268        }
30269
30270        let is_duckdb = matches!(self.config.dialect, Some(DialectType::DuckDB));
30271
30272        // For Struct-based map: DuckDB uses MAP {'key': value}, Materialize uses MAP['key' => value]
30273        self.write_keyword("MAP");
30274        if is_duckdb {
30275            self.write(" {");
30276        } else {
30277            self.write("[");
30278        }
30279        if let Expression::Struct(s) = &*e.this {
30280            for (i, (_, expr)) in s.fields.iter().enumerate() {
30281                if i > 0 {
30282                    self.write(", ");
30283                }
30284                if let Expression::PropertyEQ(op) = expr {
30285                    self.generate_expression(&op.left)?;
30286                    if is_duckdb {
30287                        self.write(": ");
30288                    } else {
30289                        self.write(" => ");
30290                    }
30291                    self.generate_expression(&op.right)?;
30292                } else {
30293                    self.generate_expression(expr)?;
30294                }
30295            }
30296        }
30297        if is_duckdb {
30298            self.write("}");
30299        } else {
30300            self.write("]");
30301        }
30302        Ok(())
30303    }
30304
30305    fn generate_localtime(&mut self, e: &Localtime) -> Result<()> {
30306        // Python: LOCALTIME or LOCALTIME(precision)
30307        self.write_keyword("LOCALTIME");
30308        if let Some(precision) = &e.this {
30309            self.write("(");
30310            self.generate_expression(precision)?;
30311            self.write(")");
30312        }
30313        Ok(())
30314    }
30315
30316    fn generate_localtimestamp(&mut self, e: &Localtimestamp) -> Result<()> {
30317        // Python: LOCALTIMESTAMP or LOCALTIMESTAMP(precision)
30318        self.write_keyword("LOCALTIMESTAMP");
30319        if let Some(precision) = &e.this {
30320            self.write("(");
30321            self.generate_expression(precision)?;
30322            self.write(")");
30323        }
30324        Ok(())
30325    }
30326
30327    fn generate_location_property(&mut self, e: &LocationProperty) -> Result<()> {
30328        // LOCATION 'path'
30329        self.write_keyword("LOCATION");
30330        self.write_space();
30331        self.generate_expression(&e.this)?;
30332        Ok(())
30333    }
30334
30335    fn generate_lock(&mut self, e: &Lock) -> Result<()> {
30336        // Python: FOR UPDATE|FOR SHARE [OF tables] [NOWAIT|WAIT n]
30337        if e.update.is_some() {
30338            if e.key.is_some() {
30339                self.write_keyword("FOR NO KEY UPDATE");
30340            } else {
30341                self.write_keyword("FOR UPDATE");
30342            }
30343        } else {
30344            if e.key.is_some() {
30345                self.write_keyword("FOR KEY SHARE");
30346            } else {
30347                self.write_keyword("FOR SHARE");
30348            }
30349        }
30350        if !e.expressions.is_empty() {
30351            self.write_keyword(" OF ");
30352            for (i, expr) in e.expressions.iter().enumerate() {
30353                if i > 0 {
30354                    self.write(", ");
30355                }
30356                self.generate_expression(expr)?;
30357            }
30358        }
30359        // Handle wait option following Python sqlglot convention:
30360        // - Boolean(true) -> NOWAIT
30361        // - Boolean(false) -> SKIP LOCKED
30362        // - Literal (number) -> WAIT n
30363        if let Some(wait) = &e.wait {
30364            match wait.as_ref() {
30365                Expression::Boolean(b) => {
30366                    if b.value {
30367                        self.write_keyword(" NOWAIT");
30368                    } else {
30369                        self.write_keyword(" SKIP LOCKED");
30370                    }
30371                }
30372                _ => {
30373                    // It's a literal (number), output WAIT n
30374                    self.write_keyword(" WAIT ");
30375                    self.generate_expression(wait)?;
30376                }
30377            }
30378        }
30379        Ok(())
30380    }
30381
30382    fn generate_lock_property(&mut self, e: &LockProperty) -> Result<()> {
30383        // LOCK property
30384        self.write_keyword("LOCK");
30385        self.write_space();
30386        self.generate_expression(&e.this)?;
30387        Ok(())
30388    }
30389
30390    fn generate_locking_property(&mut self, e: &LockingProperty) -> Result<()> {
30391        // Python: LOCKING kind [this] [for_or_in] lock_type [OVERRIDE]
30392        self.write_keyword("LOCKING");
30393        self.write_space();
30394        self.write(&e.kind);
30395        if let Some(this) = &e.this {
30396            self.write_space();
30397            self.generate_expression(this)?;
30398        }
30399        if let Some(for_or_in) = &e.for_or_in {
30400            self.write_space();
30401            self.generate_expression(for_or_in)?;
30402        }
30403        if let Some(lock_type) = &e.lock_type {
30404            self.write_space();
30405            self.generate_expression(lock_type)?;
30406        }
30407        if e.override_.is_some() {
30408            self.write_keyword(" OVERRIDE");
30409        }
30410        Ok(())
30411    }
30412
30413    fn generate_locking_statement(&mut self, e: &LockingStatement) -> Result<()> {
30414        // this expression
30415        self.generate_expression(&e.this)?;
30416        self.write_space();
30417        self.generate_expression(&e.expression)?;
30418        Ok(())
30419    }
30420
30421    fn generate_log_property(&mut self, e: &LogProperty) -> Result<()> {
30422        // [NO] LOG
30423        if e.no.is_some() {
30424            self.write_keyword("NO ");
30425        }
30426        self.write_keyword("LOG");
30427        Ok(())
30428    }
30429
30430    fn generate_md5_digest(&mut self, e: &MD5Digest) -> Result<()> {
30431        // MD5(this, expressions...)
30432        self.write_keyword("MD5");
30433        self.write("(");
30434        self.generate_expression(&e.this)?;
30435        for expr in &e.expressions {
30436            self.write(", ");
30437            self.generate_expression(expr)?;
30438        }
30439        self.write(")");
30440        Ok(())
30441    }
30442
30443    fn generate_ml_forecast(&mut self, e: &MLForecast) -> Result<()> {
30444        // ML.FORECAST(model, [params])
30445        self.write_keyword("ML.FORECAST");
30446        self.write("(");
30447        self.generate_expression(&e.this)?;
30448        if let Some(expression) = &e.expression {
30449            self.write(", ");
30450            self.generate_expression(expression)?;
30451        }
30452        if let Some(params) = &e.params_struct {
30453            self.write(", ");
30454            self.generate_expression(params)?;
30455        }
30456        self.write(")");
30457        Ok(())
30458    }
30459
30460    fn generate_ml_translate(&mut self, e: &MLTranslate) -> Result<()> {
30461        // ML.TRANSLATE(model, input, [params])
30462        self.write_keyword("ML.TRANSLATE");
30463        self.write("(");
30464        self.generate_expression(&e.this)?;
30465        self.write(", ");
30466        self.generate_expression(&e.expression)?;
30467        if let Some(params) = &e.params_struct {
30468            self.write(", ");
30469            self.generate_expression(params)?;
30470        }
30471        self.write(")");
30472        Ok(())
30473    }
30474
30475    fn generate_make_interval(&mut self, e: &MakeInterval) -> Result<()> {
30476        // MAKE_INTERVAL(years => x, months => y, ...)
30477        self.write_keyword("MAKE_INTERVAL");
30478        self.write("(");
30479        let mut first = true;
30480        if let Some(year) = &e.year {
30481            self.write("years => ");
30482            self.generate_expression(year)?;
30483            first = false;
30484        }
30485        if let Some(month) = &e.month {
30486            if !first {
30487                self.write(", ");
30488            }
30489            self.write("months => ");
30490            self.generate_expression(month)?;
30491            first = false;
30492        }
30493        if let Some(week) = &e.week {
30494            if !first {
30495                self.write(", ");
30496            }
30497            self.write("weeks => ");
30498            self.generate_expression(week)?;
30499            first = false;
30500        }
30501        if let Some(day) = &e.day {
30502            if !first {
30503                self.write(", ");
30504            }
30505            self.write("days => ");
30506            self.generate_expression(day)?;
30507            first = false;
30508        }
30509        if let Some(hour) = &e.hour {
30510            if !first {
30511                self.write(", ");
30512            }
30513            self.write("hours => ");
30514            self.generate_expression(hour)?;
30515            first = false;
30516        }
30517        if let Some(minute) = &e.minute {
30518            if !first {
30519                self.write(", ");
30520            }
30521            self.write("mins => ");
30522            self.generate_expression(minute)?;
30523            first = false;
30524        }
30525        if let Some(second) = &e.second {
30526            if !first {
30527                self.write(", ");
30528            }
30529            self.write("secs => ");
30530            self.generate_expression(second)?;
30531        }
30532        self.write(")");
30533        Ok(())
30534    }
30535
30536    fn generate_manhattan_distance(&mut self, e: &ManhattanDistance) -> Result<()> {
30537        // MANHATTAN_DISTANCE(vector1, vector2)
30538        self.write_keyword("MANHATTAN_DISTANCE");
30539        self.write("(");
30540        self.generate_expression(&e.this)?;
30541        self.write(", ");
30542        self.generate_expression(&e.expression)?;
30543        self.write(")");
30544        Ok(())
30545    }
30546
30547    fn generate_map(&mut self, e: &Map) -> Result<()> {
30548        // MAP(key1, value1, key2, value2, ...)
30549        self.write_keyword("MAP");
30550        self.write("(");
30551        for (i, (key, value)) in e.keys.iter().zip(e.values.iter()).enumerate() {
30552            if i > 0 {
30553                self.write(", ");
30554            }
30555            self.generate_expression(key)?;
30556            self.write(", ");
30557            self.generate_expression(value)?;
30558        }
30559        self.write(")");
30560        Ok(())
30561    }
30562
30563    fn generate_map_cat(&mut self, e: &MapCat) -> Result<()> {
30564        // MAP_CAT(map1, map2)
30565        self.write_keyword("MAP_CAT");
30566        self.write("(");
30567        self.generate_expression(&e.this)?;
30568        self.write(", ");
30569        self.generate_expression(&e.expression)?;
30570        self.write(")");
30571        Ok(())
30572    }
30573
30574    fn generate_map_delete(&mut self, e: &MapDelete) -> Result<()> {
30575        // MAP_DELETE(map, key1, key2, ...)
30576        self.write_keyword("MAP_DELETE");
30577        self.write("(");
30578        self.generate_expression(&e.this)?;
30579        for expr in &e.expressions {
30580            self.write(", ");
30581            self.generate_expression(expr)?;
30582        }
30583        self.write(")");
30584        Ok(())
30585    }
30586
30587    fn generate_map_insert(&mut self, e: &MapInsert) -> Result<()> {
30588        // MAP_INSERT(map, key, value, [update_flag])
30589        self.write_keyword("MAP_INSERT");
30590        self.write("(");
30591        self.generate_expression(&e.this)?;
30592        if let Some(key) = &e.key {
30593            self.write(", ");
30594            self.generate_expression(key)?;
30595        }
30596        if let Some(value) = &e.value {
30597            self.write(", ");
30598            self.generate_expression(value)?;
30599        }
30600        if let Some(update_flag) = &e.update_flag {
30601            self.write(", ");
30602            self.generate_expression(update_flag)?;
30603        }
30604        self.write(")");
30605        Ok(())
30606    }
30607
30608    fn generate_map_pick(&mut self, e: &MapPick) -> Result<()> {
30609        // MAP_PICK(map, key1, key2, ...)
30610        self.write_keyword("MAP_PICK");
30611        self.write("(");
30612        self.generate_expression(&e.this)?;
30613        for expr in &e.expressions {
30614            self.write(", ");
30615            self.generate_expression(expr)?;
30616        }
30617        self.write(")");
30618        Ok(())
30619    }
30620
30621    fn generate_masking_policy_column_constraint(
30622        &mut self,
30623        e: &MaskingPolicyColumnConstraint,
30624    ) -> Result<()> {
30625        // Python: MASKING POLICY name [USING (cols)]
30626        self.write_keyword("MASKING POLICY");
30627        self.write_space();
30628        self.generate_expression(&e.this)?;
30629        if !e.expressions.is_empty() {
30630            self.write_keyword(" USING");
30631            self.write(" (");
30632            for (i, expr) in e.expressions.iter().enumerate() {
30633                if i > 0 {
30634                    self.write(", ");
30635                }
30636                self.generate_expression(expr)?;
30637            }
30638            self.write(")");
30639        }
30640        Ok(())
30641    }
30642
30643    fn generate_match_against(&mut self, e: &MatchAgainst) -> Result<()> {
30644        if matches!(
30645            self.config.dialect,
30646            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
30647        ) {
30648            if e.expressions.len() > 1 {
30649                self.write("(");
30650            }
30651            for (i, expr) in e.expressions.iter().enumerate() {
30652                if i > 0 {
30653                    self.write_keyword(" OR ");
30654                }
30655                self.generate_expression(expr)?;
30656                self.write_space();
30657                self.write("@@");
30658                self.write_space();
30659                self.generate_expression(&e.this)?;
30660            }
30661            if e.expressions.len() > 1 {
30662                self.write(")");
30663            }
30664            return Ok(());
30665        }
30666
30667        // MATCH(columns) AGAINST(expr [modifier])
30668        self.write_keyword("MATCH");
30669        self.write("(");
30670        for (i, expr) in e.expressions.iter().enumerate() {
30671            if i > 0 {
30672                self.write(", ");
30673            }
30674            self.generate_expression(expr)?;
30675        }
30676        self.write(")");
30677        self.write_keyword(" AGAINST");
30678        self.write("(");
30679        self.generate_expression(&e.this)?;
30680        if let Some(modifier) = &e.modifier {
30681            self.write_space();
30682            self.generate_expression(modifier)?;
30683        }
30684        self.write(")");
30685        Ok(())
30686    }
30687
30688    fn generate_match_recognize_measure(&mut self, e: &MatchRecognizeMeasure) -> Result<()> {
30689        // Python: [window_frame] this
30690        if let Some(window_frame) = &e.window_frame {
30691            self.write(&format!("{:?}", window_frame).to_ascii_uppercase());
30692            self.write_space();
30693        }
30694        self.generate_expression(&e.this)?;
30695        Ok(())
30696    }
30697
30698    fn generate_materialized_property(&mut self, e: &MaterializedProperty) -> Result<()> {
30699        // MATERIALIZED [this]
30700        self.write_keyword("MATERIALIZED");
30701        if let Some(this) = &e.this {
30702            self.write_space();
30703            self.generate_expression(this)?;
30704        }
30705        Ok(())
30706    }
30707
30708    fn generate_merge(&mut self, e: &Merge) -> Result<()> {
30709        // MERGE INTO target USING source ON condition WHEN ...
30710        // DuckDB variant: MERGE INTO target USING source USING (key_columns) WHEN ...
30711        if let Some(with_) = &e.with_ {
30712            self.generate_expression(with_)?;
30713            self.write_space();
30714        }
30715        self.write_keyword("MERGE INTO");
30716        self.write_space();
30717        self.generate_expression(&e.this)?;
30718
30719        // USING clause - newline before in pretty mode
30720        if self.config.pretty {
30721            self.write_newline();
30722            self.write_indent();
30723        } else {
30724            self.write_space();
30725        }
30726        self.write_keyword("USING");
30727        self.write_space();
30728        self.generate_expression(&e.using)?;
30729
30730        // ON clause - newline before in pretty mode
30731        if let Some(on) = &e.on {
30732            if self.config.pretty {
30733                self.write_newline();
30734                self.write_indent();
30735            } else {
30736                self.write_space();
30737            }
30738            self.write_keyword("ON");
30739            self.write_space();
30740            self.generate_expression(on)?;
30741        }
30742        // DuckDB USING (key_columns) clause
30743        if let Some(using_cond) = &e.using_cond {
30744            self.write_space();
30745            self.write_keyword("USING");
30746            self.write_space();
30747            self.write("(");
30748            // using_cond is a Tuple containing the column identifiers
30749            if let Expression::Tuple(tuple) = using_cond.as_ref() {
30750                for (i, col) in tuple.expressions.iter().enumerate() {
30751                    if i > 0 {
30752                        self.write(", ");
30753                    }
30754                    self.generate_expression(col)?;
30755                }
30756            } else {
30757                self.generate_expression(using_cond)?;
30758            }
30759            self.write(")");
30760        }
30761        // For PostgreSQL dialect, extract target table name/alias to strip from UPDATE SET
30762        let saved_merge_strip = std::mem::take(&mut self.merge_strip_qualifiers);
30763        if matches!(
30764            self.config.dialect,
30765            Some(crate::DialectType::PostgreSQL)
30766                | Some(crate::DialectType::Redshift)
30767                | Some(crate::DialectType::Trino)
30768                | Some(crate::DialectType::Presto)
30769                | Some(crate::DialectType::Athena)
30770        ) {
30771            let mut names = Vec::new();
30772            match e.this.as_ref() {
30773                Expression::Alias(a) => {
30774                    // e.g., "x AS z" -> strip both "x" and "z"
30775                    if let Expression::Table(t) = &a.this {
30776                        names.push(t.name.name.clone());
30777                    } else if let Expression::Identifier(id) = &a.this {
30778                        names.push(id.name.clone());
30779                    }
30780                    names.push(a.alias.name.clone());
30781                }
30782                Expression::Table(t) => {
30783                    names.push(t.name.name.clone());
30784                }
30785                Expression::Identifier(id) => {
30786                    names.push(id.name.clone());
30787                }
30788                _ => {}
30789            }
30790            self.merge_strip_qualifiers = names;
30791        }
30792
30793        // WHEN clauses - newline before each in pretty mode
30794        if let Some(whens) = &e.whens {
30795            if self.config.pretty {
30796                self.write_newline();
30797                self.write_indent();
30798            } else {
30799                self.write_space();
30800            }
30801            self.generate_expression(whens)?;
30802        }
30803
30804        // Restore merge_strip_qualifiers
30805        self.merge_strip_qualifiers = saved_merge_strip;
30806
30807        // OUTPUT/RETURNING clause - newline before in pretty mode
30808        if let Some(returning) = &e.returning {
30809            if self.config.pretty {
30810                self.write_newline();
30811                self.write_indent();
30812            } else {
30813                self.write_space();
30814            }
30815            self.generate_expression(returning)?;
30816        }
30817        Ok(())
30818    }
30819
30820    fn generate_merge_block_ratio_property(&mut self, e: &MergeBlockRatioProperty) -> Result<()> {
30821        // Python: NO MERGEBLOCKRATIO | DEFAULT MERGEBLOCKRATIO | MERGEBLOCKRATIO=this [PERCENT]
30822        if e.no.is_some() {
30823            self.write_keyword("NO MERGEBLOCKRATIO");
30824        } else if e.default.is_some() {
30825            self.write_keyword("DEFAULT MERGEBLOCKRATIO");
30826        } else {
30827            self.write_keyword("MERGEBLOCKRATIO");
30828            self.write("=");
30829            if let Some(this) = &e.this {
30830                self.generate_expression(this)?;
30831            }
30832            if e.percent.is_some() {
30833                self.write_keyword(" PERCENT");
30834            }
30835        }
30836        Ok(())
30837    }
30838
30839    fn generate_merge_tree_ttl(&mut self, e: &MergeTreeTTL) -> Result<()> {
30840        // TTL expressions [WHERE where] [GROUP BY group] [SET aggregates]
30841        self.write_keyword("TTL");
30842        let pretty_clickhouse = self.config.pretty
30843            && matches!(
30844                self.config.dialect,
30845                Some(crate::dialects::DialectType::ClickHouse)
30846            );
30847
30848        if pretty_clickhouse {
30849            self.write_newline();
30850            self.indent_level += 1;
30851            for (i, expr) in e.expressions.iter().enumerate() {
30852                if i > 0 {
30853                    self.write(",");
30854                    self.write_newline();
30855                }
30856                self.write_indent();
30857                self.generate_expression(expr)?;
30858            }
30859            self.indent_level -= 1;
30860        } else {
30861            self.write_space();
30862            for (i, expr) in e.expressions.iter().enumerate() {
30863                if i > 0 {
30864                    self.write(", ");
30865                }
30866                self.generate_expression(expr)?;
30867            }
30868        }
30869
30870        if let Some(where_) = &e.where_ {
30871            if pretty_clickhouse {
30872                self.write_newline();
30873                if let Expression::Where(w) = where_.as_ref() {
30874                    self.write_indent();
30875                    self.write_keyword("WHERE");
30876                    self.write_newline();
30877                    self.indent_level += 1;
30878                    self.write_indent();
30879                    self.generate_expression(&w.this)?;
30880                    self.indent_level -= 1;
30881                } else {
30882                    self.write_indent();
30883                    self.generate_expression(where_)?;
30884                }
30885            } else {
30886                self.write_space();
30887                self.generate_expression(where_)?;
30888            }
30889        }
30890        if let Some(group) = &e.group {
30891            if pretty_clickhouse {
30892                self.write_newline();
30893                if let Expression::Group(g) = group.as_ref() {
30894                    self.write_indent();
30895                    self.write_keyword("GROUP BY");
30896                    self.write_newline();
30897                    self.indent_level += 1;
30898                    for (i, expr) in g.expressions.iter().enumerate() {
30899                        if i > 0 {
30900                            self.write(",");
30901                            self.write_newline();
30902                        }
30903                        self.write_indent();
30904                        self.generate_expression(expr)?;
30905                    }
30906                    self.indent_level -= 1;
30907                } else {
30908                    self.write_indent();
30909                    self.generate_expression(group)?;
30910                }
30911            } else {
30912                self.write_space();
30913                self.generate_expression(group)?;
30914            }
30915        }
30916        if let Some(aggregates) = &e.aggregates {
30917            if pretty_clickhouse {
30918                self.write_newline();
30919                self.write_indent();
30920                self.write_keyword("SET");
30921                self.write_newline();
30922                self.indent_level += 1;
30923                if let Expression::Tuple(t) = aggregates.as_ref() {
30924                    for (i, agg) in t.expressions.iter().enumerate() {
30925                        if i > 0 {
30926                            self.write(",");
30927                            self.write_newline();
30928                        }
30929                        self.write_indent();
30930                        self.generate_expression(agg)?;
30931                    }
30932                } else {
30933                    self.write_indent();
30934                    self.generate_expression(aggregates)?;
30935                }
30936                self.indent_level -= 1;
30937            } else {
30938                self.write_space();
30939                self.write_keyword("SET");
30940                self.write_space();
30941                self.generate_expression(aggregates)?;
30942            }
30943        }
30944        Ok(())
30945    }
30946
30947    fn generate_merge_tree_ttl_action(&mut self, e: &MergeTreeTTLAction) -> Result<()> {
30948        // Python: this [DELETE] [RECOMPRESS codec] [TO DISK disk] [TO VOLUME volume]
30949        self.generate_expression(&e.this)?;
30950        if e.delete.is_some() {
30951            self.write_keyword(" DELETE");
30952        }
30953        if let Some(recompress) = &e.recompress {
30954            self.write_keyword(" RECOMPRESS ");
30955            self.generate_expression(recompress)?;
30956        }
30957        if let Some(to_disk) = &e.to_disk {
30958            self.write_keyword(" TO DISK ");
30959            self.generate_expression(to_disk)?;
30960        }
30961        if let Some(to_volume) = &e.to_volume {
30962            self.write_keyword(" TO VOLUME ");
30963            self.generate_expression(to_volume)?;
30964        }
30965        Ok(())
30966    }
30967
30968    fn generate_minhash(&mut self, e: &Minhash) -> Result<()> {
30969        // MINHASH(this, expressions...)
30970        self.write_keyword("MINHASH");
30971        self.write("(");
30972        self.generate_expression(&e.this)?;
30973        for expr in &e.expressions {
30974            self.write(", ");
30975            self.generate_expression(expr)?;
30976        }
30977        self.write(")");
30978        Ok(())
30979    }
30980
30981    fn generate_model_attribute(&mut self, e: &ModelAttribute) -> Result<()> {
30982        // model!attribute - Snowflake syntax
30983        self.generate_expression(&e.this)?;
30984        self.write("!");
30985        self.generate_expression(&e.expression)?;
30986        Ok(())
30987    }
30988
30989    fn generate_monthname(&mut self, e: &Monthname) -> Result<()> {
30990        // MONTHNAME(this)
30991        self.write_keyword("MONTHNAME");
30992        self.write("(");
30993        self.generate_expression(&e.this)?;
30994        self.write(")");
30995        Ok(())
30996    }
30997
30998    fn generate_multitable_inserts(&mut self, e: &MultitableInserts) -> Result<()> {
30999        // Output leading comments
31000        for comment in &e.leading_comments {
31001            self.write_formatted_comment(comment);
31002            if self.config.pretty {
31003                self.write_newline();
31004                self.write_indent();
31005            } else {
31006                self.write_space();
31007            }
31008        }
31009        // Python: INSERT kind expressions source
31010        self.write_keyword("INSERT");
31011        self.write_space();
31012        self.write(&e.kind);
31013        if self.config.pretty {
31014            self.indent_level += 1;
31015            for expr in &e.expressions {
31016                self.write_newline();
31017                self.write_indent();
31018                self.generate_expression(expr)?;
31019            }
31020            self.indent_level -= 1;
31021        } else {
31022            for expr in &e.expressions {
31023                self.write_space();
31024                self.generate_expression(expr)?;
31025            }
31026        }
31027        if let Some(source) = &e.source {
31028            if self.config.pretty {
31029                self.write_newline();
31030                self.write_indent();
31031            } else {
31032                self.write_space();
31033            }
31034            self.generate_expression(source)?;
31035        }
31036        Ok(())
31037    }
31038
31039    fn generate_next_value_for(&mut self, e: &NextValueFor) -> Result<()> {
31040        // Python: NEXT VALUE FOR this [OVER (order)]
31041        self.write_keyword("NEXT VALUE FOR");
31042        self.write_space();
31043        self.generate_expression(&e.this)?;
31044        if let Some(order) = &e.order {
31045            self.write_space();
31046            self.write_keyword("OVER");
31047            self.write(" (");
31048            self.generate_expression(order)?;
31049            self.write(")");
31050        }
31051        Ok(())
31052    }
31053
31054    fn generate_normal(&mut self, e: &Normal) -> Result<()> {
31055        // NORMAL(mean, stddev, gen)
31056        self.write_keyword("NORMAL");
31057        self.write("(");
31058        self.generate_expression(&e.this)?;
31059        if let Some(stddev) = &e.stddev {
31060            self.write(", ");
31061            self.generate_expression(stddev)?;
31062        }
31063        if let Some(gen) = &e.gen {
31064            self.write(", ");
31065            self.generate_expression(gen)?;
31066        }
31067        self.write(")");
31068        Ok(())
31069    }
31070
31071    fn generate_normalize(&mut self, e: &Normalize) -> Result<()> {
31072        // NORMALIZE(this, form) or CASEFOLD version
31073        if e.is_casefold.is_some() {
31074            self.write_keyword("NORMALIZE_AND_CASEFOLD");
31075        } else {
31076            self.write_keyword("NORMALIZE");
31077        }
31078        self.write("(");
31079        self.generate_expression(&e.this)?;
31080        if let Some(form) = &e.form {
31081            self.write(", ");
31082            self.generate_expression(form)?;
31083        }
31084        self.write(")");
31085        Ok(())
31086    }
31087
31088    fn generate_not_null_column_constraint(&mut self, e: &NotNullColumnConstraint) -> Result<()> {
31089        // Python: [NOT ]NULL
31090        if e.allow_null.is_none() {
31091            self.write_keyword("NOT ");
31092        }
31093        self.write_keyword("NULL");
31094        Ok(())
31095    }
31096
31097    fn generate_nullif(&mut self, e: &Nullif) -> Result<()> {
31098        // NULLIF(this, expression)
31099        self.write_keyword("NULLIF");
31100        self.write("(");
31101        self.generate_expression(&e.this)?;
31102        self.write(", ");
31103        self.generate_expression(&e.expression)?;
31104        self.write(")");
31105        Ok(())
31106    }
31107
31108    fn generate_number_to_str(&mut self, e: &NumberToStr) -> Result<()> {
31109        // FORMAT(this, format, culture)
31110        self.write_keyword("FORMAT");
31111        self.write("(");
31112        self.generate_expression(&e.this)?;
31113        self.write(", '");
31114        self.write(&e.format);
31115        self.write("'");
31116        if let Some(culture) = &e.culture {
31117            self.write(", ");
31118            self.generate_expression(culture)?;
31119        }
31120        self.write(")");
31121        Ok(())
31122    }
31123
31124    fn generate_object_agg(&mut self, e: &ObjectAgg) -> Result<()> {
31125        // OBJECT_AGG(key, value)
31126        self.write_keyword("OBJECT_AGG");
31127        self.write("(");
31128        self.generate_expression(&e.this)?;
31129        self.write(", ");
31130        self.generate_expression(&e.expression)?;
31131        self.write(")");
31132        Ok(())
31133    }
31134
31135    fn generate_object_identifier(&mut self, e: &ObjectIdentifier) -> Result<()> {
31136        // Python: Just returns the name
31137        self.generate_expression(&e.this)?;
31138        Ok(())
31139    }
31140
31141    fn generate_object_insert(&mut self, e: &ObjectInsert) -> Result<()> {
31142        // OBJECT_INSERT(obj, key, value, [update_flag])
31143        self.write_keyword("OBJECT_INSERT");
31144        self.write("(");
31145        self.generate_expression(&e.this)?;
31146        if let Some(key) = &e.key {
31147            self.write(", ");
31148            self.generate_expression(key)?;
31149        }
31150        if let Some(value) = &e.value {
31151            self.write(", ");
31152            self.generate_expression(value)?;
31153        }
31154        if let Some(update_flag) = &e.update_flag {
31155            self.write(", ");
31156            self.generate_expression(update_flag)?;
31157        }
31158        self.write(")");
31159        Ok(())
31160    }
31161
31162    fn generate_offset(&mut self, e: &Offset) -> Result<()> {
31163        // OFFSET value [ROW|ROWS]
31164        self.write_keyword("OFFSET");
31165        self.write_space();
31166        self.generate_expression(&e.this)?;
31167        // Output ROWS keyword only for TSQL/Oracle targets
31168        if e.rows == Some(true)
31169            && matches!(
31170                self.config.dialect,
31171                Some(crate::dialects::DialectType::TSQL)
31172                    | Some(crate::dialects::DialectType::Oracle)
31173            )
31174        {
31175            self.write_space();
31176            self.write_keyword("ROWS");
31177        }
31178        Ok(())
31179    }
31180
31181    fn generate_qualify(&mut self, e: &Qualify) -> Result<()> {
31182        // QUALIFY condition (Snowflake/BigQuery)
31183        self.write_keyword("QUALIFY");
31184        self.write_space();
31185        self.generate_expression(&e.this)?;
31186        Ok(())
31187    }
31188
31189    fn generate_on_cluster(&mut self, e: &OnCluster) -> Result<()> {
31190        // ON CLUSTER cluster_name
31191        self.write_keyword("ON CLUSTER");
31192        self.write_space();
31193        self.generate_expression(&e.this)?;
31194        Ok(())
31195    }
31196
31197    fn generate_on_commit_property(&mut self, e: &OnCommitProperty) -> Result<()> {
31198        // ON COMMIT [DELETE ROWS | PRESERVE ROWS]
31199        self.write_keyword("ON COMMIT");
31200        if e.delete.is_some() {
31201            self.write_keyword(" DELETE ROWS");
31202        } else {
31203            self.write_keyword(" PRESERVE ROWS");
31204        }
31205        Ok(())
31206    }
31207
31208    fn generate_on_condition(&mut self, e: &OnCondition) -> Result<()> {
31209        // Python: error/empty/null handling
31210        if let Some(empty) = &e.empty {
31211            self.generate_expression(empty)?;
31212            self.write_keyword(" ON EMPTY");
31213        }
31214        if let Some(error) = &e.error {
31215            if e.empty.is_some() {
31216                self.write_space();
31217            }
31218            self.generate_expression(error)?;
31219            self.write_keyword(" ON ERROR");
31220        }
31221        if let Some(null) = &e.null {
31222            if e.empty.is_some() || e.error.is_some() {
31223                self.write_space();
31224            }
31225            self.generate_expression(null)?;
31226            self.write_keyword(" ON NULL");
31227        }
31228        Ok(())
31229    }
31230
31231    fn generate_on_conflict(&mut self, e: &OnConflict) -> Result<()> {
31232        // Materialize doesn't support ON CONFLICT - skip entirely
31233        if matches!(self.config.dialect, Some(DialectType::Materialize)) {
31234            return Ok(());
31235        }
31236        // Python: ON CONFLICT|ON DUPLICATE KEY [ON CONSTRAINT constraint] [conflict_keys] action
31237        if e.duplicate.is_some() {
31238            // MySQL: ON DUPLICATE KEY UPDATE col = val, ...
31239            self.write_keyword("ON DUPLICATE KEY UPDATE");
31240            for (i, expr) in e.expressions.iter().enumerate() {
31241                if i > 0 {
31242                    self.write(",");
31243                }
31244                self.write_space();
31245                self.generate_expression(expr)?;
31246            }
31247            return Ok(());
31248        } else {
31249            self.write_keyword("ON CONFLICT");
31250        }
31251        if let Some(constraint) = &e.constraint {
31252            self.write_keyword(" ON CONSTRAINT ");
31253            self.generate_expression(constraint)?;
31254        }
31255        if let Some(conflict_keys) = &e.conflict_keys {
31256            // conflict_keys can be a Tuple containing expressions
31257            if let Expression::Tuple(t) = conflict_keys.as_ref() {
31258                self.write("(");
31259                for (i, expr) in t.expressions.iter().enumerate() {
31260                    if i > 0 {
31261                        self.write(", ");
31262                    }
31263                    self.generate_expression(expr)?;
31264                }
31265                self.write(")");
31266            } else {
31267                self.write("(");
31268                self.generate_expression(conflict_keys)?;
31269                self.write(")");
31270            }
31271        }
31272        if let Some(index_predicate) = &e.index_predicate {
31273            self.write_keyword(" WHERE ");
31274            self.generate_expression(index_predicate)?;
31275        }
31276        if let Some(action) = &e.action {
31277            // Check if action is "NOTHING" or an UPDATE set
31278            if let Expression::Identifier(id) = action.as_ref() {
31279                if id.name.eq_ignore_ascii_case("NOTHING") {
31280                    self.write_keyword(" DO NOTHING");
31281                } else {
31282                    self.write_keyword(" DO ");
31283                    self.generate_expression(action)?;
31284                }
31285            } else if let Expression::Tuple(t) = action.as_ref() {
31286                // DO UPDATE SET col1 = val1, col2 = val2
31287                self.write_keyword(" DO UPDATE SET ");
31288                for (i, expr) in t.expressions.iter().enumerate() {
31289                    if i > 0 {
31290                        self.write(", ");
31291                    }
31292                    self.generate_expression(expr)?;
31293                }
31294            } else {
31295                self.write_keyword(" DO ");
31296                self.generate_expression(action)?;
31297            }
31298        }
31299        // WHERE clause for the UPDATE action
31300        if let Some(where_) = &e.where_ {
31301            self.write_keyword(" WHERE ");
31302            self.generate_expression(where_)?;
31303        }
31304        Ok(())
31305    }
31306
31307    fn generate_on_property(&mut self, e: &OnProperty) -> Result<()> {
31308        // ON property_value
31309        self.write_keyword("ON");
31310        self.write_space();
31311        self.generate_expression(&e.this)?;
31312        Ok(())
31313    }
31314
31315    fn generate_opclass(&mut self, e: &Opclass) -> Result<()> {
31316        // Python: this expression (e.g., column opclass)
31317        self.generate_expression(&e.this)?;
31318        self.write_space();
31319        self.generate_expression(&e.expression)?;
31320        Ok(())
31321    }
31322
31323    fn generate_open_json(&mut self, e: &OpenJSON) -> Result<()> {
31324        // Python: OPENJSON(this[, path]) [WITH (columns)]
31325        self.write_keyword("OPENJSON");
31326        self.write("(");
31327        self.generate_expression(&e.this)?;
31328        if let Some(path) = &e.path {
31329            self.write(", ");
31330            self.generate_expression(path)?;
31331        }
31332        self.write(")");
31333        if !e.expressions.is_empty() {
31334            self.write_keyword(" WITH");
31335            if self.config.pretty {
31336                self.write(" (\n");
31337                self.indent_level += 2;
31338                for (i, expr) in e.expressions.iter().enumerate() {
31339                    if i > 0 {
31340                        self.write(",\n");
31341                    }
31342                    self.write_indent();
31343                    self.generate_expression(expr)?;
31344                }
31345                self.write("\n");
31346                self.indent_level -= 2;
31347                self.write(")");
31348            } else {
31349                self.write(" (");
31350                for (i, expr) in e.expressions.iter().enumerate() {
31351                    if i > 0 {
31352                        self.write(", ");
31353                    }
31354                    self.generate_expression(expr)?;
31355                }
31356                self.write(")");
31357            }
31358        }
31359        Ok(())
31360    }
31361
31362    fn generate_open_json_column_def(&mut self, e: &OpenJSONColumnDef) -> Result<()> {
31363        // Python: this kind [path] [AS JSON]
31364        self.generate_expression(&e.this)?;
31365        self.write_space();
31366        // Use parsed data_type if available, otherwise fall back to kind string
31367        if let Some(ref dt) = e.data_type {
31368            self.generate_data_type(dt)?;
31369        } else if !e.kind.is_empty() {
31370            self.write(&e.kind);
31371        }
31372        if let Some(path) = &e.path {
31373            self.write_space();
31374            self.generate_expression(path)?;
31375        }
31376        if e.as_json.is_some() {
31377            self.write_keyword(" AS JSON");
31378        }
31379        Ok(())
31380    }
31381
31382    fn generate_operator(&mut self, e: &Operator) -> Result<()> {
31383        // this OPERATOR(op) expression
31384        self.generate_expression(&e.this)?;
31385        self.write_space();
31386        if let Some(op) = &e.operator {
31387            self.write_keyword("OPERATOR");
31388            self.write("(");
31389            self.generate_expression(op)?;
31390            self.write(")");
31391        }
31392        // Emit inline comments between OPERATOR() and the RHS
31393        for comment in &e.comments {
31394            self.write_space();
31395            self.write_formatted_comment(comment);
31396        }
31397        self.write_space();
31398        self.generate_expression(&e.expression)?;
31399        Ok(())
31400    }
31401
31402    fn generate_order_by(&mut self, e: &OrderBy) -> Result<()> {
31403        // ORDER BY expr1 [ASC|DESC] [NULLS FIRST|LAST], expr2 ...
31404        self.write_keyword("ORDER BY");
31405        let pretty_clickhouse_single_paren = self.config.pretty
31406            && matches!(self.config.dialect, Some(DialectType::ClickHouse))
31407            && e.expressions.len() == 1
31408            && matches!(e.expressions[0].this, Expression::Paren(ref p) if !matches!(p.this, Expression::Tuple(_)));
31409        let clickhouse_single_tuple = matches!(self.config.dialect, Some(DialectType::ClickHouse))
31410            && e.expressions.len() == 1
31411            && matches!(e.expressions[0].this, Expression::Tuple(_))
31412            && !e.expressions[0].desc
31413            && e.expressions[0].nulls_first.is_none();
31414
31415        if pretty_clickhouse_single_paren {
31416            self.write_space();
31417            if let Expression::Paren(p) = &e.expressions[0].this {
31418                self.write("(");
31419                self.write_newline();
31420                self.indent_level += 1;
31421                self.write_indent();
31422                self.generate_expression(&p.this)?;
31423                self.indent_level -= 1;
31424                self.write_newline();
31425                self.write(")");
31426            }
31427            return Ok(());
31428        }
31429
31430        if clickhouse_single_tuple {
31431            self.write_space();
31432            if let Expression::Tuple(t) = &e.expressions[0].this {
31433                self.write("(");
31434                for (i, expr) in t.expressions.iter().enumerate() {
31435                    if i > 0 {
31436                        self.write(", ");
31437                    }
31438                    self.generate_expression(expr)?;
31439                }
31440                self.write(")");
31441            }
31442            return Ok(());
31443        }
31444
31445        self.write_space();
31446        for (i, ordered) in e.expressions.iter().enumerate() {
31447            if i > 0 {
31448                self.write(", ");
31449            }
31450            self.generate_expression(&ordered.this)?;
31451            if ordered.desc {
31452                self.write_space();
31453                self.write_keyword("DESC");
31454            } else if ordered.explicit_asc {
31455                self.write_space();
31456                self.write_keyword("ASC");
31457            }
31458            if let Some(nulls_first) = ordered.nulls_first {
31459                // In Dremio, NULLS LAST is the default, so skip generating it
31460                let skip_nulls_last =
31461                    !nulls_first && matches!(self.config.dialect, Some(DialectType::Dremio));
31462                if !skip_nulls_last {
31463                    self.write_space();
31464                    self.write_keyword("NULLS");
31465                    self.write_space();
31466                    if nulls_first {
31467                        self.write_keyword("FIRST");
31468                    } else {
31469                        self.write_keyword("LAST");
31470                    }
31471                }
31472            }
31473        }
31474        Ok(())
31475    }
31476
31477    fn generate_output_model_property(&mut self, e: &OutputModelProperty) -> Result<()> {
31478        // OUTPUT(model)
31479        self.write_keyword("OUTPUT");
31480        self.write("(");
31481        if self.config.pretty {
31482            self.indent_level += 1;
31483            self.write_newline();
31484            self.write_indent();
31485            self.generate_expression(&e.this)?;
31486            self.indent_level -= 1;
31487            self.write_newline();
31488        } else {
31489            self.generate_expression(&e.this)?;
31490        }
31491        self.write(")");
31492        Ok(())
31493    }
31494
31495    fn generate_overflow_truncate_behavior(&mut self, e: &OverflowTruncateBehavior) -> Result<()> {
31496        // Python: TRUNCATE [filler] WITH|WITHOUT COUNT
31497        self.write_keyword("TRUNCATE");
31498        if let Some(this) = &e.this {
31499            self.write_space();
31500            self.generate_expression(this)?;
31501        }
31502        if e.with_count.is_some() {
31503            self.write_keyword(" WITH COUNT");
31504        } else {
31505            self.write_keyword(" WITHOUT COUNT");
31506        }
31507        Ok(())
31508    }
31509
31510    fn generate_parameterized_agg(&mut self, e: &ParameterizedAgg) -> Result<()> {
31511        // Python: name(expressions)(params)
31512        self.generate_expression(&e.this)?;
31513        self.write("(");
31514        for (i, expr) in e.expressions.iter().enumerate() {
31515            if i > 0 {
31516                self.write(", ");
31517            }
31518            self.generate_expression(expr)?;
31519        }
31520        self.write(")(");
31521        for (i, param) in e.params.iter().enumerate() {
31522            if i > 0 {
31523                self.write(", ");
31524            }
31525            self.generate_expression(param)?;
31526        }
31527        self.write(")");
31528        Ok(())
31529    }
31530
31531    fn generate_parse_datetime(&mut self, e: &ParseDatetime) -> Result<()> {
31532        // PARSE_DATETIME(format, this) or similar
31533        self.write_keyword("PARSE_DATETIME");
31534        self.write("(");
31535        if let Some(format) = &e.format {
31536            self.write("'");
31537            self.write(format);
31538            self.write("', ");
31539        }
31540        self.generate_expression(&e.this)?;
31541        if let Some(zone) = &e.zone {
31542            self.write(", ");
31543            self.generate_expression(zone)?;
31544        }
31545        self.write(")");
31546        Ok(())
31547    }
31548
31549    fn generate_parse_ip(&mut self, e: &ParseIp) -> Result<()> {
31550        // PARSE_IP(this, type, permissive)
31551        self.write_keyword("PARSE_IP");
31552        self.write("(");
31553        self.generate_expression(&e.this)?;
31554        if let Some(type_) = &e.type_ {
31555            self.write(", ");
31556            self.generate_expression(type_)?;
31557        }
31558        if let Some(permissive) = &e.permissive {
31559            self.write(", ");
31560            self.generate_expression(permissive)?;
31561        }
31562        self.write(")");
31563        Ok(())
31564    }
31565
31566    fn generate_parse_json(&mut self, e: &ParseJSON) -> Result<()> {
31567        // PARSE_JSON(this, [expression])
31568        self.write_keyword("PARSE_JSON");
31569        self.write("(");
31570        self.generate_expression(&e.this)?;
31571        if let Some(expression) = &e.expression {
31572            self.write(", ");
31573            self.generate_expression(expression)?;
31574        }
31575        self.write(")");
31576        Ok(())
31577    }
31578
31579    fn generate_parse_time(&mut self, e: &ParseTime) -> Result<()> {
31580        // PARSE_TIME(format, this) or STR_TO_TIME(this, format)
31581        self.write_keyword("PARSE_TIME");
31582        self.write("(");
31583        self.write(&format!("'{}'", e.format));
31584        self.write(", ");
31585        self.generate_expression(&e.this)?;
31586        self.write(")");
31587        Ok(())
31588    }
31589
31590    fn generate_parse_url(&mut self, e: &ParseUrl) -> Result<()> {
31591        // PARSE_URL(this, [part_to_extract], [key], [permissive])
31592        self.write_keyword("PARSE_URL");
31593        self.write("(");
31594        self.generate_expression(&e.this)?;
31595        if let Some(part) = &e.part_to_extract {
31596            self.write(", ");
31597            self.generate_expression(part)?;
31598        }
31599        if let Some(key) = &e.key {
31600            self.write(", ");
31601            self.generate_expression(key)?;
31602        }
31603        if let Some(permissive) = &e.permissive {
31604            self.write(", ");
31605            self.generate_expression(permissive)?;
31606        }
31607        self.write(")");
31608        Ok(())
31609    }
31610
31611    fn generate_partition_expr(&mut self, e: &Partition) -> Result<()> {
31612        // PARTITION(expr1, expr2, ...) or SUBPARTITION(expr1, expr2, ...)
31613        if e.subpartition {
31614            self.write_keyword("SUBPARTITION");
31615        } else {
31616            self.write_keyword("PARTITION");
31617        }
31618        self.write("(");
31619        for (i, expr) in e.expressions.iter().enumerate() {
31620            if i > 0 {
31621                self.write(", ");
31622            }
31623            self.generate_expression(expr)?;
31624        }
31625        self.write(")");
31626        Ok(())
31627    }
31628
31629    fn generate_partition_bound_spec(&mut self, e: &PartitionBoundSpec) -> Result<()> {
31630        // IN (values) or WITH (MODULUS this, REMAINDER expression) or FROM (from) TO (to)
31631        if let Some(this) = &e.this {
31632            if let Some(expression) = &e.expression {
31633                // WITH (MODULUS this, REMAINDER expression)
31634                self.write_keyword("WITH");
31635                self.write(" (");
31636                self.write_keyword("MODULUS");
31637                self.write_space();
31638                self.generate_expression(this)?;
31639                self.write(", ");
31640                self.write_keyword("REMAINDER");
31641                self.write_space();
31642                self.generate_expression(expression)?;
31643                self.write(")");
31644            } else {
31645                // IN (this) - this could be a list
31646                self.write_keyword("IN");
31647                self.write(" (");
31648                self.generate_partition_bound_values(this)?;
31649                self.write(")");
31650            }
31651        } else if let (Some(from), Some(to)) = (&e.from_expressions, &e.to_expressions) {
31652            // FROM (from_expressions) TO (to_expressions)
31653            self.write_keyword("FROM");
31654            self.write(" (");
31655            self.generate_partition_bound_values(from)?;
31656            self.write(") ");
31657            self.write_keyword("TO");
31658            self.write(" (");
31659            self.generate_partition_bound_values(to)?;
31660            self.write(")");
31661        }
31662        Ok(())
31663    }
31664
31665    /// Generate partition bound values - handles Tuple expressions by outputting
31666    /// contents without wrapping parens (since caller provides the parens)
31667    fn generate_partition_bound_values(&mut self, expr: &Expression) -> Result<()> {
31668        if let Expression::Tuple(t) = expr {
31669            for (i, e) in t.expressions.iter().enumerate() {
31670                if i > 0 {
31671                    self.write(", ");
31672                }
31673                self.generate_expression(e)?;
31674            }
31675            Ok(())
31676        } else {
31677            self.generate_expression(expr)
31678        }
31679    }
31680
31681    fn generate_partition_by_list_property(&mut self, e: &PartitionByListProperty) -> Result<()> {
31682        // PARTITION BY LIST (partition_expressions) (create_expressions)
31683        self.write_keyword("PARTITION BY LIST");
31684        if let Some(partition_exprs) = &e.partition_expressions {
31685            self.write(" (");
31686            // Unwrap Tuple for partition columns (don't generate outer parens from Tuple)
31687            self.generate_doris_partition_expressions(partition_exprs)?;
31688            self.write(")");
31689        }
31690        if let Some(create_exprs) = &e.create_expressions {
31691            self.write(" (");
31692            // Unwrap Tuple for partition definitions
31693            self.generate_doris_partition_definitions(create_exprs)?;
31694            self.write(")");
31695        }
31696        Ok(())
31697    }
31698
31699    fn generate_partition_by_range_property(&mut self, e: &PartitionByRangeProperty) -> Result<()> {
31700        // PARTITION BY RANGE (partition_expressions) (create_expressions)
31701        self.write_keyword("PARTITION BY RANGE");
31702        if let Some(partition_exprs) = &e.partition_expressions {
31703            self.write(" (");
31704            // Unwrap Tuple for partition columns (don't generate outer parens from Tuple)
31705            self.generate_doris_partition_expressions(partition_exprs)?;
31706            self.write(")");
31707        }
31708        if let Some(create_exprs) = &e.create_expressions {
31709            self.write(" (");
31710            // Check for dynamic partition (PartitionByRangePropertyDynamic) or static (Tuple of Partition)
31711            self.generate_doris_partition_definitions(create_exprs)?;
31712            self.write(")");
31713        }
31714        Ok(())
31715    }
31716
31717    /// Generate Doris partition column expressions (unwrap Tuple)
31718    fn generate_doris_partition_expressions(&mut self, expr: &Expression) -> Result<()> {
31719        if let Expression::Tuple(t) = expr {
31720            for (i, e) in t.expressions.iter().enumerate() {
31721                if i > 0 {
31722                    self.write(", ");
31723                }
31724                self.generate_expression(e)?;
31725            }
31726        } else {
31727            self.generate_expression(expr)?;
31728        }
31729        Ok(())
31730    }
31731
31732    /// Generate Doris partition definitions (comma-separated Partition expressions)
31733    fn generate_doris_partition_definitions(&mut self, expr: &Expression) -> Result<()> {
31734        match expr {
31735            Expression::Tuple(t) => {
31736                // Multiple partitions, comma-separated
31737                for (i, part) in t.expressions.iter().enumerate() {
31738                    if i > 0 {
31739                        self.write(", ");
31740                    }
31741                    // For Partition expressions, generate the inner PartitionRange/PartitionList directly
31742                    if let Expression::Partition(p) = part {
31743                        for (j, inner) in p.expressions.iter().enumerate() {
31744                            if j > 0 {
31745                                self.write(", ");
31746                            }
31747                            self.generate_expression(inner)?;
31748                        }
31749                    } else {
31750                        self.generate_expression(part)?;
31751                    }
31752                }
31753            }
31754            Expression::PartitionByRangePropertyDynamic(_) => {
31755                // Dynamic partition - FROM/TO/INTERVAL
31756                self.generate_expression(expr)?;
31757            }
31758            _ => {
31759                self.generate_expression(expr)?;
31760            }
31761        }
31762        Ok(())
31763    }
31764
31765    fn generate_partition_by_range_property_dynamic(
31766        &mut self,
31767        e: &PartitionByRangePropertyDynamic,
31768    ) -> Result<()> {
31769        if e.use_start_end {
31770            // StarRocks: START ('val') END ('val') EVERY (expr)
31771            if let Some(start) = &e.start {
31772                self.write_keyword("START");
31773                self.write(" (");
31774                self.generate_expression(start)?;
31775                self.write(")");
31776            }
31777            if let Some(end) = &e.end {
31778                self.write_space();
31779                self.write_keyword("END");
31780                self.write(" (");
31781                self.generate_expression(end)?;
31782                self.write(")");
31783            }
31784            if let Some(every) = &e.every {
31785                self.write_space();
31786                self.write_keyword("EVERY");
31787                self.write(" (");
31788                // Use unquoted interval format for StarRocks
31789                self.generate_doris_interval(every)?;
31790                self.write(")");
31791            }
31792        } else {
31793            // Doris: FROM (start) TO (end) INTERVAL n UNIT
31794            if let Some(start) = &e.start {
31795                self.write_keyword("FROM");
31796                self.write(" (");
31797                self.generate_expression(start)?;
31798                self.write(")");
31799            }
31800            if let Some(end) = &e.end {
31801                self.write_space();
31802                self.write_keyword("TO");
31803                self.write(" (");
31804                self.generate_expression(end)?;
31805                self.write(")");
31806            }
31807            if let Some(every) = &e.every {
31808                self.write_space();
31809                // Generate INTERVAL n UNIT (not quoted, for Doris dynamic partition)
31810                self.generate_doris_interval(every)?;
31811            }
31812        }
31813        Ok(())
31814    }
31815
31816    /// Generate Doris-style interval without quoting numbers: INTERVAL n UNIT
31817    fn generate_doris_interval(&mut self, expr: &Expression) -> Result<()> {
31818        if let Expression::Interval(interval) = expr {
31819            self.write_keyword("INTERVAL");
31820            if let Some(ref value) = interval.this {
31821                self.write_space();
31822                // If the value is a string literal that looks like a number,
31823                // output it without quotes (matching Python sqlglot's
31824                // partitionbyrangepropertydynamic_sql which converts back to number)
31825                match value {
31826                    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()) => {
31827                        if let Literal::String(s) = lit.as_ref() {
31828                            self.write(s);
31829                        }
31830                    }
31831                    _ => {
31832                        self.generate_expression(value)?;
31833                    }
31834                }
31835            }
31836            if let Some(ref unit_spec) = interval.unit {
31837                self.write_space();
31838                self.write_interval_unit_spec(unit_spec)?;
31839            }
31840            Ok(())
31841        } else {
31842            self.generate_expression(expr)
31843        }
31844    }
31845
31846    fn generate_partition_by_truncate(&mut self, e: &PartitionByTruncate) -> Result<()> {
31847        // TRUNCATE(expression, this)
31848        self.write_keyword("TRUNCATE");
31849        self.write("(");
31850        self.generate_expression(&e.expression)?;
31851        self.write(", ");
31852        self.generate_expression(&e.this)?;
31853        self.write(")");
31854        Ok(())
31855    }
31856
31857    fn generate_partition_list(&mut self, e: &PartitionList) -> Result<()> {
31858        // Doris: PARTITION name VALUES IN (val1, val2)
31859        self.write_keyword("PARTITION");
31860        self.write_space();
31861        self.generate_expression(&e.this)?;
31862        self.write_space();
31863        self.write_keyword("VALUES IN");
31864        self.write(" (");
31865        for (i, expr) in e.expressions.iter().enumerate() {
31866            if i > 0 {
31867                self.write(", ");
31868            }
31869            self.generate_expression(expr)?;
31870        }
31871        self.write(")");
31872        Ok(())
31873    }
31874
31875    fn generate_partition_range(&mut self, e: &PartitionRange) -> Result<()> {
31876        // Check if this is a TSQL-style simple range (e.g., "2 TO 5")
31877        // TSQL ranges have no expressions and just use `this TO expression`
31878        if e.expressions.is_empty() && e.expression.is_some() {
31879            // TSQL: simple range like "2 TO 5" - no PARTITION keyword
31880            self.generate_expression(&e.this)?;
31881            self.write_space();
31882            self.write_keyword("TO");
31883            self.write_space();
31884            self.generate_expression(e.expression.as_ref().unwrap())?;
31885            return Ok(());
31886        }
31887
31888        // Doris: PARTITION name VALUES LESS THAN (val) or PARTITION name VALUES [(val1), (val2))
31889        self.write_keyword("PARTITION");
31890        self.write_space();
31891        self.generate_expression(&e.this)?;
31892        self.write_space();
31893
31894        // Check if expressions contain Tuple (bracket notation) or single values (LESS THAN)
31895        if e.expressions.len() == 1 {
31896            // Single value: VALUES LESS THAN (val)
31897            self.write_keyword("VALUES LESS THAN");
31898            self.write(" (");
31899            self.generate_expression(&e.expressions[0])?;
31900            self.write(")");
31901        } else if !e.expressions.is_empty() {
31902            // Multiple values with Tuple: VALUES [(val1), (val2))
31903            self.write_keyword("VALUES");
31904            self.write(" [");
31905            for (i, expr) in e.expressions.iter().enumerate() {
31906                if i > 0 {
31907                    self.write(", ");
31908                }
31909                // If the expr is a Tuple, generate its contents wrapped in parens
31910                if let Expression::Tuple(t) = expr {
31911                    self.write("(");
31912                    for (j, inner) in t.expressions.iter().enumerate() {
31913                        if j > 0 {
31914                            self.write(", ");
31915                        }
31916                        self.generate_expression(inner)?;
31917                    }
31918                    self.write(")");
31919                } else {
31920                    self.write("(");
31921                    self.generate_expression(expr)?;
31922                    self.write(")");
31923                }
31924            }
31925            self.write(")");
31926        }
31927        Ok(())
31928    }
31929
31930    fn generate_partitioned_by_bucket(&mut self, e: &PartitionedByBucket) -> Result<()> {
31931        // BUCKET(this, expression)
31932        self.write_keyword("BUCKET");
31933        self.write("(");
31934        self.generate_expression(&e.this)?;
31935        self.write(", ");
31936        self.generate_expression(&e.expression)?;
31937        self.write(")");
31938        Ok(())
31939    }
31940
31941    fn generate_partition_by_property(&mut self, e: &PartitionByProperty) -> Result<()> {
31942        // BigQuery table property: PARTITION BY expression [, expression ...]
31943        self.write_keyword("PARTITION BY");
31944        self.write_space();
31945        for (i, expr) in e.expressions.iter().enumerate() {
31946            if i > 0 {
31947                self.write(", ");
31948            }
31949            self.generate_expression(expr)?;
31950        }
31951        Ok(())
31952    }
31953
31954    fn generate_partitioned_by_property(&mut self, e: &PartitionedByProperty) -> Result<()> {
31955        // PARTITIONED BY this (Teradata/ClickHouse use PARTITION BY)
31956        if matches!(
31957            self.config.dialect,
31958            Some(crate::dialects::DialectType::Teradata)
31959                | Some(crate::dialects::DialectType::ClickHouse)
31960        ) {
31961            self.write_keyword("PARTITION BY");
31962        } else {
31963            self.write_keyword("PARTITIONED BY");
31964        }
31965        self.write_space();
31966        // In pretty mode, always use multiline tuple format for PARTITIONED BY
31967        if self.config.pretty {
31968            if let Expression::Tuple(ref tuple) = *e.this {
31969                self.write("(");
31970                self.write_newline();
31971                self.indent_level += 1;
31972                for (i, expr) in tuple.expressions.iter().enumerate() {
31973                    if i > 0 {
31974                        self.write(",");
31975                        self.write_newline();
31976                    }
31977                    self.write_indent();
31978                    self.generate_expression(expr)?;
31979                }
31980                self.indent_level -= 1;
31981                self.write_newline();
31982                self.write(")");
31983            } else {
31984                self.generate_expression(&e.this)?;
31985            }
31986        } else {
31987            self.generate_expression(&e.this)?;
31988        }
31989        Ok(())
31990    }
31991
31992    fn generate_partitioned_of_property(&mut self, e: &PartitionedOfProperty) -> Result<()> {
31993        // PARTITION OF this FOR VALUES expression or PARTITION OF this DEFAULT
31994        self.write_keyword("PARTITION OF");
31995        self.write_space();
31996        self.generate_expression(&e.this)?;
31997        // Check if expression is a PartitionBoundSpec
31998        if let Expression::PartitionBoundSpec(_) = e.expression.as_ref() {
31999            self.write_space();
32000            self.write_keyword("FOR VALUES");
32001            self.write_space();
32002            self.generate_expression(&e.expression)?;
32003        } else {
32004            self.write_space();
32005            self.write_keyword("DEFAULT");
32006        }
32007        Ok(())
32008    }
32009
32010    fn generate_period_for_system_time_constraint(
32011        &mut self,
32012        e: &PeriodForSystemTimeConstraint,
32013    ) -> Result<()> {
32014        // PERIOD FOR SYSTEM_TIME (this, expression)
32015        self.write_keyword("PERIOD FOR SYSTEM_TIME");
32016        self.write(" (");
32017        self.generate_expression(&e.this)?;
32018        self.write(", ");
32019        self.generate_expression(&e.expression)?;
32020        self.write(")");
32021        Ok(())
32022    }
32023
32024    fn generate_pivot_alias(&mut self, e: &PivotAlias) -> Result<()> {
32025        // value AS alias
32026        // The alias can be an identifier or an expression (e.g., string concatenation)
32027        self.generate_expression(&e.this)?;
32028        self.write_space();
32029        self.write_keyword("AS");
32030        self.write_space();
32031        // When target dialect uses identifiers for UNPIVOT aliases, convert literals to identifiers
32032        if self.config.unpivot_aliases_are_identifiers {
32033            match &e.alias {
32034                Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
32035                    let Literal::String(s) = lit.as_ref() else {
32036                        unreachable!()
32037                    };
32038                    // Convert string literal to identifier
32039                    self.generate_identifier(&Identifier::new(s.clone()))?;
32040                }
32041                Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(_)) => {
32042                    let Literal::Number(n) = lit.as_ref() else {
32043                        unreachable!()
32044                    };
32045                    // Convert number literal to quoted identifier
32046                    let mut id = Identifier::new(n.clone());
32047                    id.quoted = true;
32048                    self.generate_identifier(&id)?;
32049                }
32050                other => {
32051                    self.generate_expression(other)?;
32052                }
32053            }
32054        } else {
32055            self.generate_expression(&e.alias)?;
32056        }
32057        Ok(())
32058    }
32059
32060    fn generate_pivot_any(&mut self, e: &PivotAny) -> Result<()> {
32061        // ANY or ANY [expression]
32062        self.write_keyword("ANY");
32063        if let Some(this) = &e.this {
32064            self.write_space();
32065            self.generate_expression(this)?;
32066        }
32067        Ok(())
32068    }
32069
32070    fn generate_predict(&mut self, e: &Predict) -> Result<()> {
32071        // ML.PREDICT(MODEL this, expression, [params_struct])
32072        self.write_keyword("ML.PREDICT");
32073        self.write("(");
32074        self.write_keyword("MODEL");
32075        self.write_space();
32076        self.generate_expression(&e.this)?;
32077        self.write(", ");
32078        self.generate_expression(&e.expression)?;
32079        if let Some(params) = &e.params_struct {
32080            self.write(", ");
32081            self.generate_expression(params)?;
32082        }
32083        self.write(")");
32084        Ok(())
32085    }
32086
32087    fn generate_previous_day(&mut self, e: &PreviousDay) -> Result<()> {
32088        // PREVIOUS_DAY(this, expression)
32089        self.write_keyword("PREVIOUS_DAY");
32090        self.write("(");
32091        self.generate_expression(&e.this)?;
32092        self.write(", ");
32093        self.generate_expression(&e.expression)?;
32094        self.write(")");
32095        Ok(())
32096    }
32097
32098    fn generate_primary_key(&mut self, e: &PrimaryKey) -> Result<()> {
32099        // PRIMARY KEY [name] (columns) [INCLUDE (...)] [options]
32100        self.write_keyword("PRIMARY KEY");
32101        if let Some(name) = &e.this {
32102            self.write_space();
32103            self.generate_expression(name)?;
32104        }
32105        if !e.expressions.is_empty() {
32106            self.write(" (");
32107            for (i, expr) in e.expressions.iter().enumerate() {
32108                if i > 0 {
32109                    self.write(", ");
32110                }
32111                self.generate_expression(expr)?;
32112            }
32113            self.write(")");
32114        }
32115        if let Some(include) = &e.include {
32116            self.write_space();
32117            self.generate_expression(include)?;
32118        }
32119        if !e.options.is_empty() {
32120            self.write_space();
32121            for (i, opt) in e.options.iter().enumerate() {
32122                if i > 0 {
32123                    self.write_space();
32124                }
32125                self.generate_expression(opt)?;
32126            }
32127        }
32128        Ok(())
32129    }
32130
32131    fn generate_primary_key_column_constraint(
32132        &mut self,
32133        _e: &PrimaryKeyColumnConstraint,
32134    ) -> Result<()> {
32135        // PRIMARY KEY constraint at column level
32136        self.write_keyword("PRIMARY KEY");
32137        Ok(())
32138    }
32139
32140    fn generate_path_column_constraint(&mut self, e: &PathColumnConstraint) -> Result<()> {
32141        // PATH 'xpath' constraint for XMLTABLE/JSON_TABLE columns
32142        self.write_keyword("PATH");
32143        self.write_space();
32144        self.generate_expression(&e.this)?;
32145        Ok(())
32146    }
32147
32148    fn generate_projection_def(&mut self, e: &ProjectionDef) -> Result<()> {
32149        // PROJECTION this (expression)
32150        self.write_keyword("PROJECTION");
32151        self.write_space();
32152        self.generate_expression(&e.this)?;
32153        self.write(" (");
32154        self.generate_expression(&e.expression)?;
32155        self.write(")");
32156        Ok(())
32157    }
32158
32159    fn generate_properties(&mut self, e: &Properties) -> Result<()> {
32160        // Properties list
32161        for (i, prop) in e.expressions.iter().enumerate() {
32162            if i > 0 {
32163                self.write(", ");
32164            }
32165            self.generate_expression(prop)?;
32166        }
32167        Ok(())
32168    }
32169
32170    fn generate_property(&mut self, e: &Property) -> Result<()> {
32171        // name=value
32172        self.generate_expression(&e.this)?;
32173        if let Some(value) = &e.value {
32174            self.write("=");
32175            self.generate_expression(value)?;
32176        }
32177        Ok(())
32178    }
32179
32180    fn generate_options_property(&mut self, e: &OptionsProperty) -> Result<()> {
32181        self.write_keyword("OPTIONS");
32182        if e.entries.is_empty() {
32183            self.write(" ()");
32184            return Ok(());
32185        }
32186
32187        if self.config.pretty {
32188            self.write(" (");
32189            self.write_newline();
32190            self.indent_level += 1;
32191            for (i, entry) in e.entries.iter().enumerate() {
32192                if i > 0 {
32193                    self.write(",");
32194                    self.write_newline();
32195                }
32196                self.write_indent();
32197                self.generate_identifier(&entry.key)?;
32198                self.write("=");
32199                self.generate_expression(&entry.value)?;
32200            }
32201            self.indent_level -= 1;
32202            self.write_newline();
32203            self.write(")");
32204        } else {
32205            self.write(" (");
32206            for (i, entry) in e.entries.iter().enumerate() {
32207                if i > 0 {
32208                    self.write(", ");
32209                }
32210                self.generate_identifier(&entry.key)?;
32211                self.write("=");
32212                self.generate_expression(&entry.value)?;
32213            }
32214            self.write(")");
32215        }
32216        Ok(())
32217    }
32218
32219    /// Generate BigQuery-style OPTIONS clause: OPTIONS (key=value, key=value, ...)
32220    fn generate_options_clause(&mut self, options: &[Expression]) -> Result<()> {
32221        self.write_keyword("OPTIONS");
32222        self.write(" (");
32223        for (i, opt) in options.iter().enumerate() {
32224            if i > 0 {
32225                self.write(", ");
32226            }
32227            self.generate_option_expression(opt)?;
32228        }
32229        self.write(")");
32230        Ok(())
32231    }
32232
32233    /// Generate Doris/StarRocks-style PROPERTIES clause: PROPERTIES ('key'='value', 'key'='value', ...)
32234    fn generate_properties_clause(&mut self, properties: &[Expression]) -> Result<()> {
32235        self.write_keyword("PROPERTIES");
32236        self.write(" (");
32237        for (i, prop) in properties.iter().enumerate() {
32238            if i > 0 {
32239                self.write(", ");
32240            }
32241            self.generate_option_expression(prop)?;
32242        }
32243        self.write(")");
32244        Ok(())
32245    }
32246
32247    /// Generate Databricks-style ENVIRONMENT clause: ENVIRONMENT (key = 'value', key = 'value', ...)
32248    fn generate_environment_clause(&mut self, environment: &[Expression]) -> Result<()> {
32249        self.write_keyword("ENVIRONMENT");
32250        self.write(" (");
32251        for (i, env_item) in environment.iter().enumerate() {
32252            if i > 0 {
32253                self.write(", ");
32254            }
32255            self.generate_environment_expression(env_item)?;
32256        }
32257        self.write(")");
32258        Ok(())
32259    }
32260
32261    /// Generate an environment expression with spaces around =
32262    fn generate_environment_expression(&mut self, expr: &Expression) -> Result<()> {
32263        match expr {
32264            Expression::Eq(eq) => {
32265                // Generate key = value with spaces (Databricks ENVIRONMENT style)
32266                self.generate_expression(&eq.left)?;
32267                self.write(" = ");
32268                self.generate_expression(&eq.right)?;
32269                Ok(())
32270            }
32271            _ => self.generate_expression(expr),
32272        }
32273    }
32274
32275    /// Generate Hive-style TBLPROPERTIES clause: TBLPROPERTIES ('key'='value', ...)
32276    fn generate_tblproperties_clause(&mut self, options: &[Expression]) -> Result<()> {
32277        self.write_keyword("TBLPROPERTIES");
32278        if self.config.pretty {
32279            self.write(" (");
32280            self.write_newline();
32281            self.indent_level += 1;
32282            for (i, opt) in options.iter().enumerate() {
32283                if i > 0 {
32284                    self.write(",");
32285                    self.write_newline();
32286                }
32287                self.write_indent();
32288                self.generate_option_expression(opt)?;
32289            }
32290            self.indent_level -= 1;
32291            self.write_newline();
32292            self.write(")");
32293        } else {
32294            self.write(" (");
32295            for (i, opt) in options.iter().enumerate() {
32296                if i > 0 {
32297                    self.write(", ");
32298                }
32299                self.generate_option_expression(opt)?;
32300            }
32301            self.write(")");
32302        }
32303        Ok(())
32304    }
32305
32306    /// Generate an option expression without spaces around =
32307    fn generate_option_expression(&mut self, expr: &Expression) -> Result<()> {
32308        match expr {
32309            Expression::Eq(eq) => {
32310                // Generate key=value without spaces
32311                self.generate_expression(&eq.left)?;
32312                self.write("=");
32313                self.generate_expression(&eq.right)?;
32314                Ok(())
32315            }
32316            _ => self.generate_expression(expr),
32317        }
32318    }
32319
32320    fn generate_pseudo_type(&mut self, e: &PseudoType) -> Result<()> {
32321        // Just output the name
32322        self.generate_expression(&e.this)?;
32323        Ok(())
32324    }
32325
32326    fn generate_put(&mut self, e: &PutStmt) -> Result<()> {
32327        // PUT source_file @stage [options]
32328        self.write_keyword("PUT");
32329        self.write_space();
32330
32331        // Source file path - preserve original quoting
32332        if e.source_quoted {
32333            self.write("'");
32334            self.write(&e.source);
32335            self.write("'");
32336        } else {
32337            self.write(&e.source);
32338        }
32339
32340        self.write_space();
32341
32342        // Target stage reference - output the string directly (includes @)
32343        if let Expression::Literal(lit) = &e.target {
32344            if let Literal::String(s) = lit.as_ref() {
32345                self.write(s);
32346            }
32347        } else {
32348            self.generate_expression(&e.target)?;
32349        }
32350
32351        // Optional parameters: KEY=VALUE
32352        for param in &e.params {
32353            self.write_space();
32354            self.write(&param.name);
32355            if let Some(ref value) = param.value {
32356                self.write("=");
32357                self.generate_expression(value)?;
32358            }
32359        }
32360
32361        Ok(())
32362    }
32363
32364    fn generate_quantile(&mut self, e: &Quantile) -> Result<()> {
32365        // QUANTILE(this, quantile)
32366        self.write_keyword("QUANTILE");
32367        self.write("(");
32368        self.generate_expression(&e.this)?;
32369        if let Some(quantile) = &e.quantile {
32370            self.write(", ");
32371            self.generate_expression(quantile)?;
32372        }
32373        self.write(")");
32374        Ok(())
32375    }
32376
32377    fn generate_query_band(&mut self, e: &QueryBand) -> Result<()> {
32378        // QUERY_BAND = this [UPDATE] [FOR scope]
32379        if matches!(
32380            self.config.dialect,
32381            Some(crate::dialects::DialectType::Teradata)
32382        ) {
32383            self.write_keyword("SET");
32384            self.write_space();
32385        }
32386        self.write_keyword("QUERY_BAND");
32387        self.write(" = ");
32388        self.generate_expression(&e.this)?;
32389        if e.update.is_some() {
32390            self.write_space();
32391            self.write_keyword("UPDATE");
32392        }
32393        if let Some(scope) = &e.scope {
32394            self.write_space();
32395            self.write_keyword("FOR");
32396            self.write_space();
32397            self.generate_expression(scope)?;
32398        }
32399        Ok(())
32400    }
32401
32402    fn generate_query_option(&mut self, e: &QueryOption) -> Result<()> {
32403        // this = expression
32404        self.generate_expression(&e.this)?;
32405        if let Some(expression) = &e.expression {
32406            self.write(" = ");
32407            self.generate_expression(expression)?;
32408        }
32409        Ok(())
32410    }
32411
32412    fn generate_query_transform(&mut self, e: &QueryTransform) -> Result<()> {
32413        // TRANSFORM (expressions) [row_format_before] [RECORDWRITER record_writer] USING command_script [AS schema] [row_format_after] [RECORDREADER record_reader]
32414        self.write_keyword("TRANSFORM");
32415        self.write("(");
32416        for (i, expr) in e.expressions.iter().enumerate() {
32417            if i > 0 {
32418                self.write(", ");
32419            }
32420            self.generate_expression(expr)?;
32421        }
32422        self.write(")");
32423        if let Some(row_format_before) = &e.row_format_before {
32424            self.write_space();
32425            self.generate_expression(row_format_before)?;
32426        }
32427        if let Some(record_writer) = &e.record_writer {
32428            self.write_space();
32429            self.write_keyword("RECORDWRITER");
32430            self.write_space();
32431            self.generate_expression(record_writer)?;
32432        }
32433        if let Some(command_script) = &e.command_script {
32434            self.write_space();
32435            self.write_keyword("USING");
32436            self.write_space();
32437            self.generate_expression(command_script)?;
32438        }
32439        if let Some(schema) = &e.schema {
32440            self.write_space();
32441            self.write_keyword("AS");
32442            self.write_space();
32443            self.generate_expression(schema)?;
32444        }
32445        if let Some(row_format_after) = &e.row_format_after {
32446            self.write_space();
32447            self.generate_expression(row_format_after)?;
32448        }
32449        if let Some(record_reader) = &e.record_reader {
32450            self.write_space();
32451            self.write_keyword("RECORDREADER");
32452            self.write_space();
32453            self.generate_expression(record_reader)?;
32454        }
32455        Ok(())
32456    }
32457
32458    fn generate_randn(&mut self, e: &Randn) -> Result<()> {
32459        // RANDN([seed])
32460        self.write_keyword("RANDN");
32461        self.write("(");
32462        if let Some(this) = &e.this {
32463            self.generate_expression(this)?;
32464        }
32465        self.write(")");
32466        Ok(())
32467    }
32468
32469    fn generate_randstr(&mut self, e: &Randstr) -> Result<()> {
32470        // RANDSTR(this, [generator])
32471        self.write_keyword("RANDSTR");
32472        self.write("(");
32473        self.generate_expression(&e.this)?;
32474        if let Some(generator) = &e.generator {
32475            self.write(", ");
32476            self.generate_expression(generator)?;
32477        }
32478        self.write(")");
32479        Ok(())
32480    }
32481
32482    fn generate_range_bucket(&mut self, e: &RangeBucket) -> Result<()> {
32483        // RANGE_BUCKET(this, expression)
32484        self.write_keyword("RANGE_BUCKET");
32485        self.write("(");
32486        self.generate_expression(&e.this)?;
32487        self.write(", ");
32488        self.generate_expression(&e.expression)?;
32489        self.write(")");
32490        Ok(())
32491    }
32492
32493    fn generate_range_n(&mut self, e: &RangeN) -> Result<()> {
32494        // RANGE_N(this BETWEEN expressions [EACH each])
32495        self.write_keyword("RANGE_N");
32496        self.write("(");
32497        self.generate_expression(&e.this)?;
32498        self.write_space();
32499        self.write_keyword("BETWEEN");
32500        self.write_space();
32501        for (i, expr) in e.expressions.iter().enumerate() {
32502            if i > 0 {
32503                self.write(", ");
32504            }
32505            self.generate_expression(expr)?;
32506        }
32507        if let Some(each) = &e.each {
32508            self.write_space();
32509            self.write_keyword("EACH");
32510            self.write_space();
32511            self.generate_expression(each)?;
32512        }
32513        self.write(")");
32514        Ok(())
32515    }
32516
32517    fn generate_read_csv(&mut self, e: &ReadCSV) -> Result<()> {
32518        // READ_CSV(this, expressions...)
32519        self.write_keyword("READ_CSV");
32520        self.write("(");
32521        self.generate_expression(&e.this)?;
32522        for expr in &e.expressions {
32523            self.write(", ");
32524            self.generate_expression(expr)?;
32525        }
32526        self.write(")");
32527        Ok(())
32528    }
32529
32530    fn generate_read_parquet(&mut self, e: &ReadParquet) -> Result<()> {
32531        // READ_PARQUET(expressions...)
32532        self.write_keyword("READ_PARQUET");
32533        self.write("(");
32534        for (i, expr) in e.expressions.iter().enumerate() {
32535            if i > 0 {
32536                self.write(", ");
32537            }
32538            self.generate_expression(expr)?;
32539        }
32540        self.write(")");
32541        Ok(())
32542    }
32543
32544    fn generate_recursive_with_search(&mut self, e: &RecursiveWithSearch) -> Result<()> {
32545        // SEARCH kind FIRST BY this SET expression [USING using]
32546        // or CYCLE this SET expression [USING using]
32547        if e.kind == "CYCLE" {
32548            self.write_keyword("CYCLE");
32549        } else {
32550            self.write_keyword("SEARCH");
32551            self.write_space();
32552            self.write(&e.kind);
32553            self.write_space();
32554            self.write_keyword("FIRST BY");
32555        }
32556        self.write_space();
32557        self.generate_expression(&e.this)?;
32558        self.write_space();
32559        self.write_keyword("SET");
32560        self.write_space();
32561        self.generate_expression(&e.expression)?;
32562        if let Some(using) = &e.using {
32563            self.write_space();
32564            self.write_keyword("USING");
32565            self.write_space();
32566            self.generate_expression(using)?;
32567        }
32568        Ok(())
32569    }
32570
32571    fn generate_reduce(&mut self, e: &Reduce) -> Result<()> {
32572        // REDUCE(this, initial, merge, [finish])
32573        self.write_keyword("REDUCE");
32574        self.write("(");
32575        self.generate_expression(&e.this)?;
32576        if let Some(initial) = &e.initial {
32577            self.write(", ");
32578            self.generate_expression(initial)?;
32579        }
32580        if let Some(merge) = &e.merge {
32581            self.write(", ");
32582            self.generate_expression(merge)?;
32583        }
32584        if let Some(finish) = &e.finish {
32585            self.write(", ");
32586            self.generate_expression(finish)?;
32587        }
32588        self.write(")");
32589        Ok(())
32590    }
32591
32592    fn generate_reference(&mut self, e: &Reference) -> Result<()> {
32593        // REFERENCES this (expressions) [options]
32594        self.write_keyword("REFERENCES");
32595        self.write_space();
32596        self.generate_expression(&e.this)?;
32597        if !e.expressions.is_empty() {
32598            self.write(" (");
32599            for (i, expr) in e.expressions.iter().enumerate() {
32600                if i > 0 {
32601                    self.write(", ");
32602                }
32603                self.generate_expression(expr)?;
32604            }
32605            self.write(")");
32606        }
32607        for opt in &e.options {
32608            self.write_space();
32609            self.generate_expression(opt)?;
32610        }
32611        Ok(())
32612    }
32613
32614    fn generate_refresh(&mut self, e: &Refresh) -> Result<()> {
32615        // REFRESH [kind] this
32616        self.write_keyword("REFRESH");
32617        if !e.kind.is_empty() {
32618            self.write_space();
32619            self.write_keyword(&e.kind);
32620        }
32621        self.write_space();
32622        self.generate_expression(&e.this)?;
32623        Ok(())
32624    }
32625
32626    fn generate_refresh_trigger_property(&mut self, e: &RefreshTriggerProperty) -> Result<()> {
32627        // Doris REFRESH clause: REFRESH method ON kind [EVERY n UNIT] [STARTS 'datetime']
32628        self.write_keyword("REFRESH");
32629        self.write_space();
32630        self.write_keyword(&e.method);
32631
32632        if let Some(ref kind) = e.kind {
32633            self.write_space();
32634            self.write_keyword("ON");
32635            self.write_space();
32636            self.write_keyword(kind);
32637
32638            // EVERY n UNIT
32639            if let Some(ref every) = e.every {
32640                self.write_space();
32641                self.write_keyword("EVERY");
32642                self.write_space();
32643                self.generate_expression(every)?;
32644                if let Some(ref unit) = e.unit {
32645                    self.write_space();
32646                    self.write_keyword(unit);
32647                }
32648            }
32649
32650            // STARTS 'datetime'
32651            if let Some(ref starts) = e.starts {
32652                self.write_space();
32653                self.write_keyword("STARTS");
32654                self.write_space();
32655                self.generate_expression(starts)?;
32656            }
32657        }
32658        Ok(())
32659    }
32660
32661    fn generate_regexp_count(&mut self, e: &RegexpCount) -> Result<()> {
32662        // REGEXP_COUNT(this, expression, position, parameters)
32663        self.write_keyword("REGEXP_COUNT");
32664        self.write("(");
32665        self.generate_expression(&e.this)?;
32666        self.write(", ");
32667        self.generate_expression(&e.expression)?;
32668        if let Some(position) = &e.position {
32669            self.write(", ");
32670            self.generate_expression(position)?;
32671        }
32672        if let Some(parameters) = &e.parameters {
32673            self.write(", ");
32674            self.generate_expression(parameters)?;
32675        }
32676        self.write(")");
32677        Ok(())
32678    }
32679
32680    fn generate_regexp_extract_all(&mut self, e: &RegexpExtractAll) -> Result<()> {
32681        // REGEXP_EXTRACT_ALL(this, expression, group, parameters, position, occurrence)
32682        self.write_keyword("REGEXP_EXTRACT_ALL");
32683        self.write("(");
32684        self.generate_expression(&e.this)?;
32685        self.write(", ");
32686        self.generate_expression(&e.expression)?;
32687        if let Some(group) = &e.group {
32688            self.write(", ");
32689            self.generate_expression(group)?;
32690        }
32691        self.write(")");
32692        Ok(())
32693    }
32694
32695    fn generate_regexp_full_match(&mut self, e: &RegexpFullMatch) -> Result<()> {
32696        // REGEXP_FULL_MATCH(this, expression)
32697        self.write_keyword("REGEXP_FULL_MATCH");
32698        self.write("(");
32699        self.generate_expression(&e.this)?;
32700        self.write(", ");
32701        self.generate_expression(&e.expression)?;
32702        self.write(")");
32703        Ok(())
32704    }
32705
32706    fn generate_regexp_i_like(&mut self, e: &RegexpILike) -> Result<()> {
32707        use crate::dialects::DialectType;
32708        // PostgreSQL/Redshift uses ~* operator for case-insensitive regex matching
32709        if matches!(
32710            self.config.dialect,
32711            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
32712        ) && e.flag.is_none()
32713        {
32714            self.generate_expression(&e.this)?;
32715            self.write(" ~* ");
32716            self.generate_expression(&e.expression)?;
32717        } else if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
32718            // Snowflake uses REGEXP_LIKE(x, pattern, 'i')
32719            self.write_keyword("REGEXP_LIKE");
32720            self.write("(");
32721            self.generate_expression(&e.this)?;
32722            self.write(", ");
32723            self.generate_expression(&e.expression)?;
32724            self.write(", ");
32725            if let Some(flag) = &e.flag {
32726                self.generate_expression(flag)?;
32727            } else {
32728                self.write("'i'");
32729            }
32730            self.write(")");
32731        } else {
32732            // this REGEXP_ILIKE expression or REGEXP_ILIKE(this, expression, flag)
32733            self.generate_expression(&e.this)?;
32734            self.write_space();
32735            self.write_keyword("REGEXP_ILIKE");
32736            self.write_space();
32737            self.generate_expression(&e.expression)?;
32738            if let Some(flag) = &e.flag {
32739                self.write(", ");
32740                self.generate_expression(flag)?;
32741            }
32742        }
32743        Ok(())
32744    }
32745
32746    fn generate_regexp_instr(&mut self, e: &RegexpInstr) -> Result<()> {
32747        // REGEXP_INSTR(this, expression, position, occurrence, option, parameters, group)
32748        self.write_keyword("REGEXP_INSTR");
32749        self.write("(");
32750        self.generate_expression(&e.this)?;
32751        self.write(", ");
32752        self.generate_expression(&e.expression)?;
32753        if let Some(position) = &e.position {
32754            self.write(", ");
32755            self.generate_expression(position)?;
32756        }
32757        if let Some(occurrence) = &e.occurrence {
32758            self.write(", ");
32759            self.generate_expression(occurrence)?;
32760        }
32761        if let Some(option) = &e.option {
32762            self.write(", ");
32763            self.generate_expression(option)?;
32764        }
32765        if let Some(parameters) = &e.parameters {
32766            self.write(", ");
32767            self.generate_expression(parameters)?;
32768        }
32769        if let Some(group) = &e.group {
32770            self.write(", ");
32771            self.generate_expression(group)?;
32772        }
32773        self.write(")");
32774        Ok(())
32775    }
32776
32777    fn generate_regexp_split(&mut self, e: &RegexpSplit) -> Result<()> {
32778        // REGEXP_SPLIT(this, expression, limit)
32779        self.write_keyword("REGEXP_SPLIT");
32780        self.write("(");
32781        self.generate_expression(&e.this)?;
32782        self.write(", ");
32783        self.generate_expression(&e.expression)?;
32784        if let Some(limit) = &e.limit {
32785            self.write(", ");
32786            self.generate_expression(limit)?;
32787        }
32788        self.write(")");
32789        Ok(())
32790    }
32791
32792    fn generate_regr_avgx(&mut self, e: &RegrAvgx) -> Result<()> {
32793        // REGR_AVGX(this, expression)
32794        self.write_keyword("REGR_AVGX");
32795        self.write("(");
32796        self.generate_expression(&e.this)?;
32797        self.write(", ");
32798        self.generate_expression(&e.expression)?;
32799        self.write(")");
32800        Ok(())
32801    }
32802
32803    fn generate_regr_avgy(&mut self, e: &RegrAvgy) -> Result<()> {
32804        // REGR_AVGY(this, expression)
32805        self.write_keyword("REGR_AVGY");
32806        self.write("(");
32807        self.generate_expression(&e.this)?;
32808        self.write(", ");
32809        self.generate_expression(&e.expression)?;
32810        self.write(")");
32811        Ok(())
32812    }
32813
32814    fn generate_regr_count(&mut self, e: &RegrCount) -> Result<()> {
32815        // REGR_COUNT(this, expression)
32816        self.write_keyword("REGR_COUNT");
32817        self.write("(");
32818        self.generate_expression(&e.this)?;
32819        self.write(", ");
32820        self.generate_expression(&e.expression)?;
32821        self.write(")");
32822        Ok(())
32823    }
32824
32825    fn generate_regr_intercept(&mut self, e: &RegrIntercept) -> Result<()> {
32826        // REGR_INTERCEPT(this, expression)
32827        self.write_keyword("REGR_INTERCEPT");
32828        self.write("(");
32829        self.generate_expression(&e.this)?;
32830        self.write(", ");
32831        self.generate_expression(&e.expression)?;
32832        self.write(")");
32833        Ok(())
32834    }
32835
32836    fn generate_regr_r2(&mut self, e: &RegrR2) -> Result<()> {
32837        // REGR_R2(this, expression)
32838        self.write_keyword("REGR_R2");
32839        self.write("(");
32840        self.generate_expression(&e.this)?;
32841        self.write(", ");
32842        self.generate_expression(&e.expression)?;
32843        self.write(")");
32844        Ok(())
32845    }
32846
32847    fn generate_regr_slope(&mut self, e: &RegrSlope) -> Result<()> {
32848        // REGR_SLOPE(this, expression)
32849        self.write_keyword("REGR_SLOPE");
32850        self.write("(");
32851        self.generate_expression(&e.this)?;
32852        self.write(", ");
32853        self.generate_expression(&e.expression)?;
32854        self.write(")");
32855        Ok(())
32856    }
32857
32858    fn generate_regr_sxx(&mut self, e: &RegrSxx) -> Result<()> {
32859        // REGR_SXX(this, expression)
32860        self.write_keyword("REGR_SXX");
32861        self.write("(");
32862        self.generate_expression(&e.this)?;
32863        self.write(", ");
32864        self.generate_expression(&e.expression)?;
32865        self.write(")");
32866        Ok(())
32867    }
32868
32869    fn generate_regr_sxy(&mut self, e: &RegrSxy) -> Result<()> {
32870        // REGR_SXY(this, expression)
32871        self.write_keyword("REGR_SXY");
32872        self.write("(");
32873        self.generate_expression(&e.this)?;
32874        self.write(", ");
32875        self.generate_expression(&e.expression)?;
32876        self.write(")");
32877        Ok(())
32878    }
32879
32880    fn generate_regr_syy(&mut self, e: &RegrSyy) -> Result<()> {
32881        // REGR_SYY(this, expression)
32882        self.write_keyword("REGR_SYY");
32883        self.write("(");
32884        self.generate_expression(&e.this)?;
32885        self.write(", ");
32886        self.generate_expression(&e.expression)?;
32887        self.write(")");
32888        Ok(())
32889    }
32890
32891    fn generate_regr_valx(&mut self, e: &RegrValx) -> Result<()> {
32892        // REGR_VALX(this, expression)
32893        self.write_keyword("REGR_VALX");
32894        self.write("(");
32895        self.generate_expression(&e.this)?;
32896        self.write(", ");
32897        self.generate_expression(&e.expression)?;
32898        self.write(")");
32899        Ok(())
32900    }
32901
32902    fn generate_regr_valy(&mut self, e: &RegrValy) -> Result<()> {
32903        // REGR_VALY(this, expression)
32904        self.write_keyword("REGR_VALY");
32905        self.write("(");
32906        self.generate_expression(&e.this)?;
32907        self.write(", ");
32908        self.generate_expression(&e.expression)?;
32909        self.write(")");
32910        Ok(())
32911    }
32912
32913    fn generate_remote_with_connection_model_property(
32914        &mut self,
32915        e: &RemoteWithConnectionModelProperty,
32916    ) -> Result<()> {
32917        // REMOTE WITH CONNECTION this
32918        self.write_keyword("REMOTE WITH CONNECTION");
32919        self.write_space();
32920        self.generate_expression(&e.this)?;
32921        Ok(())
32922    }
32923
32924    fn generate_rename_column(&mut self, e: &RenameColumn) -> Result<()> {
32925        // RENAME COLUMN [IF EXISTS] this TO new_name
32926        self.write_keyword("RENAME COLUMN");
32927        if e.exists {
32928            self.write_space();
32929            self.write_keyword("IF EXISTS");
32930        }
32931        self.write_space();
32932        self.generate_expression(&e.this)?;
32933        if let Some(to) = &e.to {
32934            self.write_space();
32935            self.write_keyword("TO");
32936            self.write_space();
32937            self.generate_expression(to)?;
32938        }
32939        Ok(())
32940    }
32941
32942    fn generate_replace_partition(&mut self, e: &ReplacePartition) -> Result<()> {
32943        // REPLACE PARTITION expression [FROM source]
32944        self.write_keyword("REPLACE PARTITION");
32945        self.write_space();
32946        self.generate_expression(&e.expression)?;
32947        if let Some(source) = &e.source {
32948            self.write_space();
32949            self.write_keyword("FROM");
32950            self.write_space();
32951            self.generate_expression(source)?;
32952        }
32953        Ok(())
32954    }
32955
32956    fn generate_returning(&mut self, e: &Returning) -> Result<()> {
32957        // RETURNING expressions [INTO into]
32958        // TSQL and Fabric use OUTPUT instead of RETURNING
32959        let keyword = match self.config.dialect {
32960            Some(DialectType::TSQL) | Some(DialectType::Fabric) => "OUTPUT",
32961            _ => "RETURNING",
32962        };
32963        self.write_keyword(keyword);
32964        self.write_space();
32965        for (i, expr) in e.expressions.iter().enumerate() {
32966            if i > 0 {
32967                self.write(", ");
32968            }
32969            self.generate_expression(expr)?;
32970        }
32971        if let Some(into) = &e.into {
32972            self.write_space();
32973            self.write_keyword("INTO");
32974            self.write_space();
32975            self.generate_expression(into)?;
32976        }
32977        Ok(())
32978    }
32979
32980    fn generate_output_clause(&mut self, output: &OutputClause) -> Result<()> {
32981        // OUTPUT expressions [INTO into_table]
32982        self.write_space();
32983        self.write_keyword("OUTPUT");
32984        self.write_space();
32985        for (i, expr) in output.columns.iter().enumerate() {
32986            if i > 0 {
32987                self.write(", ");
32988            }
32989            self.generate_expression(expr)?;
32990        }
32991        if let Some(into_table) = &output.into_table {
32992            self.write_space();
32993            self.write_keyword("INTO");
32994            self.write_space();
32995            self.generate_expression(into_table)?;
32996        }
32997        Ok(())
32998    }
32999
33000    fn generate_returns_property(&mut self, e: &ReturnsProperty) -> Result<()> {
33001        // RETURNS [TABLE] this [NULL ON NULL INPUT | CALLED ON NULL INPUT]
33002        self.write_keyword("RETURNS");
33003        if e.is_table.is_some() {
33004            self.write_space();
33005            self.write_keyword("TABLE");
33006        }
33007        if let Some(table) = &e.table {
33008            self.write_space();
33009            self.generate_expression(table)?;
33010        } else if let Some(this) = &e.this {
33011            self.write_space();
33012            self.generate_expression(this)?;
33013        }
33014        if e.null.is_some() {
33015            self.write_space();
33016            self.write_keyword("NULL ON NULL INPUT");
33017        }
33018        Ok(())
33019    }
33020
33021    fn generate_rollback(&mut self, e: &Rollback) -> Result<()> {
33022        // ROLLBACK [TRANSACTION [transaction_name]] [TO savepoint]
33023        self.write_keyword("ROLLBACK");
33024
33025        // TSQL always uses ROLLBACK TRANSACTION
33026        if e.this.is_none()
33027            && matches!(
33028                self.config.dialect,
33029                Some(DialectType::TSQL) | Some(DialectType::Fabric)
33030            )
33031        {
33032            self.write_space();
33033            self.write_keyword("TRANSACTION");
33034        }
33035
33036        // Check if this has TRANSACTION keyword or transaction name
33037        if let Some(this) = &e.this {
33038            // Check if it's just the "TRANSACTION" marker or an actual transaction name
33039            let is_transaction_marker = matches!(
33040                this.as_ref(),
33041                Expression::Identifier(id) if id.name == "TRANSACTION"
33042            );
33043
33044            self.write_space();
33045            self.write_keyword("TRANSACTION");
33046
33047            // If it's a real transaction name, output it
33048            if !is_transaction_marker {
33049                self.write_space();
33050                self.generate_expression(this)?;
33051            }
33052        }
33053
33054        // Output TO savepoint
33055        if let Some(savepoint) = &e.savepoint {
33056            self.write_space();
33057            self.write_keyword("TO");
33058            self.write_space();
33059            self.generate_expression(savepoint)?;
33060        }
33061        Ok(())
33062    }
33063
33064    fn generate_rollup(&mut self, e: &Rollup) -> Result<()> {
33065        // Python: return f"ROLLUP {self.wrap(expressions)}" if expressions else "WITH ROLLUP"
33066        if e.expressions.is_empty() {
33067            self.write_keyword("WITH ROLLUP");
33068        } else {
33069            self.write_keyword("ROLLUP");
33070            self.write("(");
33071            for (i, expr) in e.expressions.iter().enumerate() {
33072                if i > 0 {
33073                    self.write(", ");
33074                }
33075                self.generate_expression(expr)?;
33076            }
33077            self.write(")");
33078        }
33079        Ok(())
33080    }
33081
33082    fn generate_row_format_delimited_property(
33083        &mut self,
33084        e: &RowFormatDelimitedProperty,
33085    ) -> Result<()> {
33086        // ROW FORMAT DELIMITED [FIELDS TERMINATED BY ...] [ESCAPED BY ...] [COLLECTION ITEMS TERMINATED BY ...] [MAP KEYS TERMINATED BY ...] [LINES TERMINATED BY ...] [NULL DEFINED AS ...]
33087        self.write_keyword("ROW FORMAT DELIMITED");
33088        if let Some(fields) = &e.fields {
33089            self.write_space();
33090            self.write_keyword("FIELDS TERMINATED BY");
33091            self.write_space();
33092            self.generate_expression(fields)?;
33093        }
33094        if let Some(escaped) = &e.escaped {
33095            self.write_space();
33096            self.write_keyword("ESCAPED BY");
33097            self.write_space();
33098            self.generate_expression(escaped)?;
33099        }
33100        if let Some(items) = &e.collection_items {
33101            self.write_space();
33102            self.write_keyword("COLLECTION ITEMS TERMINATED BY");
33103            self.write_space();
33104            self.generate_expression(items)?;
33105        }
33106        if let Some(keys) = &e.map_keys {
33107            self.write_space();
33108            self.write_keyword("MAP KEYS TERMINATED BY");
33109            self.write_space();
33110            self.generate_expression(keys)?;
33111        }
33112        if let Some(lines) = &e.lines {
33113            self.write_space();
33114            self.write_keyword("LINES TERMINATED BY");
33115            self.write_space();
33116            self.generate_expression(lines)?;
33117        }
33118        if let Some(null) = &e.null {
33119            self.write_space();
33120            self.write_keyword("NULL DEFINED AS");
33121            self.write_space();
33122            self.generate_expression(null)?;
33123        }
33124        if let Some(serde) = &e.serde {
33125            self.write_space();
33126            self.generate_expression(serde)?;
33127        }
33128        Ok(())
33129    }
33130
33131    fn generate_row_format_property(&mut self, e: &RowFormatProperty) -> Result<()> {
33132        // ROW FORMAT this
33133        self.write_keyword("ROW FORMAT");
33134        self.write_space();
33135        self.generate_expression(&e.this)?;
33136        Ok(())
33137    }
33138
33139    fn generate_row_format_serde_property(&mut self, e: &RowFormatSerdeProperty) -> Result<()> {
33140        // ROW FORMAT SERDE this [WITH SERDEPROPERTIES (...)]
33141        self.write_keyword("ROW FORMAT SERDE");
33142        self.write_space();
33143        self.generate_expression(&e.this)?;
33144        if let Some(props) = &e.serde_properties {
33145            self.write_space();
33146            // SerdeProperties generates its own "[WITH] SERDEPROPERTIES (...)"
33147            self.generate_expression(props)?;
33148        }
33149        Ok(())
33150    }
33151
33152    fn generate_sha2(&mut self, e: &SHA2) -> Result<()> {
33153        // SHA2(this, length)
33154        self.write_keyword("SHA2");
33155        self.write("(");
33156        self.generate_expression(&e.this)?;
33157        if let Some(length) = e.length {
33158            self.write(", ");
33159            self.write(&length.to_string());
33160        }
33161        self.write(")");
33162        Ok(())
33163    }
33164
33165    fn generate_sha2_digest(&mut self, e: &SHA2Digest) -> Result<()> {
33166        // SHA2_DIGEST(this, length)
33167        self.write_keyword("SHA2_DIGEST");
33168        self.write("(");
33169        self.generate_expression(&e.this)?;
33170        if let Some(length) = e.length {
33171            self.write(", ");
33172            self.write(&length.to_string());
33173        }
33174        self.write(")");
33175        Ok(())
33176    }
33177
33178    fn generate_safe_add(&mut self, e: &SafeAdd) -> Result<()> {
33179        let name = if matches!(
33180            self.config.dialect,
33181            Some(crate::dialects::DialectType::Spark)
33182                | Some(crate::dialects::DialectType::Databricks)
33183        ) {
33184            "TRY_ADD"
33185        } else {
33186            "SAFE_ADD"
33187        };
33188        self.write_keyword(name);
33189        self.write("(");
33190        self.generate_expression(&e.this)?;
33191        self.write(", ");
33192        self.generate_expression(&e.expression)?;
33193        self.write(")");
33194        Ok(())
33195    }
33196
33197    fn generate_safe_divide(&mut self, e: &SafeDivide) -> Result<()> {
33198        // SAFE_DIVIDE(this, expression)
33199        self.write_keyword("SAFE_DIVIDE");
33200        self.write("(");
33201        self.generate_expression(&e.this)?;
33202        self.write(", ");
33203        self.generate_expression(&e.expression)?;
33204        self.write(")");
33205        Ok(())
33206    }
33207
33208    fn generate_safe_multiply(&mut self, e: &SafeMultiply) -> Result<()> {
33209        let name = if matches!(
33210            self.config.dialect,
33211            Some(crate::dialects::DialectType::Spark)
33212                | Some(crate::dialects::DialectType::Databricks)
33213        ) {
33214            "TRY_MULTIPLY"
33215        } else {
33216            "SAFE_MULTIPLY"
33217        };
33218        self.write_keyword(name);
33219        self.write("(");
33220        self.generate_expression(&e.this)?;
33221        self.write(", ");
33222        self.generate_expression(&e.expression)?;
33223        self.write(")");
33224        Ok(())
33225    }
33226
33227    fn generate_safe_subtract(&mut self, e: &SafeSubtract) -> Result<()> {
33228        let name = if matches!(
33229            self.config.dialect,
33230            Some(crate::dialects::DialectType::Spark)
33231                | Some(crate::dialects::DialectType::Databricks)
33232        ) {
33233            "TRY_SUBTRACT"
33234        } else {
33235            "SAFE_SUBTRACT"
33236        };
33237        self.write_keyword(name);
33238        self.write("(");
33239        self.generate_expression(&e.this)?;
33240        self.write(", ");
33241        self.generate_expression(&e.expression)?;
33242        self.write(")");
33243        Ok(())
33244    }
33245
33246    /// Generate the body of a USING SAMPLE or TABLESAMPLE clause:
33247    /// METHOD (size UNIT) [REPEATABLE (seed)]
33248    fn generate_sample_body(&mut self, sample: &Sample) -> Result<()> {
33249        // Handle BUCKET sampling: TABLESAMPLE (BUCKET n OUT OF m [ON col])
33250        if matches!(sample.method, SampleMethod::Bucket) {
33251            self.write(" (");
33252            self.write_keyword("BUCKET");
33253            self.write_space();
33254            if let Some(ref num) = sample.bucket_numerator {
33255                self.generate_expression(num)?;
33256            }
33257            self.write_space();
33258            self.write_keyword("OUT OF");
33259            self.write_space();
33260            if let Some(ref denom) = sample.bucket_denominator {
33261                self.generate_expression(denom)?;
33262            }
33263            if let Some(ref field) = sample.bucket_field {
33264                self.write_space();
33265                self.write_keyword("ON");
33266                self.write_space();
33267                self.generate_expression(field)?;
33268            }
33269            self.write(")");
33270            return Ok(());
33271        }
33272
33273        // Output method name if explicitly specified, or for dialects that always require it
33274        let is_snowflake = matches!(
33275            self.config.dialect,
33276            Some(crate::dialects::DialectType::Snowflake)
33277        );
33278        let is_postgres = matches!(
33279            self.config.dialect,
33280            Some(crate::dialects::DialectType::PostgreSQL)
33281                | Some(crate::dialects::DialectType::Redshift)
33282        );
33283        // Databricks and Spark don't output method names
33284        let is_databricks = matches!(
33285            self.config.dialect,
33286            Some(crate::dialects::DialectType::Databricks)
33287        );
33288        let is_spark = matches!(
33289            self.config.dialect,
33290            Some(crate::dialects::DialectType::Spark)
33291        );
33292        let suppress_method = is_databricks || is_spark || sample.suppress_method_output;
33293        // PostgreSQL always outputs BERNOULLI for BERNOULLI samples
33294        let force_method = is_postgres && matches!(sample.method, SampleMethod::Bernoulli);
33295        if !suppress_method && (sample.explicit_method || is_snowflake || force_method) {
33296            self.write_space();
33297            if !sample.explicit_method && (is_snowflake || force_method) {
33298                // Snowflake/PostgreSQL defaults to BERNOULLI when no method is specified
33299                self.write_keyword("BERNOULLI");
33300            } else {
33301                match sample.method {
33302                    SampleMethod::Bernoulli => self.write_keyword("BERNOULLI"),
33303                    SampleMethod::System => self.write_keyword("SYSTEM"),
33304                    SampleMethod::Block => self.write_keyword("BLOCK"),
33305                    SampleMethod::Row => self.write_keyword("ROW"),
33306                    SampleMethod::Reservoir => self.write_keyword("RESERVOIR"),
33307                    SampleMethod::Percent => self.write_keyword("SYSTEM"),
33308                    SampleMethod::Bucket => {} // handled above
33309                }
33310            }
33311        }
33312
33313        // Output size, with or without parentheses depending on dialect
33314        let emit_size_no_parens = !self.config.tablesample_requires_parens;
33315        if emit_size_no_parens {
33316            self.write_space();
33317            match &sample.size {
33318                Expression::Tuple(tuple) => {
33319                    for (i, expr) in tuple.expressions.iter().enumerate() {
33320                        if i > 0 {
33321                            self.write(", ");
33322                        }
33323                        self.generate_expression(expr)?;
33324                    }
33325                }
33326                expr => self.generate_expression(expr)?,
33327            }
33328        } else {
33329            self.write(" (");
33330            self.generate_expression(&sample.size)?;
33331        }
33332
33333        // Determine unit
33334        let is_rows_method = matches!(
33335            sample.method,
33336            SampleMethod::Reservoir | SampleMethod::Row | SampleMethod::Bucket
33337        );
33338        let is_percent = matches!(
33339            sample.method,
33340            SampleMethod::Percent
33341                | SampleMethod::System
33342                | SampleMethod::Bernoulli
33343                | SampleMethod::Block
33344        );
33345
33346        // For Snowflake, PostgreSQL, and Presto/Trino, only output ROWS/PERCENT when the user explicitly wrote it (unit_after_size).
33347        // These dialects use bare numbers for percentage by default in TABLESAMPLE METHOD(size) syntax.
33348        // For Databricks and Spark, always output PERCENT for percentage samples.
33349        let is_presto = matches!(
33350            self.config.dialect,
33351            Some(crate::dialects::DialectType::Presto)
33352                | Some(crate::dialects::DialectType::Trino)
33353                | Some(crate::dialects::DialectType::Athena)
33354        );
33355        let should_output_unit = if is_databricks || is_spark {
33356            // Always output PERCENT for percentage-based methods, or ROWS for row-based methods
33357            is_percent || is_rows_method || sample.unit_after_size
33358        } else if is_snowflake || is_postgres || is_presto {
33359            sample.unit_after_size
33360        } else {
33361            sample.unit_after_size || (sample.explicit_method && (is_rows_method || is_percent))
33362        };
33363
33364        if should_output_unit {
33365            self.write_space();
33366            if sample.is_percent {
33367                self.write_keyword("PERCENT");
33368            } else if is_rows_method && !sample.unit_after_size {
33369                self.write_keyword("ROWS");
33370            } else if sample.unit_after_size {
33371                match sample.method {
33372                    SampleMethod::Percent
33373                    | SampleMethod::System
33374                    | SampleMethod::Bernoulli
33375                    | SampleMethod::Block => {
33376                        self.write_keyword("PERCENT");
33377                    }
33378                    SampleMethod::Row | SampleMethod::Reservoir => {
33379                        self.write_keyword("ROWS");
33380                    }
33381                    _ => self.write_keyword("ROWS"),
33382                }
33383            } else {
33384                self.write_keyword("PERCENT");
33385            }
33386        }
33387
33388        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
33389            if let Some(ref offset) = sample.offset {
33390                self.write_space();
33391                self.write_keyword("OFFSET");
33392                self.write_space();
33393                self.generate_expression(offset)?;
33394            }
33395        }
33396        if !emit_size_no_parens {
33397            self.write(")");
33398        }
33399
33400        Ok(())
33401    }
33402
33403    fn generate_sample_property(&mut self, e: &SampleProperty) -> Result<()> {
33404        // SAMPLE this (ClickHouse uses SAMPLE BY)
33405        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
33406            self.write_keyword("SAMPLE BY");
33407        } else {
33408            self.write_keyword("SAMPLE");
33409        }
33410        self.write_space();
33411        self.generate_expression(&e.this)?;
33412        Ok(())
33413    }
33414
33415    fn generate_schema(&mut self, e: &Schema) -> Result<()> {
33416        // this (expressions...)
33417        if let Some(this) = &e.this {
33418            self.generate_expression(this)?;
33419        }
33420        if !e.expressions.is_empty() {
33421            // Add space before column list if there's a preceding expression
33422            if e.this.is_some() {
33423                self.write_space();
33424            }
33425            self.write("(");
33426            for (i, expr) in e.expressions.iter().enumerate() {
33427                if i > 0 {
33428                    self.write(", ");
33429                }
33430                self.generate_expression(expr)?;
33431            }
33432            self.write(")");
33433        }
33434        Ok(())
33435    }
33436
33437    fn generate_schema_comment_property(&mut self, e: &SchemaCommentProperty) -> Result<()> {
33438        // COMMENT this
33439        self.write_keyword("COMMENT");
33440        self.write_space();
33441        self.generate_expression(&e.this)?;
33442        Ok(())
33443    }
33444
33445    fn generate_scope_resolution(&mut self, e: &ScopeResolution) -> Result<()> {
33446        // [this::]expression
33447        if let Some(this) = &e.this {
33448            self.generate_expression(this)?;
33449            self.write("::");
33450        }
33451        self.generate_expression(&e.expression)?;
33452        Ok(())
33453    }
33454
33455    fn generate_search(&mut self, e: &Search) -> Result<()> {
33456        // SEARCH(this, expression, [json_scope], [analyzer], [analyzer_options], [search_mode])
33457        self.write_keyword("SEARCH");
33458        self.write("(");
33459        self.generate_expression(&e.this)?;
33460        self.write(", ");
33461        self.generate_expression(&e.expression)?;
33462        if let Some(json_scope) = &e.json_scope {
33463            self.write(", ");
33464            self.generate_expression(json_scope)?;
33465        }
33466        if let Some(analyzer) = &e.analyzer {
33467            self.write(", ");
33468            self.generate_expression(analyzer)?;
33469        }
33470        if let Some(analyzer_options) = &e.analyzer_options {
33471            self.write(", ");
33472            self.generate_expression(analyzer_options)?;
33473        }
33474        if let Some(search_mode) = &e.search_mode {
33475            self.write(", ");
33476            self.generate_expression(search_mode)?;
33477        }
33478        self.write(")");
33479        Ok(())
33480    }
33481
33482    fn generate_search_ip(&mut self, e: &SearchIp) -> Result<()> {
33483        // SEARCH_IP(this, expression)
33484        self.write_keyword("SEARCH_IP");
33485        self.write("(");
33486        self.generate_expression(&e.this)?;
33487        self.write(", ");
33488        self.generate_expression(&e.expression)?;
33489        self.write(")");
33490        Ok(())
33491    }
33492
33493    fn generate_security_property(&mut self, e: &SecurityProperty) -> Result<()> {
33494        // SECURITY this
33495        self.write_keyword("SECURITY");
33496        self.write_space();
33497        self.generate_expression(&e.this)?;
33498        Ok(())
33499    }
33500
33501    fn generate_semantic_view(&mut self, e: &SemanticView) -> Result<()> {
33502        // SEMANTIC_VIEW(this [METRICS ...] [DIMENSIONS ...] [FACTS ...] [WHERE ...])
33503        self.write("SEMANTIC_VIEW(");
33504
33505        if self.config.pretty {
33506            // Pretty print: each clause on its own line
33507            self.write_newline();
33508            self.indent_level += 1;
33509            self.write_indent();
33510            self.generate_expression(&e.this)?;
33511
33512            if let Some(metrics) = &e.metrics {
33513                self.write_newline();
33514                self.write_indent();
33515                self.write_keyword("METRICS");
33516                self.write_space();
33517                self.generate_semantic_view_tuple(metrics)?;
33518            }
33519            if let Some(dimensions) = &e.dimensions {
33520                self.write_newline();
33521                self.write_indent();
33522                self.write_keyword("DIMENSIONS");
33523                self.write_space();
33524                self.generate_semantic_view_tuple(dimensions)?;
33525            }
33526            if let Some(facts) = &e.facts {
33527                self.write_newline();
33528                self.write_indent();
33529                self.write_keyword("FACTS");
33530                self.write_space();
33531                self.generate_semantic_view_tuple(facts)?;
33532            }
33533            if let Some(where_) = &e.where_ {
33534                self.write_newline();
33535                self.write_indent();
33536                self.write_keyword("WHERE");
33537                self.write_space();
33538                self.generate_expression(where_)?;
33539            }
33540            self.write_newline();
33541            self.indent_level -= 1;
33542            self.write_indent();
33543        } else {
33544            // Compact: all on one line
33545            self.generate_expression(&e.this)?;
33546            if let Some(metrics) = &e.metrics {
33547                self.write_space();
33548                self.write_keyword("METRICS");
33549                self.write_space();
33550                self.generate_semantic_view_tuple(metrics)?;
33551            }
33552            if let Some(dimensions) = &e.dimensions {
33553                self.write_space();
33554                self.write_keyword("DIMENSIONS");
33555                self.write_space();
33556                self.generate_semantic_view_tuple(dimensions)?;
33557            }
33558            if let Some(facts) = &e.facts {
33559                self.write_space();
33560                self.write_keyword("FACTS");
33561                self.write_space();
33562                self.generate_semantic_view_tuple(facts)?;
33563            }
33564            if let Some(where_) = &e.where_ {
33565                self.write_space();
33566                self.write_keyword("WHERE");
33567                self.write_space();
33568                self.generate_expression(where_)?;
33569            }
33570        }
33571        self.write(")");
33572        Ok(())
33573    }
33574
33575    /// Helper for SEMANTIC_VIEW tuple contents (without parentheses)
33576    fn generate_semantic_view_tuple(&mut self, expr: &Expression) -> Result<()> {
33577        if let Expression::Tuple(t) = expr {
33578            for (i, e) in t.expressions.iter().enumerate() {
33579                if i > 0 {
33580                    self.write(", ");
33581                }
33582                self.generate_expression(e)?;
33583            }
33584        } else {
33585            self.generate_expression(expr)?;
33586        }
33587        Ok(())
33588    }
33589
33590    fn generate_sequence_properties(&mut self, e: &SequenceProperties) -> Result<()> {
33591        // [START WITH start] [INCREMENT BY increment] [MINVALUE minvalue] [MAXVALUE maxvalue] [CACHE cache] [OWNED BY owned]
33592        if let Some(start) = &e.start {
33593            self.write_keyword("START WITH");
33594            self.write_space();
33595            self.generate_expression(start)?;
33596        }
33597        if let Some(increment) = &e.increment {
33598            self.write_space();
33599            self.write_keyword("INCREMENT BY");
33600            self.write_space();
33601            self.generate_expression(increment)?;
33602        }
33603        if let Some(minvalue) = &e.minvalue {
33604            self.write_space();
33605            self.write_keyword("MINVALUE");
33606            self.write_space();
33607            self.generate_expression(minvalue)?;
33608        }
33609        if let Some(maxvalue) = &e.maxvalue {
33610            self.write_space();
33611            self.write_keyword("MAXVALUE");
33612            self.write_space();
33613            self.generate_expression(maxvalue)?;
33614        }
33615        if let Some(cache) = &e.cache {
33616            self.write_space();
33617            self.write_keyword("CACHE");
33618            self.write_space();
33619            self.generate_expression(cache)?;
33620        }
33621        if let Some(owned) = &e.owned {
33622            self.write_space();
33623            self.write_keyword("OWNED BY");
33624            self.write_space();
33625            self.generate_expression(owned)?;
33626        }
33627        for opt in &e.options {
33628            self.write_space();
33629            self.generate_expression(opt)?;
33630        }
33631        Ok(())
33632    }
33633
33634    fn generate_serde_properties(&mut self, e: &SerdeProperties) -> Result<()> {
33635        // [WITH] SERDEPROPERTIES (expressions)
33636        if e.with_.is_some() {
33637            self.write_keyword("WITH");
33638            self.write_space();
33639        }
33640        self.write_keyword("SERDEPROPERTIES");
33641        self.write(" (");
33642        for (i, expr) in e.expressions.iter().enumerate() {
33643            if i > 0 {
33644                self.write(", ");
33645            }
33646            // Generate key=value without spaces around =
33647            match expr {
33648                Expression::Eq(eq) => {
33649                    self.generate_expression(&eq.left)?;
33650                    self.write("=");
33651                    self.generate_expression(&eq.right)?;
33652                }
33653                _ => self.generate_expression(expr)?,
33654            }
33655        }
33656        self.write(")");
33657        Ok(())
33658    }
33659
33660    fn generate_session_parameter(&mut self, e: &SessionParameter) -> Result<()> {
33661        // @@[kind.]this
33662        self.write("@@");
33663        if let Some(kind) = &e.kind {
33664            self.write(kind);
33665            self.write(".");
33666        }
33667        self.generate_expression(&e.this)?;
33668        Ok(())
33669    }
33670
33671    fn generate_set(&mut self, e: &Set) -> Result<()> {
33672        // SET/UNSET [TAG] expressions
33673        if e.unset.is_some() {
33674            self.write_keyword("UNSET");
33675        } else {
33676            self.write_keyword("SET");
33677        }
33678        if e.tag.is_some() {
33679            self.write_space();
33680            self.write_keyword("TAG");
33681        }
33682        if !e.expressions.is_empty() {
33683            self.write_space();
33684            for (i, expr) in e.expressions.iter().enumerate() {
33685                if i > 0 {
33686                    self.write(", ");
33687                }
33688                self.generate_expression(expr)?;
33689            }
33690        }
33691        Ok(())
33692    }
33693
33694    fn generate_set_config_property(&mut self, e: &SetConfigProperty) -> Result<()> {
33695        // SET this or SETCONFIG this
33696        self.write_keyword("SET");
33697        self.write_space();
33698        self.generate_expression(&e.this)?;
33699        Ok(())
33700    }
33701
33702    fn generate_set_item(&mut self, e: &SetItem) -> Result<()> {
33703        // [kind] name = value
33704        if let Some(kind) = &e.kind {
33705            self.write_keyword(kind);
33706            self.write_space();
33707        }
33708        self.generate_expression(&e.name)?;
33709        self.write(" = ");
33710        self.generate_expression(&e.value)?;
33711        Ok(())
33712    }
33713
33714    fn generate_set_operation(&mut self, e: &SetOperation) -> Result<()> {
33715        // [WITH ...] this UNION|INTERSECT|EXCEPT [ALL|DISTINCT] [BY NAME] expression
33716        if let Some(with_) = &e.with_ {
33717            self.generate_expression(with_)?;
33718            self.write_space();
33719        }
33720        self.generate_expression(&e.this)?;
33721        self.write_space();
33722        // kind should be UNION, INTERSECT, EXCEPT, etc.
33723        if let Some(kind) = &e.kind {
33724            self.write_keyword(kind);
33725        }
33726        if e.distinct {
33727            self.write_space();
33728            self.write_keyword("DISTINCT");
33729        } else {
33730            self.write_space();
33731            self.write_keyword("ALL");
33732        }
33733        if e.by_name.is_some() {
33734            self.write_space();
33735            self.write_keyword("BY NAME");
33736        }
33737        self.write_space();
33738        self.generate_expression(&e.expression)?;
33739        Ok(())
33740    }
33741
33742    fn generate_set_property(&mut self, e: &SetProperty) -> Result<()> {
33743        // SET or MULTISET
33744        if e.multi.is_some() {
33745            self.write_keyword("MULTISET");
33746        } else {
33747            self.write_keyword("SET");
33748        }
33749        Ok(())
33750    }
33751
33752    fn generate_settings_property(&mut self, e: &SettingsProperty) -> Result<()> {
33753        // SETTINGS expressions
33754        self.write_keyword("SETTINGS");
33755        if self.config.pretty && e.expressions.len() > 1 {
33756            // Pretty print: each setting on its own line, indented
33757            self.indent_level += 1;
33758            for (i, expr) in e.expressions.iter().enumerate() {
33759                if i > 0 {
33760                    self.write(",");
33761                }
33762                self.write_newline();
33763                self.write_indent();
33764                self.generate_expression(expr)?;
33765            }
33766            self.indent_level -= 1;
33767        } else {
33768            self.write_space();
33769            for (i, expr) in e.expressions.iter().enumerate() {
33770                if i > 0 {
33771                    self.write(", ");
33772                }
33773                self.generate_expression(expr)?;
33774            }
33775        }
33776        Ok(())
33777    }
33778
33779    fn generate_sharing_property(&mut self, e: &SharingProperty) -> Result<()> {
33780        // SHARING = this
33781        self.write_keyword("SHARING");
33782        if let Some(this) = &e.this {
33783            self.write(" = ");
33784            self.generate_expression(this)?;
33785        }
33786        Ok(())
33787    }
33788
33789    fn generate_slice(&mut self, e: &Slice) -> Result<()> {
33790        // Python array slicing: begin:end:step
33791        if let Some(begin) = &e.this {
33792            self.generate_expression(begin)?;
33793        }
33794        self.write(":");
33795        if let Some(end) = &e.expression {
33796            self.generate_expression(end)?;
33797        }
33798        if let Some(step) = &e.step {
33799            self.write(":");
33800            self.generate_expression(step)?;
33801        }
33802        Ok(())
33803    }
33804
33805    fn generate_sort_array(&mut self, e: &SortArray) -> Result<()> {
33806        // SORT_ARRAY(this, asc)
33807        self.write_keyword("SORT_ARRAY");
33808        self.write("(");
33809        self.generate_expression(&e.this)?;
33810        if let Some(asc) = &e.asc {
33811            self.write(", ");
33812            self.generate_expression(asc)?;
33813        }
33814        self.write(")");
33815        Ok(())
33816    }
33817
33818    fn generate_sort_by(&mut self, e: &SortBy) -> Result<()> {
33819        // SORT BY expressions
33820        self.write_keyword("SORT BY");
33821        self.write_space();
33822        for (i, expr) in e.expressions.iter().enumerate() {
33823            if i > 0 {
33824                self.write(", ");
33825            }
33826            self.generate_ordered(expr)?;
33827        }
33828        Ok(())
33829    }
33830
33831    fn generate_sort_key_property(&mut self, e: &SortKeyProperty) -> Result<()> {
33832        // [COMPOUND] SORTKEY(col1, col2, ...) - no space before paren
33833        if e.compound.is_some() {
33834            self.write_keyword("COMPOUND");
33835            self.write_space();
33836        }
33837        self.write_keyword("SORTKEY");
33838        self.write("(");
33839        // If this is a Tuple, unwrap its contents to avoid double parentheses
33840        if let Expression::Tuple(t) = e.this.as_ref() {
33841            for (i, expr) in t.expressions.iter().enumerate() {
33842                if i > 0 {
33843                    self.write(", ");
33844                }
33845                self.generate_expression(expr)?;
33846            }
33847        } else {
33848            self.generate_expression(&e.this)?;
33849        }
33850        self.write(")");
33851        Ok(())
33852    }
33853
33854    fn generate_split_part(&mut self, e: &SplitPart) -> Result<()> {
33855        // SPLIT_PART(this, delimiter, part_index)
33856        self.write_keyword("SPLIT_PART");
33857        self.write("(");
33858        self.generate_expression(&e.this)?;
33859        if let Some(delimiter) = &e.delimiter {
33860            self.write(", ");
33861            self.generate_expression(delimiter)?;
33862        }
33863        if let Some(part_index) = &e.part_index {
33864            self.write(", ");
33865            self.generate_expression(part_index)?;
33866        }
33867        self.write(")");
33868        Ok(())
33869    }
33870
33871    fn generate_sql_read_write_property(&mut self, e: &SqlReadWriteProperty) -> Result<()> {
33872        // READS SQL DATA or MODIFIES SQL DATA, etc.
33873        self.generate_expression(&e.this)?;
33874        Ok(())
33875    }
33876
33877    fn generate_sql_security_property(&mut self, e: &SqlSecurityProperty) -> Result<()> {
33878        // SQL SECURITY DEFINER or SQL SECURITY INVOKER
33879        self.write_keyword("SQL SECURITY");
33880        self.write_space();
33881        self.generate_expression(&e.this)?;
33882        Ok(())
33883    }
33884
33885    fn generate_st_distance(&mut self, e: &StDistance) -> Result<()> {
33886        // ST_DISTANCE(this, expression, [use_spheroid])
33887        self.write_keyword("ST_DISTANCE");
33888        self.write("(");
33889        self.generate_expression(&e.this)?;
33890        self.write(", ");
33891        self.generate_expression(&e.expression)?;
33892        if let Some(use_spheroid) = &e.use_spheroid {
33893            self.write(", ");
33894            self.generate_expression(use_spheroid)?;
33895        }
33896        self.write(")");
33897        Ok(())
33898    }
33899
33900    fn generate_st_point(&mut self, e: &StPoint) -> Result<()> {
33901        // ST_POINT(this, expression)
33902        self.write_keyword("ST_POINT");
33903        self.write("(");
33904        self.generate_expression(&e.this)?;
33905        self.write(", ");
33906        self.generate_expression(&e.expression)?;
33907        self.write(")");
33908        Ok(())
33909    }
33910
33911    fn generate_stability_property(&mut self, e: &StabilityProperty) -> Result<()> {
33912        // IMMUTABLE, STABLE, VOLATILE
33913        self.generate_expression(&e.this)?;
33914        Ok(())
33915    }
33916
33917    fn generate_standard_hash(&mut self, e: &StandardHash) -> Result<()> {
33918        // STANDARD_HASH(this, [expression])
33919        self.write_keyword("STANDARD_HASH");
33920        self.write("(");
33921        self.generate_expression(&e.this)?;
33922        if let Some(expression) = &e.expression {
33923            self.write(", ");
33924            self.generate_expression(expression)?;
33925        }
33926        self.write(")");
33927        Ok(())
33928    }
33929
33930    fn generate_storage_handler_property(&mut self, e: &StorageHandlerProperty) -> Result<()> {
33931        // STORED BY this
33932        self.write_keyword("STORED BY");
33933        self.write_space();
33934        self.generate_expression(&e.this)?;
33935        Ok(())
33936    }
33937
33938    fn generate_str_position(&mut self, e: &StrPosition) -> Result<()> {
33939        // STRPOS(this, substr) or STRPOS(this, substr, position)
33940        // Different dialects have different function names
33941        use crate::dialects::DialectType;
33942        if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
33943            // Snowflake: CHARINDEX(substr, str[, position])
33944            self.write_keyword("CHARINDEX");
33945            self.write("(");
33946            if let Some(substr) = &e.substr {
33947                self.generate_expression(substr)?;
33948                self.write(", ");
33949            }
33950            self.generate_expression(&e.this)?;
33951            if let Some(position) = &e.position {
33952                self.write(", ");
33953                self.generate_expression(position)?;
33954            }
33955            self.write(")");
33956        } else if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
33957            self.write_keyword("POSITION");
33958            self.write("(");
33959            self.generate_expression(&e.this)?;
33960            if let Some(substr) = &e.substr {
33961                self.write(", ");
33962                self.generate_expression(substr)?;
33963            }
33964            if let Some(position) = &e.position {
33965                self.write(", ");
33966                self.generate_expression(position)?;
33967            }
33968            if let Some(occurrence) = &e.occurrence {
33969                self.write(", ");
33970                self.generate_expression(occurrence)?;
33971            }
33972            self.write(")");
33973        } else if matches!(
33974            self.config.dialect,
33975            Some(DialectType::SQLite)
33976                | Some(DialectType::Oracle)
33977                | Some(DialectType::BigQuery)
33978                | Some(DialectType::Teradata)
33979        ) {
33980            self.write_keyword("INSTR");
33981            self.write("(");
33982            self.generate_expression(&e.this)?;
33983            if let Some(substr) = &e.substr {
33984                self.write(", ");
33985                self.generate_expression(substr)?;
33986            }
33987            if let Some(position) = &e.position {
33988                self.write(", ");
33989                self.generate_expression(position)?;
33990            } else if e.occurrence.is_some() {
33991                // INSTR requires a position arg before occurrence: INSTR(str, substr, start, nth)
33992                // Default start position is 1
33993                self.write(", 1");
33994            }
33995            if let Some(occurrence) = &e.occurrence {
33996                self.write(", ");
33997                self.generate_expression(occurrence)?;
33998            }
33999            self.write(")");
34000        } else if matches!(
34001            self.config.dialect,
34002            Some(DialectType::MySQL)
34003                | Some(DialectType::SingleStore)
34004                | Some(DialectType::Doris)
34005                | Some(DialectType::StarRocks)
34006                | Some(DialectType::Hive)
34007                | Some(DialectType::Spark)
34008                | Some(DialectType::Databricks)
34009        ) {
34010            // LOCATE(substr, str[, position]) - substr first
34011            self.write_keyword("LOCATE");
34012            self.write("(");
34013            if let Some(substr) = &e.substr {
34014                self.generate_expression(substr)?;
34015                self.write(", ");
34016            }
34017            self.generate_expression(&e.this)?;
34018            if let Some(position) = &e.position {
34019                self.write(", ");
34020                self.generate_expression(position)?;
34021            }
34022            self.write(")");
34023        } else if matches!(self.config.dialect, Some(DialectType::TSQL)) {
34024            // CHARINDEX(substr, str[, position])
34025            self.write_keyword("CHARINDEX");
34026            self.write("(");
34027            if let Some(substr) = &e.substr {
34028                self.generate_expression(substr)?;
34029                self.write(", ");
34030            }
34031            self.generate_expression(&e.this)?;
34032            if let Some(position) = &e.position {
34033                self.write(", ");
34034                self.generate_expression(position)?;
34035            }
34036            self.write(")");
34037        } else if matches!(
34038            self.config.dialect,
34039            Some(DialectType::PostgreSQL)
34040                | Some(DialectType::Materialize)
34041                | Some(DialectType::RisingWave)
34042                | Some(DialectType::Redshift)
34043        ) {
34044            // POSITION(substr IN str) syntax
34045            self.write_keyword("POSITION");
34046            self.write("(");
34047            if let Some(substr) = &e.substr {
34048                self.generate_expression(substr)?;
34049                self.write(" IN ");
34050            }
34051            self.generate_expression(&e.this)?;
34052            self.write(")");
34053        } else {
34054            self.write_keyword("STRPOS");
34055            self.write("(");
34056            self.generate_expression(&e.this)?;
34057            if let Some(substr) = &e.substr {
34058                self.write(", ");
34059                self.generate_expression(substr)?;
34060            }
34061            if let Some(position) = &e.position {
34062                self.write(", ");
34063                self.generate_expression(position)?;
34064            }
34065            if let Some(occurrence) = &e.occurrence {
34066                self.write(", ");
34067                self.generate_expression(occurrence)?;
34068            }
34069            self.write(")");
34070        }
34071        Ok(())
34072    }
34073
34074    fn generate_str_to_date(&mut self, e: &StrToDate) -> Result<()> {
34075        match self.config.dialect {
34076            Some(DialectType::Spark) | Some(DialectType::Databricks) | Some(DialectType::Hive) => {
34077                // TO_DATE(this, java_format)
34078                self.write_keyword("TO_DATE");
34079                self.write("(");
34080                self.generate_expression(&e.this)?;
34081                if let Some(format) = &e.format {
34082                    self.write(", '");
34083                    self.write(&Self::strftime_to_java_format(format));
34084                    self.write("'");
34085                }
34086                self.write(")");
34087            }
34088            Some(DialectType::DuckDB) => {
34089                // CAST(STRPTIME(this, format) AS DATE)
34090                self.write_keyword("CAST");
34091                self.write("(");
34092                self.write_keyword("STRPTIME");
34093                self.write("(");
34094                self.generate_expression(&e.this)?;
34095                if let Some(format) = &e.format {
34096                    self.write(", '");
34097                    self.write(format);
34098                    self.write("'");
34099                }
34100                self.write(")");
34101                self.write_keyword(" AS ");
34102                self.write_keyword("DATE");
34103                self.write(")");
34104            }
34105            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift) => {
34106                // TO_DATE(this, pg_format)
34107                self.write_keyword("TO_DATE");
34108                self.write("(");
34109                self.generate_expression(&e.this)?;
34110                if let Some(format) = &e.format {
34111                    self.write(", '");
34112                    self.write(&Self::strftime_to_postgres_format(format));
34113                    self.write("'");
34114                }
34115                self.write(")");
34116            }
34117            Some(DialectType::BigQuery) => {
34118                // PARSE_DATE(format, this) - note: format comes first for BigQuery
34119                self.write_keyword("PARSE_DATE");
34120                self.write("(");
34121                if let Some(format) = &e.format {
34122                    self.write("'");
34123                    self.write(format);
34124                    self.write("'");
34125                    self.write(", ");
34126                }
34127                self.generate_expression(&e.this)?;
34128                self.write(")");
34129            }
34130            Some(DialectType::Teradata) => {
34131                // CAST(this AS DATE FORMAT 'teradata_fmt')
34132                self.write_keyword("CAST");
34133                self.write("(");
34134                self.generate_expression(&e.this)?;
34135                self.write_keyword(" AS ");
34136                self.write_keyword("DATE");
34137                if let Some(format) = &e.format {
34138                    self.write_keyword(" FORMAT ");
34139                    self.write("'");
34140                    self.write(&Self::strftime_to_teradata_format(format));
34141                    self.write("'");
34142                }
34143                self.write(")");
34144            }
34145            _ => {
34146                // STR_TO_DATE(this, format) - MySQL default
34147                self.write_keyword("STR_TO_DATE");
34148                self.write("(");
34149                self.generate_expression(&e.this)?;
34150                if let Some(format) = &e.format {
34151                    self.write(", '");
34152                    self.write(format);
34153                    self.write("'");
34154                }
34155                self.write(")");
34156            }
34157        }
34158        Ok(())
34159    }
34160
34161    /// Convert strftime format to Teradata date format (YYYY, DD, MM, etc.)
34162    fn strftime_to_teradata_format(fmt: &str) -> String {
34163        let mut result = String::with_capacity(fmt.len() * 2);
34164        let bytes = fmt.as_bytes();
34165        let len = bytes.len();
34166        let mut i = 0;
34167        while i < len {
34168            if bytes[i] == b'%' && i + 1 < len {
34169                let replacement = match bytes[i + 1] {
34170                    b'Y' => "YYYY",
34171                    b'y' => "YY",
34172                    b'm' => "MM",
34173                    b'B' => "MMMM",
34174                    b'b' => "MMM",
34175                    b'd' => "DD",
34176                    b'j' => "DDD",
34177                    b'H' => "HH",
34178                    b'M' => "MI",
34179                    b'S' => "SS",
34180                    b'f' => "SSSSSS",
34181                    b'A' => "EEEE",
34182                    b'a' => "EEE",
34183                    _ => {
34184                        result.push('%');
34185                        i += 1;
34186                        continue;
34187                    }
34188                };
34189                result.push_str(replacement);
34190                i += 2;
34191            } else {
34192                result.push(bytes[i] as char);
34193                i += 1;
34194            }
34195        }
34196        result
34197    }
34198
34199    /// Convert strftime format (%Y, %m, %d, etc.) to Java date format (yyyy, MM, dd, etc.)
34200    /// Public static version for use by other modules
34201    pub fn strftime_to_java_format_static(fmt: &str) -> String {
34202        Self::strftime_to_java_format(fmt)
34203    }
34204
34205    /// Convert strftime format (%Y, %m, %d, etc.) to Java date format (yyyy, MM, dd, etc.)
34206    fn strftime_to_java_format(fmt: &str) -> String {
34207        let mut result = String::with_capacity(fmt.len() * 2);
34208        let bytes = fmt.as_bytes();
34209        let len = bytes.len();
34210        let mut i = 0;
34211        while i < len {
34212            if bytes[i] == b'%' && i + 1 < len {
34213                // Check for non-padded variants (%-X)
34214                if bytes[i + 1] == b'-' && i + 2 < len {
34215                    let replacement = match bytes[i + 2] {
34216                        b'd' => "d",
34217                        b'm' => "M",
34218                        b'H' => "H",
34219                        b'M' => "m",
34220                        b'S' => "s",
34221                        _ => {
34222                            result.push('%');
34223                            i += 1;
34224                            continue;
34225                        }
34226                    };
34227                    result.push_str(replacement);
34228                    i += 3;
34229                } else {
34230                    let replacement = match bytes[i + 1] {
34231                        b'Y' => "yyyy",
34232                        b'y' => "yy",
34233                        b'm' => "MM",
34234                        b'B' => "MMMM",
34235                        b'b' => "MMM",
34236                        b'd' => "dd",
34237                        b'j' => "DDD",
34238                        b'H' => "HH",
34239                        b'M' => "mm",
34240                        b'S' => "ss",
34241                        b'f' => "SSSSSS",
34242                        b'A' => "EEEE",
34243                        b'a' => "EEE",
34244                        _ => {
34245                            result.push('%');
34246                            i += 1;
34247                            continue;
34248                        }
34249                    };
34250                    result.push_str(replacement);
34251                    i += 2;
34252                }
34253            } else {
34254                result.push(bytes[i] as char);
34255                i += 1;
34256            }
34257        }
34258        result
34259    }
34260
34261    /// Convert strftime format (%Y, %m, %d, etc.) to .NET date format for TSQL FORMAT()
34262    /// Similar to Java but uses ffffff for microseconds instead of SSSSSS
34263    fn strftime_to_tsql_format(fmt: &str) -> String {
34264        let mut result = String::with_capacity(fmt.len() * 2);
34265        let bytes = fmt.as_bytes();
34266        let len = bytes.len();
34267        let mut i = 0;
34268        while i < len {
34269            if bytes[i] == b'%' && i + 1 < len {
34270                // Check for non-padded variants (%-X)
34271                if bytes[i + 1] == b'-' && i + 2 < len {
34272                    let replacement = match bytes[i + 2] {
34273                        b'd' => "d",
34274                        b'm' => "M",
34275                        b'H' => "H",
34276                        b'M' => "m",
34277                        b'S' => "s",
34278                        _ => {
34279                            result.push('%');
34280                            i += 1;
34281                            continue;
34282                        }
34283                    };
34284                    result.push_str(replacement);
34285                    i += 3;
34286                } else {
34287                    let replacement = match bytes[i + 1] {
34288                        b'Y' => "yyyy",
34289                        b'y' => "yy",
34290                        b'm' => "MM",
34291                        b'B' => "MMMM",
34292                        b'b' => "MMM",
34293                        b'd' => "dd",
34294                        b'j' => "DDD",
34295                        b'H' => "HH",
34296                        b'M' => "mm",
34297                        b'S' => "ss",
34298                        b'f' => "ffffff",
34299                        b'A' => "dddd",
34300                        b'a' => "ddd",
34301                        _ => {
34302                            result.push('%');
34303                            i += 1;
34304                            continue;
34305                        }
34306                    };
34307                    result.push_str(replacement);
34308                    i += 2;
34309                }
34310            } else {
34311                result.push(bytes[i] as char);
34312                i += 1;
34313            }
34314        }
34315        result
34316    }
34317
34318    /// Decompose a JSON path string like "$.y[0].z" into individual parts: ["y", "0", "z"]
34319    /// This is used for PostgreSQL/Redshift JSON_EXTRACT_PATH / JSON_EXTRACT_PATH_TEXT
34320    fn decompose_json_path(path: &str) -> Vec<String> {
34321        let mut parts = Vec::new();
34322        // Strip leading $ and optional .
34323        let path = if path.starts_with("$.") {
34324            &path[2..]
34325        } else if path.starts_with('$') {
34326            &path[1..]
34327        } else {
34328            path
34329        };
34330        if path.is_empty() {
34331            return parts;
34332        }
34333        let mut current = String::new();
34334        let chars: Vec<char> = path.chars().collect();
34335        let mut i = 0;
34336        while i < chars.len() {
34337            match chars[i] {
34338                '.' => {
34339                    if !current.is_empty() {
34340                        parts.push(current.clone());
34341                        current.clear();
34342                    }
34343                    i += 1;
34344                }
34345                '[' => {
34346                    if !current.is_empty() {
34347                        parts.push(current.clone());
34348                        current.clear();
34349                    }
34350                    i += 1;
34351                    // Read the content inside brackets
34352                    let mut bracket_content = String::new();
34353                    while i < chars.len() && chars[i] != ']' {
34354                        // Skip quotes inside brackets
34355                        if chars[i] == '"' || chars[i] == '\'' {
34356                            let quote = chars[i];
34357                            i += 1;
34358                            while i < chars.len() && chars[i] != quote {
34359                                bracket_content.push(chars[i]);
34360                                i += 1;
34361                            }
34362                            if i < chars.len() {
34363                                i += 1;
34364                            } // skip closing quote
34365                        } else {
34366                            bracket_content.push(chars[i]);
34367                            i += 1;
34368                        }
34369                    }
34370                    if i < chars.len() {
34371                        i += 1;
34372                    } // skip ]
34373                      // Skip wildcard [*] - don't add as a part
34374                    if bracket_content != "*" {
34375                        parts.push(bracket_content);
34376                    }
34377                }
34378                _ => {
34379                    current.push(chars[i]);
34380                    i += 1;
34381                }
34382            }
34383        }
34384        if !current.is_empty() {
34385            parts.push(current);
34386        }
34387        parts
34388    }
34389
34390    /// Convert strftime format to PostgreSQL date format (YYYY, MM, DD, etc.)
34391    fn strftime_to_postgres_format(fmt: &str) -> String {
34392        let mut result = String::with_capacity(fmt.len() * 2);
34393        let bytes = fmt.as_bytes();
34394        let len = bytes.len();
34395        let mut i = 0;
34396        while i < len {
34397            if bytes[i] == b'%' && i + 1 < len {
34398                // Check for non-padded variants (%-X)
34399                if bytes[i + 1] == b'-' && i + 2 < len {
34400                    let replacement = match bytes[i + 2] {
34401                        b'd' => "FMDD",
34402                        b'm' => "FMMM",
34403                        b'H' => "FMHH24",
34404                        b'M' => "FMMI",
34405                        b'S' => "FMSS",
34406                        _ => {
34407                            result.push('%');
34408                            i += 1;
34409                            continue;
34410                        }
34411                    };
34412                    result.push_str(replacement);
34413                    i += 3;
34414                } else {
34415                    let replacement = match bytes[i + 1] {
34416                        b'Y' => "YYYY",
34417                        b'y' => "YY",
34418                        b'm' => "MM",
34419                        b'B' => "Month",
34420                        b'b' => "Mon",
34421                        b'd' => "DD",
34422                        b'j' => "DDD",
34423                        b'H' => "HH24",
34424                        b'M' => "MI",
34425                        b'S' => "SS",
34426                        b'f' => "US",
34427                        b'A' => "Day",
34428                        b'a' => "Dy",
34429                        _ => {
34430                            result.push('%');
34431                            i += 1;
34432                            continue;
34433                        }
34434                    };
34435                    result.push_str(replacement);
34436                    i += 2;
34437                }
34438            } else {
34439                result.push(bytes[i] as char);
34440                i += 1;
34441            }
34442        }
34443        result
34444    }
34445
34446    /// Convert strftime format to Snowflake date format (yyyy, mm, DD, etc.)
34447    fn strftime_to_snowflake_format(fmt: &str) -> String {
34448        let mut result = String::with_capacity(fmt.len() * 2);
34449        let bytes = fmt.as_bytes();
34450        let len = bytes.len();
34451        let mut i = 0;
34452        while i < len {
34453            if bytes[i] == b'%' && i + 1 < len {
34454                // Check for non-padded variants (%-X)
34455                if bytes[i + 1] == b'-' && i + 2 < len {
34456                    let replacement = match bytes[i + 2] {
34457                        b'd' => "dd",
34458                        b'm' => "mm",
34459                        _ => {
34460                            result.push('%');
34461                            i += 1;
34462                            continue;
34463                        }
34464                    };
34465                    result.push_str(replacement);
34466                    i += 3;
34467                } else {
34468                    let replacement = match bytes[i + 1] {
34469                        b'Y' => "yyyy",
34470                        b'y' => "yy",
34471                        b'm' => "mm",
34472                        b'd' => "DD",
34473                        b'H' => "hh24",
34474                        b'M' => "mi",
34475                        b'S' => "ss",
34476                        b'f' => "ff",
34477                        _ => {
34478                            result.push('%');
34479                            i += 1;
34480                            continue;
34481                        }
34482                    };
34483                    result.push_str(replacement);
34484                    i += 2;
34485                }
34486            } else {
34487                result.push(bytes[i] as char);
34488                i += 1;
34489            }
34490        }
34491        result
34492    }
34493
34494    fn generate_str_to_map(&mut self, e: &StrToMap) -> Result<()> {
34495        // STR_TO_MAP(this, pair_delim, key_value_delim)
34496        self.write_keyword("STR_TO_MAP");
34497        self.write("(");
34498        self.generate_expression(&e.this)?;
34499        // Spark/Hive: STR_TO_MAP needs explicit default delimiters
34500        let needs_defaults = matches!(
34501            self.config.dialect,
34502            Some(DialectType::Spark) | Some(DialectType::Hive) | Some(DialectType::Databricks)
34503        );
34504        if let Some(pair_delim) = &e.pair_delim {
34505            self.write(", ");
34506            self.generate_expression(pair_delim)?;
34507        } else if needs_defaults {
34508            self.write(", ','");
34509        }
34510        if let Some(key_value_delim) = &e.key_value_delim {
34511            self.write(", ");
34512            self.generate_expression(key_value_delim)?;
34513        } else if needs_defaults {
34514            self.write(", ':'");
34515        }
34516        self.write(")");
34517        Ok(())
34518    }
34519
34520    fn generate_str_to_time(&mut self, e: &StrToTime) -> Result<()> {
34521        // Detect format style: strftime (starts with %) vs Snowflake/Java
34522        let is_strftime = e.format.contains('%');
34523        // Helper: get strftime format from whatever style is stored
34524        let to_strftime = |f: &str| -> String {
34525            if is_strftime {
34526                f.to_string()
34527            } else {
34528                Self::snowflake_format_to_strftime(f)
34529            }
34530        };
34531        // Helper: get Java format
34532        let to_java = |f: &str| -> String {
34533            if is_strftime {
34534                Self::strftime_to_java_format(f)
34535            } else {
34536                Self::snowflake_format_to_spark(f)
34537            }
34538        };
34539        // Helper: get PG format
34540        let to_pg = |f: &str| -> String {
34541            if is_strftime {
34542                Self::strftime_to_postgres_format(f)
34543            } else {
34544                Self::convert_strptime_to_postgres_format(f)
34545            }
34546        };
34547
34548        match self.config.dialect {
34549            Some(DialectType::Exasol) => {
34550                self.write_keyword("TO_DATE");
34551                self.write("(");
34552                self.generate_expression(&e.this)?;
34553                self.write(", '");
34554                self.write(&Self::convert_strptime_to_exasol_format(&e.format));
34555                self.write("'");
34556                self.write(")");
34557            }
34558            Some(DialectType::BigQuery) => {
34559                // BigQuery: PARSE_TIMESTAMP(format, value) - note swapped args
34560                let fmt = to_strftime(&e.format);
34561                // BigQuery normalizes: %Y-%m-%d -> %F, %H:%M:%S -> %T
34562                let fmt = fmt.replace("%Y-%m-%d", "%F").replace("%H:%M:%S", "%T");
34563                self.write_keyword("PARSE_TIMESTAMP");
34564                self.write("('");
34565                self.write(&fmt);
34566                self.write("', ");
34567                self.generate_expression(&e.this)?;
34568                self.write(")");
34569            }
34570            Some(DialectType::Hive) => {
34571                // Hive: CAST(x AS TIMESTAMP) for simple date formats
34572                // Check both the raw format and the converted format (in case it's already Java)
34573                let java_fmt = to_java(&e.format);
34574                if java_fmt == "yyyy-MM-dd HH:mm:ss"
34575                    || java_fmt == "yyyy-MM-dd"
34576                    || e.format == "yyyy-MM-dd HH:mm:ss"
34577                    || e.format == "yyyy-MM-dd"
34578                {
34579                    self.write_keyword("CAST");
34580                    self.write("(");
34581                    self.generate_expression(&e.this)?;
34582                    self.write(" ");
34583                    self.write_keyword("AS TIMESTAMP");
34584                    self.write(")");
34585                } else {
34586                    // CAST(FROM_UNIXTIME(UNIX_TIMESTAMP(x, java_fmt)) AS TIMESTAMP)
34587                    self.write_keyword("CAST");
34588                    self.write("(");
34589                    self.write_keyword("FROM_UNIXTIME");
34590                    self.write("(");
34591                    self.write_keyword("UNIX_TIMESTAMP");
34592                    self.write("(");
34593                    self.generate_expression(&e.this)?;
34594                    self.write(", '");
34595                    self.write(&java_fmt);
34596                    self.write("')");
34597                    self.write(") ");
34598                    self.write_keyword("AS TIMESTAMP");
34599                    self.write(")");
34600                }
34601            }
34602            Some(DialectType::Spark) | Some(DialectType::Databricks) => {
34603                // Spark: TO_TIMESTAMP(value, java_format)
34604                let java_fmt = to_java(&e.format);
34605                self.write_keyword("TO_TIMESTAMP");
34606                self.write("(");
34607                self.generate_expression(&e.this)?;
34608                self.write(", '");
34609                self.write(&java_fmt);
34610                self.write("')");
34611            }
34612            Some(DialectType::MySQL) => {
34613                // MySQL: STR_TO_DATE(value, format)
34614                let mut fmt = to_strftime(&e.format);
34615                // MySQL uses %e for non-padded day, %T for %H:%M:%S
34616                fmt = fmt.replace("%-d", "%e");
34617                fmt = fmt.replace("%-m", "%c");
34618                fmt = fmt.replace("%H:%M:%S", "%T");
34619                self.write_keyword("STR_TO_DATE");
34620                self.write("(");
34621                self.generate_expression(&e.this)?;
34622                self.write(", '");
34623                self.write(&fmt);
34624                self.write("')");
34625            }
34626            Some(DialectType::Drill) => {
34627                // Drill: TO_TIMESTAMP(value, java_format) with T quoted in single quotes
34628                let java_fmt = to_java(&e.format);
34629                // Drill quotes literal T character: T -> ''T'' (double-quoted within SQL string literal)
34630                let java_fmt = java_fmt.replace('T', "''T''");
34631                self.write_keyword("TO_TIMESTAMP");
34632                self.write("(");
34633                self.generate_expression(&e.this)?;
34634                self.write(", '");
34635                self.write(&java_fmt);
34636                self.write("')");
34637            }
34638            Some(DialectType::Presto) | Some(DialectType::Trino) | Some(DialectType::Athena) => {
34639                // Presto: DATE_PARSE(value, strftime_format)
34640                let mut fmt = to_strftime(&e.format);
34641                // Presto uses %e for non-padded day, %T for %H:%M:%S
34642                fmt = fmt.replace("%-d", "%e");
34643                fmt = fmt.replace("%-m", "%c");
34644                fmt = fmt.replace("%H:%M:%S", "%T");
34645                self.write_keyword("DATE_PARSE");
34646                self.write("(");
34647                self.generate_expression(&e.this)?;
34648                self.write(", '");
34649                self.write(&fmt);
34650                self.write("')");
34651            }
34652            Some(DialectType::DuckDB) => {
34653                // DuckDB: STRPTIME(value, strftime_format)
34654                let fmt = to_strftime(&e.format);
34655                self.write_keyword("STRPTIME");
34656                self.write("(");
34657                self.generate_expression(&e.this)?;
34658                self.write(", '");
34659                self.write(&fmt);
34660                self.write("')");
34661            }
34662            Some(DialectType::PostgreSQL)
34663            | Some(DialectType::Redshift)
34664            | Some(DialectType::Materialize) => {
34665                // PostgreSQL/Redshift/Materialize: TO_TIMESTAMP(value, pg_format)
34666                let pg_fmt = to_pg(&e.format);
34667                self.write_keyword("TO_TIMESTAMP");
34668                self.write("(");
34669                self.generate_expression(&e.this)?;
34670                self.write(", '");
34671                self.write(&pg_fmt);
34672                self.write("')");
34673            }
34674            Some(DialectType::Oracle) => {
34675                // Oracle: TO_TIMESTAMP(value, pg_format)
34676                let pg_fmt = to_pg(&e.format);
34677                self.write_keyword("TO_TIMESTAMP");
34678                self.write("(");
34679                self.generate_expression(&e.this)?;
34680                self.write(", '");
34681                self.write(&pg_fmt);
34682                self.write("')");
34683            }
34684            Some(DialectType::Snowflake) => {
34685                // Snowflake: TO_TIMESTAMP(value, format) - native format
34686                self.write_keyword("TO_TIMESTAMP");
34687                self.write("(");
34688                self.generate_expression(&e.this)?;
34689                self.write(", '");
34690                self.write(&e.format);
34691                self.write("')");
34692            }
34693            _ => {
34694                // Default: STR_TO_TIME(this, format)
34695                self.write_keyword("STR_TO_TIME");
34696                self.write("(");
34697                self.generate_expression(&e.this)?;
34698                self.write(", '");
34699                self.write(&e.format);
34700                self.write("'");
34701                self.write(")");
34702            }
34703        }
34704        Ok(())
34705    }
34706
34707    /// Convert Snowflake normalized format to strftime-style (%Y, %m, etc.)
34708    fn snowflake_format_to_strftime(format: &str) -> String {
34709        let mut result = String::new();
34710        let chars: Vec<char> = format.chars().collect();
34711        let mut i = 0;
34712        while i < chars.len() {
34713            let remaining = &format[i..];
34714            if remaining.starts_with("yyyy") {
34715                result.push_str("%Y");
34716                i += 4;
34717            } else if remaining.starts_with("yy") {
34718                result.push_str("%y");
34719                i += 2;
34720            } else if remaining.starts_with("mmmm") {
34721                result.push_str("%B"); // full month name
34722                i += 4;
34723            } else if remaining.starts_with("mon") {
34724                result.push_str("%b"); // abbreviated month
34725                i += 3;
34726            } else if remaining.starts_with("mm") {
34727                result.push_str("%m");
34728                i += 2;
34729            } else if remaining.starts_with("DD") {
34730                result.push_str("%d");
34731                i += 2;
34732            } else if remaining.starts_with("dy") {
34733                result.push_str("%a"); // abbreviated day name
34734                i += 2;
34735            } else if remaining.starts_with("hh24") {
34736                result.push_str("%H");
34737                i += 4;
34738            } else if remaining.starts_with("hh12") {
34739                result.push_str("%I");
34740                i += 4;
34741            } else if remaining.starts_with("hh") {
34742                result.push_str("%H");
34743                i += 2;
34744            } else if remaining.starts_with("mi") {
34745                result.push_str("%M");
34746                i += 2;
34747            } else if remaining.starts_with("ss") {
34748                result.push_str("%S");
34749                i += 2;
34750            } else if remaining.starts_with("ff") {
34751                // Fractional seconds
34752                result.push_str("%f");
34753                i += 2;
34754                // Skip digits after ff (ff3, ff6, ff9)
34755                while i < chars.len() && chars[i].is_ascii_digit() {
34756                    i += 1;
34757                }
34758            } else if remaining.starts_with("am") || remaining.starts_with("pm") {
34759                result.push_str("%p");
34760                i += 2;
34761            } else if remaining.starts_with("tz") {
34762                result.push_str("%Z");
34763                i += 2;
34764            } else {
34765                result.push(chars[i]);
34766                i += 1;
34767            }
34768        }
34769        result
34770    }
34771
34772    /// Convert Snowflake normalized format to Spark format (Java-style)
34773    fn snowflake_format_to_spark(format: &str) -> String {
34774        let mut result = String::new();
34775        let chars: Vec<char> = format.chars().collect();
34776        let mut i = 0;
34777        while i < chars.len() {
34778            let remaining = &format[i..];
34779            if remaining.starts_with("yyyy") {
34780                result.push_str("yyyy");
34781                i += 4;
34782            } else if remaining.starts_with("yy") {
34783                result.push_str("yy");
34784                i += 2;
34785            } else if remaining.starts_with("mmmm") {
34786                result.push_str("MMMM"); // full month name
34787                i += 4;
34788            } else if remaining.starts_with("mon") {
34789                result.push_str("MMM"); // abbreviated month
34790                i += 3;
34791            } else if remaining.starts_with("mm") {
34792                result.push_str("MM");
34793                i += 2;
34794            } else if remaining.starts_with("DD") {
34795                result.push_str("dd");
34796                i += 2;
34797            } else if remaining.starts_with("dy") {
34798                result.push_str("EEE"); // abbreviated day name
34799                i += 2;
34800            } else if remaining.starts_with("hh24") {
34801                result.push_str("HH");
34802                i += 4;
34803            } else if remaining.starts_with("hh12") {
34804                result.push_str("hh");
34805                i += 4;
34806            } else if remaining.starts_with("hh") {
34807                result.push_str("HH");
34808                i += 2;
34809            } else if remaining.starts_with("mi") {
34810                result.push_str("mm");
34811                i += 2;
34812            } else if remaining.starts_with("ss") {
34813                result.push_str("ss");
34814                i += 2;
34815            } else if remaining.starts_with("ff") {
34816                result.push_str("SSS"); // milliseconds
34817                i += 2;
34818                // Skip digits after ff
34819                while i < chars.len() && chars[i].is_ascii_digit() {
34820                    i += 1;
34821                }
34822            } else if remaining.starts_with("am") || remaining.starts_with("pm") {
34823                result.push_str("a");
34824                i += 2;
34825            } else if remaining.starts_with("tz") {
34826                result.push_str("z");
34827                i += 2;
34828            } else {
34829                result.push(chars[i]);
34830                i += 1;
34831            }
34832        }
34833        result
34834    }
34835
34836    fn generate_str_to_unix(&mut self, e: &StrToUnix) -> Result<()> {
34837        match self.config.dialect {
34838            Some(DialectType::DuckDB) => {
34839                // DuckDB: EPOCH(STRPTIME(value, format))
34840                self.write_keyword("EPOCH");
34841                self.write("(");
34842                self.write_keyword("STRPTIME");
34843                self.write("(");
34844                if let Some(this) = &e.this {
34845                    self.generate_expression(this)?;
34846                }
34847                if let Some(format) = &e.format {
34848                    self.write(", '");
34849                    self.write(format);
34850                    self.write("'");
34851                }
34852                self.write("))");
34853            }
34854            Some(DialectType::Hive) => {
34855                // Hive: UNIX_TIMESTAMP(value, java_format) - convert C fmt to Java
34856                self.write_keyword("UNIX_TIMESTAMP");
34857                self.write("(");
34858                if let Some(this) = &e.this {
34859                    self.generate_expression(this)?;
34860                }
34861                if let Some(format) = &e.format {
34862                    let java_fmt = Self::strftime_to_java_format(format);
34863                    if java_fmt != "yyyy-MM-dd HH:mm:ss" {
34864                        self.write(", '");
34865                        self.write(&java_fmt);
34866                        self.write("'");
34867                    }
34868                }
34869                self.write(")");
34870            }
34871            Some(DialectType::Doris) | Some(DialectType::StarRocks) => {
34872                // Doris/StarRocks: UNIX_TIMESTAMP(value, format) - C format
34873                self.write_keyword("UNIX_TIMESTAMP");
34874                self.write("(");
34875                if let Some(this) = &e.this {
34876                    self.generate_expression(this)?;
34877                }
34878                if let Some(format) = &e.format {
34879                    self.write(", '");
34880                    self.write(format);
34881                    self.write("'");
34882                }
34883                self.write(")");
34884            }
34885            Some(DialectType::Presto) | Some(DialectType::Trino) => {
34886                // Presto: TO_UNIXTIME(COALESCE(TRY(DATE_PARSE(CAST(value AS VARCHAR), c_format)),
34887                //   PARSE_DATETIME(DATE_FORMAT(CAST(value AS TIMESTAMP), c_format), java_format)))
34888                let c_fmt = e.format.as_deref().unwrap_or("%Y-%m-%d %T");
34889                let java_fmt = Self::strftime_to_java_format(c_fmt);
34890                self.write_keyword("TO_UNIXTIME");
34891                self.write("(");
34892                self.write_keyword("COALESCE");
34893                self.write("(");
34894                self.write_keyword("TRY");
34895                self.write("(");
34896                self.write_keyword("DATE_PARSE");
34897                self.write("(");
34898                self.write_keyword("CAST");
34899                self.write("(");
34900                if let Some(this) = &e.this {
34901                    self.generate_expression(this)?;
34902                }
34903                self.write(" ");
34904                self.write_keyword("AS VARCHAR");
34905                self.write("), '");
34906                self.write(c_fmt);
34907                self.write("')), ");
34908                self.write_keyword("PARSE_DATETIME");
34909                self.write("(");
34910                self.write_keyword("DATE_FORMAT");
34911                self.write("(");
34912                self.write_keyword("CAST");
34913                self.write("(");
34914                if let Some(this) = &e.this {
34915                    self.generate_expression(this)?;
34916                }
34917                self.write(" ");
34918                self.write_keyword("AS TIMESTAMP");
34919                self.write("), '");
34920                self.write(c_fmt);
34921                self.write("'), '");
34922                self.write(&java_fmt);
34923                self.write("')))");
34924            }
34925            Some(DialectType::Spark) | Some(DialectType::Databricks) => {
34926                // Spark: UNIX_TIMESTAMP(value, java_format)
34927                self.write_keyword("UNIX_TIMESTAMP");
34928                self.write("(");
34929                if let Some(this) = &e.this {
34930                    self.generate_expression(this)?;
34931                }
34932                if let Some(format) = &e.format {
34933                    let java_fmt = Self::strftime_to_java_format(format);
34934                    self.write(", '");
34935                    self.write(&java_fmt);
34936                    self.write("'");
34937                }
34938                self.write(")");
34939            }
34940            _ => {
34941                // Default: STR_TO_UNIX(this, format)
34942                self.write_keyword("STR_TO_UNIX");
34943                self.write("(");
34944                if let Some(this) = &e.this {
34945                    self.generate_expression(this)?;
34946                }
34947                if let Some(format) = &e.format {
34948                    self.write(", '");
34949                    self.write(format);
34950                    self.write("'");
34951                }
34952                self.write(")");
34953            }
34954        }
34955        Ok(())
34956    }
34957
34958    fn generate_string_to_array(&mut self, e: &StringToArray) -> Result<()> {
34959        // STRING_TO_ARRAY(this, delimiter, null_string)
34960        self.write_keyword("STRING_TO_ARRAY");
34961        self.write("(");
34962        self.generate_expression(&e.this)?;
34963        if let Some(expression) = &e.expression {
34964            self.write(", ");
34965            self.generate_expression(expression)?;
34966        }
34967        if let Some(null_val) = &e.null {
34968            self.write(", ");
34969            self.generate_expression(null_val)?;
34970        }
34971        self.write(")");
34972        Ok(())
34973    }
34974
34975    fn generate_struct(&mut self, e: &Struct) -> Result<()> {
34976        if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
34977            // Snowflake: OBJECT_CONSTRUCT('key', value, 'key', value, ...)
34978            self.write_keyword("OBJECT_CONSTRUCT");
34979            self.write("(");
34980            for (i, (name, expr)) in e.fields.iter().enumerate() {
34981                if i > 0 {
34982                    self.write(", ");
34983                }
34984                if let Some(name) = name {
34985                    self.write("'");
34986                    self.write(name);
34987                    self.write("'");
34988                    self.write(", ");
34989                } else {
34990                    self.write("'_");
34991                    self.write(&i.to_string());
34992                    self.write("'");
34993                    self.write(", ");
34994                }
34995                self.generate_expression(expr)?;
34996            }
34997            self.write(")");
34998        } else if self.config.struct_curly_brace_notation {
34999            // DuckDB-style: {'key': value, ...}
35000            self.write("{");
35001            for (i, (name, expr)) in e.fields.iter().enumerate() {
35002                if i > 0 {
35003                    self.write(", ");
35004                }
35005                if let Some(name) = name {
35006                    // Quote the key as a string literal
35007                    self.write("'");
35008                    self.write(name);
35009                    self.write("'");
35010                    self.write(": ");
35011                } else {
35012                    // Unnamed field: use positional key
35013                    self.write("'_");
35014                    self.write(&i.to_string());
35015                    self.write("'");
35016                    self.write(": ");
35017                }
35018                self.generate_expression(expr)?;
35019            }
35020            self.write("}");
35021        } else {
35022            // Standard SQL struct notation
35023            // BigQuery/Spark/Databricks use: STRUCT(value AS name, ...)
35024            // Others (Presto etc.) use: STRUCT(name AS value, ...) or ROW(value, ...)
35025            let value_as_name = matches!(
35026                self.config.dialect,
35027                Some(DialectType::BigQuery)
35028                    | Some(DialectType::Spark)
35029                    | Some(DialectType::Databricks)
35030                    | Some(DialectType::Hive)
35031            );
35032            self.write_keyword("STRUCT");
35033            self.write("(");
35034            for (i, (name, expr)) in e.fields.iter().enumerate() {
35035                if i > 0 {
35036                    self.write(", ");
35037                }
35038                if let Some(name) = name {
35039                    if value_as_name {
35040                        // STRUCT(value AS name)
35041                        self.generate_expression(expr)?;
35042                        self.write_space();
35043                        self.write_keyword("AS");
35044                        self.write_space();
35045                        // Quote name if it contains spaces or special chars
35046                        let needs_quoting = name.contains(' ') || name.contains('-');
35047                        if needs_quoting {
35048                            if matches!(
35049                                self.config.dialect,
35050                                Some(DialectType::Spark)
35051                                    | Some(DialectType::Databricks)
35052                                    | Some(DialectType::Hive)
35053                            ) {
35054                                self.write("`");
35055                                self.write(name);
35056                                self.write("`");
35057                            } else {
35058                                self.write(name);
35059                            }
35060                        } else {
35061                            self.write(name);
35062                        }
35063                    } else {
35064                        // STRUCT(name AS value)
35065                        self.write(name);
35066                        self.write_space();
35067                        self.write_keyword("AS");
35068                        self.write_space();
35069                        self.generate_expression(expr)?;
35070                    }
35071                } else {
35072                    self.generate_expression(expr)?;
35073                }
35074            }
35075            self.write(")");
35076        }
35077        Ok(())
35078    }
35079
35080    fn generate_stuff(&mut self, e: &Stuff) -> Result<()> {
35081        // STUFF(this, start, length, expression)
35082        self.write_keyword("STUFF");
35083        self.write("(");
35084        self.generate_expression(&e.this)?;
35085        if let Some(start) = &e.start {
35086            self.write(", ");
35087            self.generate_expression(start)?;
35088        }
35089        if let Some(length) = e.length {
35090            self.write(", ");
35091            self.write(&length.to_string());
35092        }
35093        self.write(", ");
35094        self.generate_expression(&e.expression)?;
35095        self.write(")");
35096        Ok(())
35097    }
35098
35099    fn generate_substring_index(&mut self, e: &SubstringIndex) -> Result<()> {
35100        // SUBSTRING_INDEX(this, delimiter, count)
35101        self.write_keyword("SUBSTRING_INDEX");
35102        self.write("(");
35103        self.generate_expression(&e.this)?;
35104        if let Some(delimiter) = &e.delimiter {
35105            self.write(", ");
35106            self.generate_expression(delimiter)?;
35107        }
35108        if let Some(count) = &e.count {
35109            self.write(", ");
35110            self.generate_expression(count)?;
35111        }
35112        self.write(")");
35113        Ok(())
35114    }
35115
35116    fn generate_summarize(&mut self, e: &Summarize) -> Result<()> {
35117        // SUMMARIZE [TABLE] this
35118        self.write_keyword("SUMMARIZE");
35119        if e.table.is_some() {
35120            self.write_space();
35121            self.write_keyword("TABLE");
35122        }
35123        self.write_space();
35124        self.generate_expression(&e.this)?;
35125        Ok(())
35126    }
35127
35128    fn generate_systimestamp(&mut self, _e: &Systimestamp) -> Result<()> {
35129        // SYSTIMESTAMP
35130        self.write_keyword("SYSTIMESTAMP");
35131        Ok(())
35132    }
35133
35134    fn generate_table_alias(&mut self, e: &TableAlias) -> Result<()> {
35135        // alias (columns...)
35136        if let Some(this) = &e.this {
35137            self.generate_expression(this)?;
35138        }
35139        if !e.columns.is_empty() {
35140            self.write("(");
35141            for (i, col) in e.columns.iter().enumerate() {
35142                if i > 0 {
35143                    self.write(", ");
35144                }
35145                self.generate_expression(col)?;
35146            }
35147            self.write(")");
35148        }
35149        Ok(())
35150    }
35151
35152    fn generate_table_from_rows(&mut self, e: &TableFromRows) -> Result<()> {
35153        // TABLE(this) [AS alias]
35154        self.write_keyword("TABLE");
35155        self.write("(");
35156        self.generate_expression(&e.this)?;
35157        self.write(")");
35158        if let Some(alias) = &e.alias {
35159            self.write_space();
35160            self.write_keyword("AS");
35161            self.write_space();
35162            self.write(alias);
35163        }
35164        Ok(())
35165    }
35166
35167    fn generate_rows_from(&mut self, e: &RowsFrom) -> Result<()> {
35168        // ROWS FROM (func1(...) AS alias1(...), func2(...) AS alias2(...)) [WITH ORDINALITY] [AS alias(...)]
35169        self.write_keyword("ROWS FROM");
35170        self.write(" (");
35171        for (i, expr) in e.expressions.iter().enumerate() {
35172            if i > 0 {
35173                self.write(", ");
35174            }
35175            // Each expression is either:
35176            // - A plain function (no alias)
35177            // - A Tuple(function, TableAlias) for: FUNC() AS alias(col type, ...)
35178            match expr {
35179                Expression::Tuple(tuple) if tuple.expressions.len() == 2 => {
35180                    // First element is the function, second is the TableAlias
35181                    self.generate_expression(&tuple.expressions[0])?;
35182                    self.write_space();
35183                    self.write_keyword("AS");
35184                    self.write_space();
35185                    self.generate_expression(&tuple.expressions[1])?;
35186                }
35187                _ => {
35188                    self.generate_expression(expr)?;
35189                }
35190            }
35191        }
35192        self.write(")");
35193        if e.ordinality {
35194            self.write_space();
35195            self.write_keyword("WITH ORDINALITY");
35196        }
35197        if let Some(alias) = &e.alias {
35198            self.write_space();
35199            self.write_keyword("AS");
35200            self.write_space();
35201            self.generate_expression(alias)?;
35202        }
35203        Ok(())
35204    }
35205
35206    fn generate_table_sample(&mut self, e: &TableSample) -> Result<()> {
35207        use crate::dialects::DialectType;
35208
35209        // New wrapper pattern: expression + Sample struct
35210        if let (Some(this), Some(sample)) = (&e.this, &e.sample) {
35211            // For alias_post_tablesample dialects (Spark, Hive, Oracle): output base expr, TABLESAMPLE, then alias
35212            if self.config.alias_post_tablesample {
35213                // Handle Subquery with alias and Alias wrapper
35214                if let Expression::Subquery(ref s) = **this {
35215                    if let Some(ref alias) = s.alias {
35216                        // Create a clone without alias for output
35217                        let mut subquery_no_alias = (**s).clone();
35218                        subquery_no_alias.alias = None;
35219                        subquery_no_alias.column_aliases = Vec::new();
35220                        self.generate_expression(&Expression::Subquery(Box::new(
35221                            subquery_no_alias,
35222                        )))?;
35223                        self.write_space();
35224                        self.write_keyword("TABLESAMPLE");
35225                        self.generate_sample_body(sample)?;
35226                        if let Some(ref seed) = sample.seed {
35227                            self.write_space();
35228                            let use_seed = sample.use_seed_keyword
35229                                && !matches!(
35230                                    self.config.dialect,
35231                                    Some(crate::dialects::DialectType::Databricks)
35232                                        | Some(crate::dialects::DialectType::Spark)
35233                                );
35234                            if use_seed {
35235                                self.write_keyword("SEED");
35236                            } else {
35237                                self.write_keyword("REPEATABLE");
35238                            }
35239                            self.write(" (");
35240                            self.generate_expression(seed)?;
35241                            self.write(")");
35242                        }
35243                        self.write_space();
35244                        self.write_keyword("AS");
35245                        self.write_space();
35246                        self.generate_identifier(alias)?;
35247                        return Ok(());
35248                    }
35249                } else if let Expression::Alias(ref a) = **this {
35250                    // Output the base expression without alias
35251                    self.generate_expression(&a.this)?;
35252                    self.write_space();
35253                    self.write_keyword("TABLESAMPLE");
35254                    self.generate_sample_body(sample)?;
35255                    if let Some(ref seed) = sample.seed {
35256                        self.write_space();
35257                        let use_seed = sample.use_seed_keyword
35258                            && !matches!(
35259                                self.config.dialect,
35260                                Some(crate::dialects::DialectType::Databricks)
35261                                    | Some(crate::dialects::DialectType::Spark)
35262                            );
35263                        if use_seed {
35264                            self.write_keyword("SEED");
35265                        } else {
35266                            self.write_keyword("REPEATABLE");
35267                        }
35268                        self.write(" (");
35269                        self.generate_expression(seed)?;
35270                        self.write(")");
35271                    }
35272                    // Output alias after TABLESAMPLE
35273                    self.write_space();
35274                    self.write_keyword("AS");
35275                    self.write_space();
35276                    self.generate_identifier(&a.alias)?;
35277                    return Ok(());
35278                }
35279            }
35280            // Default: generate wrapped expression first, then TABLESAMPLE
35281            self.generate_expression(this)?;
35282            self.write_space();
35283            self.write_keyword("TABLESAMPLE");
35284            self.generate_sample_body(sample)?;
35285            // Seed for table-level sample
35286            if let Some(ref seed) = sample.seed {
35287                self.write_space();
35288                // Databricks uses REPEATABLE, not SEED
35289                let use_seed = sample.use_seed_keyword
35290                    && !matches!(
35291                        self.config.dialect,
35292                        Some(crate::dialects::DialectType::Databricks)
35293                            | Some(crate::dialects::DialectType::Spark)
35294                    );
35295                if use_seed {
35296                    self.write_keyword("SEED");
35297                } else {
35298                    self.write_keyword("REPEATABLE");
35299                }
35300                self.write(" (");
35301                self.generate_expression(seed)?;
35302                self.write(")");
35303            }
35304            return Ok(());
35305        }
35306
35307        // Legacy pattern: TABLESAMPLE [method] (expressions) or TABLESAMPLE method BUCKET numerator OUT OF denominator
35308        self.write_keyword("TABLESAMPLE");
35309        if let Some(method) = &e.method {
35310            self.write_space();
35311            self.write_keyword(method);
35312        } else if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
35313            // Snowflake defaults to BERNOULLI when no method is specified
35314            self.write_space();
35315            self.write_keyword("BERNOULLI");
35316        }
35317        if let (Some(numerator), Some(denominator)) = (&e.bucket_numerator, &e.bucket_denominator) {
35318            self.write_space();
35319            self.write_keyword("BUCKET");
35320            self.write_space();
35321            self.generate_expression(numerator)?;
35322            self.write_space();
35323            self.write_keyword("OUT OF");
35324            self.write_space();
35325            self.generate_expression(denominator)?;
35326            if let Some(field) = &e.bucket_field {
35327                self.write_space();
35328                self.write_keyword("ON");
35329                self.write_space();
35330                self.generate_expression(field)?;
35331            }
35332        } else if !e.expressions.is_empty() {
35333            self.write(" (");
35334            for (i, expr) in e.expressions.iter().enumerate() {
35335                if i > 0 {
35336                    self.write(", ");
35337                }
35338                self.generate_expression(expr)?;
35339            }
35340            self.write(")");
35341        } else if let Some(percent) = &e.percent {
35342            self.write(" (");
35343            self.generate_expression(percent)?;
35344            self.write_space();
35345            self.write_keyword("PERCENT");
35346            self.write(")");
35347        }
35348        Ok(())
35349    }
35350
35351    fn generate_tag(&mut self, e: &Tag) -> Result<()> {
35352        // [prefix]this[postfix]
35353        if let Some(prefix) = &e.prefix {
35354            self.generate_expression(prefix)?;
35355        }
35356        if let Some(this) = &e.this {
35357            self.generate_expression(this)?;
35358        }
35359        if let Some(postfix) = &e.postfix {
35360            self.generate_expression(postfix)?;
35361        }
35362        Ok(())
35363    }
35364
35365    fn generate_tags(&mut self, e: &Tags) -> Result<()> {
35366        // TAG (expressions)
35367        self.write_keyword("TAG");
35368        self.write(" (");
35369        for (i, expr) in e.expressions.iter().enumerate() {
35370            if i > 0 {
35371                self.write(", ");
35372            }
35373            self.generate_expression(expr)?;
35374        }
35375        self.write(")");
35376        Ok(())
35377    }
35378
35379    fn generate_temporary_property(&mut self, e: &TemporaryProperty) -> Result<()> {
35380        // TEMPORARY or TEMP or [this] TEMPORARY
35381        if let Some(this) = &e.this {
35382            self.generate_expression(this)?;
35383            self.write_space();
35384        }
35385        self.write_keyword("TEMPORARY");
35386        Ok(())
35387    }
35388
35389    /// Generate a Time function expression
35390    /// For most dialects: TIME('value')
35391    fn generate_time_func(&mut self, e: &UnaryFunc) -> Result<()> {
35392        // Standard: TIME(value)
35393        self.write_keyword("TIME");
35394        self.write("(");
35395        self.generate_expression(&e.this)?;
35396        self.write(")");
35397        Ok(())
35398    }
35399
35400    fn generate_time_add(&mut self, e: &TimeAdd) -> Result<()> {
35401        // TIME_ADD(this, expression, unit)
35402        self.write_keyword("TIME_ADD");
35403        self.write("(");
35404        self.generate_expression(&e.this)?;
35405        self.write(", ");
35406        self.generate_expression(&e.expression)?;
35407        if let Some(unit) = &e.unit {
35408            self.write(", ");
35409            self.write_keyword(unit);
35410        }
35411        self.write(")");
35412        Ok(())
35413    }
35414
35415    fn generate_time_diff(&mut self, e: &TimeDiff) -> Result<()> {
35416        // TIME_DIFF(this, expression, unit)
35417        self.write_keyword("TIME_DIFF");
35418        self.write("(");
35419        self.generate_expression(&e.this)?;
35420        self.write(", ");
35421        self.generate_expression(&e.expression)?;
35422        if let Some(unit) = &e.unit {
35423            self.write(", ");
35424            self.write_keyword(unit);
35425        }
35426        self.write(")");
35427        Ok(())
35428    }
35429
35430    fn generate_time_from_parts(&mut self, e: &TimeFromParts) -> Result<()> {
35431        // TIME_FROM_PARTS(hour, minute, second, nanosecond)
35432        self.write_keyword("TIME_FROM_PARTS");
35433        self.write("(");
35434        let mut first = true;
35435        if let Some(hour) = &e.hour {
35436            self.generate_expression(hour)?;
35437            first = false;
35438        }
35439        if let Some(minute) = &e.min {
35440            if !first {
35441                self.write(", ");
35442            }
35443            self.generate_expression(minute)?;
35444            first = false;
35445        }
35446        if let Some(second) = &e.sec {
35447            if !first {
35448                self.write(", ");
35449            }
35450            self.generate_expression(second)?;
35451            first = false;
35452        }
35453        if let Some(ns) = &e.nano {
35454            if !first {
35455                self.write(", ");
35456            }
35457            self.generate_expression(ns)?;
35458        }
35459        self.write(")");
35460        Ok(())
35461    }
35462
35463    fn generate_time_slice(&mut self, e: &TimeSlice) -> Result<()> {
35464        // TIME_SLICE(this, expression, unit)
35465        self.write_keyword("TIME_SLICE");
35466        self.write("(");
35467        self.generate_expression(&e.this)?;
35468        self.write(", ");
35469        self.generate_expression(&e.expression)?;
35470        self.write(", ");
35471        self.write_keyword(&e.unit);
35472        self.write(")");
35473        Ok(())
35474    }
35475
35476    fn generate_time_str_to_time(&mut self, e: &TimeStrToTime) -> Result<()> {
35477        // TIME_STR_TO_TIME(this)
35478        self.write_keyword("TIME_STR_TO_TIME");
35479        self.write("(");
35480        self.generate_expression(&e.this)?;
35481        self.write(")");
35482        Ok(())
35483    }
35484
35485    fn generate_time_sub(&mut self, e: &TimeSub) -> Result<()> {
35486        // TIME_SUB(this, expression, unit)
35487        self.write_keyword("TIME_SUB");
35488        self.write("(");
35489        self.generate_expression(&e.this)?;
35490        self.write(", ");
35491        self.generate_expression(&e.expression)?;
35492        if let Some(unit) = &e.unit {
35493            self.write(", ");
35494            self.write_keyword(unit);
35495        }
35496        self.write(")");
35497        Ok(())
35498    }
35499
35500    fn generate_time_to_str(&mut self, e: &TimeToStr) -> Result<()> {
35501        match self.config.dialect {
35502            Some(DialectType::Exasol) => {
35503                // Exasol uses TO_CHAR with Exasol-specific format
35504                self.write_keyword("TO_CHAR");
35505                self.write("(");
35506                self.generate_expression(&e.this)?;
35507                self.write(", '");
35508                self.write(&Self::convert_strptime_to_exasol_format(&e.format));
35509                self.write("'");
35510                self.write(")");
35511            }
35512            Some(DialectType::PostgreSQL)
35513            | Some(DialectType::Redshift)
35514            | Some(DialectType::Materialize) => {
35515                // PostgreSQL/Redshift/Materialize uses TO_CHAR with PG-specific format
35516                self.write_keyword("TO_CHAR");
35517                self.write("(");
35518                self.generate_expression(&e.this)?;
35519                self.write(", '");
35520                self.write(&Self::convert_strptime_to_postgres_format(&e.format));
35521                self.write("'");
35522                self.write(")");
35523            }
35524            Some(DialectType::Oracle) => {
35525                // Oracle uses TO_CHAR with PG-like format
35526                self.write_keyword("TO_CHAR");
35527                self.write("(");
35528                self.generate_expression(&e.this)?;
35529                self.write(", '");
35530                self.write(&Self::convert_strptime_to_postgres_format(&e.format));
35531                self.write("'");
35532                self.write(")");
35533            }
35534            Some(DialectType::Drill) => {
35535                // Drill: TO_CHAR with Java format
35536                self.write_keyword("TO_CHAR");
35537                self.write("(");
35538                self.generate_expression(&e.this)?;
35539                self.write(", '");
35540                self.write(&Self::strftime_to_java_format(&e.format));
35541                self.write("'");
35542                self.write(")");
35543            }
35544            Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
35545                // TSQL: FORMAT(value, format) with .NET-style format
35546                self.write_keyword("FORMAT");
35547                self.write("(");
35548                self.generate_expression(&e.this)?;
35549                self.write(", '");
35550                self.write(&Self::strftime_to_tsql_format(&e.format));
35551                self.write("'");
35552                self.write(")");
35553            }
35554            Some(DialectType::DuckDB) => {
35555                // DuckDB: STRFTIME(value, format) - keeps C format
35556                self.write_keyword("STRFTIME");
35557                self.write("(");
35558                self.generate_expression(&e.this)?;
35559                self.write(", '");
35560                self.write(&e.format);
35561                self.write("'");
35562                self.write(")");
35563            }
35564            Some(DialectType::BigQuery) => {
35565                // BigQuery: FORMAT_DATE(format, value) - note swapped arg order
35566                // Normalize: %Y-%m-%d -> %F, %H:%M:%S -> %T
35567                let fmt = e.format.replace("%Y-%m-%d", "%F").replace("%H:%M:%S", "%T");
35568                self.write_keyword("FORMAT_DATE");
35569                self.write("('");
35570                self.write(&fmt);
35571                self.write("', ");
35572                self.generate_expression(&e.this)?;
35573                self.write(")");
35574            }
35575            Some(DialectType::Hive) | Some(DialectType::Spark) | Some(DialectType::Databricks) => {
35576                // Hive/Spark: DATE_FORMAT(value, java_format)
35577                self.write_keyword("DATE_FORMAT");
35578                self.write("(");
35579                self.generate_expression(&e.this)?;
35580                self.write(", '");
35581                self.write(&Self::strftime_to_java_format(&e.format));
35582                self.write("'");
35583                self.write(")");
35584            }
35585            Some(DialectType::Presto) | Some(DialectType::Trino) | Some(DialectType::Athena) => {
35586                // Presto/Trino: DATE_FORMAT(value, format) - keeps C format
35587                self.write_keyword("DATE_FORMAT");
35588                self.write("(");
35589                self.generate_expression(&e.this)?;
35590                self.write(", '");
35591                self.write(&e.format);
35592                self.write("'");
35593                self.write(")");
35594            }
35595            Some(DialectType::Doris) | Some(DialectType::StarRocks) => {
35596                // Doris/StarRocks: DATE_FORMAT(value, format) - keeps C format
35597                self.write_keyword("DATE_FORMAT");
35598                self.write("(");
35599                self.generate_expression(&e.this)?;
35600                self.write(", '");
35601                self.write(&e.format);
35602                self.write("'");
35603                self.write(")");
35604            }
35605            _ => {
35606                // Default: TIME_TO_STR(this, format)
35607                self.write_keyword("TIME_TO_STR");
35608                self.write("(");
35609                self.generate_expression(&e.this)?;
35610                self.write(", '");
35611                self.write(&e.format);
35612                self.write("'");
35613                self.write(")");
35614            }
35615        }
35616        Ok(())
35617    }
35618
35619    fn generate_time_to_unix(&mut self, e: &crate::expressions::UnaryFunc) -> Result<()> {
35620        match self.config.dialect {
35621            Some(DialectType::DuckDB) => {
35622                // DuckDB: EPOCH(x)
35623                self.write_keyword("EPOCH");
35624                self.write("(");
35625                self.generate_expression(&e.this)?;
35626                self.write(")");
35627            }
35628            Some(DialectType::Hive)
35629            | Some(DialectType::Spark)
35630            | Some(DialectType::Databricks)
35631            | Some(DialectType::Doris)
35632            | Some(DialectType::StarRocks)
35633            | Some(DialectType::Drill) => {
35634                // Hive/Spark/Doris/StarRocks/Drill: UNIX_TIMESTAMP(x)
35635                self.write_keyword("UNIX_TIMESTAMP");
35636                self.write("(");
35637                self.generate_expression(&e.this)?;
35638                self.write(")");
35639            }
35640            Some(DialectType::Presto) | Some(DialectType::Trino) => {
35641                // Presto: TO_UNIXTIME(x)
35642                self.write_keyword("TO_UNIXTIME");
35643                self.write("(");
35644                self.generate_expression(&e.this)?;
35645                self.write(")");
35646            }
35647            _ => {
35648                // Default: TIME_TO_UNIX(x)
35649                self.write_keyword("TIME_TO_UNIX");
35650                self.write("(");
35651                self.generate_expression(&e.this)?;
35652                self.write(")");
35653            }
35654        }
35655        Ok(())
35656    }
35657
35658    fn generate_time_str_to_date(&mut self, e: &crate::expressions::UnaryFunc) -> Result<()> {
35659        match self.config.dialect {
35660            Some(DialectType::Hive) => {
35661                // Hive: TO_DATE(x)
35662                self.write_keyword("TO_DATE");
35663                self.write("(");
35664                self.generate_expression(&e.this)?;
35665                self.write(")");
35666            }
35667            _ => {
35668                // Default: TIME_STR_TO_DATE(x)
35669                self.write_keyword("TIME_STR_TO_DATE");
35670                self.write("(");
35671                self.generate_expression(&e.this)?;
35672                self.write(")");
35673            }
35674        }
35675        Ok(())
35676    }
35677
35678    fn generate_time_trunc(&mut self, e: &TimeTrunc) -> Result<()> {
35679        // TIME_TRUNC(this, unit)
35680        self.write_keyword("TIME_TRUNC");
35681        self.write("(");
35682        self.generate_expression(&e.this)?;
35683        self.write(", ");
35684        self.write_keyword(&e.unit);
35685        self.write(")");
35686        Ok(())
35687    }
35688
35689    fn generate_time_unit(&mut self, e: &TimeUnit) -> Result<()> {
35690        // Just output the unit name
35691        if let Some(unit) = &e.unit {
35692            self.write_keyword(unit);
35693        }
35694        Ok(())
35695    }
35696
35697    /// Generate a Timestamp function expression
35698    /// For Exasol: {ts'value'} -> TO_TIMESTAMP('value')
35699    /// For other dialects: TIMESTAMP('value')
35700    fn generate_timestamp_func(&mut self, e: &TimestampFunc) -> Result<()> {
35701        use crate::dialects::DialectType;
35702        use crate::expressions::Literal;
35703
35704        match self.config.dialect {
35705            // Exasol uses TO_TIMESTAMP for Timestamp expressions
35706            Some(DialectType::Exasol) => {
35707                self.write_keyword("TO_TIMESTAMP");
35708                self.write("(");
35709                // Extract the string value from the expression if it's a string literal
35710                if let Some(this) = &e.this {
35711                    match this.as_ref() {
35712                        Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
35713                            let Literal::String(s) = lit.as_ref() else {
35714                                unreachable!()
35715                            };
35716                            self.write("'");
35717                            self.write(s);
35718                            self.write("'");
35719                        }
35720                        _ => {
35721                            self.generate_expression(this)?;
35722                        }
35723                    }
35724                }
35725                self.write(")");
35726            }
35727            // Standard: TIMESTAMP(value) or TIMESTAMP(value, zone)
35728            _ => {
35729                self.write_keyword("TIMESTAMP");
35730                self.write("(");
35731                if let Some(this) = &e.this {
35732                    self.generate_expression(this)?;
35733                }
35734                if let Some(zone) = &e.zone {
35735                    self.write(", ");
35736                    self.generate_expression(zone)?;
35737                }
35738                self.write(")");
35739            }
35740        }
35741        Ok(())
35742    }
35743
35744    fn generate_timestamp_add(&mut self, e: &TimestampAdd) -> Result<()> {
35745        // TIMESTAMP_ADD(this, expression, unit)
35746        self.write_keyword("TIMESTAMP_ADD");
35747        self.write("(");
35748        self.generate_expression(&e.this)?;
35749        self.write(", ");
35750        self.generate_expression(&e.expression)?;
35751        if let Some(unit) = &e.unit {
35752            self.write(", ");
35753            self.write_keyword(unit);
35754        }
35755        self.write(")");
35756        Ok(())
35757    }
35758
35759    fn generate_timestamp_diff(&mut self, e: &TimestampDiff) -> Result<()> {
35760        // TIMESTAMP_DIFF(this, expression, unit)
35761        self.write_keyword("TIMESTAMP_DIFF");
35762        self.write("(");
35763        self.generate_expression(&e.this)?;
35764        self.write(", ");
35765        self.generate_expression(&e.expression)?;
35766        if let Some(unit) = &e.unit {
35767            self.write(", ");
35768            self.write_keyword(unit);
35769        }
35770        self.write(")");
35771        Ok(())
35772    }
35773
35774    fn generate_timestamp_from_parts(&mut self, e: &TimestampFromParts) -> Result<()> {
35775        // TIMESTAMP_FROM_PARTS(this, expression)
35776        self.write_keyword("TIMESTAMP_FROM_PARTS");
35777        self.write("(");
35778        if let Some(this) = &e.this {
35779            self.generate_expression(this)?;
35780        }
35781        if let Some(expression) = &e.expression {
35782            self.write(", ");
35783            self.generate_expression(expression)?;
35784        }
35785        if let Some(zone) = &e.zone {
35786            self.write(", ");
35787            self.generate_expression(zone)?;
35788        }
35789        if let Some(milli) = &e.milli {
35790            self.write(", ");
35791            self.generate_expression(milli)?;
35792        }
35793        self.write(")");
35794        Ok(())
35795    }
35796
35797    fn generate_timestamp_sub(&mut self, e: &TimestampSub) -> Result<()> {
35798        // TIMESTAMP_SUB(this, INTERVAL expression unit)
35799        self.write_keyword("TIMESTAMP_SUB");
35800        self.write("(");
35801        self.generate_expression(&e.this)?;
35802        self.write(", ");
35803        self.write_keyword("INTERVAL");
35804        self.write_space();
35805        self.generate_expression(&e.expression)?;
35806        if let Some(unit) = &e.unit {
35807            self.write_space();
35808            self.write_keyword(unit);
35809        }
35810        self.write(")");
35811        Ok(())
35812    }
35813
35814    fn generate_timestamp_tz_from_parts(&mut self, e: &TimestampTzFromParts) -> Result<()> {
35815        // TIMESTAMP_TZ_FROM_PARTS(...)
35816        self.write_keyword("TIMESTAMP_TZ_FROM_PARTS");
35817        self.write("(");
35818        if let Some(zone) = &e.zone {
35819            self.generate_expression(zone)?;
35820        }
35821        self.write(")");
35822        Ok(())
35823    }
35824
35825    fn generate_to_binary(&mut self, e: &ToBinary) -> Result<()> {
35826        // TO_BINARY(this, [format])
35827        self.write_keyword("TO_BINARY");
35828        self.write("(");
35829        self.generate_expression(&e.this)?;
35830        if let Some(format) = &e.format {
35831            self.write(", '");
35832            self.write(format);
35833            self.write("'");
35834        }
35835        self.write(")");
35836        Ok(())
35837    }
35838
35839    fn generate_to_boolean(&mut self, e: &ToBoolean) -> Result<()> {
35840        // TO_BOOLEAN(this)
35841        self.write_keyword("TO_BOOLEAN");
35842        self.write("(");
35843        self.generate_expression(&e.this)?;
35844        self.write(")");
35845        Ok(())
35846    }
35847
35848    fn generate_to_char(&mut self, e: &ToChar) -> Result<()> {
35849        // TO_CHAR(this, [format], [nlsparam])
35850        self.write_keyword("TO_CHAR");
35851        self.write("(");
35852        self.generate_expression(&e.this)?;
35853        if let Some(format) = &e.format {
35854            self.write(", '");
35855            self.write(format);
35856            self.write("'");
35857        }
35858        if let Some(nlsparam) = &e.nlsparam {
35859            self.write(", ");
35860            self.generate_expression(nlsparam)?;
35861        }
35862        self.write(")");
35863        Ok(())
35864    }
35865
35866    fn generate_to_decfloat(&mut self, e: &ToDecfloat) -> Result<()> {
35867        // TO_DECFLOAT(this, [format])
35868        self.write_keyword("TO_DECFLOAT");
35869        self.write("(");
35870        self.generate_expression(&e.this)?;
35871        if let Some(format) = &e.format {
35872            self.write(", '");
35873            self.write(format);
35874            self.write("'");
35875        }
35876        self.write(")");
35877        Ok(())
35878    }
35879
35880    fn generate_to_double(&mut self, e: &ToDouble) -> Result<()> {
35881        // TO_DOUBLE(this, [format])
35882        self.write_keyword("TO_DOUBLE");
35883        self.write("(");
35884        self.generate_expression(&e.this)?;
35885        if let Some(format) = &e.format {
35886            self.write(", '");
35887            self.write(format);
35888            self.write("'");
35889        }
35890        self.write(")");
35891        Ok(())
35892    }
35893
35894    fn generate_to_file(&mut self, e: &ToFile) -> Result<()> {
35895        // TO_FILE(this, path)
35896        self.write_keyword("TO_FILE");
35897        self.write("(");
35898        self.generate_expression(&e.this)?;
35899        if let Some(path) = &e.path {
35900            self.write(", ");
35901            self.generate_expression(path)?;
35902        }
35903        self.write(")");
35904        Ok(())
35905    }
35906
35907    fn generate_to_number(&mut self, e: &ToNumber) -> Result<()> {
35908        // TO_NUMBER or TRY_TO_NUMBER (this, [format], [precision], [scale])
35909        // If safe flag is set, output TRY_TO_NUMBER
35910        let is_safe = e.safe.is_some();
35911        if is_safe {
35912            self.write_keyword("TRY_TO_NUMBER");
35913        } else {
35914            self.write_keyword("TO_NUMBER");
35915        }
35916        self.write("(");
35917        self.generate_expression(&e.this)?;
35918        if let Some(format) = &e.format {
35919            self.write(", ");
35920            self.generate_expression(format)?;
35921        }
35922        if let Some(nlsparam) = &e.nlsparam {
35923            self.write(", ");
35924            self.generate_expression(nlsparam)?;
35925        }
35926        if let Some(precision) = &e.precision {
35927            self.write(", ");
35928            self.generate_expression(precision)?;
35929        }
35930        if let Some(scale) = &e.scale {
35931            self.write(", ");
35932            self.generate_expression(scale)?;
35933        }
35934        self.write(")");
35935        Ok(())
35936    }
35937
35938    fn generate_to_table_property(&mut self, e: &ToTableProperty) -> Result<()> {
35939        // TO_TABLE this
35940        self.write_keyword("TO_TABLE");
35941        self.write_space();
35942        self.generate_expression(&e.this)?;
35943        Ok(())
35944    }
35945
35946    fn generate_transaction(&mut self, e: &Transaction) -> Result<()> {
35947        // Check mark to determine the format
35948        let mark_text = e.mark.as_ref().map(|m| match m.as_ref() {
35949            Expression::Identifier(id) => id.name.clone(),
35950            Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
35951                let Literal::String(s) = lit.as_ref() else {
35952                    unreachable!()
35953                };
35954                s.clone()
35955            }
35956            _ => String::new(),
35957        });
35958
35959        let is_start = mark_text.as_ref().map_or(false, |s| s == "START");
35960        let has_transaction_keyword = mark_text.as_ref().map_or(false, |s| s == "TRANSACTION");
35961        let has_with_mark = e.mark.as_ref().map_or(false, |m| {
35962            matches!(m.as_ref(), Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)))
35963        });
35964
35965        // For Presto/Trino: always use START TRANSACTION
35966        let use_start_transaction = matches!(
35967            self.config.dialect,
35968            Some(DialectType::Presto) | Some(DialectType::Trino) | Some(DialectType::Athena)
35969        );
35970        // For most dialects: strip TRANSACTION keyword
35971        let strip_transaction = matches!(
35972            self.config.dialect,
35973            Some(DialectType::Snowflake)
35974                | Some(DialectType::PostgreSQL)
35975                | Some(DialectType::Redshift)
35976                | Some(DialectType::MySQL)
35977                | Some(DialectType::Hive)
35978                | Some(DialectType::Spark)
35979                | Some(DialectType::Databricks)
35980                | Some(DialectType::DuckDB)
35981                | Some(DialectType::Oracle)
35982                | Some(DialectType::Doris)
35983                | Some(DialectType::StarRocks)
35984                | Some(DialectType::Materialize)
35985                | Some(DialectType::ClickHouse)
35986        );
35987
35988        if is_start || use_start_transaction {
35989            // START TRANSACTION [modes]
35990            self.write_keyword("START TRANSACTION");
35991            if let Some(modes) = &e.modes {
35992                self.write_space();
35993                self.generate_expression(modes)?;
35994            }
35995        } else {
35996            // BEGIN [DEFERRED|IMMEDIATE|EXCLUSIVE] [TRANSACTION] [transaction_name] [WITH MARK 'desc']
35997            self.write_keyword("BEGIN");
35998
35999            // Check if `this` is a transaction kind (DEFERRED/IMMEDIATE/EXCLUSIVE)
36000            let is_kind = e.this.as_ref().map_or(false, |t| {
36001                if let Expression::Identifier(id) = t.as_ref() {
36002                    id.name.eq_ignore_ascii_case("DEFERRED")
36003                        || id.name.eq_ignore_ascii_case("IMMEDIATE")
36004                        || id.name.eq_ignore_ascii_case("EXCLUSIVE")
36005                } else {
36006                    false
36007                }
36008            });
36009
36010            // Output kind before TRANSACTION keyword
36011            if is_kind {
36012                if let Some(this) = &e.this {
36013                    self.write_space();
36014                    if let Expression::Identifier(id) = this.as_ref() {
36015                        self.write_keyword(&id.name);
36016                    }
36017                }
36018            }
36019
36020            // Output TRANSACTION keyword if it was present and target supports it
36021            if (has_transaction_keyword || has_with_mark) && !strip_transaction {
36022                self.write_space();
36023                self.write_keyword("TRANSACTION");
36024            }
36025
36026            // Output transaction name (not kind)
36027            if !is_kind {
36028                if let Some(this) = &e.this {
36029                    self.write_space();
36030                    self.generate_expression(this)?;
36031                }
36032            }
36033
36034            // Output WITH MARK 'description' for TSQL
36035            if has_with_mark {
36036                self.write_space();
36037                self.write_keyword("WITH MARK");
36038                if let Some(Expression::Literal(lit)) = e.mark.as_deref() {
36039                    if let Literal::String(desc) = lit.as_ref() {
36040                        if !desc.is_empty() {
36041                            self.write_space();
36042                            self.write(&format!("'{}'", desc));
36043                        }
36044                    }
36045                }
36046            }
36047
36048            // Output modes (isolation levels, etc.)
36049            if let Some(modes) = &e.modes {
36050                self.write_space();
36051                self.generate_expression(modes)?;
36052            }
36053        }
36054        Ok(())
36055    }
36056
36057    fn generate_transform(&mut self, e: &Transform) -> Result<()> {
36058        // TRANSFORM(this, expression)
36059        self.write_keyword("TRANSFORM");
36060        self.write("(");
36061        self.generate_expression(&e.this)?;
36062        self.write(", ");
36063        self.generate_expression(&e.expression)?;
36064        self.write(")");
36065        Ok(())
36066    }
36067
36068    fn generate_transform_model_property(&mut self, e: &TransformModelProperty) -> Result<()> {
36069        // TRANSFORM(expressions)
36070        self.write_keyword("TRANSFORM");
36071        self.write("(");
36072        if self.config.pretty && !e.expressions.is_empty() {
36073            self.indent_level += 1;
36074            for (i, expr) in e.expressions.iter().enumerate() {
36075                if i > 0 {
36076                    self.write(",");
36077                }
36078                self.write_newline();
36079                self.write_indent();
36080                self.generate_expression(expr)?;
36081            }
36082            self.indent_level -= 1;
36083            self.write_newline();
36084            self.write(")");
36085        } else {
36086            for (i, expr) in e.expressions.iter().enumerate() {
36087                if i > 0 {
36088                    self.write(", ");
36089                }
36090                self.generate_expression(expr)?;
36091            }
36092            self.write(")");
36093        }
36094        Ok(())
36095    }
36096
36097    fn generate_transient_property(&mut self, e: &TransientProperty) -> Result<()> {
36098        use crate::dialects::DialectType;
36099        // TRANSIENT is Snowflake-specific; skip for other dialects
36100        if let Some(this) = &e.this {
36101            self.generate_expression(this)?;
36102            if matches!(self.config.dialect, Some(DialectType::Snowflake) | None) {
36103                self.write_space();
36104            }
36105        }
36106        if matches!(self.config.dialect, Some(DialectType::Snowflake) | None) {
36107            self.write_keyword("TRANSIENT");
36108        }
36109        Ok(())
36110    }
36111
36112    fn generate_translate(&mut self, e: &Translate) -> Result<()> {
36113        // TRANSLATE(this, from_, to)
36114        self.write_keyword("TRANSLATE");
36115        self.write("(");
36116        self.generate_expression(&e.this)?;
36117        if let Some(from) = &e.from_ {
36118            self.write(", ");
36119            self.generate_expression(from)?;
36120        }
36121        if let Some(to) = &e.to {
36122            self.write(", ");
36123            self.generate_expression(to)?;
36124        }
36125        self.write(")");
36126        Ok(())
36127    }
36128
36129    fn generate_translate_characters(&mut self, e: &TranslateCharacters) -> Result<()> {
36130        // TRANSLATE(this USING expression)
36131        self.write_keyword("TRANSLATE");
36132        self.write("(");
36133        self.generate_expression(&e.this)?;
36134        self.write_space();
36135        self.write_keyword("USING");
36136        self.write_space();
36137        self.generate_expression(&e.expression)?;
36138        if e.with_error.is_some() {
36139            self.write_space();
36140            self.write_keyword("WITH ERROR");
36141        }
36142        self.write(")");
36143        Ok(())
36144    }
36145
36146    fn generate_truncate_table(&mut self, e: &TruncateTable) -> Result<()> {
36147        // TRUNCATE TABLE table1, table2, ...
36148        self.write_keyword("TRUNCATE TABLE");
36149        self.write_space();
36150        for (i, expr) in e.expressions.iter().enumerate() {
36151            if i > 0 {
36152                self.write(", ");
36153            }
36154            self.generate_expression(expr)?;
36155        }
36156        Ok(())
36157    }
36158
36159    fn generate_try_base64_decode_binary(&mut self, e: &TryBase64DecodeBinary) -> Result<()> {
36160        // TRY_BASE64_DECODE_BINARY(this, [alphabet])
36161        self.write_keyword("TRY_BASE64_DECODE_BINARY");
36162        self.write("(");
36163        self.generate_expression(&e.this)?;
36164        if let Some(alphabet) = &e.alphabet {
36165            self.write(", ");
36166            self.generate_expression(alphabet)?;
36167        }
36168        self.write(")");
36169        Ok(())
36170    }
36171
36172    fn generate_try_base64_decode_string(&mut self, e: &TryBase64DecodeString) -> Result<()> {
36173        // TRY_BASE64_DECODE_STRING(this, [alphabet])
36174        self.write_keyword("TRY_BASE64_DECODE_STRING");
36175        self.write("(");
36176        self.generate_expression(&e.this)?;
36177        if let Some(alphabet) = &e.alphabet {
36178            self.write(", ");
36179            self.generate_expression(alphabet)?;
36180        }
36181        self.write(")");
36182        Ok(())
36183    }
36184
36185    fn generate_try_to_decfloat(&mut self, e: &TryToDecfloat) -> Result<()> {
36186        // TRY_TO_DECFLOAT(this, [format])
36187        self.write_keyword("TRY_TO_DECFLOAT");
36188        self.write("(");
36189        self.generate_expression(&e.this)?;
36190        if let Some(format) = &e.format {
36191            self.write(", '");
36192            self.write(format);
36193            self.write("'");
36194        }
36195        self.write(")");
36196        Ok(())
36197    }
36198
36199    fn generate_ts_or_ds_add(&mut self, e: &TsOrDsAdd) -> Result<()> {
36200        // TS_OR_DS_ADD(this, expression, [unit], [return_type])
36201        self.write_keyword("TS_OR_DS_ADD");
36202        self.write("(");
36203        self.generate_expression(&e.this)?;
36204        self.write(", ");
36205        self.generate_expression(&e.expression)?;
36206        if let Some(unit) = &e.unit {
36207            self.write(", ");
36208            self.write_keyword(unit);
36209        }
36210        if let Some(return_type) = &e.return_type {
36211            self.write(", ");
36212            self.generate_expression(return_type)?;
36213        }
36214        self.write(")");
36215        Ok(())
36216    }
36217
36218    fn generate_ts_or_ds_diff(&mut self, e: &TsOrDsDiff) -> Result<()> {
36219        // TS_OR_DS_DIFF(this, expression, [unit])
36220        self.write_keyword("TS_OR_DS_DIFF");
36221        self.write("(");
36222        self.generate_expression(&e.this)?;
36223        self.write(", ");
36224        self.generate_expression(&e.expression)?;
36225        if let Some(unit) = &e.unit {
36226            self.write(", ");
36227            self.write_keyword(unit);
36228        }
36229        self.write(")");
36230        Ok(())
36231    }
36232
36233    fn generate_ts_or_ds_to_date(&mut self, e: &TsOrDsToDate) -> Result<()> {
36234        let default_time_format = "%Y-%m-%d %H:%M:%S";
36235        let default_date_format = "%Y-%m-%d";
36236        let has_non_default_format = e.format.as_ref().map_or(false, |f| {
36237            f != default_time_format && f != default_date_format
36238        });
36239
36240        if has_non_default_format {
36241            // With non-default format: dialect-specific handling
36242            let fmt = e.format.as_ref().unwrap();
36243            match self.config.dialect {
36244                Some(DialectType::MySQL) | Some(DialectType::StarRocks) => {
36245                    // MySQL/StarRocks: STR_TO_DATE(x, fmt) - no CAST wrapper
36246                    // STR_TO_DATE is the MySQL-native form of StrToTime
36247                    let str_to_time = crate::expressions::StrToTime {
36248                        this: Box::new((*e.this).clone()),
36249                        format: fmt.clone(),
36250                        zone: None,
36251                        safe: None,
36252                        target_type: None,
36253                    };
36254                    self.generate_str_to_time(&str_to_time)?;
36255                }
36256                Some(DialectType::Hive)
36257                | Some(DialectType::Spark)
36258                | Some(DialectType::Databricks) => {
36259                    // Hive/Spark: TO_DATE(x, java_fmt)
36260                    self.write_keyword("TO_DATE");
36261                    self.write("(");
36262                    self.generate_expression(&e.this)?;
36263                    self.write(", '");
36264                    self.write(&Self::strftime_to_java_format(fmt));
36265                    self.write("')");
36266                }
36267                Some(DialectType::Snowflake) => {
36268                    // Snowflake: TO_DATE(x, snowflake_fmt)
36269                    self.write_keyword("TO_DATE");
36270                    self.write("(");
36271                    self.generate_expression(&e.this)?;
36272                    self.write(", '");
36273                    self.write(&Self::strftime_to_snowflake_format(fmt));
36274                    self.write("')");
36275                }
36276                Some(DialectType::Doris) => {
36277                    // Doris: TO_DATE(x) - ignores format
36278                    self.write_keyword("TO_DATE");
36279                    self.write("(");
36280                    self.generate_expression(&e.this)?;
36281                    self.write(")");
36282                }
36283                _ => {
36284                    // Default: CAST(STR_TO_TIME(x, fmt) AS DATE)
36285                    self.write_keyword("CAST");
36286                    self.write("(");
36287                    let str_to_time = crate::expressions::StrToTime {
36288                        this: Box::new((*e.this).clone()),
36289                        format: fmt.clone(),
36290                        zone: None,
36291                        safe: None,
36292                        target_type: None,
36293                    };
36294                    self.generate_str_to_time(&str_to_time)?;
36295                    self.write_keyword(" AS ");
36296                    self.write_keyword("DATE");
36297                    self.write(")");
36298                }
36299            }
36300        } else {
36301            // Without format (or default format): simple date conversion
36302            match self.config.dialect {
36303                Some(DialectType::MySQL)
36304                | Some(DialectType::SQLite)
36305                | Some(DialectType::StarRocks) => {
36306                    // MySQL/SQLite/StarRocks: DATE(x)
36307                    self.write_keyword("DATE");
36308                    self.write("(");
36309                    self.generate_expression(&e.this)?;
36310                    self.write(")");
36311                }
36312                Some(DialectType::Hive)
36313                | Some(DialectType::Spark)
36314                | Some(DialectType::Databricks)
36315                | Some(DialectType::Snowflake)
36316                | Some(DialectType::Doris) => {
36317                    // Hive/Spark/Databricks/Snowflake/Doris: TO_DATE(x)
36318                    self.write_keyword("TO_DATE");
36319                    self.write("(");
36320                    self.generate_expression(&e.this)?;
36321                    self.write(")");
36322                }
36323                Some(DialectType::Presto)
36324                | Some(DialectType::Trino)
36325                | Some(DialectType::Athena) => {
36326                    // Presto/Trino: CAST(CAST(x AS TIMESTAMP) AS DATE)
36327                    self.write_keyword("CAST");
36328                    self.write("(");
36329                    self.write_keyword("CAST");
36330                    self.write("(");
36331                    self.generate_expression(&e.this)?;
36332                    self.write_keyword(" AS ");
36333                    self.write_keyword("TIMESTAMP");
36334                    self.write(")");
36335                    self.write_keyword(" AS ");
36336                    self.write_keyword("DATE");
36337                    self.write(")");
36338                }
36339                Some(DialectType::ClickHouse) => {
36340                    // ClickHouse: CAST(x AS Nullable(DATE))
36341                    self.write_keyword("CAST");
36342                    self.write("(");
36343                    self.generate_expression(&e.this)?;
36344                    self.write_keyword(" AS ");
36345                    self.write("Nullable(DATE)");
36346                    self.write(")");
36347                }
36348                _ => {
36349                    // Default: CAST(x AS DATE)
36350                    self.write_keyword("CAST");
36351                    self.write("(");
36352                    self.generate_expression(&e.this)?;
36353                    self.write_keyword(" AS ");
36354                    self.write_keyword("DATE");
36355                    self.write(")");
36356                }
36357            }
36358        }
36359        Ok(())
36360    }
36361
36362    fn generate_ts_or_ds_to_time(&mut self, e: &TsOrDsToTime) -> Result<()> {
36363        // TS_OR_DS_TO_TIME(this, [format])
36364        self.write_keyword("TS_OR_DS_TO_TIME");
36365        self.write("(");
36366        self.generate_expression(&e.this)?;
36367        if let Some(format) = &e.format {
36368            self.write(", '");
36369            self.write(format);
36370            self.write("'");
36371        }
36372        self.write(")");
36373        Ok(())
36374    }
36375
36376    fn generate_unhex(&mut self, e: &Unhex) -> Result<()> {
36377        // UNHEX(this, [expression])
36378        self.write_keyword("UNHEX");
36379        self.write("(");
36380        self.generate_expression(&e.this)?;
36381        if let Some(expression) = &e.expression {
36382            self.write(", ");
36383            self.generate_expression(expression)?;
36384        }
36385        self.write(")");
36386        Ok(())
36387    }
36388
36389    fn generate_unicode_string(&mut self, e: &UnicodeString) -> Result<()> {
36390        // U&this [UESCAPE escape]
36391        self.write("U&");
36392        self.generate_expression(&e.this)?;
36393        if let Some(escape) = &e.escape {
36394            self.write_space();
36395            self.write_keyword("UESCAPE");
36396            self.write_space();
36397            self.generate_expression(escape)?;
36398        }
36399        Ok(())
36400    }
36401
36402    fn generate_uniform(&mut self, e: &Uniform) -> Result<()> {
36403        // UNIFORM(this, expression, [gen], [seed])
36404        self.write_keyword("UNIFORM");
36405        self.write("(");
36406        self.generate_expression(&e.this)?;
36407        self.write(", ");
36408        self.generate_expression(&e.expression)?;
36409        if let Some(gen) = &e.gen {
36410            self.write(", ");
36411            self.generate_expression(gen)?;
36412        }
36413        if let Some(seed) = &e.seed {
36414            self.write(", ");
36415            self.generate_expression(seed)?;
36416        }
36417        self.write(")");
36418        Ok(())
36419    }
36420
36421    fn generate_unique_column_constraint(&mut self, e: &UniqueColumnConstraint) -> Result<()> {
36422        // UNIQUE [NULLS NOT DISTINCT] [this] [index_type] [on_conflict] [options]
36423        self.write_keyword("UNIQUE");
36424        // Output NULLS NOT DISTINCT if nulls is set (PostgreSQL 15+ feature)
36425        if e.nulls.is_some() {
36426            self.write(" NULLS NOT DISTINCT");
36427        }
36428        if let Some(this) = &e.this {
36429            self.write_space();
36430            self.generate_expression(this)?;
36431        }
36432        if let Some(index_type) = &e.index_type {
36433            self.write(" USING ");
36434            self.generate_expression(index_type)?;
36435        }
36436        if let Some(on_conflict) = &e.on_conflict {
36437            self.write_space();
36438            self.generate_expression(on_conflict)?;
36439        }
36440        for opt in &e.options {
36441            self.write_space();
36442            self.generate_expression(opt)?;
36443        }
36444        Ok(())
36445    }
36446
36447    fn generate_unique_key_property(&mut self, e: &UniqueKeyProperty) -> Result<()> {
36448        // UNIQUE KEY (expressions)
36449        self.write_keyword("UNIQUE KEY");
36450        self.write(" (");
36451        for (i, expr) in e.expressions.iter().enumerate() {
36452            if i > 0 {
36453                self.write(", ");
36454            }
36455            self.generate_expression(expr)?;
36456        }
36457        self.write(")");
36458        Ok(())
36459    }
36460
36461    fn generate_rollup_property(&mut self, e: &RollupProperty) -> Result<()> {
36462        // ROLLUP (r1(col1, col2), r2(col1))
36463        self.write_keyword("ROLLUP");
36464        self.write(" (");
36465        for (i, index) in e.expressions.iter().enumerate() {
36466            if i > 0 {
36467                self.write(", ");
36468            }
36469            self.generate_identifier(&index.name)?;
36470            self.write("(");
36471            for (j, col) in index.expressions.iter().enumerate() {
36472                if j > 0 {
36473                    self.write(", ");
36474                }
36475                self.generate_identifier(col)?;
36476            }
36477            self.write(")");
36478        }
36479        self.write(")");
36480        Ok(())
36481    }
36482
36483    fn generate_unix_to_str(&mut self, e: &UnixToStr) -> Result<()> {
36484        match self.config.dialect {
36485            Some(DialectType::DuckDB) => {
36486                // DuckDB: STRFTIME(TO_TIMESTAMP(value), format)
36487                self.write_keyword("STRFTIME");
36488                self.write("(");
36489                self.write_keyword("TO_TIMESTAMP");
36490                self.write("(");
36491                self.generate_expression(&e.this)?;
36492                self.write("), '");
36493                if let Some(format) = &e.format {
36494                    self.write(format);
36495                }
36496                self.write("')");
36497            }
36498            Some(DialectType::Hive) => {
36499                // Hive: FROM_UNIXTIME(value, format) - elide format when it's the default
36500                self.write_keyword("FROM_UNIXTIME");
36501                self.write("(");
36502                self.generate_expression(&e.this)?;
36503                if let Some(format) = &e.format {
36504                    if format != "yyyy-MM-dd HH:mm:ss" {
36505                        self.write(", '");
36506                        self.write(format);
36507                        self.write("'");
36508                    }
36509                }
36510                self.write(")");
36511            }
36512            Some(DialectType::Presto) | Some(DialectType::Trino) => {
36513                // Presto: DATE_FORMAT(FROM_UNIXTIME(value), format)
36514                self.write_keyword("DATE_FORMAT");
36515                self.write("(");
36516                self.write_keyword("FROM_UNIXTIME");
36517                self.write("(");
36518                self.generate_expression(&e.this)?;
36519                self.write("), '");
36520                if let Some(format) = &e.format {
36521                    self.write(format);
36522                }
36523                self.write("')");
36524            }
36525            Some(DialectType::Spark) | Some(DialectType::Databricks) => {
36526                // Spark: FROM_UNIXTIME(value, format)
36527                self.write_keyword("FROM_UNIXTIME");
36528                self.write("(");
36529                self.generate_expression(&e.this)?;
36530                if let Some(format) = &e.format {
36531                    self.write(", '");
36532                    self.write(format);
36533                    self.write("'");
36534                }
36535                self.write(")");
36536            }
36537            _ => {
36538                // Default: UNIX_TO_STR(this, [format])
36539                self.write_keyword("UNIX_TO_STR");
36540                self.write("(");
36541                self.generate_expression(&e.this)?;
36542                if let Some(format) = &e.format {
36543                    self.write(", '");
36544                    self.write(format);
36545                    self.write("'");
36546                }
36547                self.write(")");
36548            }
36549        }
36550        Ok(())
36551    }
36552
36553    fn generate_unix_to_time(&mut self, e: &UnixToTime) -> Result<()> {
36554        use crate::dialects::DialectType;
36555        let scale = e.scale.unwrap_or(0); // 0 = seconds
36556
36557        match self.config.dialect {
36558            Some(DialectType::Snowflake) => {
36559                // Snowflake: TO_TIMESTAMP(value[, scale]) - skip scale for seconds (0)
36560                self.write_keyword("TO_TIMESTAMP");
36561                self.write("(");
36562                self.generate_expression(&e.this)?;
36563                if let Some(s) = e.scale {
36564                    if s > 0 {
36565                        self.write(", ");
36566                        self.write(&s.to_string());
36567                    }
36568                }
36569                self.write(")");
36570            }
36571            Some(DialectType::BigQuery) => {
36572                // BigQuery: TIMESTAMP_SECONDS(value) / TIMESTAMP_MILLIS(value)
36573                // or TIMESTAMP_SECONDS(CAST(value / POWER(10, scale) AS INT64)) for other scales
36574                match scale {
36575                    0 => {
36576                        self.write_keyword("TIMESTAMP_SECONDS");
36577                        self.write("(");
36578                        self.generate_expression(&e.this)?;
36579                        self.write(")");
36580                    }
36581                    3 => {
36582                        self.write_keyword("TIMESTAMP_MILLIS");
36583                        self.write("(");
36584                        self.generate_expression(&e.this)?;
36585                        self.write(")");
36586                    }
36587                    6 => {
36588                        self.write_keyword("TIMESTAMP_MICROS");
36589                        self.write("(");
36590                        self.generate_expression(&e.this)?;
36591                        self.write(")");
36592                    }
36593                    _ => {
36594                        // TIMESTAMP_SECONDS(CAST(value / POWER(10, scale) AS INT64))
36595                        self.write_keyword("TIMESTAMP_SECONDS");
36596                        self.write("(CAST(");
36597                        self.generate_expression(&e.this)?;
36598                        self.write(&format!(" / POWER(10, {}) AS INT64))", scale));
36599                    }
36600                }
36601            }
36602            Some(DialectType::Spark) => {
36603                // Spark: CAST(FROM_UNIXTIME(value) AS TIMESTAMP) for scale=0
36604                // TIMESTAMP_MILLIS(value) for scale=3
36605                // TIMESTAMP_MICROS(value) for scale=6
36606                // TIMESTAMP_SECONDS(value / POWER(10, scale)) for other scales
36607                match scale {
36608                    0 => {
36609                        self.write_keyword("CAST");
36610                        self.write("(");
36611                        self.write_keyword("FROM_UNIXTIME");
36612                        self.write("(");
36613                        self.generate_expression(&e.this)?;
36614                        self.write(") ");
36615                        self.write_keyword("AS TIMESTAMP");
36616                        self.write(")");
36617                    }
36618                    3 => {
36619                        self.write_keyword("TIMESTAMP_MILLIS");
36620                        self.write("(");
36621                        self.generate_expression(&e.this)?;
36622                        self.write(")");
36623                    }
36624                    6 => {
36625                        self.write_keyword("TIMESTAMP_MICROS");
36626                        self.write("(");
36627                        self.generate_expression(&e.this)?;
36628                        self.write(")");
36629                    }
36630                    _ => {
36631                        self.write_keyword("TIMESTAMP_SECONDS");
36632                        self.write("(");
36633                        self.generate_expression(&e.this)?;
36634                        self.write(&format!(" / POWER(10, {}))", scale));
36635                    }
36636                }
36637            }
36638            Some(DialectType::Databricks) => {
36639                // Databricks: CAST(FROM_UNIXTIME(value) AS TIMESTAMP) for scale=0
36640                // TIMESTAMP_MILLIS(value) for scale=3
36641                // TIMESTAMP_MICROS(value) for scale=6
36642                match scale {
36643                    0 => {
36644                        self.write_keyword("CAST");
36645                        self.write("(");
36646                        self.write_keyword("FROM_UNIXTIME");
36647                        self.write("(");
36648                        self.generate_expression(&e.this)?;
36649                        self.write(") ");
36650                        self.write_keyword("AS TIMESTAMP");
36651                        self.write(")");
36652                    }
36653                    3 => {
36654                        self.write_keyword("TIMESTAMP_MILLIS");
36655                        self.write("(");
36656                        self.generate_expression(&e.this)?;
36657                        self.write(")");
36658                    }
36659                    6 => {
36660                        self.write_keyword("TIMESTAMP_MICROS");
36661                        self.write("(");
36662                        self.generate_expression(&e.this)?;
36663                        self.write(")");
36664                    }
36665                    _ => {
36666                        self.write_keyword("TIMESTAMP_SECONDS");
36667                        self.write("(");
36668                        self.generate_expression(&e.this)?;
36669                        self.write(&format!(" / POWER(10, {}))", scale));
36670                    }
36671                }
36672            }
36673            Some(DialectType::Hive) => {
36674                // Hive: FROM_UNIXTIME(value)
36675                if scale == 0 {
36676                    self.write_keyword("FROM_UNIXTIME");
36677                    self.write("(");
36678                    self.generate_expression(&e.this)?;
36679                    self.write(")");
36680                } else {
36681                    self.write_keyword("FROM_UNIXTIME");
36682                    self.write("(");
36683                    self.generate_expression(&e.this)?;
36684                    self.write(&format!(" / POWER(10, {})", scale));
36685                    self.write(")");
36686                }
36687            }
36688            Some(DialectType::Presto) | Some(DialectType::Trino) => {
36689                // Presto: FROM_UNIXTIME(CAST(value AS DOUBLE) / POW(10, scale)) for scale > 0
36690                // FROM_UNIXTIME(value) for scale=0
36691                if scale == 0 {
36692                    self.write_keyword("FROM_UNIXTIME");
36693                    self.write("(");
36694                    self.generate_expression(&e.this)?;
36695                    self.write(")");
36696                } else {
36697                    self.write_keyword("FROM_UNIXTIME");
36698                    self.write("(CAST(");
36699                    self.generate_expression(&e.this)?;
36700                    self.write(&format!(" AS DOUBLE) / POW(10, {}))", scale));
36701                }
36702            }
36703            Some(DialectType::DuckDB) => {
36704                // DuckDB: TO_TIMESTAMP(value) for scale=0
36705                // EPOCH_MS(value) for scale=3
36706                // MAKE_TIMESTAMP(value) for scale=6
36707                match scale {
36708                    0 => {
36709                        self.write_keyword("TO_TIMESTAMP");
36710                        self.write("(");
36711                        self.generate_expression(&e.this)?;
36712                        self.write(")");
36713                    }
36714                    3 => {
36715                        self.write_keyword("EPOCH_MS");
36716                        self.write("(");
36717                        self.generate_expression(&e.this)?;
36718                        self.write(")");
36719                    }
36720                    6 => {
36721                        self.write_keyword("MAKE_TIMESTAMP");
36722                        self.write("(");
36723                        self.generate_expression(&e.this)?;
36724                        self.write(")");
36725                    }
36726                    _ => {
36727                        self.write_keyword("TO_TIMESTAMP");
36728                        self.write("(");
36729                        self.generate_expression(&e.this)?;
36730                        self.write(&format!(" / POWER(10, {}))", scale));
36731                        self.write_keyword(" AT TIME ZONE");
36732                        self.write(" 'UTC'");
36733                    }
36734                }
36735            }
36736            Some(DialectType::Doris) | Some(DialectType::StarRocks) => {
36737                // Doris/StarRocks: FROM_UNIXTIME(value)
36738                self.write_keyword("FROM_UNIXTIME");
36739                self.write("(");
36740                self.generate_expression(&e.this)?;
36741                self.write(")");
36742            }
36743            Some(DialectType::Oracle) => {
36744                // Oracle: TO_DATE('1970-01-01', 'YYYY-MM-DD') + (x / 86400)
36745                self.write("TO_DATE('1970-01-01', 'YYYY-MM-DD') + (");
36746                self.generate_expression(&e.this)?;
36747                self.write(" / 86400)");
36748            }
36749            Some(DialectType::Redshift) => {
36750                // Redshift: (TIMESTAMP 'epoch' + value * INTERVAL '1 SECOND') for scale=0
36751                // (TIMESTAMP 'epoch' + (value / POWER(10, scale)) * INTERVAL '1 SECOND') for scale > 0
36752                self.write("(TIMESTAMP 'epoch' + ");
36753                if scale == 0 {
36754                    self.generate_expression(&e.this)?;
36755                } else {
36756                    self.write("(");
36757                    self.generate_expression(&e.this)?;
36758                    self.write(&format!(" / POWER(10, {}))", scale));
36759                }
36760                self.write(" * INTERVAL '1 SECOND')");
36761            }
36762            Some(DialectType::Exasol) => {
36763                // Exasol: FROM_POSIX_TIME(value)
36764                self.write_keyword("FROM_POSIX_TIME");
36765                self.write("(");
36766                self.generate_expression(&e.this)?;
36767                self.write(")");
36768            }
36769            _ => {
36770                // Default: TO_TIMESTAMP(value[, scale])
36771                self.write_keyword("TO_TIMESTAMP");
36772                self.write("(");
36773                self.generate_expression(&e.this)?;
36774                if let Some(s) = e.scale {
36775                    self.write(", ");
36776                    self.write(&s.to_string());
36777                }
36778                self.write(")");
36779            }
36780        }
36781        Ok(())
36782    }
36783
36784    fn generate_unpivot_columns(&mut self, e: &UnpivotColumns) -> Result<()> {
36785        // NAME col VALUE col1, col2, ...
36786        if !matches!(&*e.this, Expression::Null(_)) {
36787            self.write_keyword("NAME");
36788            self.write_space();
36789            self.generate_expression(&e.this)?;
36790        }
36791        if !e.expressions.is_empty() {
36792            self.write_space();
36793            self.write_keyword("VALUE");
36794            self.write_space();
36795            for (i, expr) in e.expressions.iter().enumerate() {
36796                if i > 0 {
36797                    self.write(", ");
36798                }
36799                self.generate_expression(expr)?;
36800            }
36801        }
36802        Ok(())
36803    }
36804
36805    fn generate_user_defined_function(&mut self, e: &UserDefinedFunction) -> Result<()> {
36806        // this(expressions) or (this)(expressions)
36807        if e.wrapped.is_some() {
36808            self.write("(");
36809        }
36810        self.generate_expression(&e.this)?;
36811        if e.wrapped.is_some() {
36812            self.write(")");
36813        }
36814        self.write("(");
36815        for (i, expr) in e.expressions.iter().enumerate() {
36816            if i > 0 {
36817                self.write(", ");
36818            }
36819            self.generate_expression(expr)?;
36820        }
36821        self.write(")");
36822        Ok(())
36823    }
36824
36825    fn generate_using_template_property(&mut self, e: &UsingTemplateProperty) -> Result<()> {
36826        // USING TEMPLATE this
36827        self.write_keyword("USING TEMPLATE");
36828        self.write_space();
36829        self.generate_expression(&e.this)?;
36830        Ok(())
36831    }
36832
36833    fn generate_utc_time(&mut self, _e: &UtcTime) -> Result<()> {
36834        // UTC_TIME
36835        self.write_keyword("UTC_TIME");
36836        Ok(())
36837    }
36838
36839    fn generate_utc_timestamp(&mut self, _e: &UtcTimestamp) -> Result<()> {
36840        if matches!(
36841            self.config.dialect,
36842            Some(crate::dialects::DialectType::ClickHouse)
36843        ) {
36844            self.write_keyword("CURRENT_TIMESTAMP");
36845            self.write("('UTC')");
36846        } else {
36847            self.write_keyword("UTC_TIMESTAMP");
36848        }
36849        Ok(())
36850    }
36851
36852    fn generate_uuid(&mut self, e: &Uuid) -> Result<()> {
36853        use crate::dialects::DialectType;
36854        // Choose UUID function name based on target dialect
36855        let func_name = match self.config.dialect {
36856            Some(DialectType::Snowflake) => "UUID_STRING",
36857            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift) => "GEN_RANDOM_UUID",
36858            Some(DialectType::BigQuery) => "GENERATE_UUID",
36859            _ => {
36860                if let Some(name) = &e.name {
36861                    name.as_str()
36862                } else {
36863                    "UUID"
36864                }
36865            }
36866        };
36867        self.write_keyword(func_name);
36868        self.write("(");
36869        if let Some(this) = &e.this {
36870            self.generate_expression(this)?;
36871        }
36872        self.write(")");
36873        Ok(())
36874    }
36875
36876    fn generate_var_map(&mut self, e: &VarMap) -> Result<()> {
36877        // MAP(key1, value1, key2, value2, ...)
36878        self.write_keyword("MAP");
36879        self.write("(");
36880        let mut first = true;
36881        for (k, v) in e.keys.iter().zip(e.values.iter()) {
36882            if !first {
36883                self.write(", ");
36884            }
36885            self.generate_expression(k)?;
36886            self.write(", ");
36887            self.generate_expression(v)?;
36888            first = false;
36889        }
36890        self.write(")");
36891        Ok(())
36892    }
36893
36894    fn generate_vector_search(&mut self, e: &VectorSearch) -> Result<()> {
36895        // VECTOR_SEARCH(this, column_to_search, query_table, query_column_to_search, top_k, distance_type, ...)
36896        self.write_keyword("VECTOR_SEARCH");
36897        self.write("(");
36898        self.generate_expression(&e.this)?;
36899        if let Some(col) = &e.column_to_search {
36900            self.write(", ");
36901            self.generate_expression(col)?;
36902        }
36903        if let Some(query_table) = &e.query_table {
36904            self.write(", ");
36905            self.generate_expression(query_table)?;
36906        }
36907        if let Some(query_col) = &e.query_column_to_search {
36908            self.write(", ");
36909            self.generate_expression(query_col)?;
36910        }
36911        if let Some(top_k) = &e.top_k {
36912            self.write(", ");
36913            self.generate_expression(top_k)?;
36914        }
36915        if let Some(dist_type) = &e.distance_type {
36916            self.write(", ");
36917            self.generate_expression(dist_type)?;
36918        }
36919        self.write(")");
36920        Ok(())
36921    }
36922
36923    fn generate_version(&mut self, e: &Version) -> Result<()> {
36924        // Python: f"FOR {expression.name} {kind} {expr}"
36925        // e.this = Identifier("TIMESTAMP" or "VERSION")
36926        // e.kind = "AS OF" (or "BETWEEN", etc.)
36927        // e.expression = the value expression
36928        // Hive does NOT use the FOR prefix for time travel
36929        use crate::dialects::DialectType;
36930        let skip_for = matches!(
36931            self.config.dialect,
36932            Some(DialectType::Hive) | Some(DialectType::Spark) | Some(DialectType::Databricks)
36933        );
36934        if !skip_for {
36935            self.write_keyword("FOR");
36936            self.write_space();
36937        }
36938        // Extract the name from this (which is an Identifier expression)
36939        match e.this.as_ref() {
36940            Expression::Identifier(ident) => {
36941                self.write_keyword(&ident.name);
36942            }
36943            _ => {
36944                self.generate_expression(&e.this)?;
36945            }
36946        }
36947        self.write_space();
36948        self.write_keyword(&e.kind);
36949        if let Some(expression) = &e.expression {
36950            self.write_space();
36951            self.generate_expression(expression)?;
36952        }
36953        Ok(())
36954    }
36955
36956    fn generate_view_attribute_property(&mut self, e: &ViewAttributeProperty) -> Result<()> {
36957        // Python: return self.sql(expression, "this")
36958        self.generate_expression(&e.this)?;
36959        Ok(())
36960    }
36961
36962    fn generate_volatile_property(&mut self, e: &VolatileProperty) -> Result<()> {
36963        // Python: return "VOLATILE" if expression.args.get("this") is None else "NOT VOLATILE"
36964        if e.this.is_some() {
36965            self.write_keyword("NOT VOLATILE");
36966        } else {
36967            self.write_keyword("VOLATILE");
36968        }
36969        Ok(())
36970    }
36971
36972    fn generate_watermark_column_constraint(
36973        &mut self,
36974        e: &WatermarkColumnConstraint,
36975    ) -> Result<()> {
36976        // Python: f"WATERMARK FOR {self.sql(expression, 'this')} AS {self.sql(expression, 'expression')}"
36977        self.write_keyword("WATERMARK FOR");
36978        self.write_space();
36979        self.generate_expression(&e.this)?;
36980        self.write_space();
36981        self.write_keyword("AS");
36982        self.write_space();
36983        self.generate_expression(&e.expression)?;
36984        Ok(())
36985    }
36986
36987    fn generate_week(&mut self, e: &Week) -> Result<()> {
36988        // Python: return self.func("WEEK", expression.this, expression.args.get("mode"))
36989        self.write_keyword("WEEK");
36990        self.write("(");
36991        self.generate_expression(&e.this)?;
36992        if let Some(mode) = &e.mode {
36993            self.write(", ");
36994            self.generate_expression(mode)?;
36995        }
36996        self.write(")");
36997        Ok(())
36998    }
36999
37000    fn generate_when(&mut self, e: &When) -> Result<()> {
37001        // Python: WHEN {matched}{source}{condition} THEN {then}
37002        // matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED"
37003        // source = " BY SOURCE" if MATCHED_BY_SOURCE and expression.args.get("source") else ""
37004        self.write_keyword("WHEN");
37005        self.write_space();
37006
37007        // Check if matched
37008        if let Some(matched) = &e.matched {
37009            // Check the expression - if it's a boolean true, use MATCHED, otherwise NOT MATCHED
37010            match matched.as_ref() {
37011                Expression::Boolean(b) if b.value => {
37012                    self.write_keyword("MATCHED");
37013                }
37014                _ => {
37015                    self.write_keyword("NOT MATCHED");
37016                }
37017            }
37018        } else {
37019            self.write_keyword("NOT MATCHED");
37020        }
37021
37022        // BY SOURCE / BY TARGET
37023        // source = Boolean(true) means BY SOURCE, Boolean(false) means BY TARGET
37024        // BY TARGET is the default and typically omitted in output
37025        // Only emit if the dialect supports BY SOURCE syntax
37026        if self.config.matched_by_source {
37027            if let Some(source) = &e.source {
37028                if let Expression::Boolean(b) = source.as_ref() {
37029                    if b.value {
37030                        // BY SOURCE
37031                        self.write_space();
37032                        self.write_keyword("BY SOURCE");
37033                    }
37034                    // BY TARGET (b.value == false) is omitted as it's the default
37035                } else {
37036                    // For non-boolean source, output as BY SOURCE (legacy behavior)
37037                    self.write_space();
37038                    self.write_keyword("BY SOURCE");
37039                }
37040            }
37041        }
37042
37043        // Condition
37044        if let Some(condition) = &e.condition {
37045            self.write_space();
37046            self.write_keyword("AND");
37047            self.write_space();
37048            self.generate_expression(condition)?;
37049        }
37050
37051        self.write_space();
37052        self.write_keyword("THEN");
37053        self.write_space();
37054
37055        // Generate the then expression (could be INSERT, UPDATE, DELETE)
37056        // MERGE actions are stored as Tuples with the action keyword as first element
37057        self.generate_merge_action(&e.then)?;
37058
37059        Ok(())
37060    }
37061
37062    fn generate_merge_action(&mut self, action: &Expression) -> Result<()> {
37063        match action {
37064            Expression::Tuple(tuple) => {
37065                let elements = &tuple.expressions;
37066                if elements.is_empty() {
37067                    return self.generate_expression(action);
37068                }
37069                // Check if first element is a Var (INSERT, UPDATE, DELETE, etc.)
37070                match &elements[0] {
37071                    Expression::Var(v) if v.this == "INSERT" => {
37072                        self.write_keyword("INSERT");
37073                        // Spark: INSERT * (insert all columns)
37074                        if elements.len() > 1 && matches!(&elements[1], Expression::Star(_)) {
37075                            self.write(" *");
37076                        } else {
37077                            let mut values_idx = 1;
37078                            // Check if second element is column list (Tuple)
37079                            if elements.len() > 1 {
37080                                if let Expression::Tuple(cols) = &elements[1] {
37081                                    // Could be columns or values - if there's a third element, second is columns
37082                                    if elements.len() > 2 {
37083                                        // Second is columns, third is values
37084                                        self.write(" (");
37085                                        for (i, col) in cols.expressions.iter().enumerate() {
37086                                            if i > 0 {
37087                                                self.write(", ");
37088                                            }
37089                                            // Strip MERGE target qualifiers from INSERT column list
37090                                            if !self.merge_strip_qualifiers.is_empty() {
37091                                                let stripped = self.strip_merge_qualifier(col);
37092                                                self.generate_expression(&stripped)?;
37093                                            } else {
37094                                                self.generate_expression(col)?;
37095                                            }
37096                                        }
37097                                        self.write(")");
37098                                        values_idx = 2;
37099                                    } else {
37100                                        // Only two elements: INSERT + values (no explicit columns)
37101                                        values_idx = 1;
37102                                    }
37103                                }
37104                            }
37105                            // Generate VALUES clause
37106                            if values_idx < elements.len() {
37107                                // Check if it's INSERT ROW (BigQuery) — no VALUES keyword needed
37108                                let is_row = matches!(&elements[values_idx], Expression::Var(v) if v.this == "ROW");
37109                                if !is_row {
37110                                    self.write_space();
37111                                    self.write_keyword("VALUES");
37112                                }
37113                                self.write(" ");
37114                                if let Expression::Tuple(vals) = &elements[values_idx] {
37115                                    self.write("(");
37116                                    for (i, val) in vals.expressions.iter().enumerate() {
37117                                        if i > 0 {
37118                                            self.write(", ");
37119                                        }
37120                                        self.generate_expression(val)?;
37121                                    }
37122                                    self.write(")");
37123                                } else {
37124                                    self.generate_expression(&elements[values_idx])?;
37125                                }
37126                            }
37127                        } // close else for INSERT * check
37128                    }
37129                    Expression::Var(v) if v.this == "UPDATE" => {
37130                        self.write_keyword("UPDATE");
37131                        // Spark: UPDATE * (update all columns)
37132                        if elements.len() > 1 && matches!(&elements[1], Expression::Star(_)) {
37133                            self.write(" *");
37134                        } else if elements.len() > 1 {
37135                            self.write_space();
37136                            self.write_keyword("SET");
37137                            // In pretty mode, put assignments on next line with extra indent
37138                            if self.config.pretty {
37139                                self.write_newline();
37140                                self.indent_level += 1;
37141                                self.write_indent();
37142                            } else {
37143                                self.write_space();
37144                            }
37145                            if let Expression::Tuple(assignments) = &elements[1] {
37146                                for (i, assignment) in assignments.expressions.iter().enumerate() {
37147                                    if i > 0 {
37148                                        if self.config.pretty {
37149                                            self.write(",");
37150                                            self.write_newline();
37151                                            self.write_indent();
37152                                        } else {
37153                                            self.write(", ");
37154                                        }
37155                                    }
37156                                    // Strip MERGE target qualifiers from left side of UPDATE SET
37157                                    if !self.merge_strip_qualifiers.is_empty() {
37158                                        self.generate_merge_set_assignment(assignment)?;
37159                                    } else {
37160                                        self.generate_expression(assignment)?;
37161                                    }
37162                                }
37163                            } else {
37164                                self.generate_expression(&elements[1])?;
37165                            }
37166                            if self.config.pretty {
37167                                self.indent_level -= 1;
37168                            }
37169                        }
37170                    }
37171                    _ => {
37172                        // Fallback: generic tuple generation
37173                        self.generate_expression(action)?;
37174                    }
37175                }
37176            }
37177            Expression::Var(v)
37178                if v.this == "INSERT"
37179                    || v.this == "UPDATE"
37180                    || v.this == "DELETE"
37181                    || v.this == "DO NOTHING" =>
37182            {
37183                self.write_keyword(&v.this);
37184            }
37185            _ => {
37186                self.generate_expression(action)?;
37187            }
37188        }
37189        Ok(())
37190    }
37191
37192    /// Generate a MERGE UPDATE SET assignment, stripping target table qualifier from left side
37193    fn generate_merge_set_assignment(&mut self, assignment: &Expression) -> Result<()> {
37194        match assignment {
37195            Expression::Eq(eq) => {
37196                // Strip qualifier from the left side if it matches a MERGE target name
37197                let stripped_left = self.strip_merge_qualifier(&eq.left);
37198                self.generate_expression(&stripped_left)?;
37199                self.write(" = ");
37200                self.generate_expression(&eq.right)?;
37201                Ok(())
37202            }
37203            other => self.generate_expression(other),
37204        }
37205    }
37206
37207    /// Strip table qualifier from a column reference if it matches a MERGE target name
37208    fn strip_merge_qualifier(&self, expr: &Expression) -> Expression {
37209        match expr {
37210            Expression::Column(col) => {
37211                if let Some(ref table_ident) = col.table {
37212                    if self
37213                        .merge_strip_qualifiers
37214                        .iter()
37215                        .any(|n| n.eq_ignore_ascii_case(&table_ident.name))
37216                    {
37217                        // Strip the table qualifier
37218                        let mut col = col.clone();
37219                        col.table = None;
37220                        return Expression::Column(col);
37221                    }
37222                }
37223                expr.clone()
37224            }
37225            Expression::Dot(dot) => {
37226                // table.column -> column (strip qualifier)
37227                if let Expression::Identifier(id) = &dot.this {
37228                    if self
37229                        .merge_strip_qualifiers
37230                        .iter()
37231                        .any(|n| n.eq_ignore_ascii_case(&id.name))
37232                    {
37233                        return Expression::Identifier(dot.field.clone());
37234                    }
37235                }
37236                expr.clone()
37237            }
37238            _ => expr.clone(),
37239        }
37240    }
37241
37242    fn generate_whens(&mut self, e: &Whens) -> Result<()> {
37243        // Python: return self.expressions(expression, sep=" ", indent=False)
37244        for (i, expr) in e.expressions.iter().enumerate() {
37245            if i > 0 {
37246                // In pretty mode, each WHEN clause on its own line
37247                if self.config.pretty {
37248                    self.write_newline();
37249                    self.write_indent();
37250                } else {
37251                    self.write_space();
37252                }
37253            }
37254            self.generate_expression(expr)?;
37255        }
37256        Ok(())
37257    }
37258
37259    fn generate_where(&mut self, e: &Where) -> Result<()> {
37260        // Python: return f"{self.seg('WHERE')}{self.sep()}{this}"
37261        self.write_keyword("WHERE");
37262        self.write_space();
37263        self.generate_expression(&e.this)?;
37264        Ok(())
37265    }
37266
37267    fn generate_width_bucket(&mut self, e: &WidthBucket) -> Result<()> {
37268        // Python: return self.func("WIDTH_BUCKET", expression.this, ...)
37269        self.write_keyword("WIDTH_BUCKET");
37270        self.write("(");
37271        self.generate_expression(&e.this)?;
37272        if let Some(min_value) = &e.min_value {
37273            self.write(", ");
37274            self.generate_expression(min_value)?;
37275        }
37276        if let Some(max_value) = &e.max_value {
37277            self.write(", ");
37278            self.generate_expression(max_value)?;
37279        }
37280        if let Some(num_buckets) = &e.num_buckets {
37281            self.write(", ");
37282            self.generate_expression(num_buckets)?;
37283        }
37284        self.write(")");
37285        Ok(())
37286    }
37287
37288    fn generate_window(&mut self, e: &WindowSpec) -> Result<()> {
37289        // Window specification: PARTITION BY ... ORDER BY ... frame
37290        self.generate_window_spec(e)
37291    }
37292
37293    fn generate_window_spec(&mut self, e: &WindowSpec) -> Result<()> {
37294        // Window specification: PARTITION BY ... ORDER BY ... frame
37295        let mut has_content = false;
37296
37297        // PARTITION BY
37298        if !e.partition_by.is_empty() {
37299            self.write_keyword("PARTITION BY");
37300            self.write_space();
37301            for (i, expr) in e.partition_by.iter().enumerate() {
37302                if i > 0 {
37303                    self.write(", ");
37304                }
37305                self.generate_expression(expr)?;
37306            }
37307            has_content = true;
37308        }
37309
37310        // ORDER BY
37311        if !e.order_by.is_empty() {
37312            if has_content {
37313                self.write_space();
37314            }
37315            self.write_keyword("ORDER BY");
37316            self.write_space();
37317            for (i, ordered) in e.order_by.iter().enumerate() {
37318                if i > 0 {
37319                    self.write(", ");
37320                }
37321                self.generate_expression(&ordered.this)?;
37322                if ordered.desc {
37323                    self.write_space();
37324                    self.write_keyword("DESC");
37325                } else if ordered.explicit_asc {
37326                    self.write_space();
37327                    self.write_keyword("ASC");
37328                }
37329                if let Some(nulls_first) = ordered.nulls_first {
37330                    self.write_space();
37331                    self.write_keyword("NULLS");
37332                    self.write_space();
37333                    if nulls_first {
37334                        self.write_keyword("FIRST");
37335                    } else {
37336                        self.write_keyword("LAST");
37337                    }
37338                }
37339            }
37340            has_content = true;
37341        }
37342
37343        // Frame specification
37344        if let Some(frame) = &e.frame {
37345            if has_content {
37346                self.write_space();
37347            }
37348            self.generate_window_frame(frame)?;
37349        }
37350
37351        Ok(())
37352    }
37353
37354    fn generate_with_data_property(&mut self, e: &WithDataProperty) -> Result<()> {
37355        // Python: f"WITH {'NO ' if expression.args.get('no') else ''}DATA"
37356        self.write_keyword("WITH");
37357        self.write_space();
37358        if e.no.is_some() {
37359            self.write_keyword("NO");
37360            self.write_space();
37361        }
37362        self.write_keyword("DATA");
37363
37364        // statistics
37365        if let Some(statistics) = &e.statistics {
37366            self.write_space();
37367            self.write_keyword("AND");
37368            self.write_space();
37369            // Check if statistics is true or false
37370            match statistics.as_ref() {
37371                Expression::Boolean(b) if !b.value => {
37372                    self.write_keyword("NO");
37373                    self.write_space();
37374                }
37375                _ => {}
37376            }
37377            self.write_keyword("STATISTICS");
37378        }
37379        Ok(())
37380    }
37381
37382    fn generate_with_fill(&mut self, e: &WithFill) -> Result<()> {
37383        // Python: f"WITH FILL{from_sql}{to_sql}{step_sql}{interpolate}"
37384        self.write_keyword("WITH FILL");
37385
37386        if let Some(from_) = &e.from_ {
37387            self.write_space();
37388            self.write_keyword("FROM");
37389            self.write_space();
37390            self.generate_expression(from_)?;
37391        }
37392
37393        if let Some(to) = &e.to {
37394            self.write_space();
37395            self.write_keyword("TO");
37396            self.write_space();
37397            self.generate_expression(to)?;
37398        }
37399
37400        if let Some(step) = &e.step {
37401            self.write_space();
37402            self.write_keyword("STEP");
37403            self.write_space();
37404            self.generate_expression(step)?;
37405        }
37406
37407        if let Some(staleness) = &e.staleness {
37408            self.write_space();
37409            self.write_keyword("STALENESS");
37410            self.write_space();
37411            self.generate_expression(staleness)?;
37412        }
37413
37414        if let Some(interpolate) = &e.interpolate {
37415            self.write_space();
37416            self.write_keyword("INTERPOLATE");
37417            self.write(" (");
37418            // INTERPOLATE items use reversed alias format: name AS expression
37419            self.generate_interpolate_item(interpolate)?;
37420            self.write(")");
37421        }
37422
37423        Ok(())
37424    }
37425
37426    /// Generate INTERPOLATE items with reversed alias format (name AS expression)
37427    fn generate_interpolate_item(&mut self, expr: &Expression) -> Result<()> {
37428        match expr {
37429            Expression::Alias(alias) => {
37430                // Output as: alias_name AS expression
37431                self.generate_identifier(&alias.alias)?;
37432                self.write_space();
37433                self.write_keyword("AS");
37434                self.write_space();
37435                self.generate_expression(&alias.this)?;
37436            }
37437            Expression::Tuple(tuple) => {
37438                for (i, item) in tuple.expressions.iter().enumerate() {
37439                    if i > 0 {
37440                        self.write(", ");
37441                    }
37442                    self.generate_interpolate_item(item)?;
37443                }
37444            }
37445            other => {
37446                self.generate_expression(other)?;
37447            }
37448        }
37449        Ok(())
37450    }
37451
37452    fn generate_with_journal_table_property(&mut self, e: &WithJournalTableProperty) -> Result<()> {
37453        // Python: return f"WITH JOURNAL TABLE={self.sql(expression, 'this')}"
37454        self.write_keyword("WITH JOURNAL TABLE");
37455        self.write("=");
37456        self.generate_expression(&e.this)?;
37457        Ok(())
37458    }
37459
37460    fn generate_with_operator(&mut self, e: &WithOperator) -> Result<()> {
37461        // Python: return f"{self.sql(expression, 'this')} WITH {self.sql(expression, 'op')}"
37462        self.generate_expression(&e.this)?;
37463        self.write_space();
37464        self.write_keyword("WITH");
37465        self.write_space();
37466        self.write_keyword(&e.op);
37467        Ok(())
37468    }
37469
37470    fn generate_with_procedure_options(&mut self, e: &WithProcedureOptions) -> Result<()> {
37471        // Python: return f"WITH {self.expressions(expression, flat=True)}"
37472        self.write_keyword("WITH");
37473        self.write_space();
37474        for (i, expr) in e.expressions.iter().enumerate() {
37475            if i > 0 {
37476                self.write(", ");
37477            }
37478            self.generate_expression(expr)?;
37479        }
37480        Ok(())
37481    }
37482
37483    fn generate_with_schema_binding_property(
37484        &mut self,
37485        e: &WithSchemaBindingProperty,
37486    ) -> Result<()> {
37487        // Python: return f"WITH {self.sql(expression, 'this')}"
37488        self.write_keyword("WITH");
37489        self.write_space();
37490        self.generate_expression(&e.this)?;
37491        Ok(())
37492    }
37493
37494    fn generate_with_system_versioning_property(
37495        &mut self,
37496        e: &WithSystemVersioningProperty,
37497    ) -> Result<()> {
37498        // Python: complex logic for SYSTEM_VERSIONING with options
37499        // SYSTEM_VERSIONING=ON(HISTORY_TABLE=..., DATA_CONSISTENCY_CHECK=..., HISTORY_RETENTION_PERIOD=...)
37500        // or SYSTEM_VERSIONING=ON/OFF
37501        // with WITH(...) wrapper if with_ is set
37502
37503        let mut parts = Vec::new();
37504
37505        if let Some(this) = &e.this {
37506            // HISTORY_TABLE=...
37507            let mut s = String::from("HISTORY_TABLE=");
37508            let mut gen = Generator::new();
37509            gen.generate_expression(this)?;
37510            s.push_str(&gen.output);
37511            parts.push(s);
37512        }
37513
37514        if let Some(data_consistency) = &e.data_consistency {
37515            let mut s = String::from("DATA_CONSISTENCY_CHECK=");
37516            let mut gen = Generator::new();
37517            gen.generate_expression(data_consistency)?;
37518            s.push_str(&gen.output);
37519            parts.push(s);
37520        }
37521
37522        if let Some(retention_period) = &e.retention_period {
37523            let mut s = String::from("HISTORY_RETENTION_PERIOD=");
37524            let mut gen = Generator::new();
37525            gen.generate_expression(retention_period)?;
37526            s.push_str(&gen.output);
37527            parts.push(s);
37528        }
37529
37530        self.write_keyword("SYSTEM_VERSIONING");
37531        self.write("=");
37532
37533        if !parts.is_empty() {
37534            self.write_keyword("ON");
37535            self.write("(");
37536            self.write(&parts.join(", "));
37537            self.write(")");
37538        } else if e.on.is_some() {
37539            self.write_keyword("ON");
37540        } else {
37541            self.write_keyword("OFF");
37542        }
37543
37544        // Wrap in WITH(...) if with_ is set
37545        if e.with_.is_some() {
37546            let inner = self.output.clone();
37547            self.output.clear();
37548            self.write("WITH(");
37549            self.write(&inner);
37550            self.write(")");
37551        }
37552
37553        Ok(())
37554    }
37555
37556    fn generate_with_table_hint(&mut self, e: &WithTableHint) -> Result<()> {
37557        // Python: f"WITH ({self.expressions(expression, flat=True)})"
37558        self.write_keyword("WITH");
37559        self.write(" (");
37560        for (i, expr) in e.expressions.iter().enumerate() {
37561            if i > 0 {
37562                self.write(", ");
37563            }
37564            self.generate_expression(expr)?;
37565        }
37566        self.write(")");
37567        Ok(())
37568    }
37569
37570    fn generate_xml_element(&mut self, e: &XMLElement) -> Result<()> {
37571        // Python: prefix = "EVALNAME" if expression.args.get("evalname") else "NAME"
37572        // return self.func("XMLELEMENT", name, *expression.expressions)
37573        self.write_keyword("XMLELEMENT");
37574        self.write("(");
37575
37576        if e.evalname.is_some() {
37577            self.write_keyword("EVALNAME");
37578        } else {
37579            self.write_keyword("NAME");
37580        }
37581        self.write_space();
37582        self.generate_expression(&e.this)?;
37583
37584        for expr in &e.expressions {
37585            self.write(", ");
37586            self.generate_expression(expr)?;
37587        }
37588        self.write(")");
37589        Ok(())
37590    }
37591
37592    fn generate_xml_get(&mut self, e: &XMLGet) -> Result<()> {
37593        // XMLGET(this, expression [, instance])
37594        self.write_keyword("XMLGET");
37595        self.write("(");
37596        self.generate_expression(&e.this)?;
37597        self.write(", ");
37598        self.generate_expression(&e.expression)?;
37599        if let Some(instance) = &e.instance {
37600            self.write(", ");
37601            self.generate_expression(instance)?;
37602        }
37603        self.write(")");
37604        Ok(())
37605    }
37606
37607    fn generate_xml_key_value_option(&mut self, e: &XMLKeyValueOption) -> Result<()> {
37608        // Python: this + optional (expr)
37609        self.generate_expression(&e.this)?;
37610        if let Some(expression) = &e.expression {
37611            self.write("(");
37612            self.generate_expression(expression)?;
37613            self.write(")");
37614        }
37615        Ok(())
37616    }
37617
37618    fn generate_xml_table(&mut self, e: &XMLTable) -> Result<()> {
37619        // Python: XMLTABLE(namespaces + this + passing + by_ref + columns)
37620        self.write_keyword("XMLTABLE");
37621        self.write("(");
37622
37623        if self.config.pretty {
37624            self.indent_level += 1;
37625            self.write_newline();
37626            self.write_indent();
37627            self.generate_expression(&e.this)?;
37628
37629            if let Some(passing) = &e.passing {
37630                self.write_newline();
37631                self.write_indent();
37632                self.write_keyword("PASSING");
37633                if let Expression::Tuple(tuple) = passing.as_ref() {
37634                    for expr in &tuple.expressions {
37635                        self.write_newline();
37636                        self.indent_level += 1;
37637                        self.write_indent();
37638                        self.generate_expression(expr)?;
37639                        self.indent_level -= 1;
37640                    }
37641                } else {
37642                    self.write_newline();
37643                    self.indent_level += 1;
37644                    self.write_indent();
37645                    self.generate_expression(passing)?;
37646                    self.indent_level -= 1;
37647                }
37648            }
37649
37650            if e.by_ref.is_some() {
37651                self.write_newline();
37652                self.write_indent();
37653                self.write_keyword("RETURNING SEQUENCE BY REF");
37654            }
37655
37656            if !e.columns.is_empty() {
37657                self.write_newline();
37658                self.write_indent();
37659                self.write_keyword("COLUMNS");
37660                for (i, col) in e.columns.iter().enumerate() {
37661                    self.write_newline();
37662                    self.indent_level += 1;
37663                    self.write_indent();
37664                    self.generate_expression(col)?;
37665                    self.indent_level -= 1;
37666                    if i < e.columns.len() - 1 {
37667                        self.write(",");
37668                    }
37669                }
37670            }
37671
37672            self.indent_level -= 1;
37673            self.write_newline();
37674            self.write_indent();
37675            self.write(")");
37676            return Ok(());
37677        }
37678
37679        // Namespaces - unwrap Tuple to generate comma-separated list without parentheses
37680        if let Some(namespaces) = &e.namespaces {
37681            self.write_keyword("XMLNAMESPACES");
37682            self.write("(");
37683            // Unwrap Tuple if present to avoid extra parentheses
37684            if let Expression::Tuple(tuple) = namespaces.as_ref() {
37685                for (i, expr) in tuple.expressions.iter().enumerate() {
37686                    if i > 0 {
37687                        self.write(", ");
37688                    }
37689                    // Python pattern: if it's an Alias, output as-is; otherwise prepend DEFAULT
37690                    // See xmlnamespace_sql in generator.py
37691                    if !matches!(expr, Expression::Alias(_)) {
37692                        self.write_keyword("DEFAULT");
37693                        self.write_space();
37694                    }
37695                    self.generate_expression(expr)?;
37696                }
37697            } else {
37698                // Single namespace - check if DEFAULT
37699                if !matches!(namespaces.as_ref(), Expression::Alias(_)) {
37700                    self.write_keyword("DEFAULT");
37701                    self.write_space();
37702                }
37703                self.generate_expression(namespaces)?;
37704            }
37705            self.write("), ");
37706        }
37707
37708        // XPath expression
37709        self.generate_expression(&e.this)?;
37710
37711        // PASSING clause - unwrap Tuple to generate comma-separated list without parentheses
37712        if let Some(passing) = &e.passing {
37713            self.write_space();
37714            self.write_keyword("PASSING");
37715            self.write_space();
37716            // Unwrap Tuple if present to avoid extra parentheses
37717            if let Expression::Tuple(tuple) = passing.as_ref() {
37718                for (i, expr) in tuple.expressions.iter().enumerate() {
37719                    if i > 0 {
37720                        self.write(", ");
37721                    }
37722                    self.generate_expression(expr)?;
37723                }
37724            } else {
37725                self.generate_expression(passing)?;
37726            }
37727        }
37728
37729        // RETURNING SEQUENCE BY REF
37730        if e.by_ref.is_some() {
37731            self.write_space();
37732            self.write_keyword("RETURNING SEQUENCE BY REF");
37733        }
37734
37735        // COLUMNS clause
37736        if !e.columns.is_empty() {
37737            self.write_space();
37738            self.write_keyword("COLUMNS");
37739            self.write_space();
37740            for (i, col) in e.columns.iter().enumerate() {
37741                if i > 0 {
37742                    self.write(", ");
37743                }
37744                self.generate_expression(col)?;
37745            }
37746        }
37747
37748        self.write(")");
37749        Ok(())
37750    }
37751
37752    fn generate_xor(&mut self, e: &Xor) -> Result<()> {
37753        // Python: return self.connector_sql(expression, "XOR", stack)
37754        // Handles: this XOR expression or expressions joined by XOR
37755        if let Some(this) = &e.this {
37756            self.generate_expression(this)?;
37757            if let Some(expression) = &e.expression {
37758                self.write_space();
37759                self.write_keyword("XOR");
37760                self.write_space();
37761                self.generate_expression(expression)?;
37762            }
37763        }
37764
37765        // Handle multiple expressions
37766        for (i, expr) in e.expressions.iter().enumerate() {
37767            if i > 0 || e.this.is_some() {
37768                self.write_space();
37769                self.write_keyword("XOR");
37770                self.write_space();
37771            }
37772            self.generate_expression(expr)?;
37773        }
37774        Ok(())
37775    }
37776
37777    fn generate_zipf(&mut self, e: &Zipf) -> Result<()> {
37778        // ZIPF(this, elementcount [, gen])
37779        self.write_keyword("ZIPF");
37780        self.write("(");
37781        self.generate_expression(&e.this)?;
37782        if let Some(elementcount) = &e.elementcount {
37783            self.write(", ");
37784            self.generate_expression(elementcount)?;
37785        }
37786        if let Some(gen) = &e.gen {
37787            self.write(", ");
37788            self.generate_expression(gen)?;
37789        }
37790        self.write(")");
37791        Ok(())
37792    }
37793}
37794
37795impl Default for Generator {
37796    fn default() -> Self {
37797        Self::new()
37798    }
37799}
37800
37801#[cfg(test)]
37802mod tests {
37803    use super::*;
37804    use crate::parser::Parser;
37805
37806    fn roundtrip(sql: &str) -> String {
37807        let ast = Parser::parse_sql(sql).unwrap();
37808        Generator::sql(&ast[0]).unwrap()
37809    }
37810
37811    #[test]
37812    fn test_simple_select() {
37813        let result = roundtrip("SELECT 1");
37814        assert_eq!(result, "SELECT 1");
37815    }
37816
37817    #[test]
37818    fn test_select_from() {
37819        let result = roundtrip("SELECT a, b FROM t");
37820        assert_eq!(result, "SELECT a, b FROM t");
37821    }
37822
37823    #[test]
37824    fn test_select_where() {
37825        let result = roundtrip("SELECT * FROM t WHERE x = 1");
37826        assert_eq!(result, "SELECT * FROM t WHERE x = 1");
37827    }
37828
37829    #[test]
37830    fn test_select_join() {
37831        let result = roundtrip("SELECT * FROM a JOIN b ON a.id = b.id");
37832        assert_eq!(result, "SELECT * FROM a JOIN b ON a.id = b.id");
37833    }
37834
37835    #[test]
37836    fn test_insert() {
37837        let result = roundtrip("INSERT INTO t (a, b) VALUES (1, 2)");
37838        assert_eq!(result, "INSERT INTO t (a, b) VALUES (1, 2)");
37839    }
37840
37841    #[test]
37842    fn test_pretty_print() {
37843        let ast = Parser::parse_sql("SELECT a, b FROM t WHERE x = 1").unwrap();
37844        let result = Generator::pretty_sql(&ast[0]).unwrap();
37845        assert!(result.contains('\n'));
37846    }
37847
37848    #[test]
37849    fn test_window_function() {
37850        let result = roundtrip("SELECT ROW_NUMBER() OVER (PARTITION BY category ORDER BY id)");
37851        assert_eq!(
37852            result,
37853            "SELECT ROW_NUMBER() OVER (PARTITION BY category ORDER BY id)"
37854        );
37855    }
37856
37857    #[test]
37858    fn test_window_function_with_frame() {
37859        let result = roundtrip("SELECT SUM(amount) OVER (ORDER BY order_date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)");
37860        assert_eq!(result, "SELECT SUM(amount) OVER (ORDER BY order_date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)");
37861    }
37862
37863    #[test]
37864    fn test_aggregate_with_filter() {
37865        let result = roundtrip("SELECT COUNT(*) FILTER (WHERE status = 1) FROM orders");
37866        assert_eq!(
37867            result,
37868            "SELECT COUNT(*) FILTER(WHERE status = 1) FROM orders"
37869        );
37870    }
37871
37872    #[test]
37873    fn test_subscript() {
37874        let result = roundtrip("SELECT arr[0]");
37875        assert_eq!(result, "SELECT arr[0]");
37876    }
37877
37878    // DDL tests
37879    #[test]
37880    fn test_create_table() {
37881        let result = roundtrip("CREATE TABLE users (id INT, name VARCHAR(100))");
37882        assert_eq!(result, "CREATE TABLE users (id INT, name VARCHAR(100))");
37883    }
37884
37885    #[test]
37886    fn test_create_table_with_constraints() {
37887        let result = roundtrip(
37888            "CREATE TABLE users (id INT PRIMARY KEY, email VARCHAR(255) UNIQUE NOT NULL)",
37889        );
37890        assert_eq!(
37891            result,
37892            "CREATE TABLE users (id INT PRIMARY KEY, email VARCHAR(255) UNIQUE NOT NULL)"
37893        );
37894    }
37895
37896    #[test]
37897    fn test_create_table_if_not_exists() {
37898        let result = roundtrip("CREATE TABLE IF NOT EXISTS t (id INT)");
37899        assert_eq!(result, "CREATE TABLE IF NOT EXISTS t (id INT)");
37900    }
37901
37902    #[test]
37903    fn test_drop_table() {
37904        let result = roundtrip("DROP TABLE users");
37905        assert_eq!(result, "DROP TABLE users");
37906    }
37907
37908    #[test]
37909    fn test_drop_table_if_exists_cascade() {
37910        let result = roundtrip("DROP TABLE IF EXISTS users CASCADE");
37911        assert_eq!(result, "DROP TABLE IF EXISTS users CASCADE");
37912    }
37913
37914    #[test]
37915    fn test_alter_table_add_column() {
37916        let result = roundtrip("ALTER TABLE users ADD COLUMN email VARCHAR(255)");
37917        assert_eq!(result, "ALTER TABLE users ADD COLUMN email VARCHAR(255)");
37918    }
37919
37920    #[test]
37921    fn test_alter_table_drop_column() {
37922        let result = roundtrip("ALTER TABLE users DROP COLUMN email");
37923        assert_eq!(result, "ALTER TABLE users DROP COLUMN email");
37924    }
37925
37926    #[test]
37927    fn test_create_index() {
37928        let result = roundtrip("CREATE INDEX idx_name ON users(name)");
37929        assert_eq!(result, "CREATE INDEX idx_name ON users(name)");
37930    }
37931
37932    #[test]
37933    fn test_create_unique_index() {
37934        let result = roundtrip("CREATE UNIQUE INDEX idx_email ON users(email)");
37935        assert_eq!(result, "CREATE UNIQUE INDEX idx_email ON users(email)");
37936    }
37937
37938    #[test]
37939    fn test_drop_index() {
37940        let result = roundtrip("DROP INDEX idx_name");
37941        assert_eq!(result, "DROP INDEX idx_name");
37942    }
37943
37944    #[test]
37945    fn test_create_view() {
37946        let result = roundtrip("CREATE VIEW active_users AS SELECT * FROM users WHERE active = 1");
37947        assert_eq!(
37948            result,
37949            "CREATE VIEW active_users AS SELECT * FROM users WHERE active = 1"
37950        );
37951    }
37952
37953    #[test]
37954    fn test_drop_view() {
37955        let result = roundtrip("DROP VIEW active_users");
37956        assert_eq!(result, "DROP VIEW active_users");
37957    }
37958
37959    #[test]
37960    fn test_truncate() {
37961        let result = roundtrip("TRUNCATE TABLE users");
37962        assert_eq!(result, "TRUNCATE TABLE users");
37963    }
37964
37965    #[test]
37966    fn test_string_literal_escaping_default() {
37967        // Default: double single quotes
37968        let result = roundtrip("SELECT 'hello'");
37969        assert_eq!(result, "SELECT 'hello'");
37970
37971        // Single quotes are doubled
37972        let result = roundtrip("SELECT 'it''s a test'");
37973        assert_eq!(result, "SELECT 'it''s a test'");
37974    }
37975
37976    #[test]
37977    fn test_not_in_style_prefix_default_generic() {
37978        let result = roundtrip("SELECT id FROM users WHERE status NOT IN ('deleted', 'banned')");
37979        assert_eq!(
37980            result,
37981            "SELECT id FROM users WHERE NOT status IN ('deleted', 'banned')"
37982        );
37983    }
37984
37985    #[test]
37986    fn test_not_in_style_infix_generic_override() {
37987        let ast =
37988            Parser::parse_sql("SELECT id FROM users WHERE status NOT IN ('deleted', 'banned')")
37989                .unwrap();
37990        let config = GeneratorConfig {
37991            not_in_style: NotInStyle::Infix,
37992            ..Default::default()
37993        };
37994        let mut gen = Generator::with_config(config);
37995        let result = gen.generate(&ast[0]).unwrap();
37996        assert_eq!(
37997            result,
37998            "SELECT id FROM users WHERE status NOT IN ('deleted', 'banned')"
37999        );
38000    }
38001
38002    #[test]
38003    fn test_string_literal_escaping_mysql() {
38004        use crate::dialects::DialectType;
38005
38006        let config = GeneratorConfig {
38007            dialect: Some(DialectType::MySQL),
38008            ..Default::default()
38009        };
38010
38011        let ast = Parser::parse_sql("SELECT 'hello'").unwrap();
38012        let mut gen = Generator::with_config(config.clone());
38013        let result = gen.generate(&ast[0]).unwrap();
38014        assert_eq!(result, "SELECT 'hello'");
38015
38016        // MySQL uses SQL standard quote doubling for escaping (matches Python sqlglot)
38017        let ast = Parser::parse_sql("SELECT 'it''s'").unwrap();
38018        let mut gen = Generator::with_config(config.clone());
38019        let result = gen.generate(&ast[0]).unwrap();
38020        assert_eq!(result, "SELECT 'it''s'");
38021    }
38022
38023    #[test]
38024    fn test_string_literal_escaping_postgres() {
38025        use crate::dialects::DialectType;
38026
38027        let config = GeneratorConfig {
38028            dialect: Some(DialectType::PostgreSQL),
38029            ..Default::default()
38030        };
38031
38032        let ast = Parser::parse_sql("SELECT 'hello'").unwrap();
38033        let mut gen = Generator::with_config(config.clone());
38034        let result = gen.generate(&ast[0]).unwrap();
38035        assert_eq!(result, "SELECT 'hello'");
38036
38037        // PostgreSQL uses doubled quotes for regular strings
38038        let ast = Parser::parse_sql("SELECT 'it''s'").unwrap();
38039        let mut gen = Generator::with_config(config.clone());
38040        let result = gen.generate(&ast[0]).unwrap();
38041        assert_eq!(result, "SELECT 'it''s'");
38042    }
38043
38044    #[test]
38045    fn test_string_literal_escaping_bigquery() {
38046        use crate::dialects::DialectType;
38047
38048        let config = GeneratorConfig {
38049            dialect: Some(DialectType::BigQuery),
38050            ..Default::default()
38051        };
38052
38053        let ast = Parser::parse_sql("SELECT 'hello'").unwrap();
38054        let mut gen = Generator::with_config(config.clone());
38055        let result = gen.generate(&ast[0]).unwrap();
38056        assert_eq!(result, "SELECT 'hello'");
38057
38058        // BigQuery escapes single quotes with backslash
38059        let ast = Parser::parse_sql("SELECT 'it''s'").unwrap();
38060        let mut gen = Generator::with_config(config.clone());
38061        let result = gen.generate(&ast[0]).unwrap();
38062        assert_eq!(result, "SELECT 'it\\'s'");
38063    }
38064
38065    #[test]
38066    fn test_generate_deep_and_chain_without_stack_growth() {
38067        let mut expr = Expression::Eq(Box::new(BinaryOp::new(
38068            Expression::column("c0"),
38069            Expression::number(0),
38070        )));
38071
38072        for i in 1..2500 {
38073            let predicate = Expression::Eq(Box::new(BinaryOp::new(
38074                Expression::column(format!("c{i}")),
38075                Expression::number(i as i64),
38076            )));
38077            expr = Expression::And(Box::new(BinaryOp::new(expr, predicate)));
38078        }
38079
38080        let sql = Generator::sql(&expr).expect("deep AND chain should generate");
38081        assert!(sql.contains("c2499 = 2499"), "{}", sql);
38082    }
38083
38084    #[test]
38085    fn test_generate_deep_or_chain_without_stack_growth() {
38086        let mut expr = Expression::Eq(Box::new(BinaryOp::new(
38087            Expression::column("c0"),
38088            Expression::number(0),
38089        )));
38090
38091        for i in 1..2500 {
38092            let predicate = Expression::Eq(Box::new(BinaryOp::new(
38093                Expression::column(format!("c{i}")),
38094                Expression::number(i as i64),
38095            )));
38096            expr = Expression::Or(Box::new(BinaryOp::new(expr, predicate)));
38097        }
38098
38099        let sql = Generator::sql(&expr).expect("deep OR chain should generate");
38100        assert!(sql.contains("c2499 = 2499"), "{}", sql);
38101    }
38102}